Python 3 syntax should work, but doesn't - python

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.

Related

how to change an existing variable to a global variable inside of an if condition

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

Python scope when assignment never performed

So I get that
x = 5
def f():
print(x)
f()
print(x)
gives back 5 and 5.
I also get that
x = 5
def f():
x = 7
print(x)
f()
print(x)
gives back 7 and 5.
What is wrong with the following?
x = 5
def f():
if False:
x = 7
print(x)
else:
print(x)
f()
print(x)
I would guess that since the x=7 never happens I should get 5 and 5 again. Instead I get
UnboundLocalError: local variable 'x' referenced before assignment
Does python regard x as a local variable because in this indented block there is an assignment expression regardless if it is executed or not? What is the rule exactly?
When the function is defined python interprets x as a local variable since it's assigned inside the body of the function. During runtime, when you go into the else clause, the interpreter looks for a local variable x, which is unassigned.
If you want both x to refer to the same variable, you can add global x inside the body of the function, before its assignment to essentially tell python when I call x I'll be referring to the global-scope x.
If a name binding operation occurs anywhere within a code block, all uses of the name within the block are treated as references to the current block. This can lead to errors when a name is used within a block before it is bound. This rule is subtle. Python lacks declarations and allows name binding operations to occur anywhere within a code block. The local variables of a code block can be determined by scanning the entire text of the block for name binding operations.
You need to use global in your function f() like so:
x = 5
def f():
global x
if False:
x = 7
print(x)
else:
print(x)
f()
print(x)

What is the scope of a function defined outside of a class?

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.

Why is global keyword not ignored even if it is unreachable? (Python)

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.

python: name resolution clarification?

I'm reading the python reference name resolution, which reads
A class definition is an executable statement that may use and define names. These references follow the normal rules for name resolution with an exception that unbound local variables are looked up in the global namespace.
Based on that, I would expect the following code
x = 10
def f():
x = 5
class Test:
y = x
return Test
print(f().y)
to print 10, however it prints 5. is this a mistake in the reference, or am I misunderstanding something?
In this case, 'normal' rules apply:
x = 'global'
def f():
x = 'defined in f'
class Test:
print(x) # not local, normal rules apply
f()
# defined in f
In this second case, we would expect an UnboundLocalError: local variable 'x' referenced before assignment if we were inside a function:
x = 'global'
def f():
x = 'defined in f'
class Test:
print(x) # unbound local at this time
x = 'assigned in Test'
print(x)
But for the first print(x), x will be taken from the global namespace:
f()
# global
# assigned in Test
I think #khelwood gave the answer. The value in the variable:
Test.y
is an integer that you define, but you never give the x = 10 to the function.
Maybe what you want is actually:
x = 10
def f(x=5):
class Test:
y = x
return Test
print(f().y) #print default 5
print(f(x).y)
The last line print 10 since x is given to the function, and therefore the class set y as x
You can see the exception by wrapping f in another function that defines a local variable x:
x = 10
def g():
x = 7
def f():
class Test:
y = x
return Test
return f()
print(g().y)
Now the output is 7, as the lookup of x in the class statement bypasses the local scope of g to find the global value of x.
In your code, because class establishes a new namespace, but not a new scope, the value of x is taken from the current local scope, that of f.

Categories