1

I am trying to understand how functions internally store the value for default arguments.

I thought it was stored into the attribute __defaults__ of the function object. However this attribute seems to store a copy of the objects value (the list value), and not the reference of the object itself.

# I declare a list and defines it as the default argument value for my_function
>>> my_list = [1,2,3]
>>> def my_function(value=my_list):
...     print(my_list)
... 

# the call to "my_function" displays the value of "my_list"
>>> my_function()
[1, 2, 3]

# The modification of "my_list" affects the results displayed by "my_function" 
>>> my_list.append(4)
>>> my_function()
[1, 2, 3, 4]

# The tuple storing my_function default arguments seems to store the value of "my_list"
# but not its reference (my_list identity)
>>> my_function.__defaults__
([1, 2, 3, 4],)

If my_function.__defaults__ was the only copy made, the edition of my_list should not modify the behavior of the function. However, this affects the function behavior which shows that the function object stores the reference of my_list somehwhere (and not only the value).

Could you please explain me how it is done ?

Chplouc
  • 11
  • 1
  • 1
    Why do you think the tuple doesn't store a reference? The list you got has `4` appended to it. – Barmar Oct 01 '20 at 05:46
  • 1
    Check `id(my_list) == id(my_function.__default__)` – Barmar Oct 01 '20 at 05:47
  • 2
    Python never makes copies of objects unless you explicitly tell it to. – Barmar Oct 01 '20 at 05:53
  • See https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument – Barmar Oct 01 '20 at 05:53
  • I don't understand what you don't understand. The function is storing the list in `__defaults__` as your code demonstrates unequivocally. You say "However this attribute seems to store a copy of the objects value". No, no it doesn't. Why do you say that? You've even demonstrated that that when you modify the list outside the function, that is reflected in `my_function.__defaults__`. What do you think "storing it's reference, not its value" would look like? – juanpa.arrivillaga Oct 01 '20 at 06:10
  • @Barmar. OK I get what you mean, thanks for your answer. Since `id(my_list) == id(my_function.__default__[0]`, it is indeed the reference which is passed. I was expecting to see the name `my_list` into `my_function.__default__`, so I guess my brain just went full stupid – Chplouc Oct 01 '20 at 06:46
  • If it stored the name then you could do `my_list = 3` and that would change the behavior of the function. But it doesn't, it stores a reference to the value. – Barmar Oct 01 '20 at 06:48

3 Answers3

0

The short answer is mutability of the default object.

list is a bit more complicated to comprehend, but let's look into instance of a class.

class A:
    def __init__(self):
        self.param = 1
a = A()
def foo(arg=a):
    print(arg.param)

Here the default value is an instance of a custom class. Calling foo prints 1. But Python is not told yet how to copy a. So it uses the instance with the id provided at function definition. Now it should make more sence why

a.param = 2
foo()

outputs 2.

Kate Melnykova
  • 1,863
  • 1
  • 5
  • 17
  • The question claims that it stores a copy, not a reference. It's wrong, but you're not answering that question. – Barmar Oct 01 '20 at 05:50
0

You came across the precise reason why it is discouraged to use mutable objects for default parameters. There is more on that (with examples), e.g. on this blog.

list is a mutable type, therefore my_function.__defaults__ stores only a reference to my_list you defined earlier. You can then obtain the reference and modify it as you did. It is easy to do so accidentally and that is why it is discouraged to use mutable objects as default parameters.

sophros
  • 14,672
  • 11
  • 46
  • 75
  • An edit generated a second incarnation of the answer. – sophros Oct 01 '20 at 05:50
  • Mutability is sort of irrelevant. `my_function.__defaults__` *always* stores only a reference to the object you use when creating the function. It's just that mutable objects can behave unexpectedly if you don't realize that – juanpa.arrivillaga Oct 01 '20 at 06:16
0

I guess I was expecting to see something like this :

>>> my_function.__defaults__
(my_list,)

And since instead it showed the value of the list, I thought it was just passing the value. You all showed me it is indeed the reference which is passed, sorry for wasting your time guys.

Chplouc
  • 11
  • 1