Reputation: 25
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?
Upvotes: 1
Views: 100
Reputation: 531055
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.
Upvotes: 2
Reputation: 375
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
Upvotes: 1
Reputation: 1872
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
Upvotes: 0
Reputation: 620
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.
Upvotes: 0
Reputation: 51034
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:
x += 2
, this is an attempt to assign a new value to the global variable x
, so it is not allowed.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.Upvotes: -1