python: name resolution clarification? - python

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.

Related

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.

How do global and local variables behave in this case? [duplicate]

This question already has answers here:
Using global variables in a function
(25 answers)
Closed 6 months ago.
This is saved in my file function_local.py:
x = 50
def func(x):
print('x is', x)
x = 2
print('Changed local x to', x)
func(x)
print('x is still', x)
Output:
$ python function_local.py
x is 50
Changed local x to 2
x is still 50
Question: When we print the first line inside the Function, why does it print out 50, not 2? Even if it is said, in the function, that x = 2?
In case you assign to a variable name (that wasn't declared global or nonlocal) in a function or use the variable name in the argument list of the function the variable name will become part of the function.
In that case you could've used any variable name inside the function because it will always refer to the local variable that was passed in:
x = 50
def func(another_x):
print('local x =', another_x)
another_x = 2
print('local x =', another_x)
return another_x
print('global x =', x)
x = func(x) # note that I assigned the returned value to "x" to make the change visible outside
print('global x =', x)
More explanation
I'm going to try to show what I meant earlier when I said that x with "will become part of the function".
The __code__.co_varnames of a function holds the list of local variable names of the function. So let's see what happens in a few cases:
If it's part of the signature:
def func(x): # the name "x" is part of the argument list
pass
print(func.__code__.co_varnames)
# ('x',)
If you assign to the variable (anywhere in the function):
def func():
x = 2 # assignment to name "x" inside the function
print(func.__code__.co_varnames)
# ('x',)
If you only access the variable name:
def func():
print(x)
print(func.__code__.co_varnames)
# ()
In this case it will actually look up the variable name in the outer scopes because the variable name x isn't part of the functions varnames!
I can understand how this could confuse you because just by adding a x=<whatever> anywhere in the function will make it part of the function:
def func():
print(x) # access
x = 2 # assignment
print(func.__code__.co_varnames)
# ('x',)
In that case it won't look up the variable x from the outer scope because it's part of the function now and you'll get a tell-tale Exception (because even though x is part of the function it isn't assigned to yet):
>>> func()
UnboundLocalError: local variable 'x' referenced before assignment
Obviously, it would work if you access it after assigning to it:
def func():
x = 2 # assignment
print(x) # access
Or if you pass it in when you call the function:
def func(x): # x will be passed in
print(x) # access
Another important point about local variable names is that you can't set variables from outer scopes, except when you tell Python to explicitly not make x part of the local variable names, for example with global or nonlocal:
def func():
global x # or "nonlocal x"
print(x)
x = 2
print(func.__code__.co_varnames)
# ()
This will actually overwrite what the global (or nonlocal) variable name x refers to when you call func()!
In your function
def func(x):
print('x is', x)
x = 2
print('Changed local x to', x)
x is not a global variable; it is a local variable due to the assignment on the second line of the body. If you are going to assign to a global variable, it must be explicitly declared as such:
def func(x):
global x
print('x is', x)
x = 2
print('Changed local x to', x)
(Note: I missed that x is actually a function parameter. See https://nedbatchelder.com/text/names.html for an explanation of assignments to local names do not affect global names.)
this could be easily understand by this.
x = 50
def func(x):
print "Local variables: "+str(locals())
print('x is', x)
x = 2
print "Local variables: "+str(locals())
print('Changed local x to', x)
func(x)
print('x is still', x)
and the output
Local variables: {'x': 50}
'x is', 50
Local variables: {'x': 2}
'Changed local x to', 2
'x is still', 50
however if you want to access the x from outer scope after defining the x in func there is a way, I am not sure if it would create a issue or not though.
def func(x):
x = 2
print "local x == %s" % x
print "global x == %s" % globals().get('x')
func(x)
print "x is still %s" % x
Following will be output of the run.
local x == 2
global x == 50
x is still 50

Python 3 syntax should work, but doesn't

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.

How to bring variables into functions - Python

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.

Categories