我正在尝试使用 Python (CPython 3.10.13) 进行元编程,并注意到一些奇怪的行为object.__new__
(至少对我来说很奇怪)。看看以下实验(不是实际代码,只是一个实验)和评论。请注意,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>
我的问题:
- 为什么第一个
TypeError
提到了Empty
课程而第二个只是提到了object.__new__
? - 为什么
object.__new__(Person, 'Ryan', 25)
提高了TypeError
,object.__new__(Vehicle, 'Toyota')
而object.__new__(Vehicle, 'Toyota', 100, four_wheels=True)
没提高呢?
基本上:object.__new__
引擎盖下面有什么?
在我看来,它正在对第一个参数__new__
和/或__init__
覆盖方法(如果有)执行有点奇怪的检查。
Python
object.__init__
和object.__new__
基本方法在常见情况下会抑制有关多余参数的错误,即其中一个已被覆盖,而另一个尚未覆盖。未覆盖的方法将忽略额外的参数,因为它们通常会自动传递(而不是通过显式调用__new__
或__init__
程序员应该更了解的情况)。也就是说,这两个类都不会在它们继承的方法中引起问题:
但是,当您重写其中一个方法时,在调用所重写方法的基类版本时,必须避免使用过多的参数。
此外,如果您覆盖和
__new__
,则__init__
需要同时调用基类方法而不使用任何额外的参数,因为如果您正在实现这两种方法,您应该知道自己在做什么。您可以在 CPython 源代码中看到这些检查的实现。即使您不太了解 C,也可以很清楚地知道它在做什么。有一个不同的代码路径来处理像您这样的类,
Empty
这些类不会覆盖任何一种方法(这就是为什么该异常消息有点不同)。