2010-06-30 13 views
10

Todavía soy bastante nuevo en Python, y mi experiencia OO proviene de Java. Así que tengo algo de código que he escrito en Python que está actuando muy raro para mí, dado el siguiente código:Alcance y listas de Python Class

class MyClass(): 
     mylist = [] 
     mynum = 0 

     def __init__(self): 
       # populate list with some value. 
       self.mylist.append("Hey!") 
       # increment mynum. 

       self.mynum += 1 

a = MyClass() 

print a.mylist 
print a.mynum 

b = MyClass() 

print b.mylist 
print b.mynum 

Correr esto se traduce en la siguiente salida:

['Hey!'] 
1 
['Hey!', 'Hey!'] 
1 

Claramente, yo esperaría las variables de clase dan como resultado los mismos datos exactos, y la misma salida exacta ... Lo que parece que no encuentro en ninguna parte es lo que hace que una lista sea diferente a decir una cadena o un número, ¿por qué la lista hace referencia a la misma lista? la primera instanciación en los siguientes? Claramente, probablemente estoy entendiendo mal algún tipo de mecánica de alcance o mecánica de creación de listas.

Respuesta

8

La respuesta de tlayton es parte de la historia, pero no explica todo.

Añadir un

print MyClass.mynum 

a ser aún más confuso :). Imprimirá '0'. ¿Por qué?Debido a que la línea de

self.mynum += 1 

crea una variable de instancia y posteriormente aumenta. No aumenta la variable clase.

La historia de la lista mylist es diferente.

self.mylist.append("Hey!") 

no creará una lista. Espera que exista una variable con una función 'append'. Como la instancia no tiene dicha variable, termina refiriendo la de la clase, que tiene existe, desde que la inicializó. Al igual que en Java, una instancia puede hacer referencia "implícitamente" a una variable de clase. Una advertencia como 'Los campos de clase deben ser referenciados por la clase, no por una instancia' (o algo así, ha pasado un tiempo desde que lo vi en Java) estaría en orden. Agregue una línea

print MyClass.mylist 

para verificar esta respuesta :).

En resumen: está inicializando variables de clase y actualizando variables de instancia. Las instancias pueden hacer referencia a las variables de la clase, pero algunas declaraciones de "actualización" crearán automágicamente las variables de instancia para usted.

+0

Esto todavía pierde el punto clave; ver la respuesta de @ Ken. 'self.mynum + = 1' se expande a' self.mynum = self.mynum + 1'. Cuando esto se ejecuta, primero se evalúa el lado derecho. 'self.mynum' busca' mynum' en las instancias y no lo encuentra (al menos la primera vez que se llama '__init__'), entonces busca (y encuentra)' mynum' en la clase. Entonces el 'self.mynum' en el lado derecho evalúa el valor de' MyClass.mynum'. Ese valor ahora está asignado a 'self.mynum', que aquí se refiere a la variable de instancia. –

5

Lo que estás haciendo aquí no es solo crear una variable de clase. En Python, las variables definidas en el cuerpo de la clase dan como resultado una variable de clase ("MyClass.mylist") y una variable de instancia ("a.milista"). Estas son variables separadas, no solo nombres diferentes para una sola variable.

Sin embargo, cuando una variable se inicializa de esta manera, el valor inicial solo se evalúa una vez y se pasa a las variables de cada instancia. Esto significa que, en su código, la variable mylist de cada instancia de MyClass hace referencia a un solo objeto de lista.

La diferencia entre una lista y un número en este caso es que, como en Java, los valores primitivos como los números se copian cuando pasan de una variable a otra. Esto resulta en el comportamiento que ves; aunque la inicialización de la variable solo se evalúa una vez, el 0 se copia cuando se pasa a la variable de cada instancia. Como un objeto, sin embargo, la lista no hace tal cosa, por lo que sus llamadas anexar() provienen de la misma lista. Tal vez puedas probar:

class MyClass(): 
    def __init__(self): 
     self.mylist = ["Hey"] 
     self.mynum = 1 

Esto hará que el valor que se evalúa por separado cada vez que se crea una instancia. Muy a diferencia de Java, no necesita las declaraciones del cuerpo de clase para acompañar este fragmento; las asignaciones en __init __() sirven como toda la declaración que se necesita.

+1

"Estas son variables separadas, no solo nombres diferentes para una sola variable". <- No creo que esto sea cierto. Intenta hacer 'id (a.mylist)' y 'id (MyClass.mylist)' y mira lo que obtienes. –

+0

Son variables separadas, lo que hace la distinción importante entre variables y valores. estas dos llamadas a id() volverán a ser iguales, porque tanto a.mylist como MyClass.mylist se refieren al mismo objeto (creado al evaluar la línea "mylist = []", pero aún están separadas; asignando una lista completamente diferente a uno de ellos más tarde no cambiará el valor del otro. – tlayton

+0

@Mark: Tiene razón en que no es verdad. Las primeras dos líneas de la clase crean variables de clase y solo variables de clase. Consulte mi respuesta para la historia correcta Sin embargo, su método para mostrar que no es verdadero no funciona: la instancia puede hacer referencia a la variable de clase y esos dos comandos devolverán el mismo ID. Sin embargo, cuando se repita para la variable 'mynum', lo harán devolver diferentes identificadores. – Confusion

5

creo que la diferencia es que += es una asignación (lo mismo que = y +), mientras que append cambia un objeto en el lugar.

mylist = [] 
    mynum = 0 

Esto asigna algunas variables de clase, una vez, en el tiempo de definición de clase.

self.mylist.append("Hey!") 

esto modifica el valor MyClass.mylist añadiendo una cadena.

self.mynum += 1 

Esto es lo mismo que self.mynum = self.mynum + 1, es decir, asigna self.mynum (miembro de instancia). La lectura del self.mynum le corresponde al miembro de la clase dado que en ese momento no hay ningún miembro de instancia con ese nombre.

+0

Técnicamente, MyClass.mylist no es un valor. Como self.mylist, es una variable. Llamar a self.mylist.append() no cambia el valor de MyClass.mylist; altera la lista a la que se refieren ambas variables. – tlayton

+0

+1. Buena respuesta. @tlayton: ¿Qué quieres decir cuando dices 'valor'? Ciertamente diría que al llamar 'self.mylist.append (" ¡Hola! ")' Altera el valor de 'MyClass.mylist'. –

+0

No según mi comprensión del significado de "valor" en este contexto. El valor de MyClass.mylist es la lista a la que se refiere. llamar a append() no hace que mylist haga referencia a una lista diferente, sino que altera la lista. Sin embargo, estoy interpretando el "valor" un poco demasiado técnico. – tlayton