Почему __init __ () всегда вызывается после __new __ ()? - PullRequest
498 голосов
/ 23 марта 2009

Я просто пытаюсь упростить один из моих классов и ввел некоторые функциональные возможности в том же стиле, что и шаблон проектирования в полулегком .

Однако меня немного смущает вопрос, почему __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

Почему?

Ответы [ 18 ]

523 голосов
/ 23 марта 2009

Используйте __ new __ , когда вам нужно контролировать создание нового экземпляра. использование __ init __ , когда вам нужно контролировать инициализацию нового экземпляра.

__ new __ - первый шаг создания экземпляра. Он называется первым и ответственность за возвращение нового экземпляр вашего класса. По сравнению, __ init __ ничего не возвращает; он отвечает только за инициализацию экземпляр после того, как он был создан.

В общем, вам не нужно переопределить __ новый __ , если вы не наследование неизменяемого типа, как str, int, unicode или tuple.

От: http://mail.python.org/pipermail/tutor/2008-April/061426.html

Вы должны учитывать, что то, что вы пытаетесь сделать, обычно делается с помощью Factory , и это лучший способ сделать это. Использование __ new __ не является хорошим чистым решением, поэтому, пожалуйста, рассмотрите использование фабрики. Вот вам хороший заводской пример .

158 голосов
/ 23 марта 2009

__new__ - метод статического класса, а __init__ - метод экземпляра. __new__ должен сначала создать экземпляр, поэтому __init__ может его инициализировать. Обратите внимание, что __init__ принимает self в качестве параметра. Пока вы не создадите экземпляр, self.

не существует.

Теперь я понял, что вы пытаетесь реализовать шаблон синглтона в Python. Есть несколько способов сделать это.

Также, начиная с Python 2.6, вы можете использовать декораторы класса .

def singleton(cls):
    instances = {}
    def getinstance():
        if cls not in instances:
            instances[cls] = cls()
        return instances[cls]
    return getinstance

@singleton
class MyClass:
  ...
134 голосов
/ 29 декабря 2011

В большинстве известных языков ОО выражение, такое как SomeClass(arg1, arg2), выделит новый экземпляр, инициализирует атрибуты экземпляра, а затем вернет его.

В большинстве известных ОО-языков часть «инициализировать атрибуты экземпляра» можно настроить для каждого класса, определив конструктор , который в основном представляет собой просто блок кода, который работает с новым экземпляром (используя аргументы, предоставленные выражению конструктора), чтобы установить любые начальные условия. В Python это соответствует методу класса __init__.

Python's __new__ - это не что иное, как и подобная настройка для каждого класса части "allocate a new instance". Это, конечно, позволяет вам делать необычные вещи, такие как возврат существующего экземпляра, а не выделение нового. Так что в Python мы не должны думать об этой части как об обязательном распределении; все, что нам нужно, это то, что __new__ придумывает подходящий экземпляр откуда-то.

Но это все еще только половина работы, и у системы Python нет возможности узнать, что иногда вы хотите запустить другую половину работы (__init__) впоследствии, а иногда нет. Если вы хотите такого поведения, вы должны сказать об этом явно.

Часто вы можете выполнить рефакторинг, чтобы вам требовался только __new__, или вам не нужен __new__, или __init__ ведет себя по-другому на уже инициализированном объекте. Но если вы действительно хотите, Python действительно позволяет вам переопределить «работу», так что SomeClass(arg1, arg2) не обязательно вызывает __new__, за которым следует __init__. Для этого вам нужно создать метакласс и определить его метод __call__.

Метакласс - это просто класс класса. А метод класса __call__ контролирует, что происходит, когда вы вызываете экземпляры класса. Таким образом, метод metaclass '__call__ контролирует, что происходит при вызове класса; то есть он позволяет переопределить механизм создания экземпляра от начала до конца . Это уровень, на котором вы можете наиболее элегантно реализовать совершенно нестандартный процесс создания экземпляра, такой как шаблон синглтона. Фактически, имея менее 10 строк кода, вы можете реализовать метакласс Singleton, который даже не требует от вас использовать __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

Однако это, вероятно, более глубокая магия, чем на самом деле оправдано для этой ситуации!

22 голосов
/ 23 марта 2009

Цитировать документацию :

Типичные реализации создают новый экземпляр класса, вызывая метод __new __ () суперкласса, использующий "super (currentclass, cls) .__ new __ (cls [, ...]) "с соответствующими аргументами, а затем при необходимости измените созданный экземпляр перед его возвратом.

...

Если __new __ () не возвращает экземпляр cls, то новый Метод экземпляра __init __ () вызываться не будет.

__ new __ () предназначен в основном для того, чтобы разрешить подклассам неизменяемого типы (например, int, str или tuple) для настройки создания экземпляра.

11 голосов
/ 13 мая 2013

Я понимаю, что этот вопрос довольно старый, но у меня была похожая проблема. Следующие сделали то, что я хотел:

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

9 голосов
/ 07 июля 2013

Я думаю, что простой ответ на этот вопрос заключается в том, что если __new__ возвращает значение того же типа, что и класс, функция __init__ выполняется, в противном случае это не так. В этом случае ваш код возвращает A._dict('key'), который совпадает с классом cls, поэтому будет выполнено __init__.

9 голосов
/ 27 января 2015

Когда __new__ возвращает экземпляр того же класса, впоследствии на возвращаемом объекте запускается __init__. То есть Вы не можете использовать __new__ для предотвращения запуска __init__. Даже если вы вернете ранее созданный объект из __new__, он будет дважды (трижды и т. Д.) Инициализироваться __init__ снова и снова.

Вот общий подход к шаблону Singleton, который расширяет ответ 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__, заключается в использовании методов класса:

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.

6 голосов
/ 30 июля 2015
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 как A._dict, поэтому будьте осторожны, чтобы случайно не перезаписать его.

5 голосов
/ 23 марта 2009

__ new__ должен возвращать новый пустой экземпляр класса. Затем вызывается __init__ для инициализации этого экземпляра. Вы не звоните __init__ в «новом» случае __new__, поэтому он вызывается для вас. Код, вызывающий __new__, не отслеживает, был ли вызван __init__ для конкретного экземпляра или нет, и не должен, потому что вы делаете здесь что-то очень необычное.

Вы можете добавить атрибут к объекту в функции __init__, чтобы указать, что он был инициализирован. Сначала проверьте наличие этого атрибута в __init__ и не переходите дальше, если он был.

5 голосов
/ 18 июля 2017

Обновление ответа @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 (вызов без аргументов super и метакласс в аргументах класса вместо атрибута).

В любом случае, важным моментом здесь является то, что ваш инициализатор класса (__call__ метод) не будет выполнять ни __new__, ни __init__, если ключ найден. Это намного чище, чем использование __new__, которое требует, чтобы вы отметили объект, если хотите пропустить шаг __init__ по умолчанию.

...