作者:feng2502863897 | 来源:互联网 | 2023-06-22 15:40
请看下面的代码片段:
In [1]: class A(Exception):
...: def __init__(self, b):
...: self.message = b.message
...:
In [2]: class B:
...: message = "hello"
...:
In [3]: A(B())
Out[3]: __main__.A(<__main__.B at 0x14af96790>)
In [4]: class A:
...: def __init__(self, b):
...: self.message = b.message
...:
In [5]: A(B())
Out[5]: <__main__.A at 0x10445b0a0>
如果是A
from 的子类Exception
,即使我们只传递了的 message 属性,它也会repr
返回一个对 的引用。B()
B()
Exception.repr
鉴于 cpython 代码不太可读,为什么这是有意的行为,如果可能的话,它如何在 python 伪代码中工作?
回答
创建 Python 对象时__new__
,会调用该类的方法,__init__
然后在该__new__
方法返回的新实例上调用该方法(假设它返回了一个新实例,但有时不会)。
您覆盖的__init__
方法没有保留对 的引用b
,但您没有覆盖__new__
,因此您继承了__new__
此处定义的方法(CPython 源链接):
static PyObject *
BaseException_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
// ...
if (args) {
self->args = args;
Py_INCREF(args);
return (PyObject *)self;
}
// ...
}
我省略了不相关的部分。如您所见,该类的__new__
方法BaseException
存储对创建异常时使用的参数元组的引用,因此该元组可用于该__repr__
方法打印对用于实例化异常的对象的引用。所以正是这个元组保留了对原始参数的引用b
。这与repr
应该返回 Python 代码的一般期望一致,该代码将创建处于相同状态的对象(如果可以的话)。
请注意,只有args
, notkwds
具有此行为;该__new__
方法不存储到一个参考kwds
和__repr__
没有打印出来,因此我们预计不会看到相同的行为,如果调用构造函数与关键字参数,而不是一个位置参数。事实上,这就是我们观察到的:
>>> A(B())
A(<__main__.B object at 0x7fa8e7a23860>,)
>>> A(b=B())
A()
有点奇怪,因为这两个A
对象应该具有相同的状态,但无论如何代码就是这样编写的。