views:

167

answers:

4

I'm really stuck on why the following code block 1 result in output 1 instead of output 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 goal of this code is to iterate through an array and wrap each in a parent object. This is a reduction of my actual code which adds all apples to a bag of apples and so forth. My guess is that, for some reason, it's either using the same object or acting as if the fruit container uses a static array. I have no idea how to fix this.

+8  A: 

You should never use a mutable value (like []) for a default argument to a method. The value is computed once, and then used for every invocation. When you use an empty list as a default value, that same list is used every time the method is invoked without the argument, even as the value is modified by previous function calls.

Do this instead:

def __init__(self,arr=None):
    self.array = arr or []
Ned Batchelder
Perfect!!! This is fantastic and simple.
Raymond Berg
I really do not like to see you testing for `None` by seeing if the value evaluates false. It is better to use an `is None` test. A caller could legally pass an empty list in for the initializer, and your code would discard that empty list and make a fresh empty list... this would only come up if someone was trying to make several instances of the class all share the same initially-empty list, I guess, but it is possible. In any event, using `is` to test for `None` is a good habit.
steveha
You're right, `is None` is safer.
Ned Batchelder
Oh, a better example for how your code could fail: someone could make a subclass of list with some desired extra properties, pass that as the initializer, and if it evaluated false your code would discard it in favor of an empty, standard list.
steveha
Better still, leave arr=[] and use self.array = list(arr). As it stands, the user cannot pass in a generator, or a tuple, to the constructor without the class breaking.
chrispy
+1 excellent, concise communication.
Ewan Todd
@chrispy, that is a bad idea. If the user wants to expand an iterator to a list, the user can call `FruitContainer(list(iterable))`. But what if the user wants to pass in a subclass of a list, with side-effect logging or something? Your `list(arr)` would get rid of the subclassed object and force it to be a boring list. There is a standard, time-tested Python idiom for this, and it is the test for `is None` followed by storing the provided argument if it was not `None`.
steveha
@steveha, how likely is it that the user wants to concern himself with the internal storage of the class?
chrispy
@chrispy, we are talking about passing an object in to the class. We can pass in an object that might have a side-effect we desire, such as a subclass of list() that logs when you mutate it; or we could pass in a particular object and hold another reference to it; or who knows what. Why are you so eager to prevent people from doing things you don't anticipate? As I said, there is a standard, time-tested Python idiom for how you handle passing in a mutable object. Why not just follow that idiom?
steveha
@steveha, I think you're conflating "more options" with "useful". Your approach means the user of FruitContainerClass always needs to be careful to pass in something (a) list-like, i.e. no tuples, no iterables, and (b) not aliased. Any mistake (e.g. passing in the same list to two constructors), and they get silent and difficult to debug behavioural bugs. All this just in case they might want to add something like side-effect logging? That's an anti-pattern. Never expose class internals like that. There's even a FindBugs check for it.
chrispy
@chrispy, as for (a) it is hardly a problem to require the user pass something list-like if nothing but a list-like thing makes sense; I can give you examples from Python's standard library if you like. As for (b) not aliased, how do you know the user didn't want an aliased list for some reason? Why are you so determined to make sure that no use case you didn't foresee is possible? Now, as far as your idea to force a `list()`, I'll meet you half-way: I think it's a perfectly good idea to try to use the argument as a list, and if it fails, *then* call `list()` on it.
steveha
@chrispy, one question: do you think that the design of Python with respect to passing a mutable object to a class initializer is "an anti-pattern"? After all, this very question arose from a "silent and difficult to debug behavioural bug". I'm not trolling, I'm perfectly serious, trying to understand where you are coming from on this.
steveha
As I state under chrispy's answer, I don't object to simplifying the initialization function for a specific `FruitContainer` class to take any iterable, and pull fruit from the iterable and store it internally. But in general, think twice before forcing types; for example, if you made a function that concatenated two list objects, you definitely should not call `list()` on the two objects before concatenating them; just trust them to be list-like and let the exception happen if they are not. Look up "duck typing" to see where I am coming from on this.
steveha
+1  A: 

As Ned says, the problem is you are using a list as a default argument. There is more detail here. The solution is to change __init__ function as below:

       def __init__(self,arr=None):
           if arr is not None:
               self.array = arr
           else:
               self.array = []
thrope
+2  A: 

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

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

I discussed this more fully here: http://stackoverflow.com/questions/1495666/how-to-define-a-class-in-python/1495740#1495740

steveha
A: 

A better solution than passing in None — in this particular instance, rather than in general — is to treat the arr parameter to __init__ as an enumerable set of items to pre-initialize the FruitContainer with, rather than an array to use for internal storage:

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

This will allow you to pass in other enumerable types to initialize your container, which more advanced Python users will expect to be able to do:

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

It will also defuse another potential aliasing bug:

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

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

Note: This approach is not always the right answer: "if not None" is better in other cases. Just ask yourself: am I passing in a set of objects, or a mutable container? If the class/function I'm passing my objects in to changes the storage I gave it, would that be (a) surprising or (b) desirable? In this case, I would argue that it is (a); thus, the list(...) call is the best solution. If (b), "if not None" would be the right approach.

chrispy
Hi there, I guess I am "someone". I have no objection to coding this example this way. In this example, you are passing in fruit to be added to internal storage in the FruitContainer class. The user isn't so much passing in a list object as initializing the container with some fruit. Now, in the general case, I don't think it is a good idea to silently coerce the things the user provides. I think the reason we were talking past each other is that I was focused on the general case and you were thinking of this very specific case. *In general,* don't break duck typing by coercing types.
steveha
Excellent. I've rewritten my last paragraph to reflect this. Thanks for taking the time to make your point clearer to me!
chrispy
I'm just sorry I didn't figure out where you were coming from sooner. I was in full-on pedant mode, I guess. :-)
steveha