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


Groups > comp.lang.python > #11448 > unrolled thread

surprising interaction between function scope and class namespace

Started byStefan Behnel <stefan_ml@behnel.de>
First post2011-08-15 11:33 +0200
Last post2011-08-16 13:08 +1200
Articles 4 — 4 participants

Back to article view | Back to comp.lang.python


Contents

  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

#11448 — surprising interaction between function scope and class namespace

FromStefan Behnel <stefan_ml@behnel.de>
Date2011-08-15 11:33 +0200
Subjectsurprising interaction between function scope and class namespace
Message-ID<mailman.5.1313400826.27778.python-list@python.org>
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

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?

Stefan

[toc] | [next] | [standalone]


#11451

FromDuncan Booth <duncan.booth@invalid.invalid>
Date2011-08-15 10:18 +0000
Message-ID<Xns9F4271CDF768duncanbooth@127.0.0.1>
In reply to#11448
Stefan Behnel <stefan_ml@behnel.de> wrote:

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

If it was a function rather than a class then in the first case you look up 
the non-local variable as expected and in the second case you get an 
UnboundLocalError.

The only difference with the class definition is that instead of 
UnboundLocalError the lookup falls back to the global scope.

This happens because class definitions use LOAD_NAME/STORE_NAME instead of 
LOAD_FAST/STORE_FAST and LOAD_NAME looks first in local scope and then in 
global. I suspect that 
http://docs.python.org/reference/executionmodel.html#naming-and-binding 
should say something about this but it doesn't. 

-- 
Duncan Booth http://kupuguy.blogspot.com

[toc] | [prev] | [next] | [standalone]


#11452

FromPeter Otten <__peter__@web.de>
Date2011-08-15 12:42 +0200
Message-ID<j2at40$8h6$1@solani.org>
In reply to#11448
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                  

[toc] | [prev] | [next] | [standalone]


#11492

FromGregory Ewing <greg.ewing@canterbury.ac.nz>
Date2011-08-16 13:08 +1200
Message-ID<9atu8nFebsU1@mid.individual.net>
In reply to#11452
Peter Otten wrote:

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

I doubt that it was a conscious decision -- it just falls
out of the way the compiler looks up names in its symbol
table. In case 1, the compiler finds the name 'a' in
the function's local namespace and generates a LOAD_FAST
opcode, because that's what it does for all function-local
names. In case 2, it finds it in the local namespace of
the class and generates LOAD_NAME, because that's what
it does for all class-local names.

The weirdness arises because classes make use of vestiges
of the old two-namespace system, which bypasses lexical
scoping at run time.

-- 
Greg

[toc] | [prev] | [standalone]


Back to top | Article view | comp.lang.python


csiph-web