8
votes

I want to pass a list into function by value. By default, lists and other complex objects passed to function by reference. Here is some desision:

def add_at_rank(ad, rank):
    result_ = copy.copy(ad)
    .. do something with result_
    return result_

Can this be written shorter? In other words, I wanna not to change ad.

4
if ad is a list, you can do result_ = ad[:] - avasal
It can't really be written shorter, no. You have to explicitly copy the list. You can either do that outside the function or inside the function, but you have to do it. - BrenBarn
Actually the reference is passed by value - John La Rooy
The terms "by value" and "by reference" aren't used often in Python, because they tend to be misleading to people coming from other languages. As @gnibbler says, everything is passed by value in Python—but some values are references. :) - abarnert
Also, the usual Pythonic way to deal with this is to avoid mutating result_ in the first place; create a new list by transforming the old one (e.g., with a list comprehension, map, filter, etc.). Sometimes that's not appropriate, and without knowing what your ".. do something with result_" code looks like or does, it's impossible to say whether that's true here. But it's appropriate far more often than newcomers to Python (especially from languages like Java or C++) expect. - abarnert

4 Answers

16
votes

You can use [:], but for list containing lists(or other mutable objects) you should go for copy.deepcopy():

lis[:] is equivalent to list(lis) or copy.copy(lis), and returns a shallow copy of the list.

In [33]: def func(lis):
    print id(lis)
   ....:     

In [34]: lis = [1,2,3]

In [35]: id(lis)
Out[35]: 158354604

In [36]: func(lis[:])
158065836

When to use deepcopy():

In [41]: lis = [range(3), list('abc')]

In [42]: id(lis)
Out[42]: 158066124

In [44]: lis1=lis[:]

In [45]: id(lis1)
Out[45]: 158499244  # different than lis, but the inner lists are still same

In [46]: [id(x) for x in lis1] = =[id(y) for y in lis]
Out[46]: True

In [47]: lis2 = copy.deepcopy(lis)  

In [48]: [id(x) for x in lis2] == [id(y) for y in lis]  
Out[48]: False
5
votes

This might be an interesting use case for a decorator function. Something like this:

def pass_by_value(f):
    def _f(*args, **kwargs):
        args_copied = copy.deepcopy(args)
        kwargs_copied = copy.deepcopy(kwargs)
        return f(*args_copied, **kwargs_copied)
    return _f

pass_by_value takes a function f as input and creates a new function _f that deep-copies all its parameters and then passes them to the original function f.

Usage:

@pass_by_value
def add_at_rank(ad, rank):
    ad.append(4)
    rank[3] = "bar"
    print "inside function", ad, rank

a, r = [1,2,3], {1: "foo"}
add_at_rank(a, r)
print "outside function", a, r

Output:

"inside function [1, 2, 3, 4] {1: 'foo', 3: 'bar'}"
"outside function [1, 2, 3] {1: 'foo'}"
0
votes

A shallow copy is usually good enough, and potentially mush faster than deep copy.

You can take advantage of this if the modifications you are making to result_ are not mutating the items/attributes it contains.

For a simple example if you have a chessboard

board = [[' ']*8 for x in range(8)]

You could make a shallow copy

board2 = copy.copy(board)

It's safe to append/insert/pop/delete/replace items from board2, but not the lists it contains. If you want to modify one of the contianed lists you must create a new list and replace the existing one

row = list(board2[2])
row[3] = 'K'
board2[2] = row

It's a little more work, but a lot more efficient in time and storage

0
votes

In case of ad is list you can simple call your function as add_at_rank(ad + [], rank).

This will create NEW instance of list every time you call function, that value equivalented of ad.

>>>ad == ad + []
True

>>>ad is ad +[]
False

Pure pythonic :)