Я просто пытаюсь упростить один из моих классов и ввел некоторые функциональные возможности в том же стиле, что и шаблон проектирования на основе веса .
Тем не менее, я немного запутался, почему __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
Почему?
Ответы:
С апреля 2008 пост: когда использовать
__new__
против__init__
? на mail.python.org.Вы должны учитывать, что то, что вы пытаетесь сделать, обычно делается на фабрике, и это лучший способ сделать это. Использование
__new__
не является хорошим чистым решением, поэтому, пожалуйста, рассмотрите использование фабрики. Вот вам хороший заводской пример .источник
__new__
внутри класса Factory, который стал довольно чистым, так что спасибо за ваш вклад.__new__
должно быть строго ограничено указанными случаями. Я нашел это очень полезным для реализации расширяемых универсальных фабрик классов - см. Мой ответ на вопрос « Неправильное использование__new__
для генерации классов в Python»? для примера этого.__new__
метод статического класса, а__init__
метод экземпляра.__new__
должен сначала создать экземпляр, так что__init__
можете его инициализировать. Обратите внимание, что__init__
принимает вself
качестве параметра. Пока вы создаете экземпляр, нетself
.Теперь я понял, что вы пытаетесь реализовать шаблон синглтона в Python. Есть несколько способов сделать это.
Также, начиная с Python 2.6, вы можете использовать декораторы классов .
источник
MyClass
связывается с функцией, которая возвращает экземпляры исходного класса. Но теперь нет никакого способа обратиться к исходному классу вообще, и,MyClass
будучи функцией, ломаетisinstance
/issubclass
проверяет, получает доступ к атрибутам / методам класса напрямую какMyClass.something
, называет класс вsuper
вызовах и т. Д., И т. Д., И т. Д., Является ли это проблемой или нет, зависит от класс, к которому вы применяете это (и остальная часть программы).В большинстве известных языков OO выражение like
SomeClass(arg1, arg2)
будет выделять новый экземпляр, инициализировать атрибуты экземпляра и затем возвращать его.В большинстве известных ОО-языков часть «инициализировать атрибуты экземпляра» может быть настроена для каждого класса путем определения конструктора , который по сути является просто блоком кода, который работает с новым экземпляром (используя аргументы, предоставленные выражению конструктора). ) для установки любых начальных условий. В Python это соответствует
__init__
методу класса .Python -
__new__
это не что иное, как похожая настройка для каждого класса для части «выделите новый экземпляр». Это, конечно, позволяет вам делать необычные вещи, такие как возврат существующего экземпляра, а не выделение нового. Так что в Python мы не должны думать об этой части как об обязательном распределении; все, что нам нужно, - это__new__
найти подходящий экземпляр откуда-то.Но это все еще только половина работы, и у системы Python нет возможности узнать, что иногда вы хотите запустить другую половину задания (
__init__
) впоследствии, а иногда нет. Если вы хотите такое поведение, вы должны сказать об этом явно.Часто вы можете
__new__
выполнить рефакторинг так, чтобы вам было нужно , или вам не нужно__new__
, или так, что он__init__
ведет себя по-другому на уже инициализированном объекте. Но если вы действительно хотите, Python действительно позволяет вам переопределить «работу», так чтоSomeClass(arg1, arg2)
это не обязательно вызов,__new__
а затем__init__
. Для этого вам нужно создать метакласс и определить его__call__
метод.Метакласс - это просто класс класса. И
__call__
метод класса контролирует, что происходит, когда вы вызываете экземпляры класса. Таким образом , метод метакласса__call__
контролирует, что происходит, когда вы вызываете класс; то есть позволяет переопределить механизм создания экземпляра от начала до конца . Это уровень, на котором вы можете наиболее элегантно реализовать совершенно нестандартный процесс создания экземпляра, такой как шаблон синглтона. На самом деле, с менее чем 10 строк кода вы можете реализоватьSingleton
метакласса , что тогда даже не потребует от вас futz с__new__
на всех , и может превратить любое иное-нормальный класс в синглтон, просто добавляя__metaclass__ = Singleton
!Однако это, вероятно, более глубокая магия, чем на самом деле оправдано для этой ситуации!
источник
Цитировать документацию :
источник
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.
Это важный момент. Если вы возвращаете другой экземпляр, оригинал__init__
никогда не вызывается.__new__
проверяется на предмет его класса, метод выдаст ошибку, а не пропустит неудачную проверку, потому что я не понимаю, почему вы захотите вернуть что-либо, кроме объекта класса ,Я понимаю, что этот вопрос довольно старый, но у меня была похожая проблема. Следующие сделали то, что я хотел:
Я использовал эту страницу в качестве ресурса 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__
снова и снова.Вот общий подход к шаблону Singleton, который расширяет ответ vartec выше и исправляет его:
Полная история здесь .
Другой подход, который на самом деле предполагает
__new__
использование методов класса:Обратите внимание, что при таком подходе вам необходимо украсить ВСЕ ваши методы
@classmethod
, потому что вы никогда не будете использовать какой-либо реальный экземплярMyClass
.источник
Ссылаясь на этот документ :
Относительно того, чего вы хотите достичь, в том же документе есть информация о паттерне Singleton.
Вы также можете использовать эту реализацию из PEP 318, используя декоратор
источник
init
из__new__
звуков хакеров. Для этого и нужны метаклассы.выходы:
NB. Как
M._dict
свойство побочного эффекта автоматически становится доступным из-A
за того,A._dict
чтобы не перезаписать его случайно.источник
__init__
метода, который устанавливаетcls._dict = {}
. Вы, вероятно, не хотите, чтобы словарь использовался всеми классами этого метатипа (но +1 для идеи).__new__ должен возвращать новый пустой экземпляр класса. Затем вызывается __init__ для инициализации этого экземпляра. Вы не звоните __init__ в «новом» случае __new__, поэтому он вызывается для вас. Код, который вызывает
__new__
, не отслеживает, был ли вызван __init__ в конкретном экземпляре или нет, потому что вы делаете здесь что-то очень необычное.Вы можете добавить атрибут к объекту в функции __init__, чтобы указать, что он был инициализирован. Сначала проверьте наличие этого атрибута в __init__ и больше не продолжайте, если он был.
источник
Обновление ответа @AntonyHatchkins: вам, вероятно, нужен отдельный словарь экземпляров для каждого класса метатипа, что означает, что у вас должен быть
__init__
метод в метаклассе для инициализации объекта вашего класса с этим словарем, а не для того, чтобы сделать его глобальным для всех классов.Я пошел дальше и обновил исходный код с помощью
__init__
метода и изменил синтаксис на нотацию Python 3 (вызов без аргументовsuper
и метакласс в аргументах класса, а не в качестве атрибута).В любом случае, важным моментом здесь является то, что ваш инициализатор класса (
__call__
метод) не будет выполняться__new__
или,__init__
если ключ найден. Это намного чище, чем использование__new__
, которое требует, чтобы вы отметили объект, если хотите пропустить__init__
шаг по умолчанию .источник
Копаем немного глубже в это!
Тип универсального класса в CPython - это
type
его базовый классObject
(если вы явно не определите другой базовый класс, такой как метакласс). Последовательность вызовов низкого уровня можно найти здесь . Первый вызванный метод - этоtype_call
который затем вызывает,tp_new
а затемtp_init
.Интересная часть здесь - это
tp_new
вызовObject
нового метода (базового класса),object_new
который выполняетtp_alloc
(PyType_GenericAlloc
), который выделяет память для объекта :)В этот момент объект создается в памяти, а затем вызывается
__init__
метод. Если__init__
это не реализовано в вашем классе, тоobject_init
вызывается, и это ничего не делает :)Затем
type_call
просто возвращает объект, который привязывается к вашей переменной.источник
Следует рассматривать
__init__
как простой конструктор в традиционных языках ОО. Например, если вы знакомы с Java или C ++, конструктору неявно передается указатель на его собственный экземпляр. В случае с Java этоthis
переменная. Если бы нужно было проверить байт-код, сгенерированный для Java, можно было бы заметить два вызова. Первый вызов относится к «новому» методу, а затем следующий вызов к методу init (который является фактическим вызовом определяемого пользователем конструктора). Этот двухэтапный процесс позволяет создать фактический экземпляр перед вызовом метода конструктора класса, который является просто еще одним методом этого экземпляра.Теперь, в случае с Python,
__new__
это добавленное средство, которое доступно для пользователя. Java не обеспечивает такой гибкости из-за своего типизированного характера. Если язык предоставил эту возможность, разработчик__new__
мог бы сделать много вещей в этом методе перед возвратом экземпляра, включая создание совершенно нового экземпляра несвязанного объекта в некоторых случаях. И этот подход также хорошо работает, особенно для неизменяемых типов в случае Python.источник
Я думаю, что аналогия с C ++ была бы полезна здесь:
__new__
просто выделяет память для объекта. Переменные экземпляра объекта нуждаются в памяти для его хранения, и это то, что сделал__new__
бы шаг .__init__
инициализировать внутренние переменные объекта конкретными значениями (может быть по умолчанию).источник
__init__
Вызывается после того,__new__
так что , когда вы переопределить его в подклассе, ваш добавлен код будет вызываться.Если вы пытаетесь создать подкласс для класса, у которого уже есть класс
__new__
, то кто-то, не подозревая об этом, может начать с адаптации__init__
и переадресации вызова на подкласс__init__
. Это соглашение вызова__init__
после того, как__new__
помогает этой работе , как и ожидалось.Тем не
__init__
менее, необходимо учесть все параметры, которые__new__
необходимы суперклассу , но если этого не сделать, обычно возникает явная ошибка времени выполнения. И,__new__
вероятно, следует явно разрешить*args
и '** kw', чтобы было ясно, что расширение в порядке.Из- за поведения, описанного в оригинальном постере, обычно плохо иметь обоих
__new__
и__init__
в одном классе на одном и том же уровне наследования.источник
Не так много причин, кроме того, что это просто сделано таким образом.
__new__
не несет ответственности за инициализацию класса, как это делает какой-то другой метод (__call__
возможно - я не знаю точно).Вы могли бы
__init__
ничего не делать, если он уже был инициализирован, или вы могли бы написать новый метакласс с новым,__call__
который вызывает только__init__
новые экземпляры, а в противном случае просто возвращает__new__(...)
.источник
Простая причина заключается в том, что new используется для создания экземпляра, а init используется для инициализации экземпляра. Перед инициализацией экземпляр должен быть создан первым. Вот почему new должен быть вызван перед init .
источник
Теперь у меня та же проблема, и по некоторым причинам я решил избегать декораторов, фабрик и метаклассов. Я сделал это так:
Основной файл
Примеры классов
В использовании
Попробуйте онлайн!
источник