Concatenation of the result of a function with a mutable default argument

前端 未结 2 1345
星月不相逢
星月不相逢 2020-12-30 18:29

Suppose a function with a mutable default argument:

def f(l=[]):
    l.append(len(l))
    return l

If I run this:

def f(l=[         


        
2条回答
  •  渐次进展
    2020-12-30 19:20

    Here's a way to think about it that might help it make sense:

    A function is a data structure. You create one with a def block, much the same way as you create a type with a class block or you create a list with square brackets.

    The most interesting part of that data structure is the code that gets run when the function is called, but the default arguments are also part of it! In fact, you can inspect both the code and the default arguments from Python, via attributes on the function:

    >>> def foo(a=1): pass
    ... 
    >>> dir(foo)
    ['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', ...]
    >>> foo.__code__
    ", line 1>
    >>> foo.__defaults__
    (1,)
    

    (A much nicer interface for this is inspect.signature, but all it does is examine those attributes.)

    So the reason that this modifies the list:

    def f(l=[]):
        l.append(len(l))
        return l
    

    is exactly the same reason that this also modifies the list:

    f = dict(l=[])
    f['l'].append(len(f['l']))
    

    In both cases, you're mutating a list that belongs to some parent structure, so the change will naturally be visible in the parent as well.


    Note that this is a design decision that Python specifically made, and it's not inherently necessary in a language. JavaScript recently learned about default arguments, but it treats them as expressions to be re-evaluated anew on each call — essentially, each default argument is its own tiny function. The advantage is that JS doesn't have this gotcha, but the drawback is that you can't meaningfully inspect the defaults the way you can in Python.

提交回复
热议问题