Groups | Search | Server Info | Keyboard shortcuts | Login | Register [http] [https] [nntp] [nntps]


Groups > comp.lang.python > #11452

Re: surprising interaction between function scope and class namespace

From Peter Otten <__peter__@web.de>
Newsgroups comp.lang.python
Subject Re: surprising interaction between function scope and class namespace
Followup-To comp.lang.python
Date 2011-08-15 12:42 +0200
Organization None
Message-ID <j2at40$8h6$1@solani.org> (permalink)
References <mailman.5.1313400826.27778.python-list@python.org>

Followups directed to: comp.lang.python

Show all headers | View raw


Stefan Behnel wrote:

> Hi,
> 
> I just stumbled over this:
> 
>    >>> A = 1
>    >>> def foo(x):
>    ...     A = x
>    ...     class X:
>    ...         a = A
>    ...     return X
>    ...
>    >>> foo(2).a
>    2
>    >>> def foo(x):
>    ...     A = x
>    ...     class X:
>    ...         A = A
>    ...     return X
>    ...
>    >>> foo(2).A
>    1

That's subtle.
 
> Works that way in Py2.7 and Py3.3.
> 
> I couldn't find any documentation on this, but my *guess* about the
> reasoning is that the second case contains an assignment to A inside of
> the class namespace, and assignments make a variable local to a scope, in
> this case, the function scope. Therefore, the A on the rhs is looked up in
> that scope as well. However, this is just a totally hand waving guess.

> Does anyone have a better explanation or know of a place where this
> specific behaviour is documented?

I think it's an implementation accident.

Classes have a special opcode, LOAD_NAME, that allows for

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

which would fail in 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

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.

Here's a slightly modified version of your demo:

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

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

Back to comp.lang.python | Previous | NextPrevious in thread | Next in thread | Find similar | Unroll thread


Thread

surprising interaction between function scope and class namespace Stefan Behnel <stefan_ml@behnel.de> - 2011-08-15 11:33 +0200
  Re: surprising interaction between function scope and class namespace Duncan Booth <duncan.booth@invalid.invalid> - 2011-08-15 10:18 +0000
  Re: surprising interaction between function scope and class namespace Peter Otten <__peter__@web.de> - 2011-08-15 12:42 +0200
    Re: surprising interaction between function scope and class namespace Gregory Ewing <greg.ewing@canterbury.ac.nz> - 2011-08-16 13:08 +1200

csiph-web