== 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 PythonIsWithLiteral]] 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 https://en.wikipedia.org/wiki/String_interning]] 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_ https://docs.python.org/3/library/inspect.html]] 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 ((____)) 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 ((____)) 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 PythonIsWithLiteral]]: > >>> 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 https://docs.python.org/3/reference/datamodel.html]], sort of implicitly (because it lists the ((__defaults__)) field as writable, and what other effects would that have).