Help me understand why a name in a class def sometimes goes to globals and sometimes to an enclosing function

Here is a sample program. It has a global variable named x and a function with a local variable named x. Inside this function are several classes which use x in a variety of ways, including setting it locally and/or defining new functions which capture it.

x = 'global x'
def f():
	x = 'f x'
	class C1:
		print('C1 = ', x)	# f x
	class D1:
		def g():
			print('D1.g = ', x)
		g()					# f x
	class C2:
		print('C2 = ', x)	# f x
	class D2:
		print('D2 = ', x)	# f x
		def g():
			print('D2.g = ', x)
		g()			# f x
	class C3:
		print('C3 = ', x)	# global x
		x = 'C3 x'
		print('C3 = ', x)	# C3 x
	class D3:
		print('D3 = ', x)	# global x
		def g():
			print('D3.g = ', x)
		g()					# f x
		x = 'D3 x'
		print('D3 = ', x)	# D3 x
		def g2():
			print('D3.g2 = ', x)
		g()					# f x
		g2()				# f x
	x = 'new f x'
	D1.g()					# new f x
	D2.g()					# new f x
	D3.g()					# new f x
	D3.g2()					# new f x
f()

Why do C3 and D3 print out ‘global x’ whereas the others print out ‘f x’?

I’ve looked at disassembly for this example. I don’t want to quote the entire listing here (it’s about 300 lines). What I have noticed about various cases is:

  1. The classes are built inside f() with a LOAD_CLOSURE bytecode for x, except C3, which has no closure.
  2. The classes get the value of x with a LOAD_CLASSDEREF bytecode, except C3 and D3, which use LOAD_NAME.
  3. All the nested functions are created using a LOAD_CLOSURE for x, and they reference x with a LOAD_DEREF.

Actually, now that I see that blockquotes get scrolled, I’m including the entire disassembly here.

  1           0 LOAD_CONST               0 ('global x')
              2 STORE_NAME               0 (x)

  2           4 LOAD_CONST               1 (<code object f at 0x000002CF8C154DF0, file "<dis>", line 2>)
              6 LOAD_CONST               2 ('f')
              8 MAKE_FUNCTION            0
             10 STORE_NAME               1 (f)

 38          12 LOAD_NAME                1 (f)
             14 CALL_FUNCTION            0
             16 POP_TOP
             18 LOAD_CONST               3 (None)
             20 RETURN_VALUE

Disassembly of <code object f at 0x000002CF8C154DF0, file "<dis>", line 2>:
  3           0 LOAD_CONST               1 ('f x')
              2 STORE_DEREF              0 (x)

  4           4 LOAD_BUILD_CLASS
              6 LOAD_CLOSURE             0 (x)
              8 BUILD_TUPLE              1
             10 LOAD_CONST               2 (<code object C1 at 0x000002CF8C154710, file "<dis>", line 4>)
             12 LOAD_CONST               3 ('C1')
             14 MAKE_FUNCTION            8
             16 LOAD_CONST               3 ('C1')
             18 CALL_FUNCTION            2
             20 STORE_FAST               0 (C1)

  6          22 LOAD_BUILD_CLASS
             24 LOAD_CLOSURE             0 (x)
             26 BUILD_TUPLE              1
             28 LOAD_CONST               4 (<code object D1 at 0x000002CF8C154870, file "<dis>", line 6>)
             30 LOAD_CONST               5 ('D1')
             32 MAKE_FUNCTION            8
             34 LOAD_CONST               5 ('D1')
             36 CALL_FUNCTION            2
             38 STORE_FAST               1 (D1)

 10          40 LOAD_BUILD_CLASS
             42 LOAD_CLOSURE             0 (x)
             44 BUILD_TUPLE              1
             46 LOAD_CONST               6 (<code object C2 at 0x000002CF8C154920, file "<dis>", line 10>)
             48 LOAD_CONST               7 ('C2')
             50 MAKE_FUNCTION            8
             52 LOAD_CONST               7 ('C2')
             54 CALL_FUNCTION            2
             56 STORE_FAST               2 (C2)

 12          58 LOAD_BUILD_CLASS
             60 LOAD_CLOSURE             0 (x)
             62 BUILD_TUPLE              1
             64 LOAD_CONST               8 (<code object D2 at 0x000002CF8C154A80, file "<dis>", line 12>)
             66 LOAD_CONST               9 ('D2')
             68 MAKE_FUNCTION            8
             70 LOAD_CONST               9 ('D2')
             72 CALL_FUNCTION            2
             74 STORE_FAST               3 (D2)

 17          76 LOAD_BUILD_CLASS
             78 LOAD_CONST              10 (<code object C3 at 0x000002CF8C154B30, file "<dis>", line 17>)
             80 LOAD_CONST              11 ('C3')
             82 MAKE_FUNCTION            0
             84 LOAD_CONST              11 ('C3')
             86 CALL_FUNCTION            2
             88 STORE_FAST               4 (C3)

 21          90 LOAD_BUILD_CLASS
             92 LOAD_CLOSURE             0 (x)
             94 BUILD_TUPLE              1
             96 LOAD_CONST              12 (<code object D3 at 0x000002CF8C154D40, file "<dis>", line 21>)
             98 LOAD_CONST              13 ('D3')
            100 MAKE_FUNCTION            8
            102 LOAD_CONST              13 ('D3')
            104 CALL_FUNCTION            2
            106 STORE_FAST               5 (D3)

 33         108 LOAD_CONST              14 ('new f x')
            110 STORE_DEREF              0 (x)

 34         112 LOAD_FAST                1 (D1)
            114 LOAD_METHOD              0 (g)
            116 CALL_METHOD              0
            118 POP_TOP

 35         120 LOAD_FAST                3 (D2)
            122 LOAD_METHOD              0 (g)
            124 CALL_METHOD              0
            126 POP_TOP

 36         128 LOAD_FAST                5 (D3)
            130 LOAD_METHOD              0 (g)
            132 CALL_METHOD              0
            134 POP_TOP

 37         136 LOAD_FAST                5 (D3)
            138 LOAD_METHOD              1 (g2)
            140 CALL_METHOD              0
            142 POP_TOP
            144 LOAD_CONST               0 (None)
            146 RETURN_VALUE

Disassembly of <code object C1 at 0x000002CF8C154710, file "<dis>", line 4>:
  4           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('f.<locals>.C1')
              6 STORE_NAME               2 (__qualname__)

  5           8 LOAD_NAME                3 (print)
             10 LOAD_CONST               1 ('C1 = ')
             12 LOAD_CLASSDEREF          0 (x)
             14 CALL_FUNCTION            2
             16 POP_TOP
             18 LOAD_CONST               2 (None)
             20 RETURN_VALUE

Disassembly of <code object D1 at 0x000002CF8C154870, file "<dis>", line 6>:
  6           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('f.<locals>.D1')
              6 STORE_NAME               2 (__qualname__)

  7           8 LOAD_CLOSURE             0 (x)
             10 BUILD_TUPLE              1
             12 LOAD_CONST               1 (<code object g at 0x000002CF8C1547C0, file "<dis>", line 7>)
             14 LOAD_CONST               2 ('f.<locals>.D1.g')
             16 MAKE_FUNCTION            8
             18 STORE_NAME               3 (g)

  9          20 LOAD_NAME                3 (g)
             22 CALL_FUNCTION            0
             24 POP_TOP
             26 LOAD_CONST               3 (None)
             28 RETURN_VALUE

Disassembly of <code object g at 0x000002CF8C1547C0, file "<dis>", line 7>:
  8           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('D1.g = ')
              4 LOAD_DEREF               0 (x)
              6 CALL_FUNCTION            2
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE

Disassembly of <code object C2 at 0x000002CF8C154920, file "<dis>", line 10>:
 10           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('f.<locals>.C2')
              6 STORE_NAME               2 (__qualname__)

 11           8 LOAD_NAME                3 (print)
             10 LOAD_CONST               1 ('C2 = ')
             12 LOAD_CLASSDEREF          0 (x)
             14 CALL_FUNCTION            2
             16 POP_TOP
             18 LOAD_CONST               2 (None)
             20 RETURN_VALUE

Disassembly of <code object D2 at 0x000002CF8C154A80, file "<dis>", line 12>:
 12           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('f.<locals>.D2')
              6 STORE_NAME               2 (__qualname__)

 13           8 LOAD_NAME                3 (print)
             10 LOAD_CONST               1 ('D2 = ')
             12 LOAD_CLASSDEREF          0 (x)
             14 CALL_FUNCTION            2
             16 POP_TOP

 14          18 LOAD_CLOSURE             0 (x)
             20 BUILD_TUPLE              1
             22 LOAD_CONST               2 (<code object g at 0x000002CF8C1549D0, file "<dis>", line 14>)
             24 LOAD_CONST               3 ('f.<locals>.D2.g')
             26 MAKE_FUNCTION            8
             28 STORE_NAME               4 (g)

 16          30 LOAD_NAME                4 (g)
             32 CALL_FUNCTION            0
             34 POP_TOP
             36 LOAD_CONST               4 (None)
             38 RETURN_VALUE

Disassembly of <code object g at 0x000002CF8C1549D0, file "<dis>", line 14>:
 15           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('D2.g = ')
              4 LOAD_DEREF               0 (x)
              6 CALL_FUNCTION            2
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE

Disassembly of <code object C3 at 0x000002CF8C154B30, file "<dis>", line 17>:
 17           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('f.<locals>.C3')
              6 STORE_NAME               2 (__qualname__)

 18           8 LOAD_NAME                3 (print)
             10 LOAD_CONST               1 ('C3 = ')
             12 LOAD_NAME                4 (x)
             14 CALL_FUNCTION            2
             16 POP_TOP

 19          18 LOAD_CONST               2 ('C3 x')
             20 STORE_NAME               4 (x)

 20          22 LOAD_NAME                3 (print)
             24 LOAD_CONST               1 ('C3 = ')
             26 LOAD_NAME                4 (x)
             28 CALL_FUNCTION            2
             30 POP_TOP
             32 LOAD_CONST               3 (None)
             34 RETURN_VALUE

Disassembly of <code object D3 at 0x000002CF8C154D40, file "<dis>", line 21>:
 21           0 LOAD_NAME                0 (__name__)
              2 STORE_NAME               1 (__module__)
              4 LOAD_CONST               0 ('f.<locals>.D3')
              6 STORE_NAME               2 (__qualname__)

 22           8 LOAD_NAME                3 (print)
             10 LOAD_CONST               1 ('D3 = ')
             12 LOAD_NAME                4 (x)
             14 CALL_FUNCTION            2
             16 POP_TOP

 23          18 LOAD_CLOSURE             0 (x)
             20 BUILD_TUPLE              1
             22 LOAD_CONST               2 (<code object g at 0x000002CF8C154BE0, file "<dis>", line 23>)
             24 LOAD_CONST               3 ('f.<locals>.D3.g')
             26 MAKE_FUNCTION            8
             28 STORE_NAME               5 (g)

 25          30 LOAD_NAME                5 (g)
             32 CALL_FUNCTION            0
             34 POP_TOP

 26          36 LOAD_CONST               4 ('D3 x')
             38 STORE_NAME               4 (x)

 27          40 LOAD_NAME                3 (print)
             42 LOAD_CONST               1 ('D3 = ')
             44 LOAD_NAME                4 (x)
             46 CALL_FUNCTION            2
             48 POP_TOP

 28          50 LOAD_CLOSURE             0 (x)
             52 BUILD_TUPLE              1
             54 LOAD_CONST               5 (<code object g2 at 0x000002CF8C154C90, file "<dis>", line 28>)
             56 LOAD_CONST               6 ('f.<locals>.D3.g2')
             58 MAKE_FUNCTION            8
             60 STORE_NAME               6 (g2)

 31          62 LOAD_NAME                5 (g)
             64 CALL_FUNCTION            0
             66 POP_TOP

 32          68 LOAD_NAME                6 (g2)
             70 CALL_FUNCTION            0
             72 POP_TOP
             74 LOAD_CONST               7 (None)
             76 RETURN_VALUE

Disassembly of <code object g at 0x000002CF8C154BE0, file "<dis>", line 23>:
 24           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('D3.g = ')
              4 LOAD_DEREF               0 (x)
              6 CALL_FUNCTION            2
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE

Disassembly of <code object g2 at 0x000002CF8C154C90, file "<dis>", line 28>:
 30           0 LOAD_GLOBAL              0 (print)
              2 LOAD_CONST               1 ('D3.g2 = ')
              4 LOAD_DEREF               0 (x)
              6 CALL_FUNCTION            2
              8 POP_TOP
             10 LOAD_CONST               0 (None)
             12 RETURN_VALUE
2 Likes

The scoping rules inside classes are complex and not well documented or understood. The standard “LEGB” rule (Local/Enclosing/Global/Builtins) for functions is incomplete when classes enter the scene.

I believe that the simplest way to explain it is with these cases:

When executing code inside the class body, the class counts as local scope, and LEGB applies with the slight twist that unlike the normal function LEGB scoping rule, if a variable is not found in the local (i.e. class body) scope, the search continues to globals and builtins.

x = "global"
class K:
    print(x)
    x = "class body (de facto local)"
    print(x)

When executing code inside a function defined inside a class body, the class body does not participate as part of the scope rule.

x = "global"
class K:
    def func():
        print(x)
    func()
    x = "class body"
    func()

When executing code inside a class body defined in a function, again the class body counts as the de facto local scope, and the enclosing function local is excluded from the LEGB rule.

def func():
    x = "func local"
    class K:
        print(x)
        x = "class local"
        print(x)

func()

This means that code inside class bodies is anomalous in that the same variable can refer to both a local (class body) and global at different times. This is unlike functions, where a variable is unambiguously either local, nonlocal or global for the entire code block.

My explanation may not be complete or cover all the corner cases, but I think it comes close. If anyone can spot an error in my explanation, please do tell me!

For further discussion, see this:

(For the future, please try to post minimal examples, not maximal examples that show every permutation you can think of. I’m pretty sure nobody is going to read multiple pages of disassembly. I know I didn’t.)

1 Like

Okay, the plot thickens…

(Told you the rules for classes are complex :slight_smile:

Compare these two cases:

x = "global"
def f():
    x = "func local"
    class K:
        print(x)
        x = "class local"
        print(x)

def g():
    x = "func local"
    class K:
        print(x)

Function f() prints “global” followed by “class local”, as I expected, but function g() prints “func local” which I did not expect.

I don’t have Python 2.1 or 2.2 installed, but I tried it in Python 2.4 and got the same behaviour.

So I still don’t think this is a bug, but the explanation is more complex than my previous comment suggested.

My basic point is that since the behavior is confusing, even to some experts, it ought to be conspicuously documented.

I have come up with my own answer, and verified it by generating thousands of combinations of nested functions, nested classes, and uses of the variable x. My generator predicts the value of x (or NameError) in each scope (except if x is not mentioned at all there), and if x is set, it does this both before and after the assignment. The generated code verifies the predicted value at runtime.

The rule that applies in the present situation is this:

  1. In a class, if x is assigned anywhere in the class body, and does not have a value in the local namespace at the point in question:
    1.1. x is always resolved in the global namespace, or if not there, in the builtins namespace, or not there either, it is a NameError. It does not matter whether x is local in any enclosing function, or whether x is used in any enclosed scope.

  2. For comparison, if the class does not assign x, then
    2.1, If there is a closure for x, then x is resolved in that closure, and if not assigned there, it is a NameError. The search for a closure looks at enclosing function scopes from nearest to farthest. If x is declared global, the search fails. If x is bound there, the search succeeds. If x is declared nonlocal, or if none of the above, the search continues.
    2.2. If there is no closure for x, then x is resolved in the global namespace, or if not there, in the builtins namespace, or not there either, it is a NameError. This is the same as 1.1 above.

The short version is: A class will look in the enclosing function (and only there) only if it has a local binding for the name and has no local value currently bound.
By the way, if the name is deleted from the class namespace in the class body, then it reverts back to the no local value currently bound situation.

Also, the reason this is not a bug is that Guido declared it to be a feature a long time ago.

Using the LEGB terminology, I would express the rules as:
If x is a local variable in the class def, then the search order is LGB.
Else if x is declared nonlocal, then the search order is E.
Else if x is declared global, then the search order is GB.
Else if x is a nonlocal variable (i.e. in some enclosing function), then the search order is E.
Else, the search order is GB.

Guido recently wrote up a description of Python scoping, see Everything You Always Wanted to Know About Scopes But Were Afraid to Ask | gvanrossum.github.io