On Mar 13, 6:12 am, "Diez B. Roggisch" <[EMAIL PROTECTED]> wrote: > [EMAIL PROTECTED] wrote: > > (Accompanied by Marvin Gaye) > > >>>> def f(list=[0]): > > ... list[0]+=1 > > ... return list[0] > > ... > >>>> f() > > 1 > >>>> f() > > 2 > >>>> f() # 'list' is a name bound to a list (mutable) so this makes sense > > 3 > >>>> f([5]) > > 6 > >>>>f() # What's Going On? > > 4 > > That the same default argument is mutated? What did you expect, that it got > replaced by you passing another list? That would kind of defy the meaning > of default-arguments, replacing them whenever you call a function with > actual parameters.
f( ite, itr= ite.__iter__ ). Case 1: 'ite' is bound in outer scope. Case 2: not. function(code, globals[, name[, argdefs[, closure]]]) The optional argdefs tuple specifies the default argument values. TypeError: __defaults__ must be set to a tuple object >>> import functools >>> def k(): ... print( 'k call' ) ... >>> class GT: ... def __init__( self, arg ): ... self._arg= arg ... >>> def latebound( fun ): ... @functools.wraps( fun ) ... def post( *ar ): ... lar= list( fun.__defaults__ ) ... for i, a in enumerate( lar ): ... if isinstance( lar[ i ], GT ): ... lar[ i ]= eval( a._arg) ... return fun( *( list( ar )+ lar ) ) ... return post ... >>> @latebound ... def f( ite, itr= GT('k()') ): ... return itr ... >>> f( 2 ) k call >>> f( 2 ) k call >>> f( 2 ) k call >>> Furthermore, ... >>> @latebound ... def f( ite, itr= GT('ar[0]+ 1') ): ... return itr ... >>> f( 2 ) 3 >>> f( 2 ) 3 >>> f( 2 ) 3 >>> Unfortunately, ... >>> @latebound ... def f( ite, itr= GT('ite+ 1') ): ... return itr ... >>> f( 2 ) Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 7, in post File "<string>", line 1, in <module> NameError: name 'ite' is not defined >>> The moral of the story is, wouldn't it be nice if wrappers could access the post-invocation bindings of the wrappee's internal variables? Certainly the 'TypeError: __defaults__ must be set to a tuple object' is unnecessarily restrictive: let it be a mutable, or, if it's not a tuple, call it. Just add a __call__ method to that tuple that does nothing, if a type-test isn't faster-- perhaps even a null __call__ which does -actually- nothing. (Why is __defaults__ None when empty, not an empty tuple?) What are the use case proportions of function-static vs. call-time evaluation? Function-static is nice in that it keeps objects floating around with the function, both in information design, and in encapsulation. They both have easy workarounds: >>> import functools >>> def auto( fun ): ... @functools.wraps( fun ) ... def post( *ar ): ... return fun( post, *ar ) ... return post ... >>> @auto ... def f( self ): ... print( self.ite ) ... >>> f.ite= [] >>> >>> f() [] >>> f.ite.append( 2 ) >>> f() [2] @auto adds the binding function to its called parameter list, which is nice if the function will change names ever-- that is, become bound to other variables-- because its statics still stay with it, and because it's not a global either. >>> g= f >>> del f >>> g() Traceback (most recent call last): File "<stdin>", line 1, in <module> File "<stdin>", line 4, in post File "<stdin>", line 3, in f NameError: global name 'f' is not defined But on the other hand, if the Python community on the whole wants to keep the status quo, >>> def f( ite, itr= None ): ... if None is itr: ... itr= ite+ 1 ... return itr ... >>> f( 2 ) 3 >>> f( 2 ) 3 >>> f( 2 ) 3 isn't so bad. (If you think it is, lobby!) @latebound is a good compromise. It keeps the information in the right place, but takes a little redundancy if the evalled expression refers to another variable, and but costs the additional GT class, and adds an O( n ) argument-length boilerplate rearrangement. @auto frees us to allow a different behavior to default parameters while keeping both statics and call-times explicit, keeping the information in the right place, but it's a change in the language. Lastly, we have: >>> @late( itr= 'ite+ 1' ) ... def f( ite, itr= latearg, j= 0 ): ... return itr, j ... >>> print( f( 2 ) ) (3, 0) Proof of concept: import functools latearg= object() def late( **kw ): def pre( fun ): _defs= fun.__defaults__ if None is _defs: _defs= () _names= fun.__code__.co_varnames _deflen= len( _defs ) _namelen= fun.__code__.co_argcount for k in kw: if k not in _names: raise TypeError( 'Non-parameter' ' keyword \'%s\' in \'late\'' ' call.'% k ) print( _defs ) print( _names ) print( _deflen ) for a, b in zip( _names[ -_deflen: ], _defs ): if b is latearg and a not in kw: raise TypeError( 'Non-bound' ' latearg \'%s\' in \'late\'' ' call.'% k ) @functools.wraps( fun ) def post( *ar ): _arglen= len( ar ) _defleft= _namelen- _arglen _defused= () if _defleft: _defused= _defs[ -_defleft: ] _lar= list( ar+ _defused ) _funargs= {} for i, a in enumerate( ar ): _funargs[ _names[ i ] ]= a for k, v in kw.items(): if k not in _names: raise TypeError( 'Not all latearg' ' arguments bound in call' ' of \'%s\''% fun.__name__ ) _place= _names.index( k ) if _place>= _arglen: _lar[ _place ]= eval( v, globals(), _funargs ) if latearg in _lar: raise TypeError( 'Not all latearg' ' arguments bound in call' ' of \'%s\''% fun.__name__ ) return fun( *_lar ) return post return pre @late( itr= 'ite+ 1' ) def f( ite, itr= latearg, j= 0 ): return itr, j assert f( 2 )== ( 3, 0 ) assert f( 2, 0 )== ( 0, 0 ) assert f( 2, 0, 1 )== ( 0, 1 ) assert f( 2, 1 )== ( 1, 0 ) To complete 'post' (**kw not shown) is left as an exercise to the reader. -- http://mail.python.org/mailman/listinfo/python-list