Investigation
I have this program
def gen_baz():
return 2
def gen_bar(a, b, c=1):
return a, b, c
# ---------- #
# Save the code objects
c1 = gen_bar.__code__
c2 = gen_baz.__code__
dis.dis(gen_bar) # [Disassambly 1]
print(inspect.getsource(gen_bar)) # [Inspect 1]
bar = gen_bar(1, 2) # bar: (1, 2, 1)
baz = gen_baz() # baz: 2
print(f"before: bar={bar}, baz={baz}")
# --------- #
gen_bar.__code__, gen_baz.__code__ = c2, c1 # swap code objects between 2 functions
dis.dis(gen_baz) # [Disassambly 2]
print(inspect.getsource(gen_baz)) # [Inspect 1]
bar = gen_bar() # bar: 2
baz = gen_baz(1, 2) # baz: [Error 1]
print(f"after: bar={bar}, baz={baz}")
Here are the outputs with Disassembly and Inspect
[Disassembly 1 & 2]
17 0 LOAD_FAST 0 (a)
2 LOAD_FAST 1 (b)
4 LOAD_FAST 2 (c)
6 BUILD_TUPLE 3
8 RETURN_VALUE
[Inspect 1 & 2]
def gen_bar(a, b, c=1):
return a, b, c
[Error 1]
Traceback (most recent call last):
File "C:\Users\something\test.py", line 32, in <module>
baz = gen_baz(1, 2)
TypeError: gen_baz() missing 1 required positional argument: 'c'
Question
- Why after I swapped the code objects between baz and bar, the program crashed? Even though they have identical byte code and inspect result
- Where is the default parameter in python stored? Does it come along with the function object but not the code object? If so, then how does Inspect module gives me
c=1
on the second inspection?
Thanks a lot!
CodePudding user response:
Default values are not stored in the code
object. They are stored directly in the function
object.
>>> gen_bar.__defaults__
(1,)
The text returned by getsource
comes, as implied by the name, from the source code itself, not the object generated by the source code. (At the object level, the signature is only implied by the values the function tries to load from the stack, global namespace, etc.)
You can use inspect.signature
to see that gen_baz
now takes three arguments, but that does not include the default value, which was not transferred to gen_baz
along with the code
object.
>>> inspect.signature(gen_baz)
<Signature (a, b, c)>
The use of a default argument value is buried in the evaluation of the CALL_FUNCTION
opcode. Given
def foo(a=1):
return a
You can see that neither its byte code
>>> dis.dis(foo)
2 0 LOAD_FAST 0 (a)
2 RETURN_VALUE
nor calls with or without explicit arguments
>>> dis.dis('foo(9)')
1 0 LOAD_NAME 0 (foo)
2 LOAD_CONST 0 (9)
4 CALL_FUNCTION 1
6 RETURN_VALUE
>>> dis.dis('foo()')
1 0 LOAD_NAME 0 (foo)
2 CALL_FUNCTION 0
4 RETURN_VALUE
make use of the function's __defaults__
attribute. In the function, it is simply assumed that some value can pushed on to the stack from the local variable a
, regardless of how that variable is set. In both calls, a value is simply loaded onto the stack or not before using CALL_FUNCTION
.
CodePudding user response:
Check the docs:
__code__
: code object containing compiled function bytecode__defaults__
: tuple of any default values for positional or keyword parameters__kwdefaults__
: mapping of any default values for keyword-only parameters