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?