EDIT: Looks like this is a very old "bug" or, actually, feature. See, e.g., this mail
I am trying to understand the Python scoping rules. More precisely, I thought that I understand them but then I found this code here:
x = "xtop"
y = "ytop"
def func():
x = "xlocal"
y = "ylocal"
class C:
print(x)
print(y)
y = 1
func()
In Python 3.4 the output is:
xlocal
ytop
If I replace the inner class by a function then it reasonably gives UnboundLocalError
. Could you explain me why it behaves this strange way with classes and what is the reason for such choice of scoping rules?
First focus on the case of a closure -- a function within a function:
Note the commented out
global
ininner
If you run this, it replicates theUnboundLocalError
you got. Why?Run dis.dis on it:
Note the different access mode of
x
vsy
inside offunc
. The use ofy='inner y'
inside ofinner
has created theUnboundLocalError
Now uncomment
global y
inside ofinner
. Now you have unambiguously createy
to be the top global version until resigned asy='inner y'
With
global
uncommented, prints:You can get a more sensible result with:
Prints:
The analysis of the closure class is complicated by instance vs class variables and what / when a naked class (with no instance) is being executed.
The bottom line is the same: If you reference a name outside the local namespace and then assign to the same name locally you get a surprising result.
The 'fix' is the same: use the global keyword:
Prints:
You can read more about Python 3 scope rules in PEP 3104
TL;DR: This behaviour has existed since Python 2.1 PEP 227: Nested Scopes, and was known back then. If a name is assigned to within a class body (like
y
), then it is assumed to be a local/global variable; if it is not assigned to (x
), then it also can potentially point to a closure cell. The lexical variables do not show up as local/global names to the class body.On Python 3.4,
dis.dis(func)
shows the following:The
LOAD_BUILD_CLASS
loads thebuiltins.__build_class__
on the stack; this is called with arguments__build_class__(func, name)
; wherefunc
is the class body, andname
is'C'
. The class body is the constant #3 for the functionfunc
:Within the class body,
x
is accessed withLOAD_CLASSDEREF
(15) whiley
is load withLOAD_NAME
(25). TheLOAD_CLASSDEREF
is a Python 3.4+ opcode for loading values from closure cells specifically within class bodies (in previous versions, the genericLOAD_DEREF
was used); theLOAD_NAME
is for loading values from locals and then globals. However closure cells show up neither as locals nor globals.Now, because the name
y
is stored to within the class body (35), it is consistently being used as not a closure cell but a local/global name. The closure cells do not show up as local variables to the class body.This behaviour has been true ever since implementing PEP 227 - nested scopes. And back then BDFL stated that this should not be fixed - and thus it has been for these 13+ years.
The only change since PEP 227 is the addition of
nonlocal
in Python 3; if one uses it within the class body, the class body can set the values of the cells within the containing scope:The output now is
That is,
print(y)
read the value of the celly
of the containing scope, andy = 1
set the value in that cell; in this case, no attribute was created for the classC
.