Reputation: 31797
I would like to emulate the pass-by-value behaviour in Python. In other words, I would like to make absolutely sure that the functions I write do not modify user-supplied data.
One possible way is to use deep copy:
from copy import deepcopy
def f(data):
data = deepcopy(data)
#do stuff
Is there a more efficient or more Pythonic way to achieve this goal, making as few assumptions as possible about the object being passed (such as .clone()
method)?
I'm aware that technically everything in Python is passed by value. I am interested in emulating the behaviour, i.e. making sure I don't mess with the data that was passed to the function. I guess the most general way is to clone the object in question either with its own clone mechanism or with deepcopy
.
Upvotes: 79
Views: 101404
Reputation: 1343
Many people use the standard library copy. I prefer to defining __copy__
or __deepcopy__
in my classes. The methods in copy
may have some problems.
To avoid these out-of-control behaviors, define your own shallow/deep copy methods through overwriting __copy__
and __deepcopy__
. And Alex's answer give a good example.
Upvotes: 0
Reputation: 1097
Further to user695800's answer, pass by value for lists possible with the [:] operator
def listCopy(l):
l[1] = 5
for i in l:
print i
called with
In [12]: list1 = [1,2,3,4]
In [13]: listCopy(list1[:])
1
5
3
4
list1
Out[14]: [1, 2, 3, 4]
Upvotes: 2
Reputation:
There are only a couple of builtin typs that work as references, like list
, for example.
So, for me the pythonic way for doing a pass-by-value, for list, in this example, would be:
list1 = [0,1,2,3,4]
list2 = list1[:]
list1[:]
creates a new instance of the list1, and you can assign it to a new variable.
Maybe you could write a function that could receive one argument, then check its type, and according that resulta, perform a builtin operation that could return a new instance of the argument passed.
As I said earlier, there are only a few builtin types, that their behavior is like references, lists in this example.
Any way... hope it helps.
Upvotes: 20
Reputation: 156268
Though i'm sure there's no really pythonic way to do this, i expect the pickle
module will give you copies of everything you have any business treating as a value.
import pickle
def f(data):
data = pickle.loads(pickle.dumps((data)))
#do stuff
Upvotes: 1
Reputation: 3183
usually when passing data to an external API, you can assure the integrity of your data by passing it as an immutable object, for example wrap your data into a tuple. This cannot be modified, if that is what you tried to prevent by your code.
Upvotes: 4
Reputation: 75825
There is no pythonic way of doing this.
Python provides very few facilities for enforcing things such as private or read-only data. The pythonic philosophy is that "we're all consenting adults": in this case this means that "the function shouldn't change the data" is part of the spec but not enforced in the code.
If you want to make a copy of the data, the closest you can get is your solution. But copy.deepcopy
, besides being inefficient, also has caveats such as:
Because deep copy copies everything it may copy too much, e.g., administrative data structures that should be shared even between copies.
[...]
This module does not copy types like module, method, stack trace, stack frame, file, socket, window, array, or any similar types.
So i'd only recommend it if you know that you're dealing with built-in Python types or your own objects (where you can customize copying behavior by defining the __copy__
/ __deepcopy__
special methods, there's no need to define your own clone()
method).
Upvotes: 50
Reputation: 22159
You can make a decorator and put the cloning behaviour in that.
>>> def passbyval(func):
def new(*args):
cargs = [deepcopy(arg) for arg in args]
return func(*cargs)
return new
>>> @passbyval
def myfunc(a):
print a
>>> myfunc(20)
20
This is not the most robust way, and doesn't handle key-value arguments or class methods (lack of self argument), but you get the picture.
Note that the following statements are equal:
@somedecorator
def func1(): pass
# ... same as ...
def func2(): pass
func2 = somedecorator(func2)
You could even have the decorator take some kind of function that does the cloning and thus allowing the user of the decorator to decide the cloning strategy. In that case the decorator is probably best implemented as a class with __call__
overridden.
Upvotes: 41
Reputation: 4143
I can't figure out any other pythonic option. But personally I'd prefer the more OO way.
class TheData(object):
def clone(self): """return the cloned"""
def f(data):
#do stuff
def caller():
d = TheData()
f(d.clone())
Upvotes: 4