2010-01-07 12 views
9

Creo que puede definir '__init__' o '__new__' en una clase, pero por qué todo se define en django.utils.datastructures.py.por qué se definió '__new__' y '__init__' todo en una clase

mi código:

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) 

y qué circunstancias el SortedDict.__init__ será llamada.

gracias

+3

** do ** lea documentos, ¡son exhaustivos! – SilentGhost

Respuesta

18

Puede definir uno o ambos de __new__ y __init__.

__new__ debe devolver un objeto, que puede ser uno nuevo (normalmente esa tarea se delega en type.__new__), uno existente (para implementar singletons, "reciclar" instancias de un grupo, etc.) o incluso uno que es no una instancia de la clase. Si __new__ devuelve una instancia de la clase (nueva o existente), __init__ se le llama; Si __new__ devuelve un objeto que es no una instancia de la clase, a continuación, __init__ es no llamada.

__init__ se hace pasar a una instancia de clase como su primer artículo (en el mismo estado __new__ regresaron ella, es decir, típicamente "vacío") y debe alterar según sea necesario para que quede listo para el uso (con mayor frecuencia por los atributos adición).

En general, lo mejor es utilizar __init__ para todo lo que puede hacer - y __new__, si algo se deja que __init__ no se puede hacer, para que "algo extra".

Por lo tanto, normalmente definirá ambos si hay algo útil que puede hacer en __init__, pero no todo lo que desea que ocurra cuando se crea una instancia de la clase.

Por ejemplo, considere una clase que subclases int pero también tiene una ranura foo - y usted quiere que sea una instancia con un inicializador para la int y otro para el .foo. Como int es inmutable, esa parte tiene que ocurrir en __new__, por lo pedante que uno podría código:

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

En la práctica, para un caso de este sencillo, nadie le importaría si ha perdido la __init__ y acaba de mudar el self.foo = foo a __new__ . Pero si la inicialización es lo suficientemente rica y compleja como para ubicarla mejor en __init__, esta idea vale la pena tener en cuenta.

+0

Hice una pregunta acerca de heredar múltiples clases dinámicamente ayer. Basado en su ejemplo actual, hice esto: http://gist.github.com/271098 ¿Qué pasa? –

+0

Alex, ¿podría explicar por qué '__new__' se anula en el caso particular de la implementación de SortedDict? ¿Qué hay de malo en inicializar el atributo 'keyOrder' en' __init__'? La única razón real que se me ocurre es que se hace para garantizar que el atributo se crea incluso si SortedDict está subclasificado y la subclase no llama a la clase base ''__init__'. Pero, de nuevo, puedes hacer lo mismo con '__new__', ¿verdad? – shylent

+0

@becomingGuru, muchos errores en ese código, pero el primero es que está usando clases antiguas: no use siempre las de estilo nuevo (herede de 'object'). En este caso, esto revelará el segundo error, una vez arreglado tendrá un tercero, etc. ¿Por qué no abre una pregunta de SO y pide que se depure su código, hay demasiados errores en su código para explicarlos todos en una ¡comentario! –

9

__new__ y __init__ hacen cosas completamente diferentes. El método __init__ inicia una nueva instancia de una clase --- es un constructor. __new__ es algo mucho más sutil: puede cambiar los argumentos y, de hecho, la clase del objeto iniciado. Por ejemplo, el siguiente código:

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

Si llama Meters(6) que será en realidad no crear una instancia de Meters, pero una instancia de int. Quizás se pregunte por qué esto es útil; en realidad es crucial para las metaclases, una característica ciertamente oscura (pero poderosa).

Notarás que en Python 2.x, solo las clases heredadas de object pueden aprovechar __new__, como muestra el código anterior.

El uso de __new__ que mostró en Django parece ser un intento de mantener un orden en su sano juicio resolución de métodos de SortedDict objetos. Debo admitir, sin embargo, que a menudo es difícil decir por qué es necesario __new__. El estilo estándar de Python sugiere que no se use a menos que sea necesario (como siempre, un mejor diseño de clase es la herramienta a la que recurrir primero).

+6

'__new__' es EL constructor,' __init__' es ... solo la inicialización –

+2

@Anurag: Pedadíticamente hablando, estás en lo cierto. Sin embargo, si vienes de un idioma que no sea Python, es mejor que te digan que '__init__' es el constructor --- las personas son lo suficientemente buenas como para abusar de las características sin mi ayuda. Entonces, estoy manteniendo '__init__' como constructor en mi respuesta para el beneficio de otros posibles. – pavpanchekha

2

Mi única suposición es que en este caso, ellos (autor de esta clase) quieren que la lista keyOrder exista en la clase incluso antes de que se llame SortedDict.__init__.

Tenga en cuenta que SortedDict llama super() en su __init__, esto normalmente ir a dict.__init__, lo que probablemente llamar __setitem__ y similares, para empezar a añadir elementos. SortedDict.__setitem__ espera que exista la propiedad .keyOrder, y ahí radica el problema (ya que .keyOrder normalmente no se crea hasta después de la llamada a super()). Es posible que esto sea solo un problema con la subclase dict porque mi intuición normal sería inicializar .keyOrder antes de la llamada al super().

El código en __new__ también podría usarse para permitir SortedDict a tener subclases en una estructura de herencia de diamante donde es posible SortedDict.__init__ no se llama antes de la primera __setitem__ y similares son llamados. Django tiene que lidiar con varios problemas al admitir una amplia gama de versiones de Python desde 2.3 hasta; es posible que este código sea completamente innecesario en algunas versiones y necesario en otras.


Hay un uso común para definir tanto __new__ y __init__: acceder a las propiedades de la clase que puede ser eclipsado por sus versiones de instancia sin tener que hacer type(self) o self.__class__ (que, en la existencia de metaclases, puede incluso no ser Lo correcto).

Por ejemplo:

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 

Por último, __new__ realmente puede devolver una instancia de una envoltura o una clase completamente diferente de lo que se pensaba que estaban instanciar. Esto se usa para proporcionar características similares a metaclases sin necesitar realmente una metaclase.

0

En mi opinión, no hubo necesidad de anular __new__ en el ejemplo que describió. La creación de una instancia y la asignación de memoria real ocurre en __new__, __init__ se llama después de __new__ y está destinado a la inicialización de la instancia que sirve el trabajo del constructor en términos de OOP clásicos. Por lo tanto, si todo lo que desea hacer es inicializar variables, entonces debe ir por anular __init__. La función real de __new__ entra en su lugar cuando usa Metaclasses. Allí, si desea hacer algo como cambiar los atributos o agregar atributos, eso debe suceder antes de la creación de la clase, debe pasar por anular __new__.

Considérese, un caso completamente hipotética en la que desea hacer algunos atributos de la clase privada, a pesar de que no se definen de modo (no estoy diciendo que uno nunca debe hacer eso).

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' 

Nuevamente, es solo por razones de instrucción. No estoy sugiriendo que se deba hacer algo como esto.

Cuestiones relacionadas