почему в классе определены «__new__» и «__init__» - PullRequest
9 голосов
/ 07 января 2010

Я думаю, вы можете определить '__init__' или '__new__' в классе, но почему все они определены в django.utils.datastructures.py.

мой код:

class a(object):
    def __init__(self):
        print  'aaa'
    def __new__(self):
        print 'sss'

a()#print 'sss'

class b:
    def __init__(self):
        print  'aaa'
    def __new__(self):
        print 'sss'
b()#print 'aaa'

datastructures.py:

class SortedDict(dict):
    """
    A dictionary that keeps its keys in the order in which they're inserted.
    """
    def __new__(cls, *args, **kwargs):
        instance = super(SortedDict, cls).__new__(cls, *args, **kwargs)
        instance.keyOrder = []
        return instance

    def __init__(self, data=None):
        if data is None:
            data = {}
        super(SortedDict, self).__init__(data)
        if isinstance(data, dict):
            self.keyOrder = data.keys()
        else:
            self.keyOrder = []
            for key, value in data:
                if key not in self.keyOrder:
                    self.keyOrder.append(key)

и какие обстоятельства будут вызывать SortedDict.__init__.

спасибо

Ответы [ 4 ]

19 голосов
/ 07 января 2010

Вы можете определить один или оба из __new__ и __init__.

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

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

В общем, лучше всего использовать __init__ для всего, что он может сделать - и __new__, если что-то осталось, чего не может сделать __init__, для этого "дополнительного чего-то".

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

Например, рассмотрим класс, который подклассов int, но также имеет слот foo - и вы хотите, чтобы он был создан с помощью инициализатора для int и одного для .foo. Поскольку int является неизменным, эта часть должна происходить в __new__, так что педантично можно кодировать:

>>> class x(int):
...   def __new__(cls, i, foo):
...     self = int.__new__(cls, i)
...     return self
...   def __init__(self, i, foo):
...     self.foo = foo
...   __slots__ = 'foo',
... 
>>> a = x(23, 'bah')
>>> print a
23
>>> print a.foo
bah
>>> 

На практике для такого простого случая никто бы не возражал, если бы вы потеряли __init__ и просто переместили self.foo = foo в __new__. Но если инициализация богата и достаточно сложна, чтобы ее можно было лучше поместить в __init__, то об этой идее стоит помнить.

9 голосов
/ 07 января 2010

__new__ и __init__ делают совершенно разные вещи. Метод __init__ инициирует новый экземпляр класса - это конструктор. __new__ гораздо более тонкая вещь - она ​​может изменять аргументы и, фактически, класс инициируемого объекта. Например, следующий код:

class Meters(object):
    def __new__(cls, value):
        return int(value / 3.28083)

Если вы позвоните Meters(6), вы на самом деле не создадите экземпляр Meters, а экземпляр int. Вы можете спросить, почему это полезно; это на самом деле крайне важно для метаклассов, по общему признанию неясная (но мощная) особенность.

Вы заметите, что в Python 2.x только классы, унаследованные от object, могут использовать __new__, как показано в коде выше.

Использование __new__, которое вы показали в django, похоже, является попыткой сохранить порядок разрешения вменяемого метода для SortedDict объектов. Я признаю, однако, что часто трудно сказать, почему __new__ необходим. Стандартный стиль Python предполагает, что он не должен использоваться без необходимости (как всегда, лучший дизайн класса - это инструмент, к которому вы обращаетесь в первую очередь).

2 голосов
/ 07 января 2010

Мое единственное предположение, что в этом случае они (автор этого класса) хотят, чтобы список keyOrder существовал в классе еще до вызова SortedDict.__init__.

Обратите внимание, что SortedDict вызывает super() в своем __init__, это обычно будет переходить к dict.__init__, что, вероятно, вызовет __setitem__ и тому подобное, чтобы начать добавлять элементы. SortedDict.__setitem__ ожидает, что свойство .keyOrder существует, и в этом заключается проблема (поскольку .keyOrder обычно не создается до тех пор, пока не будет вызван super().) Возможно, это просто проблема с подклассом dict, потому что мой обычный инстинкт инстинктивности состоит в том, чтобы просто инициализировать .keyOrder перед вызовом super().

Код в __new__ может также использоваться для того, чтобы позволить SortedDict быть разделенным на подклассы в структуре наследования алмазов, где возможно SortedDict.__init__ не вызывается до первого __setitem__ и тому подобное. Джанго сталкивается с различными проблемами при поддержке широкого спектра версий Python от 2.3 до; возможно, в одних версиях этот код совершенно не нужен и необходим в других.


Существует общее использование для определения __new__ и __init__: доступ к свойствам класса, которые могут затмевать их версии экземпляра, без необходимости делать type(self) или self.__class__ (что при существовании метаклассов может даже не правильно).

Например:

class MyClass(object):
    creation_counter = 0

    def __new__(cls, *args, **kwargs):
        cls.creation_counter += 1
        return super(MyClass, cls).__new__(cls)

    def __init__(self):
         print "I am the %dth myclass to be created!" % self.creation_counter

Наконец, __new__ может на самом деле возвращать экземпляр оболочки или совершенно другой класс, чем вы думали, что создавали. Это используется для предоставления функций, похожих на метаклассы, без необходимости метакласса.

0 голосов
/ 09 августа 2015

По моему мнению, не было необходимости переопределять __new__ в примере, который вы описали. Создание экземпляра и фактическое выделение памяти происходит в __new__, __init__ вызывается после __new__ и предназначено для инициализации экземпляра, обслуживающего работу конструктора в классических терминах ООП. Итак, если все, что вы хотите сделать, это инициализировать переменные, то вам следует перейти на переопределение __init__. Настоящая роль __new__ проявляется при использовании метаклассов. Там, если вы хотите сделать что-то вроде изменения атрибутов или добавления атрибутов, что должно произойти до создания класса, вы должны пойти на переопределение __new__.

Рассмотрим совершенно гипотетический случай, когда вы хотите сделать некоторые атрибуты класса приватными, даже если они не определены так (я не говорю, что это когда-либо нужно делать).

class PrivateMetaClass(type):
      def __new__(metaclass, classname, bases, attrs):
          private_attributes = ['name', 'age']

          for private_attribute in private_attributes:
              if attrs.get(private_attribute):
                 attrs['_' + private_attribute] = attrs[private_attribute]
                 attrs.pop(private_attribute)

          return super(PrivateMetaClass, metaclass).__new__(metaclass, classname, bases, attrs)


class Person(object):

      __metaclass__ = PrivateMetaClass

      name = 'Someone'
      age = 19

person = Person()
>>> hasattr(person, 'name')
False
>>> person._name
'Someone'

Опять же, это просто для учебных целей, я не предлагаю делать что-то подобное.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...