baffled classes within a function namespace. Evaluation order.

Peter Otten __peter__ at web.de
Fri Apr 26 01:39:32 EDT 2013


Alastair Thompson wrote:

> I am completely baffled by the behavior of this code with regards to the
> evaluation order of namespaces when assigning the class attributes.  Both
> classes are nested within a function I called whywhywhy.
> 
> I assumed that example1 and example2 classes would look first at their own
> namespace, then object, then the whywhywhy func namespace then global, and
> maybe module.  It seems this is not the case.
> 
> def whywhywhy(first, second, third):
>     def print_stuff():
>         print("func: first=", first)
>         print("func: second=", second)
>         print("func: third=", third)
>     print_stuff()
> 
>     class example1(object):
>         print("1cls: first=", first)
>         print("1cls: second=", second)
>         print("1cls: third=", third)
> 
>         second = second
>         foo = third
> 
>     class example2(object):
>         print("2cls: first=", first)
>         print("2cls: second=", second)
>         print("2cls: third=", third)
> 
>         second = second
>         third = third
> 
> def second():
>     pass
> 
> whywhywhy(1,2,3)
> 
> 
> The code above produces the following output
> """
> func: first= 1
> func: second= 2
> func: third= 3
> 1cls: first= 1
> 1cls: second= <function second at 0xc6d380>
> 1cls: third= 3
> 2cls: first= 1
> 2cls: second= <function second at 0xc6d380>
> Traceback (most recent call last):
>   File "error.py", line 29, in <module>
>     whywhywhy(1,2,3)
>   File "error.py", line 18, in whywhywhy
>     class example2(object):
>   File "error.py", line 21, in example2
>     print("2cls: third=", third)
> NameError: name 'third' is not defined
> """
> 
> In particular:
> 
> print_stuff behaves as I would expect
> 1cls: second #<--- Why does this look at the global namespace for second
> and not the whywhywhy func namespace first.
> 2cls: second #<--- Why can this no longer find third, it surely hasn't hit
> the line third=third
> 
> Thanks for any help you can provide. :)
> Alastair

A class body is basically a function body that is executed once. To allow an 
assignment

>>> x = 42
>>> class A:
...     x = x
... 

which is not possible inside a function

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

it uses one opcode, LOAD_NAME, that looks into the local and falls back to 
the global namespace, but knows nothing about closures:

>>> code = compile("class A:\n  x = x", "<nofile>", "exec")
>>> code.co_consts
(<code object A at 0x1c1d4f8, file "<nofile>", line 1>, 'A', None)
>>> import dis
>>> dis.dis(code.co_consts[0])
  1           0 LOAD_FAST                0 (__locals__) 
              3 STORE_LOCALS         
              4 LOAD_NAME                0 (__name__) 
              7 STORE_NAME               1 (__module__) 

  2          10 LOAD_NAME                2 (x) 
             13 STORE_NAME               2 (x)                                                                                             
             16 LOAD_CONST               0 (None)                                                                                          
             19 RETURN_VALUE                                                                                                               

For functions on the other side the compiler determines the scope of the 
name:

>>> def g():
...     y = "outer"
...     def h():
...             return x, y, z
...             z = 42
...     return h
... 
>>> dis.dis(g())
  4           0 LOAD_GLOBAL              0 (x) # global var
              3 LOAD_DEREF               0 (y) # closure
              6 LOAD_FAST                0 (z) # local
              9 BUILD_TUPLE              3 
             12 RETURN_VALUE         

  5          13 LOAD_CONST               1 (42) 
             16 STORE_FAST               0 (z) 

As you can see there are three different opcodes for global, closure, and 
local namespace.

However, while the above gives some technical background it doesn't explain 
why this scheme was chosen. My guess is that when Python got closures nobody 
was willing to do the extra work to make class bodies namespace-aware.
The alternative, disallowing x = x in classes, would have seriously broken 
backwards-compatibility.





More information about the Python-list mailing list