除了在Python中“再次”询问__new__
和__init__
之外,我可以保证,我知道它是做什么的。我将展示一些奇怪的和我的意见无证的行为,为此我寻求专业的帮助:).
后台
我正在使用一个名为ExtendedType
的用户定义元类实现几个特性,比如抽象方法、抽象类、must-override方法、单音行为、时隙类(__slots__
的自动推理)和mixin类(延迟时隙)。下面的代码作为一个整体可以在开发分支的pyTooling/pyTooling中找到。
因此,所提出的问题是一个简化的变体,展示了object.__new__
的奇怪行为。
创意
根据ExtendedType
的内部算法,它可能会决定一个类A
是 * 抽象 *。如果是这样,__new__
方法将被一个引发异常(AbstractClassError
)的伪方法替换。后来,当类B(A)
从A
继承时,元类可能会做出决定,B
不再是抽象的,因此我们希望允许再次创建对象并允许调用原始的__new__
方法。因此,原始方法作为字段保留在类中。
为了简化抽象性决策的内部算法,元类实现了一个布尔命名参数abstract
。
class AbstractClassError(Exception):
pass
class M(type):
# staticmethod
def __new__(cls, className, baseClasses, members, abstract):
newClass = type.__new__(cls, className, baseClasses, members)
if abstract:
def newnew(cls, *_, **__):
raise AbstractClassError(f"Class is abstract")
# keep original __new__ and exchange it with a dummy method throwing an error
newClass.__new_orig__ = newClass.__new__
newClass.__new__ = newnew
else:
# 1. replacing __new__ with original (preserved) method doesn't work
newClass.__new__ = newClass.__new_orig__
return newClass
class A(metaclass=M, abstract=True):
pass
class B(A, abstract=False):
def __init__(self, arg):
self.arg = arg
b = B(5)
在示例化B
时,我们将尝试两种情况:
1.只有一个参数:b = B(5)
错误信息:
TypeError: object.__new__() takes exactly one argument (the type to instantiate)
1.没有参数:b = B()
错误信息:
TypeError: B.__init__() missing 1 required positional argument: 'arg'
后一种情况的错误消息是预期的,因为B
的__init__
需要一个参数arg
。奇怪的行为是在案例1中,它报告object.__new__()
除了类型之外没有其他参数。
所以,让我们来看看交换方法是否正确工作:
print("object.__new__ ", object.__new__)
print("A.__new_orig__ ", A.__new_orig__)
print("A.__new__ ", A.__new__)
print("B.__new__ ", B.__new__)
结果如下:
object.__new__ <built-in method __new__ of type object at 0x00007FFE30EDD0C0>
A.__new_orig__ <built-in method __new__ of type object at 0x00007FFE30EDD0C0>
A.__new__ <function M.__new__.<locals>.newnew at 0x000001CF11AE5A80>
B.__new__ <built-in method __new__ of type object at 0x00007FFE30EDD0C0>
因此,__new_orig__
中保留的方法与object.__new__
相同,并且在交换回B
类中的__new__
方法之后也是相同的。
与普通类比较
让我们取两个类X
和Y(X)
并示例化它们:
class X:
pass
class Y(X):
def __init__(self, arg):
self.arg = arg
y = Y(3)
当然这是可行的,但是__new__
方法不同吗?
object.__new__ <built-in method __new__ of type object at 0x00007FFE3B61D0C0>
A.__new_orig__ <built-in method __new__ of type object at 0x00007FFE3B61D0C0>
A.__new__ <function M.__new__.<locals>.newnew at 0x000001CD1FB459E0>
B.__new__ <built-in method __new__ of type object at 0x00007FFE3B61D0C0>
X.__new__ <built-in method __new__ of type object at 0x00007FFE3B61D0C0>
Y.__new__ <built-in method __new__ of type object at 0x00007FFE3B61D0C0>
此外,X
和Y
使用与B
或object
相同的__new__
方法。
那么,让我们示例化Y
和B
并比较结果:
print("Y.__new__ ", Y.__new__)
y = Y(3)
print("y.arg ", y.arg)
print("B.__new__ ", B.__new__)
b = B(5)
print("b.arg ", y.arg)
结果如下:
Y.__new__ <built-in method __new__ of type object at 0x00007FFE3B61D0C0>
y.arg 3
B.__new__ <built-in method __new__ of type object at 0x00007FFE3B61D0C0>
Traceback (most recent call last):
File "C:\Temp\newIstKomisch.py", line 67, in <module>
b = B(5)
^^^^
TypeError: object.__new__() takes exactly one argument (the type to instantiate)
- 问题1:为什么new接受Y的参数,而不接受B的参数?**
创建对象
当创建对象时,执行元类的__call__
方法,大致可以转换为:
class M(type):
...
def __call__(cls, *args, **kwargs):
inst = cls.__new__(cls, *args, **kwargs)
inst.__init__(*args, **kwargs)
return inst
它首先调用__new__
创建一个示例,然后调用__init__
初始化对象。有人可能会争辩说:“也许在call中有神奇的行为”来检查是否调用了内置或用户定义的方法“…
让我们快速检查一下object.__new__
的行为:
o = object.__new__(object, 1)
结果:
TypeError: object() takes no arguments
观察结果:错误消息与我们之前得到的不同。这个说“没有参数”,另一个说“只有一个参数”。
或者,我们可以通过手动跳过元类来创建一个对象:
y = Y.__new__(Y, 3)
print("Y.__new__(Y, 3) ", y)
y.__init__(3)
print("y.__init__(3) ", y.arg)
结果:
Y.__new__(Y, 3) <__main__.Y object at 0x0000020ED770BD40>
y.__init__(3) 3
在这里,我们清楚地看到__new__
可以接受额外的参数并忽略它们。
让我们比较一下手动创建B
的示例:
b = B.__new__(B, 5)
print("B.__new__(B, 5) ", b)
b.__init__(5)
print("b.__init__(5) ", b.arg)
结果:
Traceback (most recent call last):
File "C:\Temp\newIstKomisch.py", line 51, in <module>
b = B.__new__(B, 5)
^^^^^^^^^^^^^^^
TypeError: object.__new__() takes exactly one argument (the type to instantiate)
- 问题2:同一个方法如何有不同的行为和异常处理?**
- 附加说明:**
- 所有行为都是在
M.__new__
中实现的,或者是在XXX.__new__
方法中交换而不是M.__call__
中实现的,所以对象创建时间不受影响。修改元类调用将对性能产生巨大影响。 - 附件:**
- Full reproducer file
1条答案
按热度按时间omqzjyyz1#
这肯定是一个问题的大量研究。
但答案更简单:
object
的__new__
和__init__
只是“原谅额外参数”的特殊情况,在某种程度上,使用自定义的__init__
方法创建新类感觉很自然,而不需要摆弄__new__
。因此,简而言之,
object
new检查它正在示例化的类是否有自定义__init__
,而没有自定义__new__
--如果有,它会“原谅”额外的args和kwargs。对象的默认值__init__
则匡威:它检查它正在“初始化”的类是否有自定义的__new__
而没有自定义的__init__
。如果是这样,它也会原谅(和忘记)任何额外的参数。这里的“自定义”验证只是检查__mro__
中的任何类的dict中是否存在__new__
方法-因此即使在子类中设置相同的object.__new__
类也不会起作用。这个听起来奇怪的特殊情况是必要的,并且在Python中已经存在了很长一段时间,因为如果没有它,每当创建一个带参数的
__init__
方法的类时,却没有实现一个可能失败的__new__
方法-所以通过使用一个更简单的__init__
方法而不是修改__new__
来“简化类定制”的情况将是毫无意义的。下面是REPL上的几个例子,可以清楚地说明这一点:
最后但并非最不重要:
对于您的情况,您不能简单地将
object.__new__
恢复到一个自定义的“孙女”类中-您将需要改为检查__orig_new__
是否是object.__new__
,如果是,使用一个自定义的__new__
,它将在调用object.__new__
之前删除额外的参数。