newbie question on Python tutorial example in section 4.7.1 (default arg values)

P

Porky Pig Jr

Hello,
hope someone can clarify this section for me (or may be there is a
'newbie FAQs' in which case I would appreciate the link

Anyway, the example is

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

and somehow L just keeps growing between the calls, so

print f(1)
returns [1]
and then
print f(2)
returns [1, 2]
etc. I'm not really clear on this example, but assume that L is set
aside somewhere (in the function definition), initialized only once
(during the def), and then all subsequent calls affect that locally
defined L.

Given this is how it works (?), I still don't understand how the
following workaround works:

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

well, it does work, but why? Seems like we initialize L to None (which
is as I undersatnd, sort of equivalent of C void), in fact it also
works if we initialize L to some arbitrary string. What I don't
understand: we check if
L is None, and then make it a list and append 'a' to it. So it becomes
a list. When then on the subsequent call it is None again rather than
list with a single value of 'a' (in the previous call)?

TIA
 
D

Dennis Lee Bieber

well, it does work, but why? Seems like we initialize L to None (which
is as I undersatnd, sort of equivalent of C void), in fact it also

"void" is a data /type/ (normally applied to pointers, or to
places where we wish to declare that there is no return from a
function).

None is closer to C's null; technically a value that is not any
type of value.
works if we initialize L to some arbitrary string. What I don't
understand: we check if
L is None, and then make it a list and append 'a' to it. So it becomes
a list. When then on the subsequent call it is None again rather than
list with a single value of 'a' (in the previous call)?

The L=None in the argument list is "created" when the
interpreter processes the def statement, not during later calls.

L=[] does not change the space created for the "None" during the
def, it creates a new space -- an empty array -- and attaches the label
L to that new space. The next time you call, if no second argument is
supplied, L is reattached to the None space.

Python is sort of backwards compared to most traditional
languages. In most languages, a label/variable "L" is associated with a
"box" (memory address), and assignment to "L" changes the contents of
the box. Python's labels are "post-it notes", assignment to "L" works by
moving the post-it label to where-ever the data is located, not by
moving the data to where L is located.

--
 
P

Piet

[email protected] (Porky Pig Jr) wrote in message news: said:
def f(a, L=[]):
L.append(a)
return L

and somehow L just keeps growing between the calls, so

print f(1)
returns [1]
and then
print f(2)
returns [1, 2]
etc. I'm not really clear on this example, but assume that L is set
aside somewhere (in the function definition), initialized only once
(during the def), and then all subsequent calls affect that locally
defined L.
I agree that this one is diffcult to get. The point is (or seems to
be) that when you do not provide a value for the second argument
(which is true for all the function calls), the default value is used.
So during the first call, the interpreter notices that L is lacking,
assigns the default value to it (empty list) and then passes this
parameter together with the already given 'a' to the function.
When you call the function a second time without a value for L, the
default L will again be passed to the function, but it will not be
evaluated, so the assignment "L = []" will not be executed and the
non-empty list L will be passed to the function.
When you change the function to
def f(a, L=None):
if L is None:
L = []
L.append(a)
return L
your first function will evaluate the assignment for L resulting in
the "value" "None" for L (I am not sure whether one should talk about
values in this context). On successive calls to the function without a
given parameter for L, the expression for L will NOT be evaluated, but
the lack of the parameter will be noted, and thus the if-clause will
be evaluated as true and assign the value "[]" to L which will then be
used on further execution.
This is at least what I thought. I just checked the behaviour of the
function when you change the default value for L from "None" to "[]".
In this case I had expected that successive calls of the function
would notice the absence of L and that the if-clause would evaluate as
true as well, but this is not the case. So when you change the
function to
def f(a, L=[]):
if L is None:
L = []
L.append(a)
return L
and write
f(1),f(2) and so on you will also see that the list is growing with
each function call.
So my post was not that helpful, I guess. Maybe a python expert has
some explanation. I am looking forward to see how that thread will
develop.
Best wishes
Piet
 
R

Rich Krauter

Hello,
hope someone can clarify this section for me (or may be there is a
'newbie FAQs' in which case I would appreciate the link

Anyway, the example is

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

and somehow L just keeps growing between the calls, so

print f(1)
returns [1]
and then
print f(2)
returns [1, 2]
etc. I'm not really clear on this example, but assume that L is set
aside somewhere (in the function definition), initialized only once
(during the def), and then all subsequent calls affect that locally
defined L.

Given this is how it works (?), I still don't understand how the
following workaround works:

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

well, it does work, but why? Seems like we initialize L to None (which
is as I undersatnd, sort of equivalent of C void), in fact it also
works if we initialize L to some arbitrary string. What I don't
understand: we check if
L is None, and then make it a list and append 'a' to it. So it becomes
a list. When then on the subsequent call it is None again rather than
list with a single value of 'a' (in the previous call)?

TIA


Not sure if this will help you, and my description may not be entirely
accurate, but it helps me to remember that a function is a an object
with attributes. One of the attributes of a function object is called
func_defaults, which contains, suprisingly enough, a tuple of function
defaults:

(as you said, "L is set aside somewhere")
L.append(a)
print L

['__call__', '__class__', '__delattr__', '__dict__', '__doc__',
'__get__', '__getattribute__', '__hash__', '__init__', '__module__',
'__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__',
'__setattr__', '__str__', 'func_closure', 'func_code', 'func_defaults',
'func_dict', 'func_doc', 'func_globals', 'func_name']
func.func_defaults ([],)
func(1) [1]
func.func_defaults ([1],)
func(2) [1, 2]
func(3) [1, 2, 3]
func.func_defaults
([1, 2, 3],)

This happens because L (the list at func.fun_defaults[0]) is mutable.
Every call to func appends a value to that list in its func_defaults
tuple.

If L is instead made immutable in the function def, you see different
behavior:
if L is None:
# rebind [] to the name L, but not
# to func2.func_defaults[0]
L = []
L.append(a)
print L

func2(1) [1]
func2.func_defaults (None,)
func2(2) [2]
func2.func_defaults (None,)
func2(3,L=[1,2,3])
# rebind [1,2,3] to the name L, but not to func2.func_defaults[0]
[1, 2, 3, 3](None,)

Hope that helps.

Rich
 
S

Scott David Daniels

Rich said:
Anyway, the example is
def f(a, L=[]):
L.append(a)
return L

I still don't understand how the following workaround works:

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

well, it does work, but why? Seems like we initialize L to None
Here is the conceptual flaw: We initialize f's default to None.
When we call f (with only a value for a), on entry to the function
we associate the name L with the default (None). When the L = []
line is executed, we re-associate L with a new empty list.

In the original definition, we set the default to a particular new
empty list. The default will remain that particular list. L is a
name that is associated with objects, not a place a value is stored.

Mr. Pig, hopefully either my explanation or Mr. Krauter's (which is
also correct) will help you figure this out. The difference between
the two is our guess at what you don't understand. If it still does
not make sense, ask again.

An interesting exercise to show the bit about objects:

a = []
b = []
print id(a), a, id(b), b
a.append(1)
print id(a), a, id(b), b
a = b
print id(a), a, id(b), b
a.append(1)
print id(a), a, id(b), b

If you can explain the output of the four print statements and are
still confused, my explanation is not on point.

-Scott David Daniels
(e-mail address removed)
 
P

Porky Pig Jr

Scott David Daniels said:
If you can explain the output of the four print statements and are
still confused, my explanation is not on point.

-Scott David Daniels
(e-mail address removed)


Thanks for all the feedback, I'll study this (and other) example.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,537
Members
45,020
Latest member
GenesisGai

Latest Threads

Top