mutable default parameter problem [Prothon]

Grégoire Dooms dooms at info.LESS.ucl.SPAM.ac.be
Fri Jun 18 03:39:57 EDT 2004


Mark Hahn wrote:
> Troy Melhase wrote:
> 
> 
>>Here's an idea:  if it ain't broke, don't fix it.
>>
>>Seriously, you see a "wart" and a "problem".  I see a pleasant
>>side-effect of the documented semantics.  True, new folks are
>>surprised by the behavior, but once it's understood, it becomes more
>>powerful.
> 
> 
> All four of the Python gotcha's, wart's and regrets lists I have found
> included this problem.  It is not only a newbie's problem as I showed in my
> posting.
> 
> 
>>How do you intend to account for code like this:
>>
>>def F(a, b, cache={}):
>>    try:
>>        return cache[(a,b)]
>>    except (IndexError, ):
>>        value = cache[(a,b)] = do_some_long_calc(a,b)
>>        return value
>>
>>Or even this:
>>
>>shared_cache = {}
>>
>>def F(a, b, cache=shared_cache):
>>    ...
> 
> 
> The first example is very unreadable and uncool in general.  Your second
> example will work just fine with our fix.
> 
> 
>>Of course you can argue that this is bad style,
> 
> 
> Yes I (and many others) will.
> 
> 
>>but the counter
>>argument is just as strong:  this is quite pythonic and quite
>>readable.
> 
> 
> I disagree strongly.  I would never be caught coding something like that and
> I love Python dearly.
> 
> 
>>Python is a tool, and you decrease the utility of that tool when you
>>limit it's idioms.
> 
> 
> So far you have only shown me an idiom that many say should not be used.
> Show me one that everyone agrees is useful.
> 
> 
>>>How much Python code would these different proposals break?
>>
>>A lot.  I ran this:
>>
>>$  find /usr/lib/python2.3/ -name "*.py" -exec grep "def.*=\[\]" {}
>>\; | wc
>>
>>And see 67 instances just in the standard library.  Multiply that by
>>a factor of 1000, 10000 or more to reflect code in the field, and you
>>might start to understand the significance of changing the language
>>definition.
> 
> 
> That count is not accurate.  Fixing this will not break every use of [] as a
> default formal param.  Using [] in __init__ for example would break nothing.
> I can think of many other cases where it is legal to use [].  The only case
> I can think of that would break would be the idiom we disagree on above.  If
> I am wrong, then show me other cases.
> 

Right. I'd like to see how many of these 67 instances of mutable def 
args actually mutates them. I hope it is 0. In this case the proposed 
modification would break nothing at all.

But I wonder what the actual semantics of the second proposal are.

You say the default argument is evaluated in its declaration context at 
each call. But is that evaluation context garanteed to be the same at 
every call ?

#Ex:
x=0
def t(a,b,c=x+1):
     # could I assert c==1 if t called with 2 args ?

def make_some_obj():
     return []
def t(a,b,c=make_some_obj()):
     # can I assert c==[] if t called with 2 args ?

I think every data coming from outside the expression (the variables in 
the expression including the global variables accessed from  a function) 
should be preserved. Let aside the eventual mutable state stored in an 
object:

#Ex:
class Inc:
     def __init__(self):
         self.x=0
     def newval():
	self.x += 1
	return self.x
x=Inc()
def t(a,b,c=x.newval()):
     # can I assert c will increase by 1 at each call with 2 args ?



> If I also might make a general argument for the fix then let me continue.
> Doing a late evaluation of the default expression makes the language more
> dynamic, which fits the overall goal of making Prothon more dynamic.  Using
> prototypes instead of classes, dynamic var scoping, this fix, and many other
> Prothon changes from Python all work towards that goal.
> 
> Dynamic var scoping fixed another Python gotcha which doesn't break
> anything.  Here are the two versions of code showing the problem and the
> fix:
> 
> --- Python ---
> 
> 
>>>>x = 1
>>>>def f():
> 
> ...         x = x + 1
> ...         print x
> ...
> 
>>>>f()
> 
> UnboundLocalError: local variable 'x' referenced before assignment
> 
> --- Prothon ---
> 
> O>> x = 1
> 1
> O>> def f():
> ...        x = x + 1
> ...        print x
> ...
> O>> f()
> 2
> 
> Prothon's scoping rules are dynamic which means that x comes from outside
> the function until the actual assignment happens.  At that point x becomes a
> local variable.  This, along with the fact that vars are inherited from
> ancestors along with methods, allow for some intuitive and simple var
> initialization techniques.

So then
0>> f()
2
0>> x
1

> 
> Obviously it is the responsibility of the programmer to make sure that the
> outer x has the proper initialization value for the local x.  This can cause
> a hiding-of-uninitialized-vars bug if the programmer uses the same names for
> unrelated variables but it is worth the extra power and intuitiveness.
> 

Nice, I had never heard about Prothon. I'll give it a look.


--
Grégoire Dooms



More information about the Python-list mailing list