Чому __init __ () завжди називається після __ne __ ()?


568

Я просто намагаюся впорядкувати один із моїх занять і запровадив деяку функціональність у тому ж стилі, що і модель з легкою вагою .

Однак я трохи розгублений, чому мене __init__завжди викликають __new__. Я цього не очікував. Хто-небудь може сказати мені, чому це відбувається і як я можу реалізувати цю функцію в іншому випадку? (Окрім того, щоб реалізувати реалізацію в__new__ якому відчуває себе цілком Hacky.)

Ось приклад:

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

Чому?


також намагався зрозуміти модель дизайну, і вперше почув про: модель легкої ваги .. і дуже гарне посилання, що має приклад майже в усіх популярних мовах.
EmmaYang

Відповіді:


598

Використовуйте, __new__коли вам потрібно контролювати створення нового екземпляра.

Використовуйте, __init__коли вам потрібно керувати ініціалізацією нового екземпляра.

__new__є першим кроком створення екземпляра. Він називається спочатку і відповідає за повернення нового примірника вашого класу.

У контрасті, __init__ нічого не повертає; він відповідає лише за ініціалізацію примірника після його створення.

Загалом, вам не потрібно буде переосмислювати, __new__якщо ви не підкласифікуєте непорушний тип, наприклад str, int, unicode або кортеж.

З квітня 2008 року пост: Коли використовувати __new__vs. __init__? на mail.python.org.

Вам слід врахувати, що те, що ви намагаєтеся зробити, зазвичай робиться з Фабрикою, і це найкращий спосіб зробити це. Використання __new__не є хорошим чистим рішенням, тому, будь ласка, врахуйте використання фабрики. Ось у вас хороший заводський приклад .


1
Добігли використання __new__всередині фабричного класу, який став досить чистим, тому дякую за ваш внесок.
День

11
Вибачте, я не погоджуюся з тим, що використання __new__слід суворо обмежувати вказаними випадками. Я вважаю це дуже корисним для впровадження розширених заводів загального класу - дивіться мою відповідь на питання Неправильне використання __new__для створення класів у Python? для прикладу цього.
мартіно

170

__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:
  ...

8
@Tyler Long, я не зовсім розумію, як працює "@singleton"? Оскільки декоратор повертає функцію, але MyClass - це клас.
Алькотт

11
Чому словник? Оскільки cls завжди буде однаковим, ви отримуєте свіжий словник, наприклад, для кожного сингтона, ви створюєте словник із лише одним елементом.
Вінстон Еверт

7
@Alcott: Думки не потрібні - документи згодні з вами.
Етан Фурман

2
@Alcott так, декоратор повертає функцію. але і клас, і функція можуть називатися. Я думаю, що екземпляри = {} мають бути глобальною змінною.
Тайлер Лонг

4
@TylerLong Alcott має хороший момент. Це призводить до того, що ім'я MyClassпов'язане з функцією, яка повертає екземпляри початкового класу. Але тепер взагалі немає можливості посилатися на початковий клас, а MyClassфункція переривання isinstance/ issubclassперевірки, доступ до атрибутів / методів класу безпосередньо як MyClass.something, іменування класу у superвикликах тощо тощо. Це проблема чи ні, залежить від клас, до якого ви застосовуєте його (та решту програми).
Бен

148

У більшості відомих мов OO такий вираз SomeClass(arg1, arg2)виділяє новий екземпляр, ініціалізує атрибути екземпляра та поверне його.

У більшості відомих мов OO частина «ініціалізація атрибутів екземпляра» може бути налаштована для кожного класу шляхом визначення конструктора , який в основному є лише кодом коду, який працює в новому екземплярі (використовуючи аргументи, надані виразу конструктора ) встановити будь-які початкові умови. У Python це відповідає __init__методу class ' .

Python - __new__це не більше і не менше, ніж аналогічна індивідуальна настройка частини "виділити новий екземпляр". Звичайно, це дозволяє робити незвичайні речі, такі як повернення наявного екземпляра, а не виділення нового. Отже, в Python ми не повинні вважати цю частину обов'язковою частиною розподілу; все, що нам потрібно, це те, що __new__десь придумує відповідний екземпляр.

Але це ще лише половина роботи, і система Python не може знати, що іноді ви хочете виконувати іншу половину завдання ( __init__), а іноді - ні. Якщо ви хочете такої поведінки, ви повинні сказати це прямо.

Часто ви можете рефакторировать так, що вам потрібно __new__, або так вам не потрібно __new__, або так, що __init__поводиться по-різному на вже ініціалізованому об'єкті. Але якщо ви дійсно цього хочете, Python насправді дозволяє вам переосмислити "роботу", так що SomeClass(arg1, arg2)не обов'язково викликати __new__наступні __init__. Для цього потрібно створити метаклас і визначити його __call__метод.

Метаклас - це просто клас класу. І __call__метод 'class' керує тим, що відбувається, коли ви викликаєте екземпляри класу. Таким чином , метод метакласу__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

Однак це, мабуть, глибша магія, ніж це дійсно гарантовано для цієї ситуації!


25

Для цитування документації :

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

...

Якщо __new __ () не повертає екземпляр cls, метод __init __ () нового примірника не буде застосовано.

__new __ () призначений головним чином для того, щоб дозволити підкласам змінних типів (наприклад, int, str чи tuple) налаштувати створення екземплярів.


18
If __new__() does not return an instance of cls, then the new instance's __init__() method will not be invoked.Це важливий момент. Якщо ви повернете інший екземпляр, оригінал __init__ніколи не називається.
Джеффрі Хосе

@tgray Я усвідомлюю, що ваша відповідь документів, але мені цікаво, якщо ви знаєте будь-який випадок використання не повернення екземпляра cls. Схоже, що коли повернутий об'єкт __new__перевіряється на його клас, метод видасть помилку, а не дозволить невдалої перевірки пройти беззвучно, тому що я не розумію, чому ви хотіли б повернути що-небудь, крім об’єкта класу .
soporific312

@ soporific312 Я не бачив випадків використання. У цій відповіді обговорюються деякі міркування для дизайну, хоча вони також не бачили жодного коду, який би використовував цю функцію.
tgray

12

Я усвідомлюю, що це питання досить старе, але у мене було подібне питання. Наступне зробив те, що я хотів:

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


7
Примітка: __new__ завжди повертає відповідний об'єкт, тому __init__ завжди викликається - навіть якщо екземпляр вже існує.
Відмінна думка

10

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


10

Коли __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

Посилаючись на цей документ :

Коли підкласирують незмінні вбудовані типи, такі як числа та рядки, а іноді й в інших ситуаціях, статичним методом стане у нагоді новий . Новий - це перший крок в побудові екземпляра, викликаний перед init .

Новий метод викликається з класом в якості першого аргументу; її відповідальність полягає у поверненні нового екземпляра цього класу.

Порівняйте це з init : init викликається з екземпляром в якості першого аргументу, і він нічого не повертає; його відповідальність полягає в ініціалізації екземпляра.

Бувають ситуації, коли новий екземпляр створюється без виклику init (наприклад, коли екземпляр завантажується з макіяжу). Там немає ніякого способу , щоб створити новий екземпляр без виклику нового (хоча в деяких випадках ви можете піти з заходом бази класу новим ).

Що стосується того, чого ви хочете досягти, там також в тій же док-інформації про шаблон Singleton

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:
...

1
Виклик перекручується форму initвід __new__звуків дійсно Hacky. Це те, для чого призначені метакласи.
Божевільний фізик

6
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так піклуватися , щоб не перезаписати його між іншим.


Вам не вистачає встановленого __init__методу cls._dict = {}. Напевно, ви не хочете, щоб словник ділився всіма класами цього метатипу (але +1 - ідея).
Божевільний фізик

5

__new__ повинен повернути новий, порожній екземпляр класу. Потім __init__ викликається для ініціалізації цього примірника. Ви не називаєте __init__ у випадку "НОВОГО" __new__, тож він вам закликається. Код, який дзвонить__new__ , не відслідковує, чи було викликано __init__ у певному екземплярі чи ні, чи не слід, тому що ви робите щось дуже незвичне.

Ви можете додати атрибут до об'єкта у функції __init__, щоб вказати, що він ініціалізований. Перевірте наявність цього атрибуту як першої речі в __init__ і не продовжуйте далі, якщо він був.


5

Оновлення відповіді @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__крок за замовчуванням .


4

Копати трохи глибше в це!

Тип загального класу в 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просто повертає об'єкт, який прив'язується до вашої змінної.


4

Слід розглядати __init__як простий конструктор у традиційних мовах ОО. Наприклад, якщо ви знайомі з Java або C ++, конструктор неявно передає вказівник на власний екземпляр. У випадку з Java - це thisзмінна. Якщо слід перевірити байт-код, сформований для Java, можна було б помітити два виклики. Перший виклик - це "новий" метод, а потім наступний - метод init (який є фактичним викликом до визначеного користувачем конструктора). Цей двоступеневий процес дозволяє створити фактичний екземпляр перед викликом методу конструктора класу, який є лише іншим методом цього екземпляра.

Тепер, у випадку з Python, __new__це додаткова програма, доступна користувачеві. Java не забезпечує такої гнучкості через свій тип тексту. Якщо мова надала цю функцію, то реалізатор __new__може зробити багато речей у цьому методі перед поверненням екземпляра, включаючи створення абсолютно нового примірника непов'язаного об'єкта в деяких випадках. Такий підхід також добре підходить для особливо непорушних типів у випадку Python.


4

Однак я трохи розгублений, чому мене __init__завжди викликають __new__.

Я думаю, що аналогія C ++ тут ​​буде корисною:

  1. __new__просто виділяє пам'ять для об'єкта. Змінні екземпляра об'єкта потребують пам'яті, щоб утримувати його, і саме це __new__робив би крок .

  2. __init__ ініціалізувати внутрішні змінні об'єкта до конкретних значень (може бути за замовчуванням).


2

The __init__викликається після__new__ того, так що , коли ви перевизначити його в підкласі, ваш доданий код буде викликатися.

Якщо ви намагаєтеся підкласи класу, у якого вже є a __new__, хтось, хто не знає про це, може початися, адаптувавши __init__виклик і перенаправляючи його вниз до підкласу __init__. Ця конвенція про дзвінок __init__після__new__ того, як допомагає цій роботі , як і очікувалося.

Все- __init__таки потрібно враховувати будь-які параметри __new__необхідного суперкласу , але, якщо цього не зробити, зазвичай створюється явна помилка виконання. І, __new__мабуть, має бути чітко передбачено *argsта "** kw", щоб було зрозуміло, що розширення нормально.

Як правило, погана форма мати обидва __new__і __init__в тому ж класі на одному рівні спадкування через поведінку, описану в оригінальному плакаті.


1

Однак я трохи розгублений, чому мене __init__завжди викликають__new__.

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

Я цього не очікував. Хтось може сказати мені, чому це відбувається і як я реалізую цю функціональність інакше? (окрім того, щоб реалізувати реалізацію, __new__яка відчувається досить хакі).

Ви можете __init__нічого не робити, якщо це вже ініціалізовано, або ви можете написати новий метаклас з новим, __call__який викликає лише __init__нові екземпляри, інакше просто повертається __new__(...).


1

Проста причина полягає в тому, що нове використовується для створення екземпляра, тоді як init використовується для ініціалізації екземпляра. Перед ініціалізацією екземпляр слід створити спочатку. Ось чому нового слід називати перед init .


1

Зараз у мене така ж проблема, і я чомусь вирішив уникати декораторів, фабрик та метакласів. Я зробив це так:

Основний файл

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>
  • Попередження: Ви повинні не ініціалізує Батько, він буде стикатися з іншими класами - якщо не визначений окремий кеш в кожному з дітей, це не те , що ми хочемо.
  • Попередження: Здається, клас з Батьком, як бабуся і дідусь, ведуть себе дивно. [Неперевірений]

Спробуйте в Інтернеті!

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.