Search code examples
pythonobjectmethodsargumentsmutable

Python: Passing mutable(?) object to method


I'm trying to implement a class with a method that calls another method with an object that's part of the class where the lowest method mutates the object. My implementation is a little more complicated, so I'll post just some dummy code so you can see what I'm talking about:

class test:
    def __init__(self,list):
        self.obj = list
    def mult(self, x, n):
        x = x*n
    def numtimes(self, n):
        self.mult(self.obj, n)

Now, if I create an object of this type and run the numtimes method, it won't update self.obj:

m = test([1,2,3,4])
m.numtimes(3)
m.obj  #returns [1,2,3,4]

Whereas I'd like it to give me [1,2,3,4,1,2,3,4,1,2,3,4]

Basically, I need to pass self.obj to the mult method and have it mutate self.obj so that when I call m.obj, I'll get [1,2,3,4,1,2,3,4,1,2,3,4] instead of [1,2,3,4].

I feel like this is just a matter of understanding how python passes objects as arguments to methods (like it's making a copy of the object, and instead I need to use a pointer), but maybe not. I'm new to python and could really use some help here.

Thanks in advance!!


Solution

  • Allow me to take on the bigger subject of mutability.

    Lists are mutable objects, and support both mutable operations, and immutable operations. That means, operations that change the list in-place, and operations that return a new list. Tuples, for contrast, only are only immutable.

    So, to multiply a list, you can choose two methods:

    1. a *= b

    This is a mutable operation, that will change 'a' in-place.

    1. a = a * b

    This is an immutable operation. It will evaluate 'a*b', create a new list with the correct value, and assign 'a' to that new list.

    Here, already, lies a solution to your problem. But, I suggest you read on a bit. When you pass around lists (and other objects) as parameters, you are only passing a new reference, or "pointer" to that same list. So running mutable operations on that list will also change the one that you passed. The result might be a very subtle bug, when you write:

    >>> my_list = [1,2,3]
    >>> t = test(my_list)
    >>> t.numtimes(2)
    >>> my_list
    [1,2,3,1,2,3]  # Not what you intended, probably!
    

    So here's my final recommendation. You can choose to use mutable operations, that's fine. But then create a new copy from your arguments, as such:

    def __init__(self,l):
        self.obj = list(l)
    

    OR use immutable operations, and reassign them to self:

    def mult(self, x, n):
        self.x = x*n
    

    Or do both, there's no harm in being extra safe :)