Strange python closure variable access

Question:

While trying to implement a decorator with a closure I ran into a somewhat strange behavior, where a variable can be read, but if try to assign it later, it becomes undefined even before the assignment.

def run_once(fn):
    to_run = True

    def decorated(*args, **kwargs):
        if to_run:
            print(to_run)    #this access works
            #to_run = False  #while this doesn't
            return fn(*args, **kwargs)

    return decorated


class A:
    def __init__(self):
        self.c=0
    @run_once
    def method1(self):
        self.c+=1
        print(f"Ran {self.c} times")

a=A()
a.method1()
a.method1()
a.method1() 

The code above runs.

but if to_run = False is uncommented, then it fails with UnboundLocalError: local variable ‘to_run’ referenced before assignment

I find it really strange that I can read it, but if I try to assign it in the body of the if, it fails where it didn’t before.

Am I missing some obvious scoping rule?

Asked By: FlashDD

||

Answers:

Since you’re trying to modify a variable from the outer scope, you need to tell python that you are trying to access the variable from the outer scope:

def run_once(fn):
    to_run = True

    def decorated(*args, **kwargs):
        nonlocal to_run  # use the variable from outer scope
        if to_run:
            print(to_run)
            to_run = False  # set the flag in the outer scope
            return fn(*args, **kwargs)

    return decorated

Result:

True
Ran 1 times
Answered By: rdas