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

BartC bc at freeuk.com
Wed Nov 25 08:20:31 EST 2015


On 25/11/2015 10:52, Steven D'Aprano wrote:
> On Wed, 25 Nov 2015 07:14 pm, Antoon Pardon wrote:

>> 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.

What do you think most people would expect to happen here? I know, 
because you gave a spoiler, that it loops forever, otherwise I wouldn't 
be sure /without trying it/ (but I tried it anyway).

Here's the same code in a somewhat different language:

L := (1,2)
for i in L do
|   L append:= i
|   println L
od

And this is the output:

(1,2,1)
(1,2,1,2)

Which output (infinite series of [1,2,1,2,1,....] or the above) is more 
sensible, and which do you think people might prefer?

The point is that the behaviour of the loop is by no means obvious, so 
neither is the behaviour of the function defaults.

> 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.

Accept that some things /are/ a source of confusion. When, in writing 
documentation, I find something hard to explain something, then I try 
and make it simpler in the program. But not enough of that goes on: it 
seems to be more lucrative to write thicker user manuals, and provide 
longer training courses, than to make software simpler.

> 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.

Sure. The problem is that it might not be obvious it's initialised once.

> 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.

The concept of variables doesn't take long to learn in an 'ordinary' 
language.

But one problem with Python is that it gives the impression that almost 
anything is possible. Your example can be made to work with a slight tweak:

  count = 1
  coin = lambda: random.choice(['heads', 'tails'])
  while coin() != 'heads':
       count += 1

In some languages, there would never be such a possibility because they 
don't have the extraordinary flexibility and dynamism of Python. But 
once you get the idea that the language can almost do magic, then it 
doesn't seem so far-fetched that you original example could work!

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

Many languages don't have the concept of code running outside a function 
body. And especially they don't have the truly bizarre one that a 
function definition is itself a piece of code that is executed. Or maybe 
not executed, if it's inside a conditional statement! And therefore does 
not exist. This is an extra hurdle with learning or even using this 
language.

-- 
bartc



More information about the Python-list mailing list