Специальный метод object.__new__()
- это статический метод, который создает новые экземпляры класса и, следовательно, может использоваться для настройки этого процесса.
Dunder методы object.__init__()
и object.__new__()
могут выглядеть довольно похожими на первый взгляд. Метод object.__init__()
инициализирует экземпляр класса, но этот экземпляр должен быть создан до того, как его можно будет инициализировать, и это работа метода dunder object.__new__()
.
object.__new__
Метод object.__new__
принимает в качестве аргументов класс, экземпляр которого пытаемся создать, и все аргументы, переданные конструктору класса, как показано во фрагменте ниже:
class C: def __new__(cls, *args, **kwargs): print(cls, args, kwargs) return super().__new__(cls) >>> C() # <class '__main__.C'> () {} >>> C(73, True, a=15) # <class '__main__.C'> (73, True) {'a': 15}
Оператор return
включает в себя super().__new__(cls)
, что является типичным способом воплощения в жизнь объекта, который необходимо создать.
object.__new__
Метод object.__new__
может возвращать любой объект, а возвращаемое значение метода object.__new__
- это результат, который получается при создании экземпляра класса.
Например, если создать класс C
, метод __new__
которого возвращает 73, то каждый раз при создании экземпляра класса, C()
получим число 73:
class C: def __new__(cls): return 73 >>> c = C() >>> print(c) # 73 >>> print(type(c)) # <class 'int'>
Обычно метод object.__new__
возвращает экземпляр класса, в котором он находится. В этом случае Python автоматически вызывает метод object.__init__
для возвращенного объекта и передает аргументы, указанные в конструкторе класса. Таким образом, если object.__init__
вызывается, то он получает те же аргументы, которые были получены object.__new__
.
Следующий фрагмент кода показывает, что метод object.__init__
вызывается только в том случае, если возвращаемое значение является экземпляром класса, в котором определен метод object.__new__
:
class C: def __new__(cls, *, return_73): if return_73: return 73 else: return super().__new__(cls) def __init__(self, *args, **kwargs): print("__init__!") >>> x = C(return_73=True) >>> print(x) # 73 >>> y = C(return_73=False) # __init__! >>> print(y) # <__main__.C object at 0x7a88c69c4f20>
Обратите внимание, что Python вызовет object.__init__
для возвращаемого объекта, даже если это экземпляр подкласса класса, в котором выполняется метод object.__new__
!
Ниже, во фрагменте кода создаем класс C
и его подкласс D
. Класс C
определяет метод __new__
и оба класса определяют свои соответствующие методы __init__
. Когда создаем экземпляр класса C
, то фактически получаем экземпляр D
, и метод D.__init__
автоматически вызывается:
class C: def __new__(cls): return super().__new__(D) def __init__(self): print("C.__init__") class D(C): def __init__(self): print("D.__init__") >>> d = C() # D.__init__ >>> print(type(d)) # <class '__main__.D'>
Это может показаться немного странным, но на самом деле это шаблон, который используется в модуле pathlib
.
Дополнительно смотрите:
Когда-нибудь замечали, что если импортировать Path
из pathlib
, а затем создать его экземпляр, то получим объект, который зависит от операционной системы?
Например, на Linux машине получим следующее:
from pathlib import Path print(Path()) # PosixPath('.')
Если бы экземпляр создавался на машине Windows, то получили WindowsPath
вместо PosixPath
. Как модуль это делает, если только создается экземпляр одного и того же класса Path
?
Класс Path
реализует метод __new__
, а метод __new__
проверяет операционную систему, на которой он запущен. Если это машина Windows, то он создаст WindowsPath
. Если это НЕ Windows, он создаст PosixPath
. Поскольку WindowsPath
и PosixPath
унаследованы от Path
, это не повлияет на оставшуюся часть создания и инициализации объекта.
Фрагмент кода ниже имитирует эту структуру (с бесполезным, но гораздо более простым примером):
class Number: def __new__(cls, value): print("Number.__new__", end=", ") if cls is Number: cls = OddNumber if value % 2 else EvenNumber return super().__new__(cls) def __init__(self, value): print("Number.__init__") self.value = value class EvenNumber(Number): def __init__(self, value): print("EvenNumber.__init__", end=", ") super().__init__(value) self.is_even = True class OddNumber(Number): def __init__(self, value): print("OddNumber.__init__", end=", ") super().__init__(value) self.is_even = False >>> x = Number(73) # Number.__new__, OddNumber.__init__, Number.__init__ >>> print(type(x)) # <class '__main__.OddNumber'> >>> print(x.value) # 73