7

I'm implementing the decorator described in What is the Python equivalent of static variables inside a function?. In the answer, the decorator is put on a normal function and it worked also in my environment.

Now I'd like to put the decorator onto a class method.

Source Code:

#decorator
def static_variabls(**kwargs):
    def new_func(old_func):
        for key in kwargs:
            setattr(old_func, key, kwargs[key])
        return old_func
    return new_func

class C:
    @static_variabls(counter = 1)
    def f_(self) -> None:
        print(self.f_.counter)
        self.f_.counter += 1

c1 = C()
c1.f_()
c1.f_()
c1.f_()

Expected Result:

1
2
3

Actual Result:

1
Traceback (most recent call last):
  File "1.py", line 16, in <module>
    c1.f_()
  File "1.py", line 13, in f_
    self.f_.counter += 1
AttributeError: 'method' object has no attribute 'counter'

I don't understand why this code doesn't work. According to the error message, self.f_.counter doesn't exist but print(self.f_.counter) works. What's happening here?

ynn
  • 3,386
  • 2
  • 19
  • 42

2 Answers2

0

c1.f_() is equivalent to C.f_.__get__(c1, C)() (due to the descriptor protocol). __get__ returns the method object that actually gets called. You attached counter to the original function object, which isn't what the method wraps: it wraps the function created by def new_func.

Note that you have the same problem with a much simpler decorator, that hard-codes the attribute and its initial value.

def add_counter(f):
    f.counter = 1
    return f


class C:
    @add_counter
    def f_(self) -> None:
        print(self.f_.counter)
        self.f_.counter += 1

or even with no decorator:

class C:
    def f_(self) -> None:
        print(self.f_.counter)
        self.f_.counter += 1

    f_.counter = 1
chepner
  • 497,756
  • 71
  • 530
  • 681
  • Are the original function object and the function object created by `new_func()` different? I thought the decorator assigns attributes in place. – ynn Jul 18 '20 at 13:18
  • The decorator *can*, but you aren't in this case. You define a new function, but then return that without calling it. The original function doesn't get its new attribute until you actually call the function. I'm not quite sure how to fix this yet; I will probably delete this answer, though. – chepner Jul 18 '20 at 13:24
  • Really? I thought `@decorator() def f` defines `decorator()(f)` according to [the official documentation](https://docs.python.org/3/reference/compound_stmts.html#function-definitions). If `new_func` is not called in the OP, why does `print(self.f_.counter)` succeed? – ynn Jul 18 '20 at 13:28
  • It is, but there is still the issue of the descriptor protocol to deal with. – chepner Jul 18 '20 at 13:28
  • OK, I think I can at least *explain* why it works the first time, but fails on the second and subsequent attempts. Let me write that up in another answer; in the process I might figure out what the solution is as well. – chepner Jul 18 '20 at 14:26
0

You can't define/use, variable, function or an objects, as a static member, and then use self to access them.

In case of self.f_.counter, I guess print() function works, as, it tries to access memory address (of the class itself) directly, in your code f_.counter, not binded with self address., it's binded with ,C.f_ adderss itself.

Solution - 1

def static_variabls(**kwargs):
  
  def new_func(old_func):
    
    for key in kwargs:
      setattr(old_func, key, kwargs[key])

    return old_func

  return new_func

class C:
    @static_variabls(counter = 1)
    def f_(self) -> None:
        print(C.f_.counter) # OR self.f_.counter
        C.f_.counter += 1

Solution - 2 (Without using decorators)

class C:
  
  counter = 0 # static variable

  def __init__(self):
    C.counter += 1
    print(self.counter)

c1 = C()
c1 = C()
c1 = C()
4.Pi.n
  • 1,151
  • 6
  • 15