Я экспериментирую с метапрограммированием на 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__()- хотя это и не дает прямого ответа на ваш вопрос, это может быть действительно полезно, чтобы понять концепцию этого!