hit counter

Timeline

My development logbook

Learn Something About Dis

Classes have a special opcode, LOAD_NAME, that allows for

1
2
3
4
5
6
>>> x = 42
>>> class A:
...     x = x
...
>>> A.x
42

which would fail in a function

1
2
3
4
5
6
7
8
>>> def f():
...     x = x
...
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
UnboundLocalError: local variable 'x' referenced before assignment

LOAD_NAME is pretty dumb, it looks into the local namespace and if that lookup fails falls back to the global namespace. Someone probably thought “I can do better”, and reused the static name lookup for nested functions for names that occur only on the right-hand side of assignments in a class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> x = "global"
>>> def foo():
...     x = "local"
...     class A:
...             x = x
...     return A
...
>>> def bar():
...     x = "local"
...     class A:
...             y = x
...     return A
...
>>> foo().x
'global'
>>> bar().y
'local'

Now let’s have a glimpse at the bytecode:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
>>> import dis
>>> foo.func_code.co_consts
(None, 'local', 'A', <code object A at 0x7ffe311bdb70, file "<stdin>", line
3>, ())
>>> dis.dis(foo.func_code.co_consts[3])
  3           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  4           6 LOAD_NAME                2 (x)
              9 STORE_NAME               2 (x)
             12 LOAD_LOCALS
             13 RETURN_VALUE
>>> bar.func_code.co_consts
(None, 'local', 'A', <code object A at 0x7ffe311bd828, file "<stdin>", line
3>, ())
>>> dis.dis(bar.func_code.co_consts[3])
  3           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)

  4           6 LOAD_DEREF               0 (x)
              9 STORE_NAME               2 (y)
             12 LOAD_LOCALS
             13 RETURN_VALUE

Source