YAI: syntax for preset locals without using dummy args with defaults

Bengt Richter bokr at oz.net
Fri Jan 10 12:34:55 EST 2003


On 10 Jan 2003 07:43:23 -0800, Cliff Wells <LogiplexSoftware at earthlink.net> wrote:

>On Fri, 2003-01-10 at 07:22, Bengt Richter wrote:
>> --------------------------------------------------------------------------------
>> [1] Something like
>> 
>>     def foo(x, y=default):
>>         presets:
>>             z = something
>>             pi = __import__('math').pi
>>             twopi = 2.0*pi # not possible in parameter list version
>>             if z < 0: raise ValueError, 'Def-time preset check, possible with presets: block'
>>         if x<0 or y<0: raise ValueError, 'Call-time parameter check'
>>         return pi*(x*x-y*y)
>> 
>> I thought the first version above might be easier to implement, but
>> either way, having a sanctioned way to accomplish safe presetting of
>> function locals should make for a performance boost in many cases.
>> 
>> I think either version will be interesting for method definitions too.
>
>If I understand your intent (only initializing the 'presets' a single
>time), you can get the same performance boost using a class:
>
>class __foo:
>    # presets
>        z = 1
>    pi = __import__('math').pi
>    twopi = 2.0*pi # not possible in parameter list version
>    if z < 0: raise ValueError, 'Def-time preset check, possible with
>presets: block'
>
>    def __call__(self, x, y = 1):
>        if x<0 or y<0: raise ValueError, 'Call-time parameter check'
>        return self.pi*(x*x-y*y)
>
>foo = __foo()
>
>print foo(1, 2)
>print foo(3, 5)
>
>
>Obviously the downside to this approach is the 'superfluous' __foo
>definition that won't be used again, but this works now and doesn't
>require any new syntax.
>
Well, there's a bunch of ways that one "can do it now"(TM). To get
the speed, I think a function returned from a factory function, with
the info in a closure will be about the same speed as default args
(LOAD_DEREF vs LOAD_FAST, which are both going to be faster than
the LOAD_FAST, LOAD_ATTR for dynamically looking up 'self'). Plus it's
not just one more byte code. LOAD_ATTR does more work, even just getting
to instance attributes).

But in any case, IMO the various current ways are workarounds wrt the the
simple concept of starting with preset values in a function and spelling it simply.

IOW, ISTM it's dragging in irrelevant concepts (unless you want to get theoretical)
and code clutter, to force one to think of class definitions or factory functions to
accomplish simple function local presets.

The actual code comparisons:

 >>> import dis
 >>> def foo(x=123): return x
 ...
 >>> dis.dis(foo)
          0 SET_LINENO               1

          3 SET_LINENO               1
          6 LOAD_FAST                0 (x)
          9 RETURN_VALUE
         10 LOAD_CONST               0 (None)
         13 RETURN_VALUE

 >>> def mkbar(x):
 ...     def bar(): return x
 ...     return bar
 ...
 >>> bar = mkbar(123)

 >>> dis.dis(bar)
          0 SET_LINENO               2

          3 SET_LINENO               2
          6 LOAD_DEREF               0 (x)
          9 RETURN_VALUE
         10 LOAD_CONST               0 (None)
         13 RETURN_VALUE

 >>> class mkbaz:
 ...     x = 123
 ...     def __call__(self): return self.x
 ...
 >>> baz = mkbaz()

 >>> dis.dis(baz)
 Disassembly of __call__:
          0 SET_LINENO               3

          3 SET_LINENO               3
          6 LOAD_FAST                0 (self)
          9 LOAD_ATTR                1 (x)
         12 RETURN_VALUE
         13 LOAD_CONST               0 (None)
         16 RETURN_VALUE

Initializing an instance variable should result in faster retrieval
of the attribute, IWT, but the method code looks identical, so the
difference is in what LOAD_ATTR finds to do:

 >>> class mkbaz2:
 ...     def __init__(self,x): self.x = x
 ...     def __call__(self): return self.x
 ... 
 >>> baz2 = mkbaz2(123)
 >>> baz2()
 123
 >>> dis.dis(baz2)
 Disassembly of __call__:
          0 SET_LINENO               3

          3 SET_LINENO               3
          6 LOAD_FAST                0 (self)
          9 LOAD_ATTR                1 (x)
         12 RETURN_VALUE
         13 LOAD_CONST               0 (None)
         16 RETURN_VALUE

 Disassembly of __init__:
          0 SET_LINENO               2

          3 SET_LINENO               2
          6 LOAD_FAST                1 (x)
          9 LOAD_FAST                0 (self)
         12 STORE_ATTR               0 (x)
         15 LOAD_CONST               0 (None)
         18 RETURN_VALUE

So, bottom line, I can do it now, and even get the speed, but I
can't do it with simple concise syntax. It might seem like a trivial
wish, but OTOH, if libraries and new code got written with appropriate
initialized locals, Python would get better speed marks. And people
won't do that if it's a lot of extra typing and clutter. They are
more likely to abuse a default argument and be done with it.

Regards,
Bengt Richter




More information about the Python-list mailing list