Pythonic way to do static local variables?

Bengt Richter bokr at oz.net
Wed Apr 27 01:58:07 EDT 2005


On Tue, 26 Apr 2005 10:26:51 -0700, Michael Spencer <mahs at telcopartners.com> wrote:

>Terry Reedy wrote:
>> "Charles Krug" <cdkrug at worldnet.att.net> wrote in message 
>> news:7Ujbe.647694$w62.444038 at bgtnsc05-news.ops.worldnet.att.net...
>> 
>>>Both of these techniques look promising here.
>> 
>> 
>> Here a third, the closure approach (obviously not directly tested):
>> 
>> 
>> 
>Just for grins, here's a fourth approach, using the descriptor protocol
>
>  >>> def func_with_state(state,a):
>  ...     state.append(a)
>  ...     return state
>  ...
>  >>> f = func_with_state.__get__([])
>  >>> f(1)
>  [1]
>  >>> f(2)
>  [1, 2]
>  >>> f(3)
>  [1, 2, 3]
>  >>>
>
For those who don't recognize it, this is the mechanism that binds instances to
methods of their type to make bound methods. It's as if you had given list a
new method and picked up the bound method from an instance like [].func_with_state

 >>> def func_with_state(state, a):
 ...     state.append(a)
 ...     return state
 ...
 >>> f = func_with_state.__get__([])
 >>> f
 <bound method ?.func_with_state of []>

The '?' is because we didn't supply the second argument to __get__, i.e., type([])

 >>> f = func_with_state.__get__([], list)
 >>> f
 <bound method list.func_with_state of []>

 >>> f.im_func
 <function func_with_state at 0x02EFD614>
 >>> func_with_state
 <function func_with_state at 0x02EFD614>


For grins, here's a fifth approach (I'm assuming you counted correctly to the fourth ;-)

 >>> from ut.presets import presets
 >>> @presets(state=[])
 ... def f(a):
 ...     state.append(a)
 ...     return state
 ...
 >>> f(1)
 [1]
 >>> f(2)
 [1, 2]
 >>> f(3)
 [1, 2, 3]

This is a straight function, with byte-code hack preset of internal state as specified.
The disassemby shows [1, 2, 3] because that is the object referred to still. What would have
been a global reference got hacked to LOAD_FAST 1 (state) after preset from "constant".

 >>> import dis
 >>> dis.dis(f)
   1           0 LOAD_CONST               1 ([1, 2, 3])
               3 STORE_FAST               1 (state)

   3           6 LOAD_FAST                1 (state)
               9 LOAD_ATTR                1 (append)
              12 LOAD_FAST                0 (a)
              15 CALL_FUNCTION            1
              18 POP_TOP

   4          19 LOAD_FAST                1 (state) 
              22 RETURN_VALUE

To see the initial state, we have to re-decorate another instance of f:
 >>>
 >>> @presets(state=[])
 ... def f(a):
 ...     state.append(a)
 ...     return state
 ...
 >>> dis.dis(f)
   1           0 LOAD_CONST               1 ([])
               3 STORE_FAST               1 (state)

   3           6 LOAD_FAST                1 (state)
               9 LOAD_ATTR                1 (append)
              12 LOAD_FAST                0 (a)
              15 CALL_FUNCTION            1
              18 POP_TOP

Of course, we could put this inside a factory and pass the desired state

 >>> def factory(state):
 ...     @presets(state=state)
 ...     def f(a):
 ...         state.append(a)
 ...         return state
 ...     return f
 ...
 >>> f2 = factory([111,222])
 >>> f2('third')
 [111, 222, 'third']
 >>> dis.dis(f2)
   2           0 LOAD_CONST               1 ([111, 222, 'third'])
               3 STORE_FAST               1 (state)

   4           6 LOAD_FAST                1 (state)
               9 LOAD_ATTR                1 (append)
              12 LOAD_FAST                0 (a)
              15 CALL_FUNCTION            1
              18 POP_TOP

   5          19 LOAD_FAST                1 (state)
              22 RETURN_VALUE
 >>> dis.dis(factory('append will fail'))
   2           0 LOAD_CONST               1 ('append will fail')
               3 STORE_FAST               1 (state)

   4           6 LOAD_FAST                1 (state)
               9 LOAD_ATTR                1 (append)
              12 LOAD_FAST                0 (a)
              15 CALL_FUNCTION            1
              18 POP_TOP

   5          19 LOAD_FAST                1 (state)
              22 RETURN_VALUE

Note that the function takes only one argument:

 >>> f2
 <function f at 0x02EF841C>
 >>> f2.func_code.co_argcount
 1
 >>> f2(10, 'fails')
 Traceback (most recent call last):
   File "<stdin>", line 1, in ?
 TypeError: f() takes exactly 1 argument (2 given)

Regards,
Bengt Richter



More information about the Python-list mailing list