11

When referencing global variables, one can see that functions and classes handle this differently. The first is fine and the second causes an error:

x = 10
class Foo():
    x = x + 1
a = foo()

Vs:

x = 10
def faa():
    x = x + 1
faa()

In the Python execution model, this is described as:

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.

But why?

The only other hint I have come across is this bit:

The class’s suite is then executed in a new execution frame (see section Naming and binding), using a newly created local namespace and the original global namespace. (Usually, the suite contains only function definitions.) When the class’s suite finishes execution, its execution frame is discarded but its local namespace is saved. 4 A class object is then created using the inheritance list for the base classes and the saved local namespace for the attribute dictionary.

Which still offers no explanation why this should have the consequence that unbound locals are looked up in the global namespace.

Both links are from this answer which does not adress the why in more details though.

Andrew Myers
  • 2,754
  • 5
  • 32
  • 40
Kuhlambo
  • 371
  • 3
  • 13
  • I would not think so, at least my question in not answered there. That question relates to public and private and is broadly formulated at that. – Kuhlambo Feb 06 '18 at 08:44
  • If you have any suggestions on how to clarify muy question I would be glab about them though. – Kuhlambo Feb 06 '18 at 08:47
  • I think you can learn more about scope in python [here](http://python-textbok.readthedocs.io/en/1.0/Variables_and_Scope.html) – Vikas Periyadath Feb 06 '18 at 08:53
  • take a look here: https://stackoverflow.com/q/31614464/1924666 – Shahriar Feb 06 '18 at 08:56
  • I am sorry but I know this article. It does not contain the answer to my question. – Kuhlambo Feb 06 '18 at 08:56
  • so where is the difference explained in that link? @aerokite? The post is about classes exclusively not about functions and classes... – Kuhlambo Feb 06 '18 at 08:58
  • 1
    See [here](https://docs.python.org/3/reference/executionmodel.html#resolution-of-names): " 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." and some more information at https://stackoverflow.com/questions/291978/short-description-of-the-scoping-rules (Python3 answer here: https://stackoverflow.com/a/23471004/92092) – stephan Feb 06 '18 at 09:01
  • 1
    @pindakaas in particular, look at [this](https://stackoverflow.com/a/23471004/5014455) answer instead of the accepted one for an understanding of the special nature of class body scope in Python's scoping rules. – juanpa.arrivillaga Feb 06 '18 at 09:10
  • @stephan Thanks that at least shows that it is written down that this difference exists^^. I would love to know why this difference exists though. But I guess the answer at least in first order is, it is just defined that way. – Kuhlambo Feb 06 '18 at 09:13
  • I closed this as a duplicate, because the answer to this question is addressed explicitly, although the question is more general. I think this is a subtlety that is not often addressed, though. – juanpa.arrivillaga Feb 06 '18 at 09:13
  • @juanpa.arrivillaga maybe somebody knows the why? why not leave it open? I think it would be so much easiyer to remember if I knew the why behind it. But maybe there isn't one... – Kuhlambo Feb 06 '18 at 09:14
  • Maybe edit the question and link to the documented behavior and make it explicitly about *why* this is the case in name resolution in class-body scope compared to function local scope. As the the "why", I think it likely boils down to class body scope being a special thing in Python already, for example, it doesnt create an enclosing scope for functions defined in the body. – juanpa.arrivillaga Feb 06 '18 at 09:20
  • 1
    See [this question](https://stackoverflow.com/questions/13905741/accessing-class-variables-from-a-list-comprehension-in-the-class-definition) about another suprising aspect of class body scope, which goes into a lot of details. One takeaway from that superb answer: "Because the scope is repurposed as the attributes on a class object, allowing it to be used as a scope as well leads to undefined behaviour... Python has to treat a class scope differently as it is very different from a function scope." – juanpa.arrivillaga Feb 06 '18 at 09:24
  • thanks I'll read these in every detail – Kuhlambo Feb 06 '18 at 09:39
  • @pindakaas: ah, I see you have clarified your question. As for the *Why*, you would probably have to look at old mailings or ask the BDFL. In my mind, this is some obscure corner of the language which many see as some sort of wart but nobody seems to really care. It is quite subtle, because the global lookup happens only for unbound local variables (not for normal variable lookup inside a class, which follows normal scoping rules). Every now and then it is discussed on the python mailing list, eg [here](https://mail.python.org/pipermail/python-dev/2015-June/140539.html) (just click through). – stephan Feb 07 '18 at 09:58

1 Answers1

1

As explained in Python FAQ - Why am I getting an UnboundLocalError?

... because when you make an assignment to a variable in a scope, that variable becomes local to that scope and shadows any similarly named variable in the outer scope. Since the last statement in foo assigns a new value to x, the compiler recognizes it as a local variable.

Consequently when earlier attempting to access the uninitialized local variable an error results.

This explains why this:

x = 10
def foo():
    x = x+1  # raises UnboundLocalError
foo()

raises an Exception, but not this:

x = 10
def faa():
    y = x+1  # x stays in global scope, since it is not assigned in local
faa()

For the class foo() it's the same, since python allows assignments of attributes to objects to any given time. The class-code assigns x as new attribute of the object a.

x = 10
class foo():
    x = x+1  # scope of x: class
a = foo()  # x = 10; a.x = foo.x = 11

is the same as:

x = 10
class foo():
    def __init__(self):
        self.x = x+1  # scope of x: instance
a = foo()  # x = 10; a.x = 11

where obviously self.x is assigned instead of x, therefore also staying in global scope. (see also here)

Skandix
  • 1,916
  • 6
  • 27
  • 36
  • Sorry but this just restates what I already know. also you may want to look at foo.x this is a variable local to foo that shadows the global variable x. So no a.x is not assigned to the local space of a. a looks it up in foo. – Kuhlambo Feb 06 '18 at 08:55
  • python is indeed assigning a.x as 11! Therefore no `local x` is used, since the target is the lokal space of the class, therefore again python is looking in global space for the unknown value. In your function you are assigning x as `local` when assigning a value to it, since you dont use any keywords, resulting in python using it in in local space instead of shadowing it from global. The function would work if you would assing `x+1` to a different var. Thanks for the quick downvote btw. – Skandix Feb 06 '18 at 09:04
  • was not me. But again look at a.__dict__ you will see there is no x in there a looks it up in foo.x. – Kuhlambo Feb 06 '18 at 09:07
  • thats interesting, `print(a.x)` shows me 11, while `a.__dict__` is showns as empty (tested in python2.7 and 3.6) – Skandix Feb 06 '18 at 09:11
  • @pindakaas this post was realy helpfull, I reworked the answer based on the insight from this – Skandix Feb 06 '18 at 10:35
  • I am sorry, but the two snippets you give for the classes are not the same. In the first, `x` is a class attribute while in the second, `x` is an instance attribute. Even though you state this as well, they are not the same thing. – 1313e May 03 '19 at 01:53
  • Also, `a.__dict__` will show you `x` when it is an instance attribute, but not when it is a class attribute. This is because `__dict__` gives you all bound namespaces that are unique to that object. If `x` is a class attribute, then it is not unique to that instance as it is shared among all instances of the same class, and thus it will not show up. Instead, it will show up for `foo.__dict__` (as it is bound to that class). – 1313e May 03 '19 at 01:53