2009-05-15 17 views
36

Recientemente he combatido un error en Python. Era uno de esos bichos novatos tontos, pero me hizo pensar sobre los mecanismos de Python (soy un programador de C++ desde hace mucho tiempo, nuevo para Python). Voy a diseñar el código de error y explicar lo que hice para solucionarlo, y luego tengo un par de preguntas ...Inicialización de miembros de la clase de Python

El escenario: Tengo una clase llamada A, que tiene un miembro de datos del diccionario, lo siguiente es su código (esto es una simplificación, por supuesto):

class A: 
    dict1={} 

    def add_stuff_to_1(self, k, v): 
     self.dict1[k]=v 

    def print_stuff(self): 
     print(self.dict1) 

La clase utilizando este código es la clase B:

class B: 

    def do_something_with_a1(self): 
     a_instance = A() 
     a_instance.print_stuff()   
     a_instance.add_stuff_to_1('a', 1) 
     a_instance.add_stuff_to_1('b', 2)  
     a_instance.print_stuff() 

    def do_something_with_a2(self): 
     a_instance = A()  
     a_instance.print_stuff()    
     a_instance.add_stuff_to_1('c', 1) 
     a_instance.add_stuff_to_1('d', 2)  
     a_instance.print_stuff() 

    def do_something_with_a3(self): 
     a_instance = A()  
     a_instance.print_stuff()    
     a_instance.add_stuff_to_1('e', 1) 
     a_instance.add_stuff_to_1('f', 2)  
     a_instance.print_stuff() 

    def __init__(self): 
     self.do_something_with_a1() 
     print("---") 
     self.do_something_with_a2() 
     print("---") 
     self.do_something_with_a3() 

en cuenta que cada llamada a do_something_with_aX() inicializa una nueva instancia "limpia" de la clase a, e imprime el diccionario antes y después de la adición.

El error (en caso de que no se ha descubierto todavía):

>>> b_instance = B() 
{} 
{'a': 1, 'b': 2} 
--- 
{'a': 1, 'b': 2} 
{'a': 1, 'c': 1, 'b': 2, 'd': 2} 
--- 
{'a': 1, 'c': 1, 'b': 2, 'd': 2} 
{'a': 1, 'c': 1, 'b': 2, 'e': 1, 'd': 2, 'f': 2} 

En la segunda inicialización de la clase A, los diccionarios no están vacíos, pero comienzan con el contenido de la última inicialización, Etcétera. Esperaba que comenzaran "frescos".

Lo que resuelve este "error" es, obviamente, añadiendo:

self.dict1 = {} 

En el __init__ constructor de la clase A. Sin embargo, lo que me hizo preguntarme:

  1. ¿Cuál es el significado de la "dict1 = {} "inicialización en el punto de la declaración de dict1 (primera línea en la clase A)? No tiene sentido?
  2. ¿Cuál es el mecanismo de creación de instancias que causa la copia de la referencia de la última inicialización?
  3. Si agrego "self.dict1 = {}" en el constructor (o cualquier otro miembro de datos), ¿cómo no afecta el miembro del diccionario de las instancias previamente inicializadas?

EDIT: Después de las respuestas ahora entiendo que al declarar un miembro de datos y no se hace referencia a ella en el __init__ o en otro lugar como self.dict1, estoy prácticamente la definición de lo que se llama en C++/Java un miembro de datos estáticos. Al llamarlo self.dict1 lo estoy haciendo "vinculado a instancia".

+0

Debe usar clases de estilo nuevo, derivar del objeto. – nikow

Respuesta

49

Lo que sigue refiriéndose a un error es el documented, el comportamiento estándar de las clases de Python.

Declarar un dict fuera de __init__ como inicialmente lo hizo es declarar una variable de nivel de clase. Solo se crea una vez al principio, cada vez que creas objetos nuevos reutilizará este mismo dict. Para crear variables de instancia, las declara con self en __init__; Es tan simple como eso.

+12

No dije que era un error de Python, dije que era mi error ... Cuando un programador hace algo en contra de la documentación, sigue siendo un error (una tontería como mencioné). Mi pregunta es acerca de los mecanismos que desencadenan este "comportamiento estándar documentado": me gustaría entender qué hay debajo del capó. Gracias. Y específicamente mi primera pregunta, me gustaría entender si la inicialización de la declaración es inútil. –

+3

No es inútil; si desea que una variable persista en las instancias, la usará. Tu problema es que cuando haces x = A() solo se ejecuta el código __init__. La variable de clase persiste. –

+0

Ok, siempre que una declaración no esté en el __init__, es lo mismo que un miembro de datos "estático" en C++, y la declaración en "__init__" lo cambia de ser estático a estar vinculado a la instancia? Ahora está claro, gracias. –

1

Al acceder al atributo de instancia, digamos, self.foo, python encontrará primero 'foo' en self.__dict__. Si no se encuentra, Python encontrará 'foo' en TheClass.__dict__

En su caso, dict1 es de clase A, no instancia.

+1

Esto realmente ayudó a mi modelo intuitivo de cómo funciona esto. Entonces, una vez que enlaza self.x a algo en su objeto (ya sea en __init__ o en otro lugar), las búsquedas futuras de self.x harán referencia a esa nueva variable en el ámbito de su instancia. Si no lo encuentran en el alcance de la instancia, miran hacia el alcance de la clase. –

0

Las declaraciones de clase Python se ejecutan como un bloque de código y cualquier definición de variable local (de las cuales las definiciones de función son un tipo especial de) se almacenan en la instancia de clase construida. Debido a la forma en que funciona la búsqueda de atributos en Python, si no se encuentra un atributo en la instancia, se usa el valor de la clase.

El es an interesting article about the class syntax en la historia del blog de Python.

0

Si este es su código:

class ClassA: 
    dict1 = {} 
a = ClassA() 

Entonces es probable que espera que esto suceda en el interior de Python:

class ClassA: 
    __defaults__['dict1'] = {} 

a = instance(ClassA) 
# a bit of pseudo-code here: 
for name, value in ClassA.__defaults__: 
    a.<name> = value 

Por lo que yo puedo decir, que es lo que pase, excepto que un dict tiene su puntero copiado, en lugar del valor, que es el comportamiento predeterminado en todas partes en Python. Mira este código:

a = {} 
b = a 
a['foo'] = 'bar' 
print b 
2

@Matthew: Por favor revise la diferencia entre un miembro de la clase y un miembro de objeto en Programación Orientada a Objetos. Este problema ocurre debido a que la declaración del dict original lo convierte en un miembro de la clase y no en un miembro del objeto (como era la intención original del póster). En consecuencia, existe una vez para (se comparte) todas las instancias de la clase (es decir, para la clase misma, como miembro del objeto de la clase en sí), por lo que el comportamiento es perfectamente correcto.

Cuestiones relacionadas