Nested scopes and class variables

Alex Martelli aleaxit at yahoo.com
Mon Jan 31 07:16:38 EST 2005


Dave Benjamin <ramen at lackingtalent.com> wrote:

> I ran into an odd little edge case while experimenting with functions that
> create classes on the fly (don't ask me why):

"Why not?".  But classes have little to do with it, in my view.


>   >>> def f(x):
>   ...   class C(object):
>   ...     x = x

You bind x, so x is local (to the class), not free.  Videat, classless:

>>> def f(x):
...   def g():
...     x=x
...     return x
...   return g
... 
>>> z=f(23)
>>> z()
Traceback (most recent call last):
  File "<stdin>", line 1, in ?
  File "<stdin>", line 3, in g
UnboundLocalError: local variable 'x' referenced before assignment

In this example the error is discovered when the body of g executes; in
your case, the body of C executes as part of the class statement, i.e.
when f is called, so the error is discovered earlier.

> "x" clearly is defined, but apparently Python is not looking at the nested
> variable scope to find it. What's stranger is that if I rename the parameter
> "x" to "y", the error goes away:

Why is this strange?  There's no name conflict then.

> So, it's not like nested scopes aren't supported in the class block. Rather,
> when it sees "x = x", it seems like Python is determining at that point that
> "x" is a class variable, and refuses to search any further.

That's like saying that nested scopes aren't supported in a function...
when Python sees "x = x", etc etc.

> At the top-level, it works as expected:
> 
>   >>> x = 5
>   >>> class C(object):
>   ...   x = x
>   ...
>   >>> C.x
>   5
> 
> Any implementation gurus have some insight into what's going on here?

Class bodies and function bodies are compiled slightly differently,
leading to a "handy specialcasing" of globals in the latter example
which is probably what's confusing you.  OK, let's try digging into more
detail:

>>> def f(x):
...   class C:
...     x = x
...   return C
... 
>>> dis.dis(f)
  2           0 LOAD_CONST               1 ('C')
              3 BUILD_TUPLE              0
              6 LOAD_CONST               2 (<code object C at 0x389860,
file "<stdin>", line 2>)
              9 MAKE_FUNCTION            0
             12 CALL_FUNCTION            0
             15 BUILD_CLASS         
             16 STORE_FAST               1 (C)

  4          19 LOAD_FAST                1 (C)
             22 RETURN_VALUE        

this shows you where the codeobject for C's body is kept -- constant
number two among f's constants.  OK then:

>>> dis.dis(f.func_code.co_consts[2])
  2           0 LOAD_GLOBAL              0 (__name__)
              3 STORE_NAME               1 (__module__)

  3           6 LOAD_NAME                2 (x)
              9 STORE_NAME               2 (x)
             12 LOAD_LOCALS         
             13 RETURN_VALUE        

Compare with:

>>> def f(x):
...   def g():
...     x = x
...     return x
...   return g
... 
>>> dis.dis(f)
  2           0 LOAD_CONST               1 (<code object g at 0x389620,
file "<stdin>", line 2>)
              3 MAKE_FUNCTION            0
              6 STORE_FAST               1 (g)

  5           9 LOAD_FAST                1 (g)
             12 RETURN_VALUE        
>>> 

and:

>>> dis.dis(f.func_code.co_consts[1])
  3           0 LOAD_FAST                0 (x)
              3 STORE_FAST               0 (x)

  4           6 LOAD_FAST                0 (x)
              9 RETURN_VALUE        


See the difference?  In a function, the 'x = x' compiles into LOAD_FAST,
STORE_FAST, which only looks at locals and nowhere else.  In a
classbody, it compiles to LOAD_NAME, STORE_NAME, which looks at locals
AND globals -- but still not at closure cells...


Alex



More information about the Python-list mailing list