[Edu-sig] teaching Python

Bill Punch punch at cse.msu.edu
Fri Apr 23 20:46:38 CEST 2010


Default mutables are very confusing indeed. The recommended procedure is 
to assign the default to be None, then check for that value in the 
function and do the assignment there, such as:

def myFun(myList=None):
    if myList == None:
        myList = []
     ...

So is the example of replicating a mutable as Mark showed.  To round out 
the top three, which are the ones that get even my best students, look 
at the below:

myVar = 27

def myFun ():
    print myVar
    myVar += 1
    return myVar

print myFun

What happens when you run this?

        >>>bill<<<

kirby urner wrote:
> I had the good fortune to lead a three day
> immersive training in Python for scientist
> technicians working with astronomical data
> from Hubble.
>
> The focus was basic, core Python, although
> the workaday environment is intensively
> NumPy and Pyfits related (also matplotlib).
>
> Students found this behavior somewhat
> confusing.
>
>   
>>>> def f(arg, y=[]):
>>>>         
> 	y.append(arg)
> 	return y
>
>   
>>>> r = f(10)
>>>> r
>>>>         
> [10]
>   
>>>> r = f(11)
>>>> r
>>>>         
> [10, 11]
>   
>>>> r.append(100)
>>>> f(12)
>>>>         
> [10, 11, 100, 12]
>   
>
> Here's one of the explanations for this behavior:
>
> http://mail.python.org/pipermail/python-list/2007-March/1116072.html
>
> In the above example, y is bound to a mutable object
> at the time the function def is evaluated, not at runtime
> each time the function is called.  Once this object
> starts filling with data, it doesn't go out of scope but
> persists between function calls -- even though "y" is not
> in the globals namespace (it "lives" in the function).
>
> It gets worse:
>
>   
>>>> f(11,[])
>>>>         
> [11]
>   
>>>> f(12,[])
>>>>         
> [12]
>   
>>>> f(13)
>>>>         
> [10, 11, 100, 12, 13]
>   
>>>> f(14)
>>>>         
> [10, 11, 100, 12, 13, 14]
>
> One would think the empty list passed in as y would
> "override" and/or "reinitialize" y to the empty list.
> Instead, the passed in argument is bound to y at
> runtime, then goes out of scope.  Meanwhile, the
> object assigned to y at the time of evaluation is
> still there in the background, ready for duty if no
> 2nd argument is passed.
>
>   
>>>> r = f(12, [])
>>>> r
>>>>         
> [12]
>   
>>>> r = f(r)
>>>> r
>>>>         
> [10, 11, 100, 12, 13, 14, [12]]
>
> A metaphor I use is the "guard at the castle gate".
>
> At the time of evaluation (when the module is compiled
> and the function is defined -- whether or not it gets
> called), objects get stored in a closet at the gate
> entrance, and those parameters assigned to defaults
> will always get those same objects out of the
> closet, dust them off, and use them whenever
> nothing gets passed for the parameter to "run with"
> at the time the function is called.
>
> If nothing is handed over at "call time" then use
> whatever you've got in the closet, is the rule.
>
> If the default object is mutable and nothing is passed
> in, then the guard at the gate (the parameter in
> question) is bound to the "closet object" and does
> whatever work with that object, returning it to the
> closet when done.
>
> There's no re-evaluation or re-initialization of the
> default object at the time the function is called so
> if it's mutable and stuff got added or changed, then
> it returns to the closet in its changed form.  It
> does not "revert" to some evaluation-time value.
>
>   
>>>> del r
>>>> def f(arg, y=[]):
>>>>         
> 	global r
> 	y.append(arg)
> 	r = y
> 	return y
>
>   
>>>> r
>>>>         
>
> Traceback (most recent call last):
>   File "<pyshell#68>", line 1, in <module>
>     r
> NameError: name 'r' is not defined
>   
>>>> r = f(10)
>>>> r
>>>>         
> [10]
>   
>>>> r = f(11)
>>>> r
>>>>         
> [10, 11]
>   
>>>> r = []
>>>> r = f(11)
>>>> r
>>>>         
> [10, 11, 11]
>
> In the above case, r has been made a global variable
> inside the function.  Assigning r to the empty list
> above merely rebinds it externally however, does
> not affect y's default object in the closet.  When
> the function is run with no argument for y, r is
> rebound within the function to our growing default
> list object.
>
>   
>>>> f(9)
>>>>         
> [10, 11, 11, 9]
>   
>>>> r
>>>>         
> [10, 11, 11, 9]
>   
>>>> f(12)
>>>>         
> [10, 11, 11, 9, 12]
>   
>>>> r
>>>>         
> [10, 11, 11, 9, 12]
>   
>>>> r = []
>>>> f(12)
>>>>         
> [10, 11, 11, 9, 12, 12]
>
> Again, it does no good to set r to the empty list
> with the expectation of reaching the y default in
> the castle closet.  r is simply being rebound and
> is on the receiving end for y, which, in getting
> no arguments, simply reverts to using the growing
> list.
>
> At the end of the function call, however, r is bound
> to the same object as y (because of r = y, with
> r declared global), so we do have an opportunity
> to affect the y default object...
>
>   
>>>> r[0]=999
>>>> r
>>>>         
> [999, 11, 11, 9, 12, 12]
>   
>>>> f(12)
>>>>         
> [999, 11, 11, 9, 12, 12, 12]
>
> Ta dah!
>
>   
>>>> r.pop(0)
>>>>         
> 999
>   
>>>> r.pop(0)
>>>>         
> 11
>   
>>>> r.pop(0)
>>>>         
> 11
>   
>>>> r.pop(0)
>>>>         
> 9
>   
>>>> r.pop(0)
>>>>         
> 12
>   
>>>> r.pop(0)
>>>>         
> 12
>   
>>>> r.pop(0)
>>>>         
> 12
>
> The closet object has now had its members popped.  We're
> back to an empty list to start with, thanks to runtime
> operations:
>
>   
>>>> f(9)
>>>>         
> [9]
>   
>>>> f(9)
>>>>         
> [9, 9]
>
> So what's a quick way to empty a list without rebinding, i.e.
> we don't want to pop everything or remove one by one.  Nor
> do we want to end up with a bunch of None objects.
>
> Here's our global r:
>
>   
>>>> r
>>>>         
> [9, 9]
>
> Check it out with a new global:  we're able to delete slices:
>
>   
>>>> test
>>>>         
> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
>   
>>>> del test[0]
>>>> test
>>>>         
> [1, 2, 3, 4, 5, 6, 7, 8, 9]
>   
>>>> del test[1:5]
>>>> test
>>>>         
> [1, 6, 7, 8, 9]
>
> ... so that must mean we're able to delete all members of a
> list, without rebinding to a new list object:
>
>   
>>>> del test[:]
>>>> test
>>>>         
> []
>
> Experimenting with our function some more:
>
>   
>>>> f(11)
>>>>         
> [9, 9, 11]
>   
>>>> f(12)
>>>>         
> [9, 9, 11, 12]
>   
>>>> f(13)
>>>>         
> [9, 9, 11, 12, 13]
>
> So this is r before:
>
>   
>>>> r
>>>>         
> [9, 9, 11, 12, 13]
>
> And this is r after:
>
>   
>>>> del r[:]
>>>> r
>>>>         
> []
>
> ... meaning the y's default object in the
> castle gate closet has likewise been emptied
> out, given r is bound to it.
>
>   
>>>> f(13)
>>>>         
> [13]
>   
>>>> f(13)
>>>>         
> [13, 13]
>
> ... like starting over eh?
>
> This is safe syntax BTW:
>
> del [][:]
>
> In other words, it's safe to delete "all members" from
> an empty list without first checking to see if the list
> is empty or not.
>
>   
>>>> r = []
>>>> del r[:]
>>>> r
>>>>         
> []
>
> Note that there's really no need to declare r global in
> order to reach in and change y, provided there's binding
> going on at the time of return:
>
>   
>>>> del r
>>>> def f(arg, y=[]):
>>>>         
> 	y.append(arg)
> 	return y
>
>   
>>>> f(10)
>>>>         
> [10]
>   
>>>> f(11)
>>>>         
> [10, 11]
>   
>>>> f(12)
>>>>         
> [10, 11, 12]
>   
>>>> r
>>>>         
>
> Traceback (most recent call last):
>   File "<pyshell#148>", line 1, in <module>
>     r
> NameError: name 'r' is not defined
>   
>>>> r = f(13)
>>>> r
>>>>         
> [10, 11, 12, 13]
>   
>>>> del r[:]
>>>> r
>>>>         
> []
>   
>>>> f(10)
>>>>         
> [10]
>   
>>>> f(11)
>>>>         
> [10, 11]
>
> Kirby
> _______________________________________________
> Edu-sig mailing list
> Edu-sig at python.org
> http://mail.python.org/mailman/listinfo/edu-sig
>   
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/edu-sig/attachments/20100423/42f31036/attachment-0001.html>


More information about the Edu-sig mailing list