Use nonlocal
so the symbol table lookup resolves to the enclosing scope:
def logging_calc(func):
counter = 0
def inner(a, b):
nonlocal counter
counter += 1
print(counter)
return func(a, b)
return inner
This avoids the UnboundLocalError: local variable 'counter' referenced before assignment
error.
The docs for nonlocal
say:
The nonlocal statement causes the listed identifiers to refer to previously bound variables in the nearest enclosing scope excluding globals. This is important because the default behavior for binding is to search the local namespace first. The statement allows encapsulated code to rebind variables outside of the local scope besides the global (module) scope.
Names listed in a nonlocal
statement, unlike those listed in a global
statement, must refer to pre-existing bindings in an enclosing scope (the scope in which a new binding should be created cannot be determined unambiguously).
Names listed in a nonlocal
statement must not collide with pre-existing bindings in the local scope.
The reason the second version with the dictionary works is because you only modify a key (or property) of the dict. There's no attempt to bind the dict variable itself locally at the block scope before definition. Basically, avoid +=
and there's no problem:
def fn():
counter = 0
def inner():
print(counter) # this works--no assignment
#counter += 1 # this doesn't work; UnboundLocalError
inner()
if __name__ == "__main__":
fn()
As an aside, it's not possible to use counter.calls = 0
for a dictionary key value set. It has to be counter["calls"] = 0
, so I'm not sure what counter
is.
You might also want to use varargs so that your logging counter function is a bit more generic.
def logging_calc(func):
counter = 0
def wrapper(*args, **kwargs):
nonlocal counter
counter += 1
print(func, "has been called", counter, "times")
return func(*args, **kwargs)
return wrapper
@logging_calc
def hello(): pass
@logging_calc
def goodbye(a, b, c): pass
if __name__ == "__main__":
hello()
hello()
hello()
goodbye(1, 2, 3)
goodbye(1, 2, 3)
Output:
<function hello at 0x0000027B14A8BB80> has been called 1 times
<function hello at 0x0000027B14A8BB80> has been called 2 times
<function hello at 0x0000027B14A8BB80> has been called 3 times
<function goodbye at 0x0000027B14A8BCA0> has been called 1 times
<function goodbye at 0x0000027B14A8BCA0> has been called 2 times