Pythonic way to do static local variables?

C

Charles Krug

I've a function that needs to maintain an ordered sequence between
calls.

In C or C++, I'd declare the pointer (or collection object) static at
the function scope.

What's the Pythonic way to do this?

Is there a better solution than putting the sequence at module scope?

Thanks.
 
G

George Sakkis

I've a function that needs to maintain an ordered sequence between
calls.

In C or C++, I'd declare the pointer (or collection object) static at
the function scope.

What's the Pythonic way to do this?

Is there a better solution than putting the sequence at module scope?

Yes, there is; it's called "object oriented programming". In general,
whenever you find that one or more functions have to access a context
that is not passed in explicitly as arguments, the proper (pythonic and
non-pythonic) way is to define them as a method in a class that stores
this context.

class Foo(object):
def __init__(self):
self._context = # something

def method(self):
x = self._context
# do stuff
self._context.update()


Another solution is function/method attributes. This is handy if OO
seems an overkill for a single function, or in case of methods, if you
don't want to pollute the instance namespace with something used by a
single method:

def foo():
# foo.x is like a C static
try: foo.x +=1
except AttributeError:
foo.x = 1
return foo.x

for i in xrange(10): print foo()

if foo() is a method, it becomes a little more cumbersome, first
because you have to refer to the class name and (less obviously)
because user defined attributes are not allowed in instance methods (I
wonder why); you have to refer to the wrapped function:

class Foo(object):
def foo(self):
try: Foo.foo.im_func.x += 1
except AttributeError:
Foo.foo.im_func.x = 1
return Foo.foo.x

foo = Foo().foo
for i in xrange(10): print foo()


Hope this helps,

George
 
J

Jaime Wyant

Well, if you're a c++ programmer, then you've probably ran into
`functors' at one time or another. You can emulate it by making a
python object that is `callable'.

class functor:
def __init__(self):
self.ordered_sequence = [1, 2, 3, 4, 5]
def __call__(self, arg1, arg2):
self.ordered_sequence.extend((arg1,arg2))
self.ordered_sequence.sort()
[1, 2, 3, 3, 4, 5, 5]

Hope that helps some.
jw
 
M

Mike Meyer

Charles Krug said:
I've a function that needs to maintain an ordered sequence between
calls.

In C or C++, I'd declare the pointer (or collection object) static at
the function scope.

What's the Pythonic way to do this?

Is there a better solution than putting the sequence at module scope?

I'm not sure what you mean by "an ordered sequence". Assuming that a
static counter variable will do the trick (i.e. - it's values will
sequence across calls), you can do this in a number of ways:

1) Use a class:

class counterClass:
def __init__(self):
self.count = 1
def __call__(self):
self.count = self.count + 1

counterFunc = counterClass()

2) Use an instance variable of the function:

def counterFunc():
foo.counter = foo.counter + 1
foo.counter = 1

You ought to be able to do this with closures as well, but I couldn't
seem to get that to work.

<mike
 
N

Nicolas Fleury

Charles said:
I've a function that needs to maintain an ordered sequence between
calls.

In C or C++, I'd declare the pointer (or collection object) static at
the function scope.

What's the Pythonic way to do this?

Is there a better solution than putting the sequence at module scope?

Thanks.

You might want a generator. Search for yield keyword.
Regards,
Nicolas
 
C

Charles Krug

Well, if you're a c++ programmer,

Well, my forte is embedded systems and device controls . . .
then you've probably ran into
`functors' at one time or another. You can emulate it by making a
python object that is `callable'.

class functor:
def __init__(self):
self.ordered_sequence = [1, 2, 3, 4, 5]
def __call__(self, arg1, arg2):
self.ordered_sequence.extend((arg1,arg2))
self.ordered_sequence.sort()

"ordered" in this case doesn't mean "sorted." . . .
:cool:

It's the set of filter coefficients and cumulative remainders for an
overlap add convolution. Sorting would be . . . bad. Like crossing the
streams bad.

Both of these techniques look promising here.


Thanks
 
T

Terry Reedy

Charles Krug said:
Both of these techniques look promising here.

Here a third, the closure approach (obviously not directly tested):

def func_with_static(datalist):
def real_func(otherargs):
<code that uses datalist>
return real_func

func1 = func_with_static([.1, 99, 1e-10,...])

If needed, real_func can *modify* (but not *rebind*) datalist.
Unlike the attribute approach, but like the class approach, you can
simultaneously have have multiple closures with different static data

func2 = func_with_static([.2, 152, 1e-9, ...])

Terry J. Reedy
 
L

Lonnie Princehouse

A quick, hackish way to keep a static variable is to declare it as a
parameter and give it a default value. The parameter list is evaluated
when the function is compiled, not when it is called. The underscores
are added as per convention to indicate that the variable is
special/private.

Example-

def cumulative_sum(arg, __static__ = []):
__static__.append(arg)
return reduce(lambda a,b: a + b, __static__)

#-------------------
3
 
L

Lonnie Princehouse

A quick, hackish way to keep a static variable is to declare it as a
parameter and give it a default value. The parameter list is evaluated
when the function is compiled, not when it is called. The underscores
are added as per convention to indicate that the variable is
special/private.

Example-

def cumulative_sum(arg, __static__ = []):
__static__.append(arg)
return reduce(lambda a,b: a + b, __static__)

#-------------------
3
 
B

Bengt Richter

Terry said:
Here a third, the closure approach (obviously not directly tested):
Just for grins, here's a fourth approach, using the descriptor protocol
... 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
... 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
>>> f.im_func
>>> 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".
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:
... def f(a):
... state.append(a)
... return state
... 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
... @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 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:
Traceback (most recent call last):
File "<stdin>", line 1, in ?
TypeError: f() takes exactly 1 argument (2 given)

Regards,
Bengt Richter
 
B

Bengt Richter

A quick, hackish way to keep a static variable is to declare it as a
parameter and give it a default value. The parameter list is evaluated
when the function is compiled, not when it is called. The underscores
are added as per convention to indicate that the variable is
special/private.

Example-

def cumulative_sum(arg, __static__ = []):
__static__.append(arg)
return reduce(lambda a,b: a + b, __static__)

#-------------------
3
This default-value hack is what my presets decorator was motivated to replace
(if you are willing to depend to a byte-code-hacking decorator ;-)

Regards,
Bengt Richter
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,744
Messages
2,569,484
Members
44,904
Latest member
HealthyVisionsCBDPrice

Latest Threads

Top