[lnkForumImage]
TotalShareware - Download Free Software

Confronta i prezzi di migliaia di prodotti.
Asp Forum
 Home | Login | Register | Search 


 

Forums >

comp.lang.python

What's Going On?

Martin Rinehart

3/13/2008 10:53:00 AM

(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

Off topic: Motown chief Berry Gordy tells Gaye he won't release the
"uncommercial" song. Gaye tells Gordy
he'll never have another Gaye song if he doesn't release it. Gordy
backs down. 2.5 million singles plus title
track for blockbuster album.
3 Answers

Peter Otten

3/13/2008 11:07:00 AM

0

MartinRinehart@gmail.com 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

http://effbot.org/pyfaq/why-are-default-values-shared-between-o...

You heard it through the grapevine ;)

Peter

Diez B. Roggisch

3/13/2008 11:13:00 AM

0

MartinRinehart@gmail.com 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.


Diez

Aaron Brady

3/13/2008 4:06:00 PM

0

On Mar 13, 6:12 am, "Diez B. Roggisch" <de...@nospam.web.de> wrote:
> MartinRineh...@gmail.com 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.