简体   繁体   中英

Python passing object as argument without overwriting

Consider this code:

class MyClass:
    def __init__(self, x):
        self.x = x

def myfunct(arg):
    arg.x += 1
    return arg

a = MyClass(0)
print(a.x)

b = myfunct(a)
print(a.x)
print(b.x)

This returns:

0
1
1

I would expect this code to behave in the same way as this one:

def myfunct(arg):
    arg += 1
    return arg

c = 0
print(c)

d = myfunct(c)
print(c)
print(d)

However the latter returns:

0
0
1

I understand this is due to Python's way of passing arguments by assignment, as explained in this post , or this post .

However, I can't figure out a way to work around the behavior exhibited in the first code, which is unwanted in the project I am working on. How can I pass an object as an argument to a function, return a madified object, and keep the original one untouched?

You're explicitly modifying your object. Python supports this behavior by default, but if you want to prevent modification of your object you may want to update the __setattr__ to manage attribute modification.

If you want to prevent the original object from modifying and you want to modify the object sent to the function you can add a __copy__ method to your object to be copyable in a way you like, then pass a copy of your object to the function using copy.copy() .

class MyClass:
    def __init__(self, x):
        self.x = x

    # default copy  
    def __copy__(self):
        cls = self.__class__
        result = cls.__new__(cls)
        result.__dict__.update(self.__dict__)
        return result

Demo:

In [21]: a = MyClass(0)
    ...: print(a.x)
    ...: 
0
# import copy
In [22]: b = myfunct(copy.copy(a))

In [24]: a.x
Out[24]: 0

In [25]: b.x
Out[25]: 1

The simple solution is, just create or pass a copy. To do that you have to possibiltys. Either you create a .copy() method on the class or use the copy module.

A copy method could look like this:

class MyClass:
    def __init__(self, x):
        self.x = x
    def copy(self):
        return self.__class__(self.x)

The copy module works like this:

import copy
b = copy.copy(a)

You can use either way to create a function that simply returns a new copy of the argument:

def myfunct(arg):
    arg = arg.copy() # Or copy.copy(arg)
    arg.x += 1
    return arg

Edit: As many other answers say, my approach shown above doesn't work if you have mutable objects in mutable objects (as example an object of your class, that has another object of your class in its args attribute). In that case use the copy.deepcopy function instead:

def myfunct(arg):
    arg =  copy.deepcopy(arg)
    arg.x += 1
    return arg
from copy import deepcopy
def myfunct(arg):
    new_arg = deepcopy(arg)
    new_arg.x += 1
    return new_arg

I would recommend deepcopy over copy since you want to make sure that all references to the original object are cut.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM