Python (и Python C API): __new__ против __init__ - PullRequest
114 голосов
/ 01 февраля 2011

Вопрос, который я собираюсь задать, кажется дубликатом использования Python __new__ и __init __? , но, тем не менее, мне все еще неясно, в чем конкретно практическая разница между __new__ и __init__ есть.

Прежде чем вы поспешите сказать мне, что __new__ для создания объектов, а __init__ для инициализации объектов, позвольте мне прояснить: Я понял. На самом деле, это различие вполне естественно для меня, так как у меня есть опыт в C ++, где у нас есть размещение нового , которое аналогичным образом отделяет размещение объекта от инициализации.

В руководстве по API Python C 1016 * это объясняется так:

Новый участник несет ответственность за создание (в отличие от инициализации) объекты типа. Выставлен в Python как метод __new__(). ... Одной из причин реализации нового метода является обеспечение начальных значений переменные экземпляра .

Итак, да - я получаю , что __new__ делает, но, несмотря на это, я все еще не понимаю, почему это полезно в Python. В приведенном примере говорится, что __new__ может быть полезно, если вы хотите "обеспечить начальные значения переменных экземпляра". Ну, разве это не то, что __init__ сделает?

В учебном пособии по API C показан пример, где создается новый тип (называемый "Noddy") и определяется функция __new__ типа. Тип Noddy содержит строковый член с именем first, и этот строковый член инициализируется пустой строкой, например:

static PyObject * Noddy_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    .....

    self->first = PyString_FromString("");
    if (self->first == NULL)
    {
       Py_DECREF(self);
       return NULL;
    }

    .....
}

Обратите внимание, что без определенного здесь __new__ метода нам пришлось бы использовать PyType_GenericNew, который просто инициализирует все переменные-члены экземпляра в NULL. Таким образом, единственным преимуществом метода __new__ является то, что переменная экземпляра будет начинаться как пустая строка, а не как NULL. Но почему это всегда полезно, поскольку, если бы мы заботились о том, чтобы переменные нашего экземпляра инициализировались к какому-либо значению по умолчанию, мы могли бы просто сделать это в методе __init__?

Ответы [ 5 ]

128 голосов
/ 01 февраля 2011

Разница в основном возникает с изменяемыми и неизменяемыми типами.

__new__ принимает тип в качестве первого аргумента и (обычно) возвращает новый экземпляр этого типа. Таким образом, он подходит для использования как с изменяемыми, так и с неизменяемыми типами.

__init__ принимает экземпляр в качестве первого аргумента и изменяет атрибуты этого экземпляра. Это неуместно для неизменяемого типа, так как это позволит изменять их после создания, вызывая obj.__init__(*args).

Сравните поведение tuple и list:

>>> x = (1, 2)
>>> x
(1, 2)
>>> x.__init__([3, 4])
>>> x # tuple.__init__ does nothing
(1, 2)
>>> y = [1, 2]
>>> y
[1, 2]
>>> y.__init__([3, 4])
>>> y # list.__init__ reinitialises the object
[3, 4]

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

Кроме методов __init__, которые проще писать, и различия между изменяемыми и неизменяемыми, отмеченными выше, разделение также можно использовать для необязательного вызова вызова родительского класса __init__ в подклассах путем настройки любых абсолютно необходимых инвариантов экземпляров __new__. Хотя это обычно сомнительная практика - обычно яснее просто вызывать методы родительского класса __init__ по мере необходимости.

34 голосов
/ 01 февраля 2011

Возможно, есть и другие варианты использования __new__, но есть одно действительно очевидное: вы не можете создать подкласс неизменного типа без использования __new__.Например, предположим, что вы хотите создать подкласс кортежа, который может содержать только целые значения от 0 до size.

class ModularTuple(tuple):
    def __new__(cls, tup, size=100):
        tup = (int(x) % size for x in tup)
        return super(ModularTuple, cls).__new__(cls, tup)

Вы просто не можете сделать это с помощью __init__ - если выпри попытке изменить self в __init__ интерпретатор будет жаловаться, что вы пытаетесь изменить неизменяемый объект.

30 голосов
/ 01 февраля 2011

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

>>> class C(object):
...   def __new__(cls):
...     return 5
...
>>> c = C()
>>> print type(c)
<type 'int'>
>>> print c
5
11 голосов
/ 28 февраля 2011

Не полный ответ, но, возможно, что-то, что иллюстрирует разницу.

__new__ всегда будет вызываться при создании объекта.В некоторых ситуациях __init__ не будет вызван.Например, когда вы извлекаете объекты из файла выбора, они будут выделены (__new__), но не инициализированы (__init__).

2 голосов
/ 12 апреля 2017

Просто хочу добавить слово о намерении (в отличие от поведения) определения __new__ против __init__.

Я столкнулся с этим вопросом (среди прочего), когда пытался понять, как лучше определить фабрику классов. Я понял, что одним из способов, которыми __new__ концептуально отличается от __init__, является тот факт, что выгода __new__ - это именно то, что было указано в вопросе:

Таким образом, единственным преимуществом метода __new__ является то, что переменная экземпляра будет начинаться как пустая строка, а не как NULL. Но почему это всегда полезно, так как если бы мы заботились о том, чтобы переменные нашего экземпляра инициализировались к какому-либо значению по умолчанию, мы могли бы просто сделать это в методе __init__?

Учитывая заявленный сценарий, мы заботимся о начальных значениях переменных экземпляра, когда instance на самом деле сам является классом. Итак, если мы динамически создаем объект класса во время выполнения, и нам нужно определить / контролировать что-то особенное в последующих экземплярах этого класса, мы определяем эти условия / свойства в методе __new__ метакласса.

Я был смущен этим, пока не подумал о применении концепции, а не о ее значении. Вот пример, который, надеюсь, прояснит разницу:

a = Shape(sides=3, base=2, height=12)
b = Shape(sides=4, length=2)
print(a.area())
print(b.area())

# I want `a` and `b` to be an instances of either of 'Square' or 'Triangle'
# depending on number of sides and also the `.area()` method to do the right
# thing. How do I do that without creating a Shape class with all the
# methods having a bunch of `if`s ? Here is one possibility

class Shape:
    def __new__(cls, sides, *args, **kwargs):
        if sides == 3:
            return Triangle(*args, **kwargs)
        else:
            return Square(*args, **kwargs)

class Triangle:
    def __init__(self, base, height):
        self.base = base
        self.height = height

    def area(self):
        return (self.base * self.height) / 2

class Square:
    def __init__(self, length):
        self.length = length

    def area(self):
        return self.length*self.length

Обратите внимание, что это только наглядный пример. Существует несколько способов получить решение, не прибегая к подходу фабрики классов, как описано выше, и даже если мы решим реализовать решение таким образом, для краткости остаются некоторые оговорки (например, явное объявление метакласса). )

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

...