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?

Upvotes: 1

Views: 100

Answers (5)

chepner
chepner

Reputation: 531055

All augmented assignments effectively desugar to one of three method calls, but only in one case is an actual assignment performed.

  1. 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.

  2. 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.

  3. 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

John
John

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

Rafael Marques
Rafael Marques

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

J Arun Mani
J Arun Mani

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

kaya3
kaya3

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:

  • 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.

Upvotes: -1

Related Questions