代码之家  ›  专栏  ›  技术社区  ›  Cornélio Sousa

对象的行为__new__ Python dunder。引擎盖下面发生了什么?

  •  4
  • Cornélio Sousa  · 技术社区  · 1 年前

    我正在Python中尝试元编程(CPython 3.10.13),并注意到一些奇怪的行为 object.__new__ (至少对我来说很奇怪)。看看下面的实验(不是实用的代码,只是一个 实验 )以及评论。请注意 对象新 似乎根据第一个论点改变了它的行为:

    # Empty class inherit __new__ and __init__ from object
    class Empty:
        pass
    
    # Confirmation of inheritance
    assert Empty.__new__ is object.__new__, "Different __new__"
    assert Empty.__init__ is object.__init__, "Different __init__"
    
    empty_obj = Empty()
    uinit_empty_obj = object.__new__(Empty)
    
    assert type(empty_obj) is type(uinit_empty_obj), "Different types"
    
    try:
        object.__new__(Empty, 10, 'hi', hello='bye')
    except TypeError as e:
        # repr(e) mentioned the Empty class
        print(repr(e))
    
    # Overwrite the object __new__ and __init__ methods
    # __new__ and __init__ with the same signature
    class Person:
        def __new__(cls, name, age):
            """Does nothing bassicaly. Just overwrite `object.__new__`."""
            print(f'Inside {cls.__name__}.__new__')
            return super().__new__(cls)
        
        def __init__(self, name, age):
            print(f'Inside {type(self).__name__}.__init__')
            self.name = name
            self.age = age
    
    a_person = Person('John Doe', 25)
    uinit_person = Person.__new__(Person, 'Michael', 40)
    
    try:
        # Seems an obvious error since object() doesn't take any arguments
        another_uinit_person = object.__new__(Person, 'Ryan', 25)
    except TypeError as e:
        # Indeed raises TypeError, but now there isn't a mention of the Person class in repr(e)
        print('`another_uinit_person` :', repr(e))
    
    # Now, some weird things happen (well, weird for me).
    # Inherit __new__ from object and overwrite __init__.
    # __new__ and __init__ with unmatching signatures.
    # A basic Python class. Works just fine like suppose to.
    class Vehicle:
        def __init__(self, model):
            self.model = model
    
    # Confirmation of __new__ inheritance.
    assert Vehicle.__new__ is object.__new__, "Nop, it isn't"
    
    a_vehicle = Vehicle('Honda')
    
    # I would understand if CPython autogenerated a __new__ method matching __init__
    # or a __new__ method that accepts all arguments.
    # The following try-except-else suggests the last, but the assert statement above 
    # indicates that Vehicle.__new__ is actually object.__new__.
    try:
        # Doesn't raise any exceptions
        uinit_vehicle = Vehicle.__new__(Vehicle, 'Honda', 10, ('four-wheels',), hello='bye')
    except Exception as e:
        print(repr(e))
    else:
        print('`uinit_vehicle` : constructed just fine', uinit_vehicle)
    
    # Now the following runs just fine
    try:
        # Doesn't raise any exceptions
        another_unit_vehicle = object.__new__(Vehicle, 'Toyota')
        another_unit_vehicle = object.__new__(Vehicle, 'Toyota', 100, four_wheels=True)
    except Exception as e:
        print(repr(e))
    else:
        print('`another_unit_vehicle` : constructed just fine:', another_unit_vehicle)
    

    我得到了以下输出:

    TypeError('Empty() takes no arguments')
    Inside Person.__new__
    Inside Person.__init__
    Inside Person.__new__
    `another_uinit_person` : TypeError('object.__new__() takes exactly one argument (the type to instantiate)')
    `uinit_vehicle` : constructed just fine <__main__.Vehicle object at 0x00000244D15A7A90>
    `another_unit_vehicle` : constructed just fine: <__main__.Vehicle object at 0x00000244D15A7A30>
    

    我的问题:

    1. 为什么是第一个 TypeError 提到了 Empty 第二节课 对象新 ?
    2. 为什么? object.__new__(Person, 'Ryan', 25) 提高 类型错误 object.__new__(Vehicle, 'Toyota') object.__new__(Vehicle, 'Toyota', 100, four_wheels=True) 没有

    大体上 什么 对象新 在引擎盖下面做什么?

    在我看来,它对第一个参数的执行了一个有点奇怪的检查 __new__ 和/或 __init__ 重写方法(如果有的话)。

    1 回复  |  直到 1 年前
        1
  •  5
  •   Blckknght    1 年前

    Python的 object.__init__ object.__new__ 在通常的情况下,当其中一个参数被重写,而另一个没有被重写时,基本方法会抑制关于多余参数的错误。非overriden方法将忽略额外的参数,因为它们通常是自动传入的(而不是通过显式调用 __new__ __init__ 程序员应该更清楚的地方)。

    也就是说,这两个类都不会在它们继承的方法中引起问题:

    class OnlyNew:
        def __new__(self, *args):
            pass
    
        # __init__ is inherited from object
    
    class OnlyInit:
        def __init__(self, *args):
            pass
    
        # __new__ is inherited from object
    
    # tests:
    object.__new__(OnlyInit, 1, 2, 3, 4)                  # no error
    object.__init__(object.__new__(OnlyNew), 1, 2,3, 4)   # also no error
    

    但是,当重写其中一个方法时,在调用重写方法的基类版本时,必须避免参数过多。

    # bad tests:
    try:
        object.__new__(OnlyNew, 1, 2, 3, 4)
    except Exception as e:
        print(e) # object.__new__() takes exactly one argument (the type to instantiate)
    try:
        object.__init__(object.__new__(OnlyInit), 1, 2, 3, 4)
    except Exception as e:
        print(e) # object.__init__() takes exactly one argument (the type to instantiate)
    

    此外,如果您覆盖 二者都 __初始化__ ,您需要在没有额外参数的情况下调用基类方法,因为您 应该 如果你同时实现这两种方法,就要知道你在做什么。

    class OverrideBoth:
        def __new__(self, *args):
            pass
    
        def __init__(self, *args):
            pass
    
    # more bad tests, object has zero tolerance for extra arguments in this situation
    try:
        object.__new__(OverrideBoth, 1, 2, 3, 4)
    except Exception as e:
        print(e) # object.__new__() takes exactly one argument (the type to instantiate)
    
    try:
        object.__init__(object.__new__(OverrideBoth), 1, 2,3, 4)
    except Exception as e:
        print(e) # object.__init__() takes exactly one argument (the instance to initialize)
    

    您可以看到这些检查的实施情况 in the CPython source code 。即使你对C不是很了解,也很清楚它在做什么。有一个不同的代码路径处理类,如 Empty 这不会覆盖任何一种方法(这就是为什么异常消息有点不同)。