What is a function parameter =[] for?

Steven D'Aprano steve at pearwood.info
Thu Nov 19 20:05:55 EST 2015


On Fri, 20 Nov 2015 04:30 am, BartC wrote:

> On 19/11/2015 16:01, Steven D'Aprano wrote:
[...]

> The whole concept of 'mutable' default is alien to me. A default is just 
> a convenient device to avoid having to write:
> 
>    fn(0) or fn("") or fn([])

Says who?

Here's another use for function defaults, as static storage:


# Ackermann's function
def ack(m, n, _memo={}):
    key = m, n
    if key not in _memo:
        if m==0: v = n + 1
        elif n==0: v = ack(m-1, 1)
        else: v = ack(m-1, ack(m, n-1))
        _memo[key] = v
    return _memo[key]


This is a quick and easy way to memoise a function which would otherwise be
horribly slow. And it only works because _memo is bound to a mutable object
once, and once only.

 
> You just write fn() instead. But it shouldn't come at the cost of
> completely different semantics! Because then it can't really be called a
> default value at all.

But it doesn't come with completely different semantics. This is the stock
standard semantics for assignment.

Since we're not passing an argument, let's get rid of the argument
altogether. If we put the assignment inside the body of the function, that
code is executed every time the function is called. "arg" gets a brand new,
empty list each time:

def test():
    arg = []
    arg.append(1)
    return len(arg)


If we put the assignment *outside* of the body of the function, that code is
executed once only, and the same list is used over and over again:

arg = []
def test():
    arg.append(1)
    return len(arg)



This is *exactly* the same semantics as for parameter defaults (except that
they aren't stored as globals, but as hidden fields deep inside the
function object itself). Now, look at the function declaration:


def test(arg=[]):
    arg.append(1)
    return len(arg)


The binding arg=[] is NOT inside the body of the function. Therefore, it is
NOT executed repeatedly, but only once.


>   isn't surprising to
>> somebody coming from a completely different paradigm. I was surprised by
>> it too, the first time I got bitten.
> 
> So you didn't bother reading the LRM either!

LRM? Left Right Manual?

No, I read it. I knew that the list was only created once. But like I said,
I didn't follow the implications of that. If it is only created once, and
you modify the value, the value will be modified.


>> py> def demo_const(x, y=[]):
>> ...     return x + len(y)
> 
>> Exactly as you should expect. Where you run into trouble is when the
>> default value is NOT a constant:
>>
>> py> def demo_variable(x, y=[]):
>> ...     y.append(1)
>> ...     return x + len(y)
> 
> Sorry, what is the default value in each of these? As the first lines of
> the defintions look identical apart from the function names.

The *value* is whatever contents the list holds.

a = []
a.append(1)
a.append(2)
a.append(3)

What's the value of a? Are you shocked and horrified to discover that the
value of a is not the empty list, but the list [1, 2, 3]?



>> py> demo_variable(5)
>> 6
>> py> demo_variable(5)
>> 7
>> py> demo_variable(5)
>> 8
>>
>>
>> If you modify the value, the value will be modified. Why are you
>> surprised by this?
> 
> Which value is being modified? The []?

The object bound to y is being modified. y.append(1) modifies the object
bound to y. That object gets taken from the defaults if you don't supply a
value yourself. That default object starts of life as an empty list, but it
doesn't stay empty if you append to it.



>>>> When you deal with mutable objects, you have to expect them to mutate.
>>>> The whole point of mutability is that their value can change.
>>>
>>> That [] doesn't look like an object that could change.
>>
>> Of course it does.
> 
> You've lost me know.
> 
> Are you saying that:
> 
>    a=[]
> 
> why sometimes not assign an empty list, because that [] could have been
> modified?

No. But [] is syntax for an empty list, and lists can change -- they are
mutable.

a = []
a.append(1)

Are you shocked to learn that a is no longer an empty list? (I know I asked
a similar question before, but this is an important point.)


>> It is a list literal, like int literals, float literals,
>> string literals and the rest.
> 
> Another surprise? Literals by definition can't change:

Says who? The existence of literal syntax is a feature of the programming
language. The mutability of values is a *separate* feature. The two are
unrelated.


> def fn():
> a=[10,20,30]
> a.append(999)
> 
> I would hope that a is set to [10,20,30] at each entry to the function!

Naturally. Each time you call the function, the body of the function
executes and creates a new list. You take that list, and then modify it in
the very next line with a.append(999).

What happens if you only create the list *once*?

a = [10,20,30]
def fn():
    a.append(999)
    print(a)


Are you surprised that a doesn't get reset to [10, 20, 30] each time you
call the function?


>> Assignments are not copies at all.
> 
> if you write A=B then something of B needs to have been copied into A,
> even if it's just the reference that B contains. Otherwise it would be
> difficult to get A to refer to the same object as B.

B doesn't contain the reference to itself. The reference is external to B.
Nothing of B is copied.



-- 
Steven




More information about the Python-list mailing list