2

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!

Int-0
  • 99
  • 1
  • 5
  • 1
    As a programmer you should try very, *very*, **very** hard not to use globals. – quamrana Aug 12 '21 at 15:01
  • 2
    There is a rule in the python compiler which checks all the variables in functions. If that variable *could* be written to, then it assumes it will be and thus marks it as a local variable, but any reference to it later it also checked at runtime. – quamrana Aug 12 '21 at 15:08
  • 1
    Does this answer your question? [Local (?) variable referenced before assignment](https://stackoverflow.com/questions/11904981/local-variable-referenced-before-assignment) – quamrana Aug 12 '21 at 15:19

1 Answers1

2

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()
S.B
  • 13,077
  • 10
  • 22
  • 49
  • Your solution is fine for this example, however in my final code is a little dirty because I should use that "if" in many methods. A colleague suggest me to make "patch_function" global and make a decorator to use original method or patched method according to "patch_function" value. – Int-0 Aug 13 '21 at 09:18