0

I looked at this page to try building a decorator that can be used with OR without arguments (i.e. callable with either @deco or @deco(arg1='x',arg2='y')). But I have trouble using the arguments: if I uncomment the 2 commented lines below, I get an UnboundLocalError: local variable 'arg1' referenced before assignment, but logically this error should also happen when the lines are commented since the variable is not assigned any value in between.

def deco(_func=None, *, arg1=False, arg2='default'):
    def outer_wrap(func):
        def inner_wrap(*args, **kwargs):
            # if not arg1:
            #     arg1 = 'blah'
            return 'arg1: '+str(arg1)+', arg2: '+str(arg2)
        return inner_wrap
    if _func is None: # case when @deco is called with arguments
        return outer_wrap
    else: # case without arguments
        return outer_wrap(_func)

@deco
def I(n):
    return n
I(1) 

There must be something I'm missing here, any help to understand why this happens would be welcome. (I used Python 3.8.3)

EDIT Thanks to the following answers I modified my code by adding arg1 and arg2 as arguments of the wrapper functions as well:

def deco(_func=None, *, arg1=False, arg2='default'):
    def outer_wrap(func, arg1=arg1, arg2=arg2):
        def inner_wrap( *args, arg1=arg1, arg2=arg2, **kwargs):
            if not arg1:
                arg1 = 'blah'
            return 'arg1: '+str(arg1)+', arg2: '+str(arg2)
        return inner_wrap
    if _func is None:
        return outer_wrap
    else:
        return outer_wrap(_func)

@deco #can add or remove arguments here
def I(n):
    return n
print(I(5))

3 Answers3

0

With assignment commented out, arg1 is not a local variable in the inner function, since it's a parameter of the outer function.

The assignment arg1 = 'blah' in the inner function makes a local variable of the same name, changing the meaning of arg1 in the next line; it is now a local variable of the inner function, with no assigned value when the if statement's condition is false, and Python doesn't then go looking for a different variable of the same name.

This behaviour is described in more detail in this other Stack Overflow Q&A.

kaya3
  • 47,440
  • 4
  • 68
  • 97
0

Just completing @kaya3 answer you can probably therefore do instead:

        if arg1 == False:
            return 'arg1: '+str('blah')+', arg2: '+str(arg2)
        else:
            return 'arg1: '+str(arg1)+', arg2: '+str(arg2) 
Aguy
  • 7,851
  • 5
  • 31
  • 58
  • Simpler would be to do something like `arg1local = arg1` at the start of the function, and then use or reassign that local variable instead. – kaya3 Aug 14 '20 at 09:34
  • It was a minimal example, in my use case it would have required many if/else statements. I found another solution, thanks anyway – Joseph Chataignon Aug 17 '20 at 07:35
0

This questions is related to the 'Closure' function in python. A Closure is a function object that remembers values in enclosing scopes even if they are not present in memory.

By using arg1=blah, makes it a local variable of inner function. To overcome this instead of using arg1 as a non-mutable object with boolean value arg1=False in the outer function argument, you can make arg1 a mutable object of type list, set, dict.

For example:

enter code here
def deco(_func=None, arg1=[], arg2='default'):
    def outer_wrap(func):
        def inner_wrap(*args, **kwargs):
            if len(arg1) is 0:
                arg1.append('blah')
            return 'arg1: ' + arg1[0] + ' arg2: ' + str(arg2)
        return inner_wrap
    if _func is None: # case when @deco is called with arguments
        return outer_wrap
    else: # case without arguments
        return outer_wrap(_func)

@deco
def I(n):
    return n
I(1)

You can learn more about the closure functions in python on the internet.(for ex: https://www.openbookproject.net/py4fun/decorator/decorator.html)

krishna
  • 136
  • 6