[Python-Dev] Unbound locals in class scopes

Guido van Rossum guido at python.org
Mon Jun 22 09:13:58 CEST 2015


On Mon, Jun 22, 2015 at 3:03 AM, Nick Coghlan <ncoghlan at gmail.com> wrote:

> On 22 June 2015 at 08:46, Ivan Levkivskyi <levkivskyi at gmail.com> wrote:
> >
> >
> > On 21 June 2015 at 22:05, Guido van Rossum <guido at python.org> wrote:
> >>
> >> On Sun, Jun 21, 2015 at 6:22 PM, Ivan Levkivskyi <levkivskyi at gmail.com>
> >> wrote:
> >>>
> >>> It is still not clear whether Guido's comment still stands for not
> >>> raising an UnboundLocalError in class definitions but using globals
> instead.
> >>
> >>
> >> Can you phrase this in the form of an example, showing what it currently
> >> does and what you think it should do, instead?
> >>
> >
> > Here is an example:
> >
> > x = "xtop"
> > y = "ytop"
> > def func():
> >     x = "xlocal"
> >     y = "ylocal"
> >     class C:
> >         print(x)
> >         print(y)
> >         y = 1
> > func()
> >
> > prints
> >
> > xlocal
> > ytop
> >
> > Intuitively, one might think that it should raise UnboundLocalError or
> print
> > ylocal instead of ytop.
> > This question was discussed 13 years ago and then you said that this
> lookup
> > in globals
> > is an intentional behavior.
> >
> > This behavior is not documented, and I started an issue on bug tracker
> > about documenting it.
> > Then, Eric proposed to ask you again, whether this is still an
> intentional
> > behavior.
>
> Diving into some bytecode details:
>

In particular, the bytecode for C is:

>>> dis.dis(func.__code__.co_consts[3])
  6           0 LOAD_NAME                0 (__name__)
              3 STORE_NAME               1 (__module__)
              6 LOAD_CONST               0 ('func.<locals>.C')
              9 STORE_NAME               2 (__qualname__)

  7          12 LOAD_NAME                3 (print)
             15 LOAD_CLASSDEREF          0 (x)
             18 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             21 POP_TOP

  8          22 LOAD_NAME                3 (print)
             25 LOAD_NAME                4 (y)
             28 CALL_FUNCTION            1 (1 positional, 0 keyword pair)
             31 POP_TOP

  9          32 LOAD_CONST               1 (1)
             35 STORE_NAME               4 (y)
             38 LOAD_CONST               2 (None)
             41 RETURN_VALUE
>>>


> We added LOAD_CLASSDEREF
> (https://docs.python.org/3/library/dis.html#opcode-LOAD_CLASSDEREF) a
> while back to account for the fact that __prepare__ may inject locals
> into a class body that the compiler doesn't know about. Unlike
> LOAD_DEREF, LOAD_CLASSDEREF checks the locals before it checks the
> closure cell.
>
> However, neither LOAD_CLASSDEREF *nor* LOAD_DEREF is used for names
> that are *assigned* in the class body - those get flagged as local
> variables, so we end up emitting LOAD_NAME for them, and hence ignore
> any nonlocal variables with that name. If a module global or builtin
> exists, we'll find that, otherwise we'll throw NameError.
>
> With nested_scopes having been the default since 2.2, and accounting
> for the fact that folks *do* write code like "name = name" at class
> scope and expect it to "do the right thing", it seems reasonable to me
> to just make this work properly, rather than having to explain why it
> doesn't work as one might expected based on the behaviour of module
> level class definitions and other references to nonlocal variables
> from a class body.
>

But what *is* the correct behavior? I suspect people's intuitions differ.
If you think of this as similar to function scopes you're likely to be
wrong.

Also, since classes inside functions are commonly used in unit tests (at
least mine :-), I worry that *any* changes in this behavior might break
working code (no matter how much that "working" could be considered an
accident, it's still going to be a bear to debug if this happens to you).

-- 
--Guido van Rossum (python.org/~guido)
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.python.org/pipermail/python-dev/attachments/20150622/297ed937/attachment.html>


More information about the Python-Dev mailing list