Search code examples
pythonmutability

Python: Adding attributes to mutables inside a function


I try to understand what best practices are in cases like this please. Suppose we have a dict (or a list or some other mutable) which is altered inside a function (but defined outside it)

d = {'a': 0}

def my_fun(x):
    for i, el in enumerate(x):
        d[el] = i + 1

Calling my_fun(['b', 'c']) and then print(d) will print {'a':0, 'b':1, 'c':2} which is fine. However the same can be done by adding a return statement to the function (although not necessary). Or even, the return statement plus a second arg in the function representing the dict:

d = {'a': 0}

def my_fun(x, d):
    for i, el in enumerate(x):
        d[el] = i + 1
    return d

Both the return statement and the second arg are redundant here, I just find they help in terms of clarity. It is easier to read. Calling d = my_fun(['b', 'c'], d) and then print(d) will print again {'a':0, 'b':1, 'c':2}

Finally, you could choose to pass a copy of the mutable object: d = my_fun(['b', 'c'], d.copy()) which is probably more pythonic but I dont know for example if making copies is good practice in terms of memory management.

What is considered best practice here? How do you handle these types and add attributes to mutables inside functions?


Solution

  • It's not good practice to modify a mutable like that in a function. You're right that both the methods you've suggested modify the original object. It's better to return a new object for a simple case like this.

    In this simple example you could, as you say, copy the dict. IMO you should do this in the function. You need to be clear from the outset what the structure of the dict is, because you might need a deep copy.

    def my_fun(x, d):
        d = d.copy()
        for i, el in enumerate(x):
            d[el] = i + 1
        return d
    

    Or you could create a new dict and update it with the old one. I prefer this, but you'd have to be careful about repeat keys.

    def my_func(x, d):
        result = {el: i for i, el in enumerate(x)}
        result.update(d)
        return result
    

    For a more complicated thing, you might have a class to encapsulate the entire thing.

    class Foo:
        def __init__(self, d):
            self.d = d
    
        def update(self, x):
            for i, el in enumerate(x):
                self.d[el] = i