Python函数参数的默认值存放在哪里

113 阅读2分钟

当我研究昨天关于使用is 与字面意义的文章时,让我吃惊的一件事是我无法弄清楚 (C) Python 在哪里保存函数参数的缺省值。最后我不需要确定,因为我能够证明函数的缺省参数值是和函数代码中使用的常量一起存放的,但是它困扰着我。今天我解决了这个问题,现在我可以展示一些更有趣的东西。

最后,找到答案就像阅读模块的文档一样简单。 inspect模块。代码中使用的常量可以在代码对象的co_consts 属性中找到,但函数参数的默认值可以在函数对象的__defaults____kwdefaults__ 属性中找到。一旦我想明白了,这种分割就很有意义了。代码对象可以来自很多来源(例如,compile() ),而不是所有这些来源实际上都有任何参数的概念(有或没有默认参数)。所以将 "函数参数的默认值 "附加到代码对象上是错误的;它们需要放在函数对象上,在那里它们才有意义。

(命名方式的不同(很可能)是由于Python 3限制了它愿意破坏和强迫人们更新的代码的数量。 在Python 2中,函数缺省参数值在一个func_defaults 属性中暴露出来,同时还有其他一些func_*。在Python 3中,所有这些都被重新命名为__<name>__版本,而代码对象的属性名称则保持不变。如果Python今天被从头创建,我怀疑代码对象也将只有__<name>__ 属性。)

这意味着 CPython 的常量互译比我想象的要聪明一些。由于默认参数值不在co_consts ,CPython以某种方式建立了一个整体的常量池,然后将其(重新)用于co_consts__defaults__ 。CPython肯定在这两个属性中使用了相同的对象,现在我可以用一种与上一篇文章不同的、更直接的方式来证明这一点:

>>> 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

我所看到的函数__defaults__ 的一个次要用途是检查函数默认参数值的当前状态,以防有人设法突变了其中的一个。

PS: 在阅读CPython代码时,我发现实际上你可以通过赋值给__defaults__ ,为这些默认参数值设置新的值。这在Python数据模型一章中有描述,有点隐含的意思(因为它把__defaults__ 字段列为可写的,那还有什么其他影响)。