2

After 20 years of C++ experience I am struggling to learn something of Python.

Now I'd like to have a method (a function inside a class) that has a "static" variable of its own, and not a static class variable.

Probably a pseudo code example can illustrate better what I want.

class dummy:
    @staticmethod
    def foo():
        foo.counter += 1
        print "You have called me {} times.".format(foo.counter)
    foo.counter = 0

NOTE 1: I used @staticmethod just for simplicity, but this is irrelevant.

NOTE 2: This crashes with AttributeError: 'staticmethod' object has no attribute 'counter' but as I said above, this is a pseudo code to clarify my objective.

I have already learned that this works outside a class:

def foo():
    foo.counter += 1
    print "You have called me {} times.".format(foo.counter)
foo.counter = 0

But the same trick doesn't seem to work for member-functions.

Last minute information, I am restricted to using Python 2.7 (not my choice).

Is there any legal and reliable way to have a persistent variable (or constant) with scope restricted to the member-function scope?

Some relevant links

Thanks in advance.

j4x
  • 3,595
  • 3
  • 33
  • 64
  • you use static when you want predefined vales or calultion for that class, in the first link foo is used as decorator', if you want to use foo.counter in class, then create a counter variable in clss (not in __init__) and in increment it in the function whenever this is called – sahasrara62 Jun 28 '19 at 18:49
  • @prashantrana sorry but I cannot understand your comment. Could you please elaborate? – j4x Jun 28 '19 at 18:52
  • There is no reason you should need a method of a class to have its own variable. Just assign the variable to the class. The method will be able to freely modify the variable if you do not make it a ```staticmethod```. – Tom Lubenow Jun 28 '19 at 18:58
  • What are you trying to emulate that would make a class variable insufficient? – Edward Minnix Jun 28 '19 at 18:59
  • Thanks @EdwardMinnix for replying. As I mentioned, I come from a C++ background and thus, having something that is useless on class level pollutes and bloats the class namespace and also creates a hole for misuses. By having it and the function scope, I'll be 100% safe. – j4x Jun 28 '19 at 19:06
  • 1
    @j4x Do you expect the static variable inside a method to be shared across multiple objects? (also: to overcome *namespace* pollution it is customary to add `_` to your names to indicate that they are an implementation detail that should not be messed with.) – norok2 Jun 28 '19 at 19:11

5 Answers5

2

No, there is not. You've already found the Python version: a class variable that you, the supreme overlord of class dummy development, will access only within function foo.

If it would help to know the rationale for this, you can start that path here. I expect that you've already been through much of this; however, this answer gives Python specifics for more Pythonic ways to implement what you need.

Prune
  • 76,765
  • 14
  • 60
  • 81
2

One way to achieve this is to tuck your variable away in a closure, so it will effectively be static for your purposes. Unfortunately, Python 2 does not support the nonlocal keyword, so we have to wrap our variable's value in an object (unless you only mean to reference and not mutate the variable (i.e. assign to the variable) in the method:

In [7]: class _Nonlocal:
   ...:     def __init__(self, value):
   ...:         self.counter = value
   ...:
   ...: def foo_maker():
   ...:     nonlocal = _Nonlocal(0)
   ...:     def foo(self):
   ...:         nonlocal.counter += 1
   ...:         print "You have called me {} times.".format(nonlocal.counter)
   ...:     return foo
   ...:

In [8]: class Dummy(object): #you should always inherit from object explicitely in python 2
   ...:     foo = foo_maker()
   ...:

In [9]: dummy = Dummy()

In [10]: dummy.foo()
You have called me 1 times.

In [11]: dummy.foo()
You have called me 2 times.

Of course, this is a lot of rigamarole simply to avoid using an instance variable. Perhaps the best solution is to make your method a custom object, and you can implement the descriptor protocol to make it callable as a method, and it will be usable as an instance method if you'd like:

In [35]: import types
    ...:
    ...: class Foo(object):
    ...:     def __init__(this):
    ...:         this.counter = 0
    ...:     def __call__(this, self):
    ...:         this.counter += 1
    ...:         print "You have called me {} times.".format(this.counter)
    ...:         print "here is some instance state, self.bar: {}".format(self.bar)
    ...:     def __get__(this, obj, objtype=None):
    ...:         "Simulate func_descr_get() in Objects/funcobject.c"
    ...:         if obj is None:
    ...:             return this
    ...:         return types.MethodType(this, obj)
    ...:

In [36]: class Dummy(object): #you should always inherit from object explicitely in python 2
    ...:     foo = Foo()
    ...:     def __init__(self):
    ...:         self.bar = 42
    ...:

In [37]: dummy = Dummy()

In [38]: dummy.foo()
You have called me 1 times.
here is some instance state, self.bar: 42

In [39]: dummy.bar = 99

In [40]: dummy.foo()
You have called me 2 times.
here is some instance state, self.bar: 99

All of this would be highly irregular and confusing to someone else who is used to python conventions, although I hope you see, the Python data-model offers a lot of power to customize things.

note, i've used this as the name of the first argument to avoid confusion with self that will actually come from the object that Foo get's bound to as a method.

Again, I should reiterate, I would never do this. I would just use an instance variable, or perhaps a generator if your function needs to maintain state, and could be used as an iterator.

juanpa.arrivillaga
  • 88,713
  • 10
  • 131
  • 172
  • Good. You explained that language has no support but still gave an elegant solution for not hurting encapsulation. Thanks @juanpa.arrivillaga – j4x Jul 01 '19 at 20:17
1

As @Prune already mentioned there is no real way of doing so.

However, if you want the static variable inside a method to be available only to the object it belongs to (as it is in C++ as far as I remember), you should define it in the constructor or as a class variable with a non-static method:

from __future__ import print_function

class dummy:
    def __init__(self, counter=0):
        self._foo_counter = 0

    def foo(self):
        self._foo_counter += 1
        print("You have called me {} times.".format(self._foo_counter))

or:

class dummy:
    def foo(self):
        self._foo_counter += 1
        print("You have called me {} times.".format(self._foo_counter))

    _foo_counter = 0

This way, running:

x = dummy()
for _ in range(4):
    x.foo()

y = dummy()
for _ in range(4):
    y.foo()

Results in:

You have called me 1 times.
You have called me 2 times.
You have called me 3 times.
You have called me 4 times.
You have called me 1 times.
You have called me 2 times.
You have called me 3 times.
You have called me 4 times.

Note that the two versions do not behave in exactly the same way. When you define _foo_counter in the class directly, you will have access to the _foo_counter variable both for the object (self._foo_counter) and for the class itself (dummy._foo_counter). The dummy._foo_counter will be static for every use of the class and will persist across multiple instances of the class, so across multiple objects. This is also the only variable that you can access if you use the @staticmethod decorator on dummy.foo():

class dummy:
    @staticmethod
    def foo():
        dummy._foo_counter += 1
        print("You have called me {} times.".format(dummy._foo_counter))

    _foo_counter = 0

Here, self or _foo_counter will not be accessible, and your only option is to use the class-wide variable dummy._foo_counter (which, as already mentioned, you could use with methods not decorated with @staticmethod as well).

So that running again:

x = dummy()
for _ in range(4):
    x.foo()

y = dummy()
for _ in range(4):
    y.foo()

results in:

You have called me 1 times.
You have called me 2 times.
You have called me 3 times.
You have called me 4 times.
You have called me 5 times.
You have called me 6 times.
You have called me 7 times.
You have called me 8 times.
norok2
  • 25,683
  • 4
  • 73
  • 99
1

Using a mutable type as the default value for a keyword argument for your function is maybe the simplest approach:

class Dummy:

    @staticmethod
    def foo(_counter=[0]):   # here using a list, but you could use a dictionary, or a deque
        _counter[0] += 1
        print "You have called me {} times.".format(_counter[0])

The rationale is that this variable is initialized only once; its latest value remains in the closure formed.

Reblochon Masque
  • 35,405
  • 10
  • 55
  • 80
0

I already posted this in an old post, but nobody noticed it

As I have a different idiomatic objective with static variables, I would like to expose the following: In a function, I want to initialize a variable only once with a calculated value which may be a bit costly. As I love nice-writing, and being an old C-style programmer. I tried to define a macro-like writing:

def  Foo () :
   StaticVar( Foo, ‘Var’, CalculateStatic())
   StaticVar( Foo, ‘Step’, CalculateStep())
   Foo.Var += Foo.Step
   print(‘Value of Var : ‘, Foo.Var)

Then, I wrote ‘StaticVar’ like this:

  def StaticVar(Cls, Var, StaticVal) :
     if not hasattr(Cls, Var) :
        setattr(Cls, Var, StaticVal)

I can even write nicer code in Python:

def StaticVars(Cls, **Vars) :
    for Var, StaticVal in Vars.items() :
        if not hasattr(Cls, Var) :
            setattr(Cls, Var, StaticVal)

  def  Foo () :
      StaticVars( Foo, Var = CalculateStatic(),Step= CalculateStep()))
      Foo.Var += Foo. Step
      print(‘Value of Var : ‘, Foo.Var)

Sure, this is a nice way to write the code, but my objective (only one call of initialization functions) is not met (just add a print in the initialization function to see that the it is called often) ! The fact is that, in a function call, the parameter value is evaluated even before the function is called.

def CalculateStatic() :
    print("Costly Initialization")
    return 0

def CalculateStep() :
    return 2

def Test() :
    Foo()
    Foo()
    Foo()

>>> Test()
Costly Initialization
Value of Var : 2
Costly Initialization
Value of Var : 4
Costly Initialization
Value of Var : 6

To meet my objective, I’d rather write something like this:

def  Foo () :
    if not hasattr(Foo, ‘Var’) :
        setattr ( Foo, ‘Var’, CalculateStatic())
        setattr ( Foo, ‘Step’, CalculateStep())

    Foo.Var += Foo. Step
    print(‘Value of Var : ‘, Foo.Var)

>>> Test()
Costly Initialization
Value of Var : 2
Value of Var : 4
Value of Var : 6

And it could be “nicely written” like this (I used the underscore notation refering to “private == static”):

def StaticVars(Cls, **Vars) :
    for Var, StaticVal in Vars.items() :
        setattr(Cls, Var, StaticVal)

def  Foo () :
    _ = Foo
    try :
        __ = _.Var
    except AttributeError : # The above code could only generate AttributeError Exception
                    # the following code is executed only once
        StaticDefVars(_, Var= CalculateStatic(), Step = CalculateStep())

    _.Var += _. Step
    print(‘Value of Var : ‘, Foo.Var)

Attention must be paid to not put 'calculation code' in the 'try' clause which could generate extra 'AttributeError' exception.

Sure, if Python had had 'Marcro preprocessing', it would be even nicer "'