Late-binding of function defaults (was Re: What is a function parameter =[] for?)

Steven D'Aprano steve at pearwood.info
Wed Nov 25 05:52:24 EST 2015


On Wed, 25 Nov 2015 07:14 pm, Antoon Pardon wrote:

> Op 20-11-15 om 01:33 schreef Steven D'Aprano:
>> On Fri, 20 Nov 2015 07:57 am, Marko Rauhamaa wrote:
>>
>>> Laura Creighton <lac at openend.se>:
>>>
>>>> My experience says that the people who are confused want lists to
>>>> behave like tuples. period. i.e. they don't want lists to be mutable.
>>> I think it's simpler than that. When you have:
>>>
>>>    def f(x=[]):
>>>        y = []
>>>
>>> the first [] is evaluated when "def" is executed, while the latter [] is
>>> evaluated whenever "f" is executed. It's easy to be confused.
>> It shouldn't be. The function declaration
>>
>>     def f(x=[]):
>>
>> is executed only once. The function body, conveniently indented to make
>> it stand out:
>>
>>         y = []
>>
>> is executed every time you call the function.
> 
> What exactly is your point? 

That there is a simple analogy between the distinction between code
inside/outside a for-loop, and code inside/outside a function. If you can
understand why this loops forever, instead of just twice, then you can
understand why function defaults work the way they do:

L = [1, 2]
for i in L:
    L.append(i)
    print(L)


There is nothing "bizarre" or complicated or difficult to understand
happening here. It might not be what you want to happen. It might not be
what you expect to happen. But if you can't understand why it happens even
after multiple explanations, then your learning skills are severely
lacking.



> People's confusions don't disappear 
> because you as an expert have a good understanding of what is
> going on and so are no longer confused.

I'm an expert? Awesome!

No. But people's confusion would disappear if they would:

- think about the process
- try to follow the steps of what is happening
- pay attention to the explanations given
- and ask for clarification instead of arguing.

I have come to the conclusion that there is nobody as stupid as an
intelligent person who refuses to learn.

I really don't know how more clear we can possibly be. If you take a list,
initialised as the empty list ONCE, and then modify it repeatedly, the list
doesn't magically become empty just because you want it to be empty.

I completely understand beginners making this mistake:

# Simulate tossing a coin until it is heads.
count = 1
coin = random.choice(['heads', 'tails'])
while coin != 'heads':
    count += 1

"Why does the loop run forever?"


The coin doesn't magically toss itself, no matter how intuitively obvious it
is that it should. (And I'm not making this example up -- I've seen three
or four beginners make equivalent errors. It seems to be a very common
conceptual mistake.)

These beginners, at least, don't argue back that it is "bizarre" and stupid
and crazy that you have to choose a new value for the coin variable each
time through the loop. They soon learn that code outside the loop only runs
once, if you want it to run each time through the loop, you should put it
inside the body of the loop.

Just like functions. If you want code to run each time you call the
function, PUT IT INSIDE THE FUNCTION BODY.


> Some aspects in the langauage are easily grasped and other
> aspects tend to create confusion. 

You're right. Some aspects of the language are inherently confusing and hard
to reason about. Threads are an example of that. The more powerful, and
tricky, aspects of regular expressions. The weird stuff that happens during
interpreter shutdown if you have __del__ methods in your objects. __del__
methods in general. There are many things which are hard to reason about,
and therefore confusing.

*This is not one of them.*

This is not hard to reason about. The rules are no different from what
happens in simple, ordinary code:

L = []  # Initialise the list *once*.
def func():
    L.append(1)
    return L

If I call func() three times, what value does it return? If you can get
that, you can get this:

def func(L=[]):  # Initialise the list *once*.
    L.append(1)
    return L

Confusing? Absolutely not. Or rather, if anyone cannot understand the
behaviour of either function *after having it explained multiple times*,
then programming is the wrong area for them. They should take up something
more suited to their intellectual limitations, like dish washing, ditch
digging, or politics.

But is it predictable or intuitive? No, I agree, this behaviour is not
intuitive, if you are coming from a background without equivalent rules. I
will completely grant you that most people without Python experience would
not correctly predict the behaviour of mutable defaults.

I was caught out on that too, as I already admitted. Even though I already
knew all the facts I needed to predict the behaviour correctly, I didn't
put 2+2 together and get 4. That's *my bad*, not the language's fault.

How much harder must it be for those who don't know the essential facts? I
don't expect anyone to intuit the behaviour of Python defaults. That would
be unreasonable. There's no shame in guessing wrong from a position of
ignorance.

Me, on the other hand... I should have known better. I did know better.

But there's a big difference between those who guess wrong from a position
of ignorance, and then make an honest attempt to understand the behaviour
and why it actually does make sense and is even sometimes useful (even if
they don't like it), and those people who insist that it is nonsensical,
magical and "bizarre".


 

-- 
Steven




More information about the Python-list mailing list