'Keep state in decorator

I am trying to write a @assert_logged_in decorator.

On paper, it's easy:

def assert_logged_in(meth: Callable):
    logged_in = False
    def wrapper(self, *args, **kwargs):
        if not logged_in:
            try:
                test_login()
            except as err: # bare except only for this example
                raise NotLoggedIn from err

        logged_in = True # <= that is what I do not understand ‽‽‽

        return meth(self, *args, **kwargs)

    return wrapper

# somewhere else
@assert_logged_in
def do_something(self):
    pass

As long as I do not try to update logged_in, the code runs, but would test loggedinness every time.

If I try to update logged_in as in my example, I receive: UnboundLocalError: local variable 'logged_in' referenced before assignment. Even if I try to use global. Note that I do not want to keep the state in self (which works), because this decorator will be used across multiple classes.

I am very confused because this example:

def memoize(f):
    memo = {}
    def memoized_func(n):
        if n not in memo:            
            memo[n] = f(n)
        return memo[n]
    return memoized_func

does work as intended and keeps state.

There is obviously something I do not understand. What could it be, and how could I get my decorator working?



Solution 1:[1]

From the comments, there are 2 options:

Cleanest option:

Declare logged_in normally outside the wrapper, and then again as nonlocal inside the wrapper.

Other possibility

  • from a wrapper, you cannot not change an external variable
  • but if this variable happens to be a reference (dict, collections...) then the wrapper can update its content without changing its reference.

For instance, have a dict instead of a boolean:

logged_in = {"logged_in": False}

instead of

logged_in = False

Anotherj comment five

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1