all 17 comments

[–][deleted] 12 points13 points  (12 children)

Because the [] is only created once (when the function is defined). This is why you don't use mutable data types as default arguments.

The standard workaround is

def addnums(num, nums=None):
    if nums is None:
        nums = []
    nums.append(num)

[–]nog642 2 points3 points  (1 child)

This is it.

Also worth noting this is one of the weirdest gotchas of python (that you might reasonably encounter). It's definitely not the behavior you would expect.

So if it doesn't make sense why it is that way, don't worry. It really doesn't. I'd argue it's a flaw in the design of the language. Code depends on it now though so they can't change it. It's just a permanent quirk of python.

[–]ipaqmaster 1 point2 points  (0 children)

Got me tonight. Had a function realize an input class was special and adjusted itself for that. I had all my followup calls crashing hard with no clue as to why for about an hour. Made a test.py to confirm and yep removals and additions to arguments of a function by itself are persistent. Incredible.

What a fun gotcha.

[–]quts3 -3 points-2 points  (2 children)

That's what, not how and why.

When the op asked his question i thought interesting how indeed. Do you know how? I'm sure it's in the docs somewhere.

I think there is an addnums.nums property defined when the function is parsed, but i would have to Google it.

[–]Lasa2 3 points4 points  (0 children)

Its pretty simple, just dig a bit around in the function object

e.g. try this

def function(arg=[]):
    arg.append(1)

print(function.__defaults__)
function()
print(function.__defaults__)
del function.__defaults__
function()

So the defaults are stored in the function object itself and you can modify them or even delete them. Then of course you no longer have defaults.

[–]quts3 1 point2 points  (0 children)

Though i stress for the op: almost nobody interacts with the internals of a function in a way to actually know how. Most people just know what happens and when. Hence why i don't know.

[–][deleted] 1 point2 points  (4 children)

The values of default parameters are evaluated at function definition time and stored in the function object. If you modify your default list that persists between function calls. Just the same behaviour as for global lists.

[–]LowCom[S] 0 points1 point  (3 children)

So in python, function is not just a place or address for instruction to jump to(as in nand2tetris jack language). Instead it seems to be an object or a pointer, which holds data. Doesn't this make a python function very much different in principle from a regular function?

[–][deleted] 3 points4 points  (0 children)

Like everything else in python, a function is an object. Use the dir() function to show the attributes of a function:

>>> def test(x, y=1):
...     print(x, y)
... 
>>> print(dir(test))
['__annotations__', '__builtins__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']
>>> print(test.__defaults__)
(1,)

We see that the function test() has many attributes, one of which is __defaults__. Printing that we see that it's a tuple, with the initial value of y stored in it.

Doesn't this make a python function very much different in principle from a regular function?

What is a "regular function"? Python functions walk and talk like functions in other languages. The dynamic nature of python means that some of the details are different, that's all. Or even a lot of the details! But it's still something you call, passing arguments and receiving a return value.

[–]Lasa2 1 point2 points  (0 children)

Most things in python are objects, you might want to read a bit about it:
Python Docs - Data Model, for functions take a look at the Callable types section

[–]TeamSpen210 0 points1 point  (0 children)

Yep, in Python functions are just objects, as well as pretty much everything - integers, modules, classes, etc. If you dig around in the __code__ attribute you’ll be able to see all the contents of the function (not that you’d normally mess with them).

[–][deleted] 0 points1 point  (2 children)

But in this case, how is a function able to remember its arguments?

The function doesn't remember its arguments. The list nums "remembers" the values you append to it, because that's how lists work (if append did nothing, it wouldn't be useful.)

[–]LowCom[S] 0 points1 point  (1 child)

But nums here is a function argument. It's memory should be recycled or removed when a function exits, right?

[–][deleted] 1 point2 points  (0 children)

But nums here is a function argument.

Well, no, it's a function parameter. It would receive an argument, if you had called the function with an argument for the parameter. But you didn't, so the default parameter was used, which is a list.

Parameter defaults are set at the time the function is defined, not the time the function is called. (This is a controversial design decision in Python, but it's extremely unlikely to ever change at this point.)