11

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.

Camellia
  • 141
  • 14
dondublon
  • 701
  • 1
  • 6
  • 13
  • 1
    if `ad` is a list, you can do `result_ = ad[:]` – avasal Mar 13 '13 at 04:25
  • 1
    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 Mar 13 '13 at 04:26
  • 2
    Actually the _reference_ is passed by value – John La Rooy Mar 13 '13 at 04:56
  • 4
    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 Mar 13 '13 at 05:15
  • 3
    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 Mar 13 '13 at 05:19
  • abarnet, you right, I come from another world :) - the world of compilation-language. – dondublon Mar 13 '13 at 06:36
  • 1
    What you call variables are actually [names](https://nedbatchelder.com/text/names.html) in Python. They are *references* to objects, not the objects themselves. Read the linked article for a better understanding of names and values. – Roland Smith Jul 09 '17 at 10:11

4 Answers4

21

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
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
7

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'}"
tobias_k
  • 81,265
  • 12
  • 120
  • 179
0

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

John La Rooy
  • 295,403
  • 53
  • 369
  • 502
0

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 :)