mutable default parameter problem [Prothon]

Rob Williscroft rtw at freenet.co.uk
Sat Jun 19 14:49:25 CEST 2004


Hung Jung Lu wrote in news:8ef9bea6.0406180809.10053ba6 at posting.google.com 
in comp.lang.python:

> Rob Williscroft <rtw at freenet.co.uk> wrote:
>> 
>> But python has static variables.
>> 
>> def another( x ):
>>   y = getattr( another, 'static', 10 )
>>   another.static = x
>>   return y
>> 
>> print another(1), another(2), another(4)
> 
> What about the following case:
> 
> def f():
>     f.static = getattr(f, 'static', 0)
>     f.static += 1
>     return f.static
> 
> print f(), f(), f() # prints 1 2 3

Yep this would be the way I would instinctivly write it. However
Andrea suggested:

def f():
  if not hasattr( f, 'static', 0 ):
    f.static = 0
  f.static += 1
  return f.static

Which my primitive timing tests show to be faster.

> 
> As opposed to C++, you now have a line of code that is always executed
> in subsequent calls, for no good reason. This is worse than:

Well C++ has `if ( __compiler_generated_bool__ )` that will always
be executed ( except in the case of POD's (char, int, double etc) ).

> 
> def f(static=[0]):
>     static[0] += 1
>     return static[0]


I don't know but surely the interpreter is doing some kind of
extra if/attribute lookup in there, its just faster than the 
other versions.

I've timed both, and this was by far the fastest. I don't think 
the problems is the `if` though. I don't know but I suspect this 
version finds the local/paramiter 'static' with __slots__ like 
performance, all other version's suffer from attribute lookup
problems.

> 
> in the sense that you have a wasteful call ("getattr") that doesn't do
> anything productive in subsequent calls. (You could change that to
> f.static = getattr(f, 'static', 0) + 1, but the "getattr" is surely
> inefficient compared to f.static += 1, and internally likely incurs
> some conditional statement at some level.)
> 
> Maybe one can do instead:
> 
> def f():
>     global f
>     def f():
>         f.static += 1
>         return f.static
>     f.static = 0 # initialization
>     return f()
>     
> print f(), f(), f() # prints 1 2 3
> 
> The advantage is there is no more wasteful statements in subsequent
> calls. No "if" conditional statement. 

Intresting, but it looses (in my timing tests) to the f(static=[0])
version, static[0] must be faster than f.static I guess.

> The above is of course a toy
> example to illustrate the case of a function that needs to perform
> something special the first time it is called. (I am well aware of the
> outside assignment like:
> 
> def f():
>     f.static += 1
>     return f.static
> f.static = 0
> 
> mentioned in this thread, but I am talking about something more
> general. Notice that in the latter case, f.static=0 is done before f()
> is called, which may not be what one wants. E.g.: if f() is never
> called, this assignment is wasteful. Not a problem in this case, for
> if complicated initialization is needed, like requiring a timestamp,
> it may not be a good idea.)

Indeed, the fast version is:

_f_static = 0
def f():
  global _f_static
  _f_static += 1
  return _f_static

This is directly equivalent to 
<c++>
  static int f_static = 0;
  int f() { return ++f_static; }
</c++>

C++ name hiding is with the static keyword, python name hiding
is with a leading underscore.

> 
> In code refactoring, the equivalent is to replace conditional
> statements by polymorphism. In terms of codeblocks, what I mean is
> dynamic hook-on and hook-off of codeblocks. If the underlying language
> is powerful enough, one should be able to achieve runtime
> restructuring of code, without performance impact for subsequent
> calls.
> 

Nice.

<c++>
  static int f_static;
  static int f_init();
  static int f_run();
  int (*f)() = f_init();

  static int f_init() 
  {
    f_static = /* dynamic value */ 0;
    f = f_run;
    return f();
  } 
  static int f_run()
  {
    return ++f_static;
  }
</c++>

In C++ the (performance) cost is visible and only f() and its callers
pay for it, in Python the the (performance) cost is invisible and
everybody pays for it.

In C++ I write:

int f()
{
  static int var = 0;
  return ++var;
}

And I let the compiler worry about the best way to implement it,
in Python I write:

class f( object ):
  def __init__( self ):
    self.var = 0;
  def run( self ):
    self.var += 1
    return self.var

Though in a simple case (as all the examples have been) I 
might write:

def f():
  if not hasattr( f, 'static' ):
    f.static = 0
  f.static += 1
  return f.static

Clarity (my clairty of purpose, as a programmer) wins in 
both languages.

I won't be writing:

def f(static=[0]):
   #etc

It simply isn't clear.

Rob.
-- 
http://www.victim-prime.dsl.pipex.com/



More information about the Python-list mailing list