2009-10-31 13 views
6

Estoy realmente atascado en por qué el siguiente bloque de código 1 da como resultado la salida 1 en lugar de la salida 2?Alcance de Python/malentendido estático

bloque de código 1:

class FruitContainer: 
     def __init__(self,arr=[]): 
      self.array = arr 
     def addTo(self,something): 
      self.array.append(something) 
     def __str__(self): 
      ret = "[" 
      for item in self.array: 
       ret = "%s%s," % (ret,item) 
      return "%s]" % ret 

arrayOfFruit = ['apple', 'banana', 'pear'] 
arrayOfFruitContainers = [] 

while len(arrayOfFruit) > 0: 
    tempFruit = arrayOfFruit.pop(0) 
    tempB = FruitContainer() 
    tempB.addTo(tempFruit) 
    arrayOfFruitContainers.append(tempB) 

for container in arrayOfFruitContainers: 
    print container 

**Output 1 (actual):** 
[apple,banana,pear,] 
[apple,banana,pear,] 
[apple,banana,pear,] 

**Output 2 (desired):** 
[apple,] 
[banana,] 
[pear,] 

El objetivo de este código es para recorrer una matriz y envolver cada una en un objeto padre. Esta es una reducción de mi código real que agrega todas las manzanas a una bolsa de manzanas y demás. Mi suposición es que, por alguna razón, está usando el mismo objeto o actuando como si el contenedor de fruta usa una matriz estática. No tengo idea de cómo arreglar esto.

+1

No es una respuesta a su pregunta, pero también es digno de mención: "while len (arrayOfFruit)> 0:" es equivalente a "while arrayOfFruit:".El último es preferible, según la Guía de estilo de Python al menos. –

Respuesta

2

Su código tiene un argumento por defecto para inicializar la clase. El valor del argumento predeterminado se evalúa una vez, en tiempo de compilación, por lo que cada instancia se inicializa con la misma lista. Cambiarlo así:

def __init__(self, arr=None): 
    if arr is None: 
     self.array = [] 
    else: 
     self.array = arr 

He hablado de ello con más detalle aquí: How to define a class in Python

8

Nunca debe usar un valor mutable (como []) para un argumento predeterminado para un método. El valor se calcula una vez y luego se usa para cada invocación. Cuando utiliza una lista vacía como valor predeterminado, esa misma lista se utiliza cada vez que se invoca el método sin el argumento, incluso cuando el valor se modifique mediante llamadas a funciones previas.

hacer esto en su lugar:

def __init__(self,arr=None): 
    self.array = arr or [] 
+0

Perfecto !!! Esto es fantástico y simple. –

+4

Realmente no me gusta verte probando 'None' al ver si el valor es falso. Es mejor usar una prueba 'is None'. Una persona que llama podría pasar legalmente una lista vacía para el inicializador, y su código descartaría esa lista vacía y haría una nueva lista vacía ... esto solo aparecería si alguien estuviera tratando de hacer que varias instancias de la clase compartan el mismo lista inicialmente vacía, supongo, pero es posible. En cualquier caso, usar 'is' para probar' None' es un buen hábito. – steveha

+0

Tiene razón, 'is None' es más seguro. –

1

Como dice Ned, el problema es que está utilizando una lista como argumento por defecto. Hay más detalles here. La solución es cambiar __init__ función de la siguiente manera:

 def __init__(self,arr=None): 
      if arr is not None: 
       self.array = arr 
      else: 
       self.array = [] 
0

una mejor solución que pasa en Ninguno - en este caso particular, en lugar de en general - es para tratar el parámetro arr de __init__ como un conjunto numerable de elementos pre-inicializar el FruitContainer con, en lugar de una matriz a utilizar para el almacenamiento interno:

class FruitContainer: 
    def __init__(self, arr=()): 
    self.array = list(arr) 
    ... 

Esto le permitirá pasar en otros tipos enumerables para inicializar el contenedor, el cual los usuarios de Python más avanzados esperan ser capaces de hacer:

myFruit = ('apple', 'pear') # Pass a tuple 
myFruitContainer = FruitContainer(myFruit) 
myOtherFruit = file('fruitFile', 'r') # Pass a file 
myOtherFruitContainer = FruitContainer(myOtherFruit) 

También distender otro potencial de errores de aliasing:

myFruit = ['apple', 'pear'] 
myFruitContainer1 = FruitContainer(myFruit) 
myFruitContainer2 = FruitContainer(myFruit) 
myFruitContainer1.addTo('banana') 
'banana' in str(myFruitContainer2) 

Con todas las otras implementaciones de esta página, esto devolverá cierto, porque ha aliasado accidentalmente el almacenamiento interno de sus contenedores.

Nota: Este enfoque no siempre es la respuesta correcta: "si no es ninguno" es mejor que en otros casos. Pregúntese: ¿estoy pasando un conjunto de objetos o un contenedor mutable? Si la clase/función en la que estoy pasando mis objetos cambia el almacenamiento que le di, ¿sería (a) sorprendente o (b) deseable? En este caso, yo diría que es (a); por lo tanto, la llamada a la lista (...) es la mejor solución. Si (b), "si no ninguno" sería el enfoque correcto.

+1

Hola, supongo que soy "alguien". No tengo inconveniente en codificar este ejemplo de esta manera. En este ejemplo, está pasando fruta para agregarla al almacenamiento interno en la clase FruitContainer. El usuario no está pasando tanto un objeto de lista como inicializando el contenedor con algo de fruta. Ahora, en el caso general, no creo que sea una buena idea coaccionar silenciosamente las cosas que el usuario proporciona.Creo que la razón por la que estábamos hablando el uno del otro es que estaba concentrado en el caso general y que pensabas en este caso tan específico. * En general, * no rompa el tipado de pato coaccionando tipos. – steveha

+1

Excelente. He reescrito mi último párrafo para reflejar esto. ¡Gracias por tomarse el tiempo de aclararme su punto! –

+0

Lamento no haber averiguado de dónde vienes antes. Estaba en modo pedante completo, supongo. :-) – steveha