How to access the nonlocal variable in enclosing function from inner function if inner function already has its variable with the same name Python - python

I need to find the way to reference variable x = "Nonlocal" from inner_function_nonlocal(). Probably, the way how I referenced the x = "Global": globals()['x'], but I need your help with that one, please!
Please note: I can NOT comment or delete x = "Local" in order to write nonlocal x instead of it.
x = "Global"
def enclosing_funcion():
x = "Nonlocal"
def inner_function_global():
x = "Local"
print(globals()['x']) # Call the global a
def inner_function_nonlocal():
x = "Local" # <- This line can NOT be commented!!!
print(_?_?_?_?_) # <- What should I specify here in order to print x which is nonlocal?
inner_function_global()
inner_function_nonlocal()
if __name__ == '__main__':
enclosing_funcion()
output should be:
Global (this is already achieved)
Nonlocal (need help to get this one)

You can add a method to get at the Nonlocal value:
x = "Global"
def enclosing_funcion():
x = "Nonlocal"
def non_local():
return x
def inner_function_global():
x = "Local"
print(globals()['x']) # Call the global a
def inner_function_nonlocal():
x = "Local" # <- This line can NOT be commented!!!
print(non_local()) # <- What should I specify here in order to print x which is nonlocal?
inner_function_global()
inner_function_nonlocal()
if __name__ == '__main__':
enclosing_funcion()
Result:
Global
Nonlocal

Related

Python3 method not callable due to UnboundLocalError

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!
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()

Global variable not defined even though it appears in globals()

I wrote this code:
def openFile():
f = open("test.txt", "r")
mainInput = f.read()
global tupleMain
tupleMain = [tuple(mainInput.split(" ")) for mainInput in mainInput.strip(",").split("\n")]
As you can see, I have defined tupleMain as a global variable, but when I try to use it outside the function, I get:
NameError: name 'tupleMain' is not defined
If I run:
is_global = "tupleMain" in globals()
print(is_global)
The output is:
True
I just don't get why it says it's not defined if it's in globals() and have set it to global.
Thanks in advance
EDIT: I use the variable in the following function:
def tableFunction():
fname = [x[2] for x in tupleMain]
sname = [x[3] for x in tupleMain]
position = [x[1] for x in tupleMain]
salary = [x[4] for x in tupleMain]
team = [x[0] for x in tupleMain]
playerTable = PrettyTable()
playerTable.field_names= ["Surname", "First Name", "Salary", "Position", "Team"]
for x in tupleMain:
playerTable.add_row([x[3], x[2], x[4], x[1], x[0]])
print(playerTable)
You never called the function before using the global variable it declares in some other function, so the code inside the function which declares the variable as global never got executed. You need to at-least execute or call the function before referencing the global variable.
Either call the function before you use the global variable elsewhere or define the global variable at the module level inside your code.

Variable binding time in Python closures

From Python's Programming FAQ, this doesn't work as many people expect:
>>> squares = []
>>> for x in range(5):
... squares.append(lambda: x**2)
>>> squares[2]() # outputs 16, not 4
The following explanation is offered:
This happens because x is not local to the lambdas, but is defined in
the outer scope, and it is accessed when the lambda is called --- not
when it is defined.
This is fine. However, the explanation does not sound entirely correct to me. Consider this:
def buildFunction():
myNumber = 6 # Won't compile without this line
def fun():
return myNumber
return fun
def main():
myNumber = 3 # Ignored
myFun = buildFunction()
myNumber = 3 # Ignored
print(myFun()) # prints 6, not 3
This makes sense, but can someone please offer a more accurate/general definition of when variable binding of Python closures occurs?
Consider this:
def main():
def buildFunction():
def fun():
return myNumber
return fun
myNumber = 3 # can use this...
myFun = buildFunction()
myNumber = 3 # ... or this
print(myFun()) # prints 3 this time
This is more comparable to the lambda example because the closure function is nested within the scope that declares the variable of interest. Your example had two different scopes so had two, completely unrelated, myNumbers.
In case you haven't come across the nonlocal keyword, can you guess what this will print?
def main():
def buildFunction():
nonlocal myNumber
myNumber = 6
def fun():
return myNumber
return fun
myNumber = 3
myFun = buildFunction()
# myNumber = 3
print(myFun())
Every variable has a scope.
Name lookup (getting the value that the name points to) resolves by default to the most inner scope.
You can only override names in local scope or a containing scope(using the keywords nonlocal and global).
In your second example in main you assign myNumber to a different scope that your fun function can access.
squares = []
for x in range(5):
squares.append(lambda: x**2) # scope = global / lambda
print(squares[2]())
def buildFunction():
myNumber = 6 # scope = global / buildFunction
def fun():
return myNumber # scope = global / buildFunction / fun
return fun
myNumber = 1 # scope = global
def main():
myNumber = 3 # scope = global / main. Out of scope for global / buildFunction
myFun = buildFunction()
print(myFun())
When fun is called, Python looks at fun's local scope and can't find myNumber.
So it looks at its containing scope.
The buildFunction scope has myNumber in scope (myNumber = 6) so fun returns 6.
If buildFunction did not have myNumber in scope then Python looks at the next scope.
In my version the global scope has myNumber = 1
So fun would return 1.
If myNumber also does not exist in the global scope, a NameError is raised because myNumber could not be found in local scope nor any of its containing scopes.

Global variable's value lost between function calls in python

I know I must have missed something basic -- just want to make sure I get the precise answer.
I have the following code. Why CACHE_KEYS is still None after load() while CACHE is not?
import bisect
import csv
DB_FILE = "GeoLiteCity-Location.csv"
# ['locId', 'country', 'region', 'city', 'postalCode', 'latitude', 'longitude', 'metroCode', 'areaCode']
CACHE = []
CACHE_KEYS = None
def load():
R = csv.reader(open(DB_FILE))
for line in R:
CACHE.append(line)
# sort by city
CACHE.sort(key=lambda x: x[3])
CACHE_KEYS = [x[3] for x in CACHE]
if __name__ == "__main__":
load()
# test
# print get_geo("Ruther Glen")
I think making it global would work. Any variable defined in the global scope which is to be edited should be specified as global in that function. Your code just makes a local variable CACHE_KEYS and stores the information correctly. But, to make sure that it is copied to the global variable, declare the variable as global in the function. You call the append function on CACHE and hence that works fine. Your code after the changes.
import bisect
import csv
DB_FILE = "GeoLiteCity-Location.csv"
# ['locId', 'country', 'region', 'city', 'postalCode', 'latitude', 'longitude', 'metroCode', 'areaCode']
CACHE = []
CACHE_KEYS = None
def load():
R = csv.reader(open(DB_FILE))
for line in R:
CACHE.append(line)
# sort by city
CACHE.sort(key=lambda x: x[3])
global CACHE_KEYS
CACHE_KEYS = [x[3] for x in CACHE]
if __name__ == "__main__":
load()
Anytime you assign a value to a global variable you have to declare it as global. See the following code snippet.
listOne = []
def load():
listOne+=[2]
if __name__=="__main__":
load()
The above code has an assignment and not an append call. This gives the following error.
UnboundLocalError: local variable 'listOne' referenced before assignment
But, executing the following snippet.
listOne = []
def load():
listOne.append(2)
if __name__=="__main__":
load()
gives the following output.
>>> print listOne
[2]
The thing is in
CACHE_KEYS = [x[3] for x in CACHE]
CACHE_KEYS is defined in the global scope. In the above line, you are assigning it a new value, bringing it into the local scope, In order to manipulate the variable in a function (and keep it's value later), global it :
def load():
global CACHE_KEYS
...
CACHE.sort(key=lambda x: x[3])
CACHE_KEYS = [x[3] for x in CACHE]

How to refer to the local module in Python?

Let's say we have a module m:
var = None
def get_var():
return var
def set_var(v):
var = v
This will not work as expected, because set_var() will not store v in the module-wide var. It will create a local variable var instead.
So I need a way of referring the module m from within set_var(), which itself is a member of module m. How should I do this?
def set_var(v):
global var
var = v
The global keyword will allow you to change global variables from within in a function.
As Jeffrey Aylesworth's answer shows, you don't actually need a reference to the local module to achieve the OP's aim. The global keyword can achieve this aim.
However for the sake of answering the OP title, How to refer to the local module in Python?:
import sys
var = None
def set_var(v):
sys.modules[__name__].var = v
def get_var():
return var
As a follow up to Jeffrey's answer, I would like to add that, in Python 3, you can more generally access a variable from the closest enclosing scope:
def set_local_var():
var = None
def set_var(v):
nonlocal var
var = v
return (var, set_var)
# Test:
(my_var, my_set) = set_local_var()
print my_var # None
my_set(3)
print my_var # Should now be 3
(Caveat: I have not tested this, as I don't have Python 3.)

Categories