Wandering Thoughts archives

2021-02-13

Where the default values for Python function arguments are stored

One of the things that surprised me when I was researching yesterday's entry on using is with literals was that I couldn't work out where (C)Python kept the default values for function arguments. In the end I didn't need to know for sure because I was able to demonstrate that function default argument values are interned along with constants used in the function code, but it bugged me. Today I worked it out and now I can show some more interesting things.

In the end, finding the answer was as simple as reading the documentation for the inspect module. Constants used in code are found in the co_consts attribute on code objects, but the default values for function arguments are found in the __defaults__ and __kwdefaults__ attributes of function objects. Once I thought about it this split made a lot of sense. Code objects can come from many sources (for instance, compile()) and not all of those sources actually have any concept of arguments (with or without default arguments). So attaching 'default values for function arguments' to code objects would be wrong; they need to go on function objects, where they make sense.

(The difference in naming style is (likely) due to Python 3 limiting how much code it was willing to break and force people to update. In Python 2, function default argument values are exposed in a func_defaults attribute, along with a number of other func_* ones. In Python 3, all of those were renamed to __<name>__ versions, while code objects had their attribute names left alone. If Python was being recreated from scratch today, I suspect that code objects would have only __<name>__ attributes too.)

This means that CPython's constant interning is being somewhat more clever than I expected. Since default argument values don't go in co_consts, CPython is somehow building an overall constant pool, then (re)using it for both co_consts and __defaults__. CPython is definitely making use of the same objects in these two attributes, which I can now demonstrate in a different and more direct way than I did in the last entry:

>>> def a(b=3000):
...   return b == 3000
>>> a.__defaults__
(3000,)
>>> a.__code__.co_consts
(None, 3000)
>>> a.__defaults__[0] is a.__code__.co_consts[1]
True

One of the minor uses of function __defaults__ that I can see is to examine the current state of function default argument values, just in case someone has managed to mutate one of them.

PS: In reading the CPython code, I discovered that you can actually set new values for these default argument values by assigning to __defaults__. This is described in the Python data model chapter, sort of implicitly (because it lists the __defaults__ field as writable, and what other effects would that have).

FunctionDefaultArgsWhere written at 22:43:01; Add Comment


Page tools: See As Normal.
Search:
Login: Password:
Atom Syndication: Recent Pages, Recent Comments.

This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.