python-3.x 为什么object.__new__接受参数?

yvt65v4c  于 2023-06-25  发布在  Python
关注(0)|答案(1)|浏览(91)

除了在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__方法之后也是相同的。

与普通类比较

让我们取两个类XY(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>

此外,XY使用与Bobject相同的__new__方法。
那么,让我们示例化YB并比较结果:

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
omqzjyyz

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上的几个例子,可以清楚地说明这一点:

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)

最后但并非最不重要:

对于您的情况,您不能简单地将object.__new__恢复到一个自定义的“孙女”类中-您将需要改为检查__orig_new__是否是object.__new__,如果是,使用一个自定义的__new__,它将在调用object.__new__之前删除额外的参数。

相关问题