Pae*_*els 5 python metaclass instantiation object-construction python-3.x
除了“再次”询问有关Python__new__和__init__Python 的明显问题之外,我可以保证,我知道它的作用。我将展示一些奇怪的、在我看来没有记录的行为,为此我寻求专业帮助:)。
__slots__我正在使用用户定义的元类(称为 )实现多个功能,例如抽象方法、抽象类、必须重写的方法、单调行为、槽类(自动推断)和混合类(延迟槽) ExtendedType。以下代码可以在开发分支的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)
Run Code Online (Sandbox Code Playgroud)
实例化时B我们会尝试两种情况:
b = B(5)TypeError: object.__new__() takes exactly one argument (the type to instantiate)
Run Code Online (Sandbox Code Playgroud)
b = B()TypeError: B.__init__() missing 1 required positional argument: 'arg'
Run Code Online (Sandbox Code Playgroud)
后一种情况的错误消息是预期的,因为__init__需要B一个参数arg。奇怪的行为是在情况 1 中,它报告object.__new__()除了类型之外不接受其他参数。
因此,让我们研究一下交换方法是否正常工作:
TypeError: object.__new__() takes exactly one argument (the type to instantiate)
Run Code Online (Sandbox Code Playgroud)
结果:
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>
Run Code Online (Sandbox Code Playgroud)
因此,保留的方法与类中的方法__new_orig__相同,并且在交换回类中的方法object.__new__后再次相同。__new__B
让我们看两个类X并Y(X)实例化它们:
TypeError: B.__init__() missing 1 required positional argument: 'arg'
Run Code Online (Sandbox Code Playgroud)
这当然可行,但是__new__方法不同吗?
print("object.__new__ ", object.__new__)
print("A.__new_orig__ ", A.__new_orig__)
print("A.__new__ ", A.__new__)
print("B.__new__ ", B.__new__)
Run Code Online (Sandbox Code Playgroud)
也X可以Y使用与或相同的__new__方法。Bobject
因此,让我们实例化YandB并比较结果:
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>
Run Code Online (Sandbox Code Playgroud)
结果:
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)
Run Code Online (Sandbox Code Playgroud)
问题1:为什么new接受Y的参数,而不接受B的参数?
当一个对象被创建时,__call__会执行元类的方法,大致翻译为:
class X:
pass
class Y(X):
def __init__(self, arg):
self.arg = arg
y = Y(3)
Run Code Online (Sandbox Code Playgroud)
它首先调用__new__创建实例,然后调用__init__初始化对象。有人可能会争论并说:“也许在调用中有神奇的行为”来检查是否调用了内置方法或用户定义的方法”......
让我们快速检查一下object.__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>
Run Code Online (Sandbox Code Playgroud)
结果:
TypeError: object() takes no arguments
Run Code Online (Sandbox Code Playgroud)
观察:错误消息与我们之前得到的不同。这表示“没有参数”,另一个表示“只有一个参数”。
或者,我们可以跳过元类来手动创建一个对象:
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)
Run Code Online (Sandbox Code Playgroud)
结果:
Y.__new__(Y, 3) <__main__.Y object at 0x0000020ED770BD40>
y.__init__(3) 3
Run Code Online (Sandbox Code Playgroud)
在这里我们清楚地看到__new__可以接受额外的参数并忽略它们。
因此,让我们与手动实例创建进行比较B:
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)
Run Code Online (Sandbox Code Playgroud)
结果:
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)
Run Code Online (Sandbox Code Playgroud)
问题2:同一个方法怎么会有不同的行为和异常处理?
补充笔记:
M.__new__或交换XXX.__new__方法而不是M.__call__,因此对象创建时间不受影响。修改元类调用会对性能产生巨大影响。附件:
对于一个问题肯定需要进行大量研究。
但答案更简单:
objects__new__和__init__简单的特殊情况“额外参数的宽恕”,以一种使用自定义__init__方法创建新类感觉很自然的方式,而无需摆弄__new__.
所以,简而言之,objectnew 检查它实例化的类是否有自定义__init__和没有自定义__new__- 如果是这样,它“原谅”额外的 args 和 kwargs。对象的默认值则__init__相反:它检查它正在“初始化”的类是否有 custom__new__和没有 custom __init__。如果是这样,它也会原谅(并忘记)任何额外的参数。这里的“自定义”验证只是检查-中__new__的任何类的字典__mro__中是否存在方法,因此即使object.__new__在子类中设置相同的类也不起作用。
这种听起来奇怪的特殊情况是必要的,并且在 Python 中已经存在很长时间了,因为如果没有它,每当创建一个带有__init__参数的方法的类时,如果不实现__new__也会失败的方法 - 所以“简化”的情况通过使用更简单的__init__方法而不是修改来进行类定制”__new__是没有意义的。
以下是 REPL 上的一些示例,可以清楚地说明这一点:
In [11]: class A(object): pass
In [12]: b = A.__new__(A, 3)
TypeError (...)
TypeError: A() takes no arguments
# There is no custom `__init__`, so it fails
In [13]: class A(object):
...: def __init__(self, *args):
...: pass
...:
In [14]: b = A.__new__(A, 3)
# There was a custom `__init__` so, object.__new__ forgives us.
# and finally your case, both a __new__ and __init__ even if `cls.__new__ is object.__new__` is true, errors:
In [17]: class A(object):
...: def __new__(cls, *args):
...: raise NotImplementedError()
...: def __init__(self, *args):
...: pass
...:
In [18]: class B(A):
...: __new__ = object.__new__
...:
In [19]: c = B() #<- works with no args
In [20]: c = B(3)
TypeError
TypeError: object.__new__() takes exactly one argument (the type to instantiate)
# And just one example with __init__ to show the converse case:
In [28]: class A(object):
...: def __new__(cls, *args):
...: # I strip down the extra args:
...: return super().__new__(cls)
...: # no custom __init__
...:
In [29]: b = A(3) # <- works
In [30]: class A(object):
...: def __new__(cls, *args):
...: # I strip down the extra args:
...: return super().__new__(cls)
...: # with a custom __init__ forwarding extra args
...: def __init__(self, *args):
...: print("init")
...: super().__init__(*args)
...:
In [31]: b = A(3) # <- errors out
init
TypeError (...)
Cell In[30], line 8, in A.__init__(self, *args)
6 def __init__(self, *args):
7 print("init")
----> 8 super().__init__(*args)
TypeError: object.__init__() takes exactly one argument (the instance to initialize)
Run Code Online (Sandbox Code Playgroud)
对于您的情况,您不能简单地object.__new__在自定义“孙女”类中恢复 - 您将需要检查是否__orig_new__是object.__new__,如果是,则使用__new__在调用之前删除额外参数的自定义object.__new__。
| 归档时间: |
|
| 查看次数: |
249 次 |
| 最近记录: |