Python Scoping / Static Misunderstanding

I am really obsessed with why the next block of code 1 leads to pin 1 instead of pin 2?

Code Block 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,] 

The purpose of this code is to iterate over the array and wrap each in the parent object. This is an abbreviation of my actual code, which adds all apples to a bag of apples and so on. I assume that for some reason it either uses the same object, or acts as if the fruit container is using a static array. I do not know how to fix this.

+6
scope python iteration class static-members
source share
4 answers

Your code has a default argument to initialize the class. The default argument value is evaluated once at compile time, so each instance is initialized with the same list. Change it like this:

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

I discussed this in more detail here: How to define a class in Python

+2
source share

You should never use a mutable value (for example, []) for the default argument to a method. The value is calculated once and then used for each call. When you use an empty list as the default value, the same list is used every time the method is called without an argument, even when the value is changed using previous function calls.

Do this instead:

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

As Ned says, the problem is that you are using the list as the default argument. Here in more detail. The solution is to change the __init__ function, as shown below:

  def __init__(self,arr=None): if arr is not None: self.array = arr else: self.array = [] 
+1
source share

A better solution than passing to None - in this particular case, and not in general - is to consider the arr parameter for __init__ as an enumerated set of elements for initializing FruitContainer, and not for the array used for internal storage:

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

This will allow you to pass in other enumerated types to initialize your container that more advanced Python users are expected to expect:

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

It will also defuse another possible alias error:

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

With all the other implementations on this page, this will return True, because you accidentally overlaid the internal storage of your containers.

Note: This approach is not always the correct answer: "if not," then it is better in other cases. Just ask yourself: am I passing a set of objects or a mutable container? If the class / function with which I pass my objects modifies the store I gave it would be (a) unexpected or (b) desirable? In this case, I would say that it is (a); thus, calling a list (...) is the best solution. If (b), "if not None" would be the right approach.

0
source share

All Articles