surprising interaction between function scope and class namespace

S

Stefan Behnel

Hi,

I just stumbled over this:
... A = x
... class X:
... a = A
... return X
...... A = x
... class X:
... A = A
... return X
...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
 
P

Peter Otten

Stefan said:
Hi,

I just stumbled over this:

... A = x
... class X:
... a = A
... return X
...
... A = x
... class X:
... A = A
... return X
...
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 = x
....42

which would fail in a function
.... x = x
....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 = "local"
.... class A:
.... x = x
.... return A
........ x = "local"
.... class A:
.... y = x
.... return A
....'local'

Now let's have a glimpse at the bytecode:
import dis
foo.func_code.co_consts
(None said:
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 said:
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
 
G

Gregory Ewing

Peter said:
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.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,570
Members
45,045
Latest member
DRCM

Latest Threads

Top