¿Por qué se definieron ‘__new__’ y ‘__init__’ en una clase?

Creo que puedes definir ‘ __init__ ‘ o ‘ __new__ ‘ en una clase, pero ¿por qué todo está definido en django.utils.datastructures.py?

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 en qué circunstancias se SortedDict.__init__ .

Gracias

Puede definir uno o ambos de __new__ y __init__ .

__new__ debe devolver un objeto, que puede ser uno nuevo (normalmente esa tarea se delega para type.__new__ ), uno existente (para implementar singletons, “reciclar” instancias de un grupo, etc.), o incluso uno que sea No es una instancia de la clase. Si __new__ devuelve una instancia de la clase (nueva o existente), __init__ se llama en ella; Si __new__ devuelve un objeto que no es una instancia de la clase, entonces __init__ no se llama.

__init__ se pasa una instancia de clase como su primer elemento (en el mismo estado __new__ devolvió, es decir, típicamente “vacío”) y debe modificarlo según sea necesario para que esté listo para su uso (la mayoría de las veces agregando atributos).

En general, es mejor usar __init__ para todo lo que puede hacer, y __new__ , si queda algo que __init__ no puede hacer, por ese “algo extra”.

Así que normalmente definirás ambas cosas si hay algo útil que puedes hacer en __init__ , pero no todo lo que quieres que suceda cuando la clase se ejemplifique.

Por ejemplo, considere una clase en la que las subclases int pero también tiene una ranura foo , y desea que se cree una instancia con un inicializador para el int y otro para el .foo . Como int es inmutable, esa parte tiene que suceder en __new__ , por lo que, de manera pedante, se podría codificar:

 >>> 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 así de simple, a nadie le importaría si perdieras el __init__ y simplemente self.foo = foo el self.foo = foo a __new__ . Pero si la inicialización es lo suficientemente rica y compleja como para __init__ mejor en __init__ , vale la pena tener en cuenta esta idea.

__new__ y __init__ hacen cosas completamente diferentes. El método __init__ inicia una nueva instancia de una clase — es un constructor. __new__ es una cosa 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 a Meters(6) , no creará realmente una instancia de Meters , sino una instancia de int . Usted podría preguntarse por qué esto es útil; En realidad, es crucial para las metaclases, una característica ciertamente oscura (pero poderosa).

__new__ que en Python 2.x, solo las clases que se heredan de un object pueden aprovechar __new__ , como se muestra en el código anterior.

El uso de __new__ que mostró en django parece ser un bash de mantener un orden de resolución de método sano en los objetos SortedDict . Sin embargo, debo admitir que a menudo es difícil decir por qué __new__ es necesario. El estilo estándar de Python sugiere que no se use a menos que sea necesario (como siempre, la mejor herramienta de diseño es la primera herramienta).

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

Tenga en cuenta que SortedDict llama a super() en su __init__ , esto normalmente iría a dict.__init__ , que probablemente llamaría __setitem__ y similares para comenzar a agregar elementos. SortedDict.__setitem__ espera que la propiedad .keyOrder exista, 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 el dict subclases porque mi instinto normal sería simplemente inicializar .keyOrder antes de la llamada a super() .

El código en __new__ también podría usarse para permitir que SortedDict sea subclasificado en una estructura de herencia de diamante donde es posible que SortedDict.__init__ no se llame antes del primer __setitem__ y similares. Django tiene que lidiar con varios problemas para admitir una amplia gama de versiones de python desde 2.3 hasta arriba; es posible que este código sea completamente innecesario en algunas versiones y necesario en otras.


Existe un uso común para definir __new__ y __init__ : acceder a propiedades de clase que pueden eclipsarse por sus versiones de instancia sin tener que hacer type(self) o self.__class__ (que, en la existencia de metaclases, ni siquiera es lo correcto cosa).

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 

Finalmente, __new__ puede devolver una instancia de un contenedor o una clase completamente diferente de lo que pensabas que estabas creando. Esto se usa para proporcionar características similares a las metaclases sin que realmente se necesite una metaclase.

En mi opinión, no había necesidad de anular __new__ en el ejemplo que describiste. 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á destinada a la inicialización de la instancia que sirve el trabajo de constructor en términos clásicos de POO. Por lo tanto, si todo lo que desea hacer es inicializar las variables, entonces debe ir por sobrescribir __init__ . El verdadero rol de __new__ entra en su lugar cuando utiliza Metaclasses. Allí, si desea hacer algo como cambiar atributos o agregar atributos, eso debe suceder antes de la creación de la clase, debe ir para anular __new__ .

Considere, un caso completamente hipotético en el que desea hacer que algunos atributos de la clase sean privados, aunque no estén definidos (no estoy diciendo que uno deba 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 para propósitos de instrucción, no estoy sugiriendo que uno deba hacer algo como esto.