Я экспериментирую с метапрограммированием на 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__
, о чем программисту следует знать лучше).
То есть ни один из этих классов не вызовет проблем в методах, которые они наследуют:
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)
Более того, если вы переопределяете оба метода: __new__
и __init__
, вам нужно будет вызывать оба метода базового класса без дополнительных аргументов, поскольку вы должны знать, что делаете, если реализуете оба метода.
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)
Реализацию этих проверок вы можете увидеть в исходном коде CPython. Даже если вы не очень хорошо знаете C, вам совершенно ясно, что он делает. Существует другой путь кода, который обрабатывает такие классы, как ваш Empty
, которые не переопределяют ни один из методов (именно поэтому это сообщение об исключении немного отличается).
Есть отличный ответ относительно метакласса, который объясняет
__new__()
- хотя это и не дает прямого ответа на ваш вопрос, это может быть действительно полезно, чтобы понять концепцию этого!