surprising interaction between function scope and class namespace

Discussion in 'Python' started by Stefan Behnel, Aug 15, 2011.

  1. 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
     
    Stefan Behnel, Aug 15, 2011
    #1
    1. Advertising

  2. Stefan Behnel

    Peter Otten Guest

    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
     
    Peter Otten, Aug 15, 2011
    #2
    1. Advertising

  3. 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
     
    Gregory Ewing, Aug 16, 2011
    #3
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Anonymous
    Replies:
    3
    Views:
    534
    Ron Natalie
    Aug 18, 2003
  2. Steven T. Hatton
    Replies:
    9
    Views:
    485
  3. Eric Hanchrow
    Replies:
    1
    Views:
    368
    Arnaud Delobelle
    May 9, 2008
  4. J. Cliff Dyer
    Replies:
    2
    Views:
    331
    Chuckk Hubbard
    May 8, 2008
  5. Skip Montanaro
    Replies:
    0
    Views:
    155
    Skip Montanaro
    May 30, 2013
Loading...

Share This Page