I'm trying to make a simple Python decorator, below is a simplified example of my use case.
def decorator(x):
def inner(f):
if x == 2:
x = 1
return f
return inner
#decorator(2)
def f():
pass
But I get this error
if x == 2:
UnboundLocalError: local variable 'x' referenced before assignment
Why is x not available? It's confusing because if I use print(x) instead of if x == 2 it works as expected.
Python can't determine xs actual scope, so before it runs, in the inner function it would raise the UnboundLocalError.
The way to fix it is modify the arguments of inner a bit, instead of:
def inner(f):
Make it to be:
def inner(f, x=x):
Then your code would work fine.
Or you could also use nonlocal, by adding nonlocal x in the beginning of the function inner.
def inner(f):
nonlocal x
if x == 2:
x = 1
return f
Related
I am really new to programming and Python, so please really forgive me for my ignorance.
I just learned that using global can make a variable inside function be a global one.
However, I discovered something not with my expectation:
I tried the following code in Python 3.8 (forgive me for my ignorance as I don't know what else information I should provide):
>>> x = 0
>>>
>>> def function():
... if False:
... global x
... x = 1
...
>>> function()
>>> print(x)
and the result is 1.
However, I expected the code to have the same effect as the following code:
>>> x = 0
>>>
>>> def function():
... x = 1
...
>>> function()
>>> print(x)
which the result should be 0.
In my mind, the statement inside if False should not be executed, so it sounds strange to me.
Also, personally, I think that in some situation I would expect the variable inside a function, whether local or global, to be dependent on other codes... what I mean is, I would like to change if False to something like if A == 'A', while (I hope) I can control whether the x is global/local according to my conditional statement.
I tried to change if to while, but it's the same... there isn't a infinite loop, but the code global x is still executed/compiled...
I admit that it may sounds naive, and perhaps it just won't work in Python, but I really wonder why... It seems that the code global x is unreachable, but how come it is not ignored?
Can anyone please tell me about the reason? I would like to know more about the mechanism behind compilation(?)
Any help would be appreciated, thank you!
In python the global statement (and the nonlocal statement) are very different from the normal python code. Essentially no matter where a global statement in a function is, it influences always the current codeblock, and is never "executed". You should think more of it as a compiler directive instead of a command.
Note that the statement itself must come before any usage of the variable it modifies, i.e.
print(x)
global x
is a syntax error. The global statement can only modify variable behavior in the whole codeblock, you can't first have a non-global variable that later gets global and you can also not have conditional global variable
(I couldn't really find good documentation for this behavior, here it says "The global statement is a declaration which holds for the entire current code block." and "global is a directive to the parser. It applies only to code parsed at the same time as the global statement." but that doesn't seem super clear to me.)
There are more compiler directives in python, although they don't always look like one. One is the from __future__ import statements which look like module imports but change python behavior.
Global is not in execution path but in a scope. The scope is whole function. Statements like if for don't make scopes. If you use any assignment you create local variable. The same with global or nonlocal you bind symbol to variable from outside.
As Stanislas Morbieu typed, see doc.
Programmer’s note: global is a directive to the parser. It applies only to code parsed at the same time as the global statement.
Not at execution time.
x = 1
def fun():
y = x + 1
print(f'local x = {x}, y = {y}')
fun()
print(f'global x = {x}')
# Output:
# local x = 1, y = 2
# global x = 1
In example above, y uses global x (and adds 1).
x = 1
def fun():
y = x
x = y + 1
print(f'local x = {x}')
fun()
print(f'global x = {x}')
# Output:
# UnboundLocalError: local variable 'x' referenced before assignment
Look at last example. It doesn't assign y from global x because assignment in second line creates local x and y can not read local x before x assignment. The same with:
x = 1
def fun():
if False:
x += 1
fun()
# Output
# UnboundLocalError: local variable 'x' referenced before assignment
x assignment creates local variable.
If you want to change global variable under condition you can use globals().
x = 1
def set_x(do_set, value):
if do_set:
globals()['x'] = value
print(f'global x = {x} (init)')
set_x(False, 2)
print(f'global x = {x} (false)')
set_x(True, 3)
print(f'global x = {x} (true)')
# Output
# global x = 1 (init)
# global x = 1 (false)
# global x = 3 (true)
Proxy
I you want to decide with variable you want to use later (in the same scope) you need some kind of proxy IMO.
x = 1
def fun(use_global):
x = 2 # local
scope = globals() if use_global else locals()
scope['x'] += 1
print(f'local ({use_global}): x = {scope["x"]}')
print(f'global: x = {x} (init)')
fun(False)
print(f'global: x = {x} (false)')
fun(True)
print(f'global: x = {x} (true)')
# Output:
# global: x = 1 (init)
# local (False): x = 3
# global: x = 1 (false)
# local (True): x = 2
# global: x = 2 (true)
Maybe you can think about refactoring of your code if you need it.
If you can change local variable name (if not use globals() as above), you can proxy:
use dict (like in example above)
use list (x=[1]) and usage x[0]
use object (with builtin dict), example:
class X:
def __init__(self, x):
self.x = x
x = X(1)
def fun(use_global):
global x
my_x = x if use_global else X(2)
my_x.x += 1
print(f'local ({use_global}): x = {my_x.x}')
print(f'global: x = {x.x} (init)')
fun(False)
print(f'global: x = {x.x} (false)')
fun(True)
print(f'global: x = {x.x} (true)')
# Output:
# global: x = 1 (init)
# local (False): x = 3
# global: x = 1 (false)
# local (True): x = 2
# global: x = 2 (true)
Note. Variables in Python are only references. It is way you can not change x = 1 without global (or globals()). You change reference to local value 1.
But you can change z[0] or z['x'] or z.x. Because z referents to list or dict or object and you modify it content.
See: https://realpython.com/python-variables/#object-references
You can check real object by id() function, ex. print(id(x), id(my_x)).
As per the Python documentation, global is a directive to the parser so it is taken into account before the execution, therefore it does not matter if the code is reachable or not. The variable is global for the entire scope, which is the function in your case.
This question already has answers here:
Global dictionaries don't need keyword global to modify them? [duplicate]
(2 answers)
Why is the global keyword not required in this case? [duplicate]
(1 answer)
Closed 2 years ago.
I have declared a dictionary globally and a variable as well. Now, when accessing both in a class, I can access the dictionary but for accessing the other variable, I get the UnboundLocalError. For solving this, I used the line of code: global curr_length and then access it and it ran. But I wanted to known why is this additional line of code required for a normal integer variable whereas not required for a dictionary?
The code is:
memoized = {}
curr_length = 0
curr_pal = ""
class Solution:
def check_palindrome(self, str_):
if len(str_) <= 1:
return False
global curr_length
if len(str_) <= curr_length:
return False
if memoized.get(str_, None):
return memoized[str_]
if str_ == str_[::-1]:
memoized[str_] = True
curr_length = len(str_)
return True
memoized[str_] = False
return False
def longest_palindrome(self, str_, starting_index):
if len(str_) <= 1:
return None
n = len(str_)
if self.check_palindrome(str_[starting_index:n]):
return str_
n -= 1
self.longest_palindrome(str_[starting_index:n], starting_index)
def longestPalindrome(self, s: str) -> str:
for starting_index in range(len(s)):
pal = self.longest_palindrome(s, starting_index)
if pal:
return pal
return ""
solution = Solution()
print(solution.longestPalindrome("babad"))
Short:
You are trying to change the value of curr_length with curr_length = len(str_) inside a function which looks for a local curr_length variable, and can't find it. It needs the line global curr_length to know that it's a global variable.
As far as why you're wondering as to why a dict object does not need global memoized line, you should read the answer to:
Global dictionaries don't need keyword global to modify them? or Why is the global keyword not required in this case?
EXPLANATION:
In Python, a variable declared outside of the function or in global scope is known as global variable. This means, global variable can be accessed inside or outside of the function.
Let's see an example on how a global variable is created in Python.
x = "global"
def foo():
print("x inside :", x)
foo()
print("x outside:", x)
When we run the code, the will output be:
x inside : global
x outside: global
In above code, we created x as a global variable and defined a foo() to print the global variable x. Finally, we call the foo() which will print the value of x.
What if you want to change value of x inside a function?
def foo():
x = x * 2
print(x)
foo()
When we run the code, the will output be:
UnboundLocalError: local variable 'x' referenced before assignment
The output shows an error because Python treats x as a local variable and x is also not defined inside foo().
To make this work we use global keyword
Consider the following code, I expected the output to be 2 since the x to be printed is a local variable in the scope of inner function. However, it printed 2 instead. I don't understand why that is the case. Any explanation and/or comment is appreciated.
def outer():
x = 1
def inner():
x = 1
x += 1
print x
return x
There are two separate x variables here: the outer function has an x variable and the inner function has its own x variable.
From the moment there is an assignment to a variable somewhere in a function, the variable has a local scope.
So when you call outer(), you will return 1. The x in the inner function is a different one, and furthermore inner() is never called. As a result, the (local) x is not printed.
If you thus would have written:
def outer():
x = 3
def inner():
x = 1
x += 1
print x
return x
Then calling outer() will return 3. Even if you would have called inner() in the outer() function, it would not make any difference. Since the x in the inner() function is another one than the one in the outer() function (there is a local scope defined in inner). Although it would mean that you print 2, you will return 3.
I have a function which will recursively execute another function inside and I want to share variable for all execution of that function.
Something like that:
def testglobal():
x = 0
def incx():
global x
x += 2
incx()
return x
testglobal() # should return 2
However, I'm getting error NameError: name 'x' is not defined
There is hacky solution to make list and use first value of that list as x. But this is so ugly.
So how can I share x with incx function ? Or should I use completely different approach ?
This will work unless you are still using Python 2.x:
def testglobal():
x = 0
def incx():
nonlocal x
x += 2
incx()
return x
testglobal() # should return 2
Possible a cleaner solution though would be to define a class to store your state between method calls.
Use the nonlocal statement, so incx will use the x variable from testglobal:
def testglobal():
x = 0
def incx():
nonlocal x
x += 2
incx()
return x
testglobal()
You want to use the nonlocal statement to access x, which is not global but local to testglobal.
def testglobal():
x = 0
def incx():
nonlocal x
x += 2
incx()
return x
assert 2 == testglobal()
The closest you can come to doing this in Python 2 is to replace x with a mutable value, similar to the argument hack you mentioned in your question.
def testglobal():
x = [0]
def incx():
x[0] += 2
incx()
return x[0]
assert 2 == testglobal()
Here's an example using a function attribute instead of a list, an alternative that you might find more attractive.
def testglobal():
def incx():
incx.x += 2
incx.x = 0
incx()
return inc.x
assert 2 == testglobal()
I'm wondering if it's possible for a closure in Python to manipulate variables in its namespace. You might call this side-effects because the state is being changed outside the closure itself. I'd like to do something like this
def closureMaker():
x = 0
def closure():
x+=1
print x
return closure
a = closureMaker()
a()
1
a()
2
Obviously what I hope to do is more complicated, but this example illustrates what I'm talking about.
You can't do exactly that in Python 2.x, but you can use a trick to get the same effect: use a mutable object such as a list.
def closureMaker():
x = [0]
def closure():
x[0] += 1
print x[0]
return closure
You can also make x an object with a named attribute, or a dictionary. This can be more readable than a list, especially if you have more than one such variable to modify.
In Python 3.x, you just need to add nonlocal x to your inner function. This causes assignments to x to go to the outer scope.
What limitations have closures in Python compared to language X closures?
nonlocal keyword in Python 2.x
Example:
def closureMaker():
x = 0
def closure():
nonlocal x
x += 1
print(x)
return closure