Python3 method not callable due to UnboundLocalError

Viewed 67

Take into account the following code:

def main():
    print('Calling methodA()')
    methodA()

    print('Calling methodB(True)')
    methodB(True)

    print('Calling methodB(False)')
    try:
        methodB(False)
    except UnboundLocalError as error:
        print(f'--> "UnboundLocalError" raised: {error}')
    
def methodA():
    print('Running methodA()')
    print('"method_original" in globals(): ' + str('method_original' in globals()))
    method_original()

def methodB(patch_function):
    print(f'Running methodB({patch_function})')
    print('"method_original" in globals(): ' + str('method_original' in globals()))
    if patch_function:
        method_original=method_patched
    method_original()

def method_original():
    print('Running method_original()')

def method_patched():
    print('Running method_patched()')
    
if __name__ == '__main__':
    main()

It produces the following output:

Calling methodA()
Running methodA()
"method_original" in globals(): True
Running method_original()
Calling methodB(True)
Running methodB(True)
"method_original" in globals(): True
Running method_patched()
Calling methodA(False)
Running methodB(False)
"method_original" in globals(): True
--> "UnboundLocalError" raised: local variable 'method_original' referenced before assignment

Which makes no sense because "method_original" is in globals(). This error can be fixed simply adding global method_original at the beginning of the methodB() but in some cases we have a lot of functions and it could be a pain in the ass to put all of them at the beginning of every method.

Are there any rules to avoid this behavior?

//BR!

1 Answers

Let me explain it in a simpler example :

def fn(a):
    if a % 2 == 0:
        x = a
    return x

print(fn(10))  # Fine
print(fn(9))   # UnboundLocalError: local variable 'x' referenced before assignment

In compile time, when interpreter reaches the function, it sees that there is an assignment to x, so it marks x as a "local" variable. Then in "runtime" interpreter tries to find it only in local namespace ! On the other hand, x is only defined, if a is even.

It doesn't matter if it presents in global namespace, now I want to add a global variable named x, to my example:

def fn(a):
    if a % 2 == 0:
        x = a
    return x

x = 50


print(fn(10))  # Fine
print(fn(9))   # UnboundLocalError: local variable 'x' referenced before assignment

Nothing changed. Interpreter still tries to find x inside the function in local namespace.

Same thing happened in your example.

This is to show which variables are "local":

def fn(a):
    if a % 2 == 0:
        x = a
    return x

print(fn.__code__.co_varnames)

co_varnames is a tuple containing the names of the local variables (starting with the argument names)


Solution:

Either use global (which I see you don't like) , or do not do assignment inside the function, for example change your methodB to :

def methodB(patch_function):
    print(f'Running methodB({patch_function})')
    print('"method_original" in globals(): ' + str('method_original' in globals()))
    if patch_function:
        method_patched()
    else:
        method_original()
Related