4

I was looking for about static value in Python.

And I found this.

def static_var(varname, value):
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate

@static_var("counter", 0)
def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter

It is using python decorator for static variable in a function.

The decorator(static_var) initialize static value(foo.counter) before returning the function(foo) decorated.

So I think it should not work as expected, because decorator(static_var) initialize foo.counter every time when foo is called.

As a result, I think if foo() is called two times, it should print 1 two times

foo()     
foo()     

But It prints 1 and 2, increasing foo.counter

Why...?

Why isn't foo.counter initialized to 0 every time when foo is called?

Community
  • 1
  • 1
SangminKim
  • 8,358
  • 14
  • 69
  • 125

3 Answers3

3

Because:

def static_var(varname, value):
    def decorate(func):
        setattr(func, varname, value)
        return func
    return decorate


@static_var("counter", 0)
def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter

is equivalent to:

foo = static_var("counter", 0)(foo)

Notice that static_var() actually gets called? Now walk through your decorator; in fact step through it with import pdb; pdb.set_trace()

Decorators wrap other functions potentially either mutating them or returning new functions or both.

See: Understanding Python Decorators in 12 easy steps

If we stick a few print(s) in your decorator watch what happens:

def static_var(varname, value):
    print "1"

    def decorate(func):
        print "2"
        setattr(func, varname, value)
        return func
    print "3"
    return decorate


@static_var("counter", 0)
def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter


foo()
foo()

Output:

$ python foo.py
1
3
2
Counter is 1
Counter is 2

So as I said above; firstly static_var("counter", 0) is called; then the return of that (which returns a function) is called with foo as it's first argument which sets up the initial "counter" and returns the same argument (the function foo).

James Mills
  • 18,669
  • 3
  • 49
  • 62
2

"foo.counter initialized to 0 every time when foo is called?"

Short answer: No, foo.counter is set to 0 only when static_var is called.

I think maybe you have little confusion with decorator and the function decorator created.

@decorator
def func():
    pass

Is just a syntax sugar for

func = decorator(func)

With this, we will know that your prior code is equal to:

def foo():
    foo.counter += 1
    print "Counter is %d" % foo.counter

foo = static_val(foo, "counter", 0)

Then, when you are calling foo(), you are calling the new foo function, not static_val in which counter attribute will be set to 0.

WKPlus
  • 6,955
  • 2
  • 35
  • 53
1

The behaviour you see is correct. The decorator is executed once only when declared, not on each invocation of the decorated function. Therefore foo.counter is initialised to 0 only once.

Think about what a static variable is. Typically (e.g. in C) it is a local variable of a function that persists across calls to the function. The same affect is being achieved here through use of a decorator aptly named static_var, so it makes sense that foo.counter is incremented each time that foo() is called, and not reset.

mhawke
  • 84,695
  • 9
  • 117
  • 138