Question: I'm trying to understand the python data model better and ran into something odd. def foo(a, b = 2): return a / b assert foo(20) == 10.0 # note: for sanity purposes, should also change signature, but not needed for effect foo.__defaults__ = (10,) assert foo(20) == 2.0 foo.__defaults__ = () foo.__kwdefaults__ = {'b': 10} foo(20) # raises TypeError: foo() missing 1 required positional argument: 'b'  An error is expected: __kwdefaults__ is for keyword-only arguments, so let's make b a keyword-only argument to try to solve this problem: from inspect import signature foo.__signature__ = signature(lambda a, *, b=10: None) foo(20) # still raises TypeError: foo() missing 1 required positional argument: 'b'  How does the error message relate to what's happening?. What I find strange is that neither the original function, nor my doctored one required b (it always had a default!). Also, b has never been a positional-only argument. What is happening here? How can one transform foo to make b be a keyword-only argument with default 10. If my original function had the signature I "injected" above, all goes well though: def foo(a, *, b=2): # same as previous `foo`, with signature we want return a / b foo.__kwdefaults__ = {'b': 10} # change kwdefault assert foo(20) == 2.0 # it works!!  Preemptive note: I know of functools wraps and partial, which I could use -- though in my context, I'd rather change the function itself, not a wrapped version. My question is about the behavior I created in the code above: How did it come about?

Answer: Purpose of __signature__ Your issue is, that you think that you change a function's signature by setting foo.__signature__. However, this is not what's happening. It is equally useless to set it to foo.signature or foo.any_other_name. You just set a signature object to the respective property of the function, which changes nothing with regards to the function's behaviour. The only thing that __signature__ does is to change the behaviour of inspect.signature(), since it will return the signature of the function as stored in function.__signature__ iff it is set. I.e. the only thing, that __signature__ changes is the behaviour of inspect.signature(), but not the function itself. See ekhumoro's comment for the link to the appropriate PEP. TypeError As for the type error: In foo() b is not a kwarg-only argument: def foo(a, b = 2): return a / b  It is a positional argument with a default value. Hence its default value is stored in foo.__defaults__. When you set foo.__defaults__ = () you erased those defaults. After that, b hence has no longer a default value and needs to be passed explicitly. Changing signatures  How can one transform foo to make b be a keyword-only argument with default 10.  You cannot change a function's signature during runtime. Period. Changing default values You can, however, change b's default value to 10 via >>> foo.__defaults__ = (10,) >>> foo(2) 0.2  Since positional arguments with default values cannot be followed by positional arguments without defaults, the tuple __defaults__ is applied to the positional arguments from right to left. So you can also give a a default value of e.g. 20 via >>> foo.__defaults__ = (20, 10) >>> foo() 2.0