Pythonic way to do static local variables?

Discussion in 'Python' started by Charles Krug, Apr 26, 2005.

  1. Charles Krug

    Charles Krug Guest

    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.
     
    Charles Krug, Apr 26, 2005
    #1
    1. Advertising

  2. > 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
     
    George Sakkis, Apr 26, 2005
    #2
    1. Advertising

  3. Charles Krug

    Jaime Wyant Guest

    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()

    >>> f = functor()
    >>> f(3,5)
    >>> f.ordered_sequence

    [1, 2, 3, 3, 4, 5, 5]

    Hope that helps some.
    jw

    On 4/25/05, Charles Krug <> wrote:
    > 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.
    >
    > --
    > http://mail.python.org/mailman/listinfo/python-list
    >
     
    Jaime Wyant, Apr 26, 2005
    #3
  4. Charles Krug

    Mike Meyer Guest

    Charles Krug <> writes:

    > 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
    --
    Mike Meyer <> http://www.mired.org/home/mwm/
    Independent WWW/Perforce/FreeBSD/Unix consultant, email for more information.
     
    Mike Meyer, Apr 26, 2005
    #4
  5. Charles Krug wrote:
    > 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
     
    Nicolas Fleury, Apr 26, 2005
    #5
  6. Charles Krug

    Charles Krug Guest

    On Mon, 25 Apr 2005 21:30:18 -0500, Jaime Wyant
    <> wrote:
    > 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
     
    Charles Krug, Apr 26, 2005
    #6
  7. Charles Krug

    Terry Reedy Guest

    "Charles Krug" <> wrote in message
    news:7Ujbe.647694$...
    > 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
     
    Terry Reedy, Apr 26, 2005
    #7
  8. Terry Reedy wrote:
    > "Charles Krug" <> wrote in message
    > news:7Ujbe.647694$...
    >
    >>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]
    >>>


    Michael
     
    Michael Spencer, Apr 26, 2005
    #8
  9. 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__)

    #-------------------

    >>> cumulative_sum(1)

    1
    >>> cumulative_sum(1)

    2
    >>> cumulative_sum(1)

    3
     
    Lonnie Princehouse, Apr 26, 2005
    #9
  10. 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__)

    #-------------------

    >>> cumulative_sum(1)

    1
    >>> cumulative_sum(1)

    2
    >>> cumulative_sum(1)

    3
     
    Lonnie Princehouse, Apr 26, 2005
    #10
  11. On Tue, 26 Apr 2005 10:26:51 -0700, Michael Spencer <> wrote:

    >Terry Reedy wrote:
    >> "Charles Krug" <> wrote in message
    >> news:7Ujbe.647694$...
    >>
    >>>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
     
    Bengt Richter, Apr 27, 2005
    #11
  12. On 26 Apr 2005 13:58:23 -0700, "Lonnie Princehouse" <> wrote:

    >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__)
    >
    >#-------------------
    >
    >>>> cumulative_sum(1)

    >1
    >>>> cumulative_sum(1)

    >2
    >>>> cumulative_sum(1)

    >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
     
    Bengt Richter, Apr 27, 2005
    #12
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. John Davison

    lack of C's static local variables

    John Davison, Mar 3, 2004, in forum: Java
    Replies:
    21
    Views:
    1,007
  2. Patrick Hoffmann
    Replies:
    3
    Views:
    2,911
    Christian Jan├čen
    Aug 8, 2003
  3. Carl J. Van Arsdall
    Replies:
    4
    Views:
    519
    Bruno Desthuilliers
    Feb 7, 2006
  4. Martin Wells

    Static inline functions with static local variables

    Martin Wells, Oct 6, 2007, in forum: C Programming
    Replies:
    10
    Views:
    726
    Army1987
    Oct 8, 2007
  5. Sullivan WxPyQtKinter
    Replies:
    10
    Views:
    706
    Antoon Pardon
    Nov 8, 2007
Loading...

Share This Page