0

When an object is used to create another object from a different class, it seems that arrays passed on as parameters are actually shared between both classes. Integers don't exhibit this sort of behaviour. I don't know how to explain it further, hopefully the code will speak for itself. Is this behaviour intended, and if it is, how is it possible to make the arrays separate?

class foo:
    def __init__(self, array, integer):
        self.list = array
        self.list[0] = self.list[0] + 1
        self.number = integer
        self.number = self.number + 1
        print('list: ' + str(self.list[0]))
        print('number: ' + str(self.number))
class bar:
    def __init__(self):
        self.objects = []
        self.defaultarray = [1]
        self.defaultnumber = 1
        for i in range(0,3):
            print('default list: ' + str(self.defaultarray))
            self.objects.append(foo(self.defaultarray, self.defaultnumber))

item = bar()   

#expected result:
#default list: [1]
#list: 2, number: 2
#default list: [1]
#list: 2, number: 2
#default list: [1]
#list: 2, number: 2

#actual result:
#default list: [1]
#list: 2, number: 2
#default list: [2]
#list: 3, number: 2
#default list: [3]
#list: 4, number: 2
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
  • 1
    I'm not sure what you're expecting? You're passing the same list to each instance of `foo` (the one defined at `self.defaultarray = [1]`) then mutating it in foo `self.list[0] = self.list[0] + 1` – Adam Smith Mar 18 '19 at 03:46
  • Possible duplicate of [How do I pass a variable by reference?](https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference) - The question title is misleading, but the most voted answer explains what you are seeing. – rayryeng Mar 18 '19 at 03:47
  • Why not initialize with a *copy* of the list that you pass the constructor? – John Coleman Mar 18 '19 at 03:47
  • Read this: https://nedbatchelder.com/text/names.html – juanpa.arrivillaga Mar 18 '19 at 05:57
  • " Integers don't exhibit this sort of behaviour. " Very important to understand, both `int` objects and `list` objects *have the exact same behavior with regards to the evaluation strategy*, however, you simply cannot mutate an `int` object, but you can mutate a `list` object – juanpa.arrivillaga Mar 18 '19 at 06:03

1 Answers1

0

I actually wasn't aware of this behavior, but from what I can tell, Python passes arrays by reference rather than value when instantiating. This means, as you can tell from your code example, that you will be mutating the array from both objects.

If you're strictly looking to pass a copy of the array and not have both objects mutate it, you can use:

self.objects.append(foo(self.defaultarray.copy(), self.defaultnumber))

If you're familiar with C, think of Python passing the address of the array to the object. Both objects reference the same address, therefore both mutate the same array. I hope this helps alleviate some of the mystery surrounding what's actually going on here.

RElliott
  • 93
  • 1
  • 8
  • 1
    _everything_ is passed by reference. Nothing is passed by value in Python. – Adam Smith Mar 18 '19 at 03:54
  • 1
    Python passes by assignment. However for objects, the reference is passed by value so you're allowed to mutate the object within methods. https://stackoverflow.com/questions/986006/how-do-i-pass-a-variable-by-reference – rayryeng Mar 18 '19 at 03:54
  • 1
    @AdamSmith nothing in Python is passed by reference. Frequently, passing a reference is incorrectly described as "pass by reference" but that is not what pass by reference means. The defining characteristic of pass by reference is that assignments to variables are seen *in the caller*. E.g. `def Foo(&a): a = 42` then `x = 0; Foo(x); print(x)` would print `42`, using a non-existent pass by reference function. Nothing in python can do this. – juanpa.arrivillaga Mar 18 '19 at 05:56
  • 1
    @rayryeng just to be clear, *everything* is an object in Python. The evaluation strategy of python functions don't change based on the type of the objects. It always works the same. – juanpa.arrivillaga Mar 18 '19 at 06:01
  • @juanpa.arrivillaga fair, but slightly muddying the point. I would disagree with you about the "defining characteristic" and instead claim that the lack of value copies make this "pass by reference." Also note that `def Foo(&lst): lst.append(42)` matches your behavior exactly -- what is passed is a reference to the value, not a reference to the memory location of the value. That said: "pass by assignment" is how the Python docs officially characterize the behavior. – Adam Smith Mar 18 '19 at 15:51
  • 1
    @AdamSmith no, it does not match the behavior I was highlighting. This would work in call by reference, but it also works in call by sharing (assignment), which also doesn't copy arguments passed as parameters. In call by reference, the argument is just an alias to the *variable* that gets passed. So again, most importantly, assignments are seen in the caller. If not the "defining" characteristic I would call it "distinguishing". One could write a `def swap(&a, &b)` and pass in `x = 1; y = 2; swap(1, 2); print(x, y)` and get `2 1`. try that in Python. Or Java, or Javascript etc – juanpa.arrivillaga Mar 18 '19 at 16:36
  • @juanpa.arrivillaga I know how the behavior you're describing works -- Python isn't my only language :), we're only squabbling on terminology (what the "reference" in "pass by reference" means). – Adam Smith Mar 18 '19 at 17:08