New submission from Edward Segall:

I am using the tutorial to learn Python. I know many other languages, and I've 
taught programming language theory, but even so I found the warning in Section 
4.7.1 about Default Argument Values to be confusing. After I spent some effort 
investigating what actually happens, I realized that the warning is incomplete. 

I'll suggest a fix below, after explaining what concerns me. 

Here is the warning in question:

-----------------------------------------------------------------
Important warning: The default value is evaluated only once. This makes a 
difference when the default is a mutable object such as a list, dictionary, or 
instances of most classes. ...

def f(a, L=[]):
    L.append(a)
    return L

print(f(1))
print(f(2))
print(f(3))

This will print

[1]
[1, 2]
[1, 2, 3]
-----------------------------------------------------------------

It's clear from this example that values are carried from one function 
invocation to another. That's pretty unusual behavior for a "traditional" 
function, but it's certainly not unheard of -- in C/C++/Java, you can preserve 
state across invocations by declaring that a local variable has static 
lifetime. When using this capability, though, it's essential to understand 
exactly what's happening -- or at least well enough to anticipate its behavior 
under a range of conditions. I don't believe the warning and example are 
sufficient to convey such an understanding. 

After playing with it for a while, I've concluded the following: "regular" 
local variables have the usual behavior (called "automatic" lifetime in C/C++ 
jargon), as do the function's formal parameters, EXCEPT when a default value is 
defined. Each default value is stored in a location that has static lifetime, 
and THAT is the reason it matters that (per the warning) the expression 
defining the default value is evaluated only once. 

This is very unfamiliar behavior -- I don't think I have used another modern 
language with this feature. So I think it's important that the explanation be 
very clear. 

I would like to suggest revising the warning and example to something more like 
the following: 

-----------------------------------------------------------------
Important warning: When you define a function with a default argument value, 
the expression defining the default value is evaluated only once, but the 
resultant value persists as long as the function is defined. If this value is a 
mutable object such as a list, dictionary, or instance of most classes, it is 
possible to change that object after the function is defined, and if you do 
that, the new (mutated) value will subsequently be used as the default value.  

For example, the following function accepts two arguments:

def f(a, L=[]):
    L.append(a)
    return L

This function is defined with a default value for its second formal parameter, 
called L. The expression that defines the default value denotes an empty list. 
When the function is defined, this expression is evaluated once. The resultant 
list is saved as the default value for L. 

Each time the function is called, it appends the first argument to the second 
one by invoking the second argument's append method. 

If we call the function with two arguments, the default value is not used. 
Instead, the list that is passed in as the second argument is modified. 
However, if we call the function with one argument, the default value is 
modified. 

Consider the following sequence of calls. First, we define a list and pass it 
in each time as the second argument. This list accumulates the first arguments, 
as follows: 


myList=[]
print(f(0, myList))
print(f(1, myList))

This will print: 

[0]
[0, 1]

As you can see, myList is being used to accumulate the values passed in to the 
first as the first argument.
 
If we then use the default value by passing in only one argument, as follows:

print(f(2))
print(f(3))

we will see: 

[2]
[2, 3]

Here, the two invocations appended values to to the default list. 

Let's continue, this time going back to myList:

print(f(4,myList))

Now the result will be:

[0, 1, 4]

because myList still contains the earlier values.

The default value still has its earlier values too, as we can see here:

print(f(5))

[2, 3, 5]

To summarize, there are two distinct cases: 

1) When the function is invoked with an argument list that includes a value for 
L, that L (the one being passed in) is changed by the function. 

2) When the function is invoked with an argument list that does not include a 
value for L, the default value for L is changed, and that change persists 
through future invocations. 
-----------------------------------------------------------------

I hope this is useful. I realize it is much longer than the original. I had 
hoped to make it shorter, but when I did I found I was glossing over important 
details.

----------
assignee: docs@python
components: Documentation
messages: 264135
nosy: docs@python, esegall
priority: normal
severity: normal
status: open
title: Python Tutorial 4.7.1: Need to explain default parameter lifetime
type: enhancement
versions: Python 3.5

_______________________________________
Python tracker <rep...@bugs.python.org>
<http://bugs.python.org/issue26842>
_______________________________________
_______________________________________________
Python-bugs-list mailing list
Unsubscribe: 
https://mail.python.org/mailman/options/python-bugs-list/archive%40mail-archive.com

Reply via email to