Почему init () всегда вызывается после new ()?
Я просто пытаюсь оптимизировать один из моих классов и ввел некоторые функции в том же стиле, что и flyweight шаблон дизайна.
тем не менее, я немного смущен, почему __init__
всегда вызывается после __new__
. Я этого не ожидал. Может кто-нибудь сказать мне, почему это происходит и как я могу реализовать эту функцию по-другому? (Помимо ввода реализации в __new__
, который чувствует себя довольно суховато.)
вот пример:
class A(object):
_dict = dict()
def __new__(cls):
if 'key' in A._dict:
print "EXISTS"
return A._dict['key']
else:
print "NEW"
return super(A, cls).__new__(cls)
def __init__(self):
print "INIT"
A._dict['key'] = self
print ""
a1 = A()
a2 = A()
a3 = A()
выходы:
NEW
INIT
EXISTS
INIT
EXISTS
INIT
почему?
17 ответов
использовать _ _ new__ когда вам нужно контролировать создание нового экземпляра. Использовать _ _ init__ когда вам нужно контролировать инициализацию нового экземпляра.
_ _ new__ является первым шагом создания экземпляра. Это называется first, и это ответственный за возвращение нового экземпляр вашего класса. В отличие от, _ _ init__ ничего не возвращает; он отвечает только за инициализацию экземпляр после он был создан.
В общем, вам не нужно переопределить _ _ new__ Если вы подклассы неизменяемого типа str, int, unicode или кортеж.
From:http://mail.python.org/pipermail/tutor/2008-April/061426.html
вы должны учитывать, что то, что вы пытаетесь сделать, обычно делается с помощью завод и это лучший способ сделать это. Используя _ _ new__ - это не хорошо чистое решение поэтому, пожалуйста, рассмотрите использование фабрики. Вот вам хороший пример фабрики.
__new__
является статическим методом класса, в то время как __init__
- это метод экземпляра.
__new__
должен сначала создать экземпляр, поэтому __init__
можете инициализировать его. Обратите внимание, что __init__
принимает self
в качестве параметра. Пока вы не создадите экземпляр нет self
.
теперь, я понимаю, что вы пытаетесь реализовать синглтон шаблон в Python. Есть несколько способы сделать это.
кроме того, начиная с Python 2.6, вы можете использовать class декораторы.
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
в большинстве известных языков OO выражение типа SomeClass(arg1, arg2)
выделит новый экземпляр, инициализирует атрибуты экземпляра, а затем вернет его.
в большинстве известных языков OO часть "инициализация атрибутов экземпляра" может быть настроена для каждого класса путем определения конструктор, который в основном является просто блоком кода, который работает на новом экземпляре (используя аргументы, предоставленные выражению конструктора), чтобы настроить все начальные условия желательны. В Python, это соответствует классу' __init__
метод.
в Python __new__
- это не что иное, как аналогичная настройка для каждого класса части "выделить новый экземпляр". Это, конечно, позволяет вам делать необычные вещи, такие как возврат существующего экземпляра, а не выделение нового. Поэтому в Python мы не должны думать об этой части как обязательно включающей выделение; все, что нам нужно, это __new__
придумывает подходящий экземпляр откуда-то.
но это все еще только половина задания, и система Python не может знать, что иногда вы хотите запустить другую половину задания (__init__
) после этого, а иногда и нет. Если вы хотите такого поведения, вы должны сказать это прямо.
часто, вы можете выполнить рефакторинг, так что вам нужно только __new__
, или так вам не нужно __new__
или так __init__
ведет себя по-разному на уже инициализированный объект. Но если вы действительно хотите, Python действительно позволяет вам переопределить "работу", так что SomeClass(arg1, arg2)
Не обязательно называть __new__
следовал по __init__
. Для этого необходимо создать метакласс и определить его __call__
метод.
метакласс-это просто класс класса. И класс!--12--> метод управляет тем, что происходит при вызове экземпляров класса. Так метакласс' __call__
метод управляет тем, что происходит при вызове класса; т. е. он позволяет переопределите механизм создания экземпляра от начала до конца. Это уровень, на котором вы можете наиболее элегантно реализовать полностью нестандартный процесс создания экземпляра, такой как одноэлементный шаблон. Фактически, с менее чем 10 строками кода Вы можете реализовать Singleton
метакласс, который тогда даже не требует от вас futz с __new__
на всех, и любой в противном случае-нормальный класс в синглтон, просто добавив __metaclass__ = Singleton
!
class Singleton(type):
def __init__(self, *args, **kwargs):
super(Singleton, self).__init__(*args, **kwargs)
self.__instance = None
def __call__(self, *args, **kwargs):
if self.__instance is None:
self.__instance = super(Singleton, self).__call__(*args, **kwargs)
return self.__instance
однако это, вероятно, более глубокая магия, чем действительно оправдано для этой ситуации!
цитировать документация:
типичные реализации создайте новый экземпляр класса, вызвав метод __new _ _ () суперкласса с использованием " super (currentclass, cls).__ new__(cls [,...])"с соответствующими аргументами, а затем изменение вновь созданного экземпляра по мере необходимости перед его возвратом.
...
Если _ _ new__ () не возвращает экземпляр cls, то новый метод __init_ _ () экземпляра будет не вызываться.
_ _ new__ () предназначен главным образом для разрешения подклассов неизменяемых типы (например, int, str или tuple) для настройки создания экземпляра.
Я понимаю, что этот вопрос довольно старый, но у меня была аналогичная проблема. Следующие сделали то, что я хотел:
class Agent(object):
_agents = dict()
def __new__(cls, *p):
number = p[0]
if not number in cls._agents:
cls._agents[number] = object.__new__(cls)
return cls._agents[number]
def __init__(self, number):
self.number = number
def __eq__(self, rhs):
return self.number == rhs.number
Agent("a") is Agent("a") == True
я использовал эту страницу как ресурс http://infohost.nmt.edu/tcc/help/pubs/python/web/new-new-method.html
Я думаю, что простой ответ на этот вопрос заключается в том, что, если __new__
возвращает значение того же типа, что и класс __init__
функция выполняется, иначе она не будет. В этом случае ваш код возвращает A._dict('key')
который является тем же классом, что и cls
, так что __init__
будет выполнен.
, когда __new__
возвращает экземпляр того же класса, __init__
запускается после возвращаемого объекта. Т. е. вы не можете использовать __new__
предупреждения __init__
запуск. Даже если вы возвращаете ранее созданный объект из __new__
, это будет двойным (тройным и т. д...) инициализировано __init__
снова и снова.
вот общий подход к Одноэлементному шаблону, который расширяет ответ vartec выше и исправляет его:
def SingletonClass(cls):
class Single(cls):
__doc__ = cls.__doc__
_initialized = False
_instance = None
def __new__(cls, *args, **kwargs):
if not cls._instance:
cls._instance = super(Single, cls).__new__(cls, *args, **kwargs)
return cls._instance
def __init__(self, *args, **kwargs):
if self._initialized:
return
super(Single, self).__init__(*args, **kwargs)
self.__class__._initialized = True # Its crucial to set this variable on the class!
return Single
вся история здесь.
другой подход, который на самом деле включает в себя __new__
использовать classmethods:
class Singleton(object):
__initialized = False
def __new__(cls, *args, **kwargs):
if not cls.__initialized:
cls.__init__(*args, **kwargs)
cls.__initialized = True
return cls
class MyClass(Singleton):
@classmethod
def __init__(cls, x, y):
print "init is here"
@classmethod
def do(cls):
print "doing stuff"
Пожалуйста, обратите внимание, что при таком подходе вам нужно украсить все ваши методы с @classmethod
, потому что вы никогда не будете использовать любой реальный экземпляр MyClass
.
class M(type):
_dict = {}
def __call__(cls, key):
if key in cls._dict:
print 'EXISTS'
return cls._dict[key]
else:
print 'NEW'
instance = super(M, cls).__call__(key)
cls._dict[key] = instance
return instance
class A(object):
__metaclass__ = M
def __init__(self, key):
print 'INIT'
self.key = key
print
a1 = A('aaa')
a2 = A('bbb')
a3 = A('aaa')
выходы:
NEW
INIT
NEW
INIT
EXISTS
NB как побочный эффект M._dict
свойство автоматически становится доступным из A
as A._dict
так что будьте осторожны, чтобы не перезаписать случайно.
__новый__ должен возвращать новый экземпляр класса. __init__, которая затем вызывается для инициализации экземпляра. Вы не вызываете _ _ init__ в "новом" случае _ _ new__, поэтому он вызывается для вас. Код, который вызывает __new__
не отслеживает, был ли__ init _ _ вызван в конкретном экземпляре или нет, и не должен, потому что вы делаете что-то очень необычное здесь.
вы можете добавить атрибут к объекту в функцию __init__, чтобы указать, что это инициализирован. Проверьте наличие этого атрибута как первое в __init__ и не продолжайте дальше, если это было.
обновление до @AntonyHatchkins ответ, вы, вероятно, хотите отдельный словарь экземпляров для каждого класса метатипа, что означает, что вы должны иметь __init__
метод класса для инициализации объектов класса С этот словарь вместо того, чтобы сделать его глобальным для всех классов.
class MetaQuasiSingleton(type):
def __init__(cls, name, bases, attibutes):
cls._dict = {}
def __call__(cls, key):
if key in cls._dict:
print('EXISTS')
instance = cls._dict[key]
else:
print('NEW')
instance = super().__call__(key)
cls._dict[key] = instance
return instance
class A(metaclass=MetaQuasiSingleton):
def __init__(self, key):
print 'INIT'
self.key = key
print()
я пошел вперед и обновил исходный код с помощью __init__
метод и изменил синтаксис на нотацию Python 3 (вызов no-arg на super
и метакласс в аргументы класса вместо атрибута).
в любом случае, важным моментом здесь является то, что ваш инициализатор класса (__call__
способ) не будет выполняться либо __new__
или __init__
если ключ найден. Это намного чище, чем использование __new__
, что требует от вас пометить объект, если вы хотите пропустить значение по умолчанию __init__
шаг.
при подклассах неизменяемых встроенных типов, таких как числа и строки, и иногда в других ситуациях, статический метод новая приходит пригодится. новая является первым шагом в создании экземпляра, вызывается до init.
на новая метод вызывается с классом как его первый аргумент; его обязанность-вернуть новый экземпляр что класс.
сравните это с init: init вызывается с экземпляром как его первый аргумент, и он ничего не возвращает; его ответственность заключается в инициализации экземпляра.
есть ситуации где новый экземпляр создается без вызова init (например когда экземпляр загружается из рассола). Нет способа создать новый экземпляр без вызова новая (хотя в некоторые случаи вы можете сойдет с рук вызов базового класса новая).
Что касается того, что вы хотите достичь, там же doc информация о Singleton pattern
class Singleton(object):
def __new__(cls, *args, **kwds):
it = cls.__dict__.get("__it__")
if it is not None:
return it
cls.__it__ = it = object.__new__(cls)
it.init(*args, **kwds)
return it
def init(self, *args, **kwds):
pass
вы также можете использовать эту реализацию из PEP 318, используя декоратор
def singleton(cls):
instances = {}
def getinstance():
if cls not in instances:
instances[cls] = cls()
return instances[cls]
return getinstance
@singleton
class MyClass:
...
копать немного глубже в это!
тип универсального класса в CPython -type
и его базовый класс Object
(Если вы явно не определяете другой базовый класс, такой как метакласс). Последовательность вызовов низкого уровня можно найти здесь. Первый метод называется type_call
, который затем вызывает tp_new
а то tp_init
.
интересная часть здесь в том, что tp_new
будем называть Object
' s (базовый класс) новый метод object_new
что делает а tp_alloc
(PyType_GenericAlloc
), которая выделяет память для объекта :)
в этот момент объект создается в памяти, а затем __init__
метод вызывается. Если __init__
не реализован в вашем классе, тогда object_init
вызывается, и он ничего не делает :)
затем type_call
просто возвращает объект, который привязывается к вашему переменной.
надо смотреть на __init__
как простой конструктор на традиционных языках OO. Например, если вы знакомы с Java или c++, конструктор передается указатель на свой собственный экземпляр неявно. В случае Java это this
переменной. Если бы нужно было проверить байтовый код, сгенерированный для Java, можно было бы заметить два вызова. Первый вызов-это" новый " метод, а затем следующий вызов-метод init (который является фактическим вызовом определяемого пользователем конструктора). Эта два шаг процесс позволяет создать фактический экземпляр перед вызовом метода конструктора класса, который является просто другой метод этого экземпляра.
теперь, в случае Python,__new__
- это добавленное средство, доступное пользователю. Java не обеспечивает эту гибкость из-за ее типизированного характера. Если язык предоставил это средство, то реализующий __new__
может сделать много вещей в этом методе перед возвратом экземпляра, включая создание совершенно нового экземпляра в некоторых случаях несвязанного объекта. И этот подход также хорошо работает, особенно для неизменяемых типов в случае Python.
на __init__
вызывается после __new__
Так что, когда вы переопределяете его в подклассе, ваш добавленный код все равно будет вызван.
если вы пытаетесь подкласс класса, который уже имеет __new__
, кто-то не знает об этом, может начать с адаптации __init__
и переадресация вызова в подкласс __init__
. Это соглашение призвания __init__
после __new__
помогает работать так, как ожидалось.
на __init__
все еще нужно учитывать любые параметры суперкласс!--1--> необходимо, но если этого не сделать, обычно создается явная ошибка выполнения. И __new__
вероятно, следует явно разрешить *args
и '* * kw', чтобы было ясно, что расширение в порядке.
это вообще дурной тон иметь оба __new__
и __init__
в том же классе, на том же уровне наследования, из-за поведения автора описано.
тем не менее, я немного смущен, почему
__init__
всегда вызывается после__new__
.
не так много причин, кроме того, что это просто делается таким образом. __new__
не несут ответственность за инициализацию класса, какой-то другой метод (__call__
, возможно ... я не знаю точно).
Я не ожидал этого. Может ли кто-нибудь сказать мне, почему это происходит и как я реализую эту функциональность в противном случае? (кроме сдачи реализация в
__new__
, который чувствует себя довольно суховато).
мог бы __init__
ничего не делать, если он уже инициализирован, или вы можете написать новый метакласс с новым __call__
это только звонки __init__
на новых экземплярах, а в противном случае просто возвращает __new__(...)
.
простая причина в том, что new используется для создания экземпляра, в то время как init используется для инициализации экземпляра. Перед инициализацией, экземпляр должен быть создан первым. Вот почему new стоит перед init.
основной файл
def _alt(func):
import functools
@functools.wraps(func)
def init(self, *p, **k):
if hasattr(self, "parent_initialized"):
return
else:
self.parent_initialized = True
func(self, *p, **k)
return init
class Parent:
# Empty dictionary, shouldn't ever be filled with anything else
parent_cache = {}
def __new__(cls, n, *args, **kwargs):
# Checks if object with this ID (n) has been created
if n in cls.parent_cache:
# It was, return it
return cls.parent_cache[n]
else:
# Check if it was modified by this function
if not hasattr(cls, "parent_modified"):
# Add the attribute
cls.parent_modified = True
cls.parent_cache = {}
# Apply it
cls.__init__ = _alt(cls.__init__)
# Get the instance
obj = super().__new__(cls)
# Push it to cache
cls.parent_cache[n] = obj
# Return it
return obj
пример классы
class A(Parent):
def __init__(self, n):
print("A.__init__", n)
class B(Parent):
def __init__(self, n):
print("B.__init__", n)
при использовании
>>> A(1)
A.__init__ 1 # First A(1) initialized
<__main__.A object at 0x000001A73A4A2E48>
>>> A(1) # Returned previous A(1)
<__main__.A object at 0x000001A73A4A2E48>
>>> A(2)
A.__init__ 2 # First A(2) initialized
<__main__.A object at 0x000001A7395D9C88>
>>> B(2)
B.__init__ 2 # B class doesn't collide with A, thanks to separate cache
<__main__.B object at 0x000001A73951B080>
- предупреждение: вы не должны инициализировать Parent, it будет сталкиваются с другими классами-если вы не определили отдельный кэш в каждом из детей, это не то, что мы хотеть.
- предупреждение: кажется, класс с родителем, поскольку дедушка и бабушка ведут себя странно. [Непроверенный]