0

I assumed that whenever you passed an object into a function, it automatically created a separate copy for you that you could modify without changing the original. However, the following code terminates with this error every time.

>>> testvar = ['a' for a in range(100000)]
>>> def func(arg):
        while arg is testvar:
            arg.pop()
>>> func(testvar)
Traceback (most recent call last):
  File "<pyshell#47>", line 1, in <module>
    thing(m)
  File "<pyshell#43>", line 3, in thing
    obj.pop()
IndexError: pop from empty list
computergorl
  • 133
  • 1
  • 8
  • Arrays are not passed by value – vsoftco Aug 07 '15 at 19:25
  • possible duplicate of [In Python, why can a function modify some arguments as perceived by the caller, but not others?](http://stackoverflow.com/questions/575196/in-python-why-can-a-function-modify-some-arguments-as-perceived-by-the-caller) – TigerhawkT3 Aug 07 '15 at 19:26

2 Answers2

2

"I assumed that whenever you passed an object into a function, it automatically created a separate copy for you that you could modify without changing the original."

Well, your assumption there is wrong. Variables are passed by reference, not value. This includes if you set a default value for an argument to an empty list, and change it in the function, the next time you try to use that default value, it won't be an empty list anymore.

def foo(bar=[]):
    bar.append("baz")

foo()
foo()
# bar is now ["baz", "baz"]
chepner
  • 497,756
  • 71
  • 530
  • 681
fishsticks
  • 703
  • 1
  • 6
  • 16
  • 1
    "Most" variables? I'd say all. – chepner Aug 07 '15 at 20:37
  • @chepner Not integers and floats, at least. – fishsticks Aug 07 '15 at 20:38
  • 1
    @fishsticks: integers behave exactly the same way as lists-- no copies are made. It's simply that integers aren't mutable. Try `the_num = 1; print(id(the_num)); def add(num): print(id(num)); add(the_num)`. – DSM Aug 07 '15 at 20:40
  • `num += 1` has the same effect as `num = num + 1` because `int`s are immutable; the original object reference by `num` is still the same object that is passed as the argument. – chepner Aug 07 '15 at 20:43
  • @DSM in that particular case, it's because `1` is special (small integers are cached). You are correct in general though. – thebjorn Aug 07 '15 at 20:44
  • @thebjorn No. Try the same thing with `the_num = 13483209`. Even immutable objects are passed by reference; using operations like `+=` merely rebind the local name rather than mutate the original value. – chepner Aug 07 '15 at 20:45
  • @thebjorn: even in that case, the small-int cache plays no role in that snippet because at no point is an attempt made to create a new object which is pulled from the cache. Probably I should have used a bigger number to avoid the confusion, though. – DSM Aug 07 '15 at 20:57
  • @DSM I was simply saying that `id(1)` will always be the same (and the same as `id(3-2)`). Your code is correct in general, i.e. for any number both large and small, it's just that for small integers it's correct due to the small-int cache and not because of call-by-object-reference (Frederik has a nice page explaining/exploring Python's calling convention compared to standard terminology: http://effbot.org/zone/call-by-object.htm). – thebjorn Aug 07 '15 at 21:34
2

from: https://docs.python.org/3.4/library/copy.html

Assignment statements in Python do not copy objects, they create bindings between a target and an object. For collections that are mutable or contain mutable items, a copy is sometimes needed so one can change one copy without changing the other. This module provides generic shallow and deep copy operations (explained below).

when you are calling your function, you are doing the following:

func(arg=testvar)

If you want to have an actual copy of your data you could modify your function to the following:

def func(arg):
    arg_copy = arg[:]
    # mutate arg_copy here without mutating arg
Alex Jadczak
  • 550
  • 3
  • 11