I have tried creating a function like this:
n = ["one", "two"]
result=""
def join_strings(words):
for word in words:
result+=word
return result
print (join_strings(n))
This does not work and outputs:
UnboundLocalError: local variable 'result' referenced before assignment
The result variable has to be inside the function for it to work.
However, when I have this simple function:
x=3
def price():
return (x+3)
print (price())
This works and prints the price, even though x is outside of the function. Why is this happening?
Actually there is no inconsistency as the examples you gave are different from each other. It would still fail in the second function if you have tried to assign x to itself, like:
>>> x = 3
>>> def price():
... x +=3
... return x
...
>>> price()
UnboundLocalError: local variable 'x' referenced before assignment
Instead of assigning back to x if you choose another name, it would run with no problem:
>>> def price():
... y = x + 3
... return y
...
>>> price()
6
But, why it happens?
It's because Python's scoping rules. You can read the value of a variable outside of function but you can't change it**. When you do x += 3, which is same as x = x + 3 for integers, that means "I have a variable x that I have write access to it in the current scope." You don't have such variable, thus as the error says: you are referencing a "local variable" before assignment.
Is there a way to modify them in the function?
Yes. You can use global keyword, changes will be applied to your global variable:
>>> x = 3
>>> def price():
... global x
... x += 3
... return x
...
>>> x
3
>>> price()
6
>>> x
6
** By changing, I mean assigning something else to it so it's id will change.
The difference is that in the second example you aren't trying to modify or reassign x. (You'll get the same error if you say something like x += 3 in the price function.) Once you use an assignment operator, you're binding a new value to that name, shadowing the outer scope.
If you want to be able to modify something from an outer scope, put it inside a mutable container (like a list); you can then modify the contents without reassigning that variable.
result = [""]
def join_strings(words):
for word in words:
result[0] += word
return result[0]
If you just want to be able to reference the result value, without modifying it, that's fine too, but you have to assign it to a new variable then:
result = ""
def join_strings(words):
ret = result
for word in words:
ret += word
return ret
Related
I'm trying to make a little choose your own adventure game for fun and can't find a way to make this work. I want to be able to have a consistent pick up method where a specific action adds one to a variable and unlocks lines of dialogue under other actions. What I've got right now is something like this.
item = 0
while True:
act = input()
if "action" in act:
if(item != 1):
print("You don't have that item.")
continue
else:
print("You use the item.")
continue
if "take item" in act:
print("You take the item.")
global item
item = item + 1
continue
The issue I'm finding is that when I try to set the global variable, the program claims a syntax warning:
main.py:107: SyntaxWarning: name 'item' is assigned to before global declaration global item
since the variable is stated before the global, but if I don't state the variable before hand, the system I have in place to prevent the wrong dialogue from printing won't work. How do I have a conditional global variable that updates an existing local variable?
TLDR
You don't need the global keyword. Remove that line and your code will work.
Short Explanation
In Python, everything is "global" (not accurate, I will explain in long explanation) so you rarely need to use the global keyword. You can try the following code in the interactive Python REPL:
>>> def test(y):
... return x, y
...
>>> test(3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "<stdin>", line 2, in test
NameError: name 'x' is not defined
>>> x = 4
>>> test(3)
(4, 3)
Here, I'm using a function as an example, if and while/for loops are exactly the same.
I'm declaring a function called test that takes in a parameter y, but it will return the tuple (x, y). Here, x is undefined. You would expect that it returns an error, but it actually doesn't until you run it.
When we run test(3), Python will complain that x is not defined so it cannot run the function. However, if we define x = 4 even after our function declaration, the function will work. Hence test(3) will return (4, 3). Here, x is treated as a global variable.
Long Explanation
>>> x = 3
>>> y = 4
>>> def test2():
... x = 1
... y = 2
... return x, y
...
>>> test2()
(1, 2)
>>> x
3
>>> y
4
>>>
Notice that y is not touched, even though it's clearly being assigned a different value in the function test2.
How is this possible? Actually, Python functions technically do have a scope of their own, and changes to function variables are isolated and do not change the variables of the same name outside the function. Loops and if statements don't have their own scope, so the variables inside if statements are the exact variables outside them.
>>> x = 3
>>> y = 4
>>> if True:
... x = 1
... y = 2
...
>>> x
1
>>> y
2
When you reference a variable in functions, Python will check if a value is assigned to a variable of that name in that function (either as a parameter or inside the function block). If there is a variable with that name, Python will use the variable in the function (within the scope of the function). If not, Python will try to look for a function that's in the scope outside (either another function or the global scope). If Python searched till the global scope and there is no variable of that name, it will finally produce an error.
>>> x = 3
>>> y = 4
>>> def function1():
... x = 4 # this x is being used
... def function2():
... print(x)
... function2()
...
>>> function1()
4
>>> def test3():
... x = 1
... y = 2
... return (x, y)
...
>>> test3()
(1, 2)
>>> x
3
>>> y
4
This means, only when you are trying to change the value of a variable outside of the current scope, you will use the global keyword. However, this is BAD PRACTICE. Whenever you find yourself doing this, you need to think about whether you should refactor your code to using classes. It's a very bad practice to change the value of global variables inside functions no matter the language you are using.
>>> x = 3
>>> y = 4
>>> def test4():
... global x, y # refers to the global x and y
... x = 1
... y = 2
... return (x, y)
...
>>> test4()
(1, 2)
>>> x
1
>>> y
2
class player(object):
def __init__(self, a, b):
self.a = a
self.b = b
def foo():
x += 2
obj.a += 10
obj = player(0,0)
x = 4
foo()
I understand that my foo function is not able to assign a value to the local variable x since it was previously defined globally outside the foo scope. Yet the same problem won't occur for a variable a which is an attribute of the instance obj from the player class, which in turn has nothing to do with the function foo().
Apart from the error with x += 2, I would expect an error in obj.a, i.e., I think foo() should not be able to modify obj.a, but it does! It should have thrown the same error as for x += 2 perhaps, no?
All augmented assignments effectively desugar to one of three method calls, but only in one case is an actual assignment performed.
If the lefthand side is a name, then x += y desugars to x = x.__iadd__(y). Because this is an ordinary assignment statement, the parser makes x a local variable (unless preceded by a global x or nonlocal x). Regardless of scope, x must already have a value so that the righthand side can be evaluated. In other words, x is not a free variable.
If the lefthand side is an indexed name, then x[i] += y desugars to x.__setitem__(i, x[i] + y). If x is not already defined as a local variable, it is a free variable, and so will resolve to the value
of x in the first enclosing scope.
If the lefthand side is an attribute lookup, then x.a = y desugars to
x.__setattr__('a', x.a + y). Again, x is a free variable unless a local variable x already exists.
Your comparison is apples to oranges. An example which shows you the behavior you're expecting would be:
class player(object):
def __init__(self, a, b):
self.a = a
self.b = b
def __add__(self, value):
self.a += value
def foo():
obj += 10
obj = player(0,0)
x = 4
foo()
Python permits you to read global variables inside of foo, but not redefine them. The statement obj.a += 10 only reads the variable of obj from the global scope (even though it sets a value on the instance of obj) so it is allowed. x += 2 attempts to redefine the value of x which is a global, so it is not allowed. This foo will work because we are only reading x but not redefining it:
def foo():
obj.a += 10
y = x + 10
Actually, you can't call x += 2 because x is not yet defined in your code. You can read x as a global var, or you can change it's value using global.
If you run your code, you'll get the error:
UnboundLocalError: local variable 'x' referenced before assignment
What it is saying to you is: I know there is a global x variable in your code, but you can't change it here. But, if you wish, you can create a local variable here named x, and then change it's value. Or, if you really want to modify the value from the global variable, use the global keyword, so I'll know exactly what you want to do.
Declaring it locally:
def foo():
x = 0
x += 2
obj.a += 10
obj = player(0,0)
x = 4
foo()
With global:
def foo():
global x
x += 2
obj.a += 10
obj = player(0,0)
x = 4
foo()
Now, why x.a += 10 works?
Because you are not changing the x object at all.
As mentioned by John in his answer, the same error would occur if you were changing the object itself, not its properties.
if you try to run the code below:
def foo():
obj += 10
obj = player(0,0)
x = 4
foo()
You'll get the same error as before:
UnboundLocalError: local variable 'obj' referenced before assignment
a is an attribute of player instance while x is a variable or simply value. When you are modifying x in the function, it is in local scope and results in NameError. But obj is an object in global scope and when you modify a, you are simply updating its attribute, you won't get any error here.
Without using one of the keywords global or nonlocal, a function can read but not assign a new value to a variable outside of the function's own local scope. Let's consider these two cases:
For x += 2, this is an attempt to assign a new value to the global variable x, so it is not allowed.
For obj.a += 10, it is the variable obj which is global; obj.a is an object attribute, not a "variable" in this sense. The way obj.a += 10 is evaluated is something like this: first look up the object referenced by the variable obj, and then add 10 to the attribute named a belonging to that object. Since this only reads the global variable obj and does not attempt to assign a new value to obj, it is allowed.
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.
It's so basic it should work. I want a function that adds a value to something
There must be something I don't know about python 3, so here we go.
x = 0
def foo(x=x): ##### with out x=x there is a error
x = x + 1 # option one
x = 1 # option two
# when we run it
foo()
print(x)
# it returns 0, it should return 1
x is a local variable in foo(); Assigning x as a default value for a keyword argument won't make it any less a local.
If you wanted it to be a global, mark it as such:
x = 0
def foo():
global x
x = x + 1
print(x)
foo()
print(x)
but you probably just wanted to pass in the value as an argument instead:
def foo(value):
return value + 1
x = 0
print(x)
x = foo(x)
print(x)
This is basically and example of scoping rules. The variable x within foo is local to foo, so nothing that happens to the local x changes anything outside foo, including the global x with which is actually a different variable. When the interpreter exits foo the global x comes back into scope and it hasn't changed from its initial value of 0. The function header foo(x=x) defines a local x whose default value is the global x. The interpreter allows it but it's generally considered bad programming practice to have the same variable name representing two variables because it leads to this kind of confusion.
I am having an issue where I want to bring a variable into a function by name.
I have tried this so far:
x = 5
def func(y = x):
return y ** 2
>>>print func()
25
>>>x = 4
>>>print func()
25
However if I try to edit that input variable after I input the code the number the function detects the old input so it would still print 25 as oppose to the new input squared.
Is there a cleaner way to pull a variable into a function that actually works?
Default functions values are calculated during function creation time, so even if you change the value of x it is not going to be changed in the function.
From docs:
Default parameter values are evaluated when the function definition is
executed. This means that the expression is evaluated once, when the
function is defined, and that the same “pre-computed” value is used
for each call.
Read: Using mutable objects as default value can lead to unexpected results.
If you want to access a global value then simply do:
def func():
return x**2
Or better pass it explicitly:
def func(y):
return y**2
Another reason why not to use global variables inside function:
>>> x = 10
>>> def func():
... print x
... x = 5
...
>>> func()
Traceback (most recent call last):
func()
File "<ipython-input-5-ab38e6cadf6b>", line 2, in func
print x
UnboundLocalError: local variable 'x' referenced before assignment
Using classes:
class A:
x = 5
#staticmethod
def func():
return A.x**2
...
>>> A.func()
25
>>> A.x = 10
>>> A.func()
100
Just reference the global variable directly, instead of using it as the default value of an argument.
def func():
return x ** 2
The default value of y is set during function definition, not when running it. So default value for y is set to value of x on def line.
This should work:
def func(y=None):
if y is None:
y = x
return y ** 2
Generally in python Keyword Arguments are always assigned a value and you cant change that assigned value . So in your code
x=5
def func(y = x):
return y ** 2
print func()
x=4
print func()
For the keyword argument y the value of x i.e 5 is assigned and we cant change that value. So even if you change the value of x you will get the same output as 25
To solve this the function might directly access the global variable and return the value like the below code
x=5
def func():
return x ** 2
print func()
x=4
print func()
This is the correct way to do this
x=5
def foo():
global x
return x**2
Or you can simply do it like this
x=5
def foo():
return x**2
In this case the same global variable is referenced if it exists.