This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

Title: Missing nested scope vars in class scope (bis)
Type: enhancement Stage: resolved
Components: Interpreter Core Versions: Python 3.5
Status: closed Resolution: duplicate
Dependencies: Superseder: erroneous behavior when creating classes inside a closure
View: 9226
Assigned To: Nosy List: arigo, ncoghlan, serhiy.storchaka, taleinat, terry.reedy
Priority: normal Keywords:

Created on 2013-12-13 21:31 by arigo, last changed 2022-04-11 14:57 by admin. This issue is now closed.

Messages (8)
msg206149 - (view) Author: Armin Rigo (arigo) * (Python committer) Date: 2013-12-13 21:31
This is a repeat of the old issue 532860: "NameError assigning to class in a func".  It is about class statements' variable lookups, which has different behavior at module level or in a nested scope:

def f(n):
    class A:
        n = n     # doesn't work, tries to look up 'n' as a global

The point of repeating this very old issue is the much more recent issue 17853: "Conflict between lexical scoping and name injection in __prepare__".  This was a slightly different problem, but resolved by adding the exact opcode that would be needed to fix the old issue too: LOAD_CLASSDEREF.  It's an opcode which looks in the locals dict for a name, and if not found, falls back to freevars instead of to globals.

The present bug report is here to argue that this new opcode should be used a bit more systematically by the compiler.  Contrary to the conclusions reached in the very old bug report, I argue that nowadays it would seem reasonable to expect that the previous example should work.  By no means is it an essential issue in my opinion, but this would probably be a slight simplification and prevent corner-case surprizes.  Moreover it probably leads to a simplification in the compiler: no need to track which variables are local or not in class bodies --- instead just compile the loading of 'n' above as a LOAD_CLASSDEREF without worrying about the fact that the same 'n' is also assigned to in the same scope.
msg206875 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2013-12-23 21:34
As near as I can tell, "class A: n = n" currently works the same at module and nested scope, the latter with or without nonlocal n.

>>> class A: n=n
NameError: name 'n' is not defined

>>> def f():
	class A: n=n
>>> f()
NameError: name 'n' is not defined

>>> def f(n):
	class A: n=n
>>> f(2)
NameError: name 'n' is not defined

Repeat after 'n=1' at module scope and the NameErrors disappear. It appears that you are asking that the class statement be made to act differently when nested instead of the same. This would break code that depends on the current behavior. This would need discussion on python-ideas and pydev lists.
msg218778 - (view) Author: Terry J. Reedy (terry.reedy) * (Python committer) Date: 2014-05-19 02:58
#17853 was in the context of metaclasses. Even so, I am puzzled by the opening statement there that

from enum import Enum  # I added this as necessary
class Season(Enum):
    SPRING = Season()

"works beautifully" at top level as it indeed raises
NameError: name 'Season' is not defined

My point here is that changing the output of

def f(n=2):
    class A: n=n
    return A

from 1, as it has been from the beginning (except for the print change) to 2, which I believe is implied in the request, is not the sort of syntax change we do. Hence I think this should be closed.
msg218779 - (view) Author: Ethan Furman (ethan.furman) * (Python committer) Date: 2014-05-19 03:08
Terry remarked:
> I am puzzled by the opening statement there that
>   from enum import Enum  # I added this as necessary
>   class Season(Enum):
>       SPRING = Season()
> "works beautifully" at top level as it indeed raises
> NameError: name 'Season' is not defined

Pay close attention to the line just before that example:

> I tried having the metaclass insert an object into the custom dict
> (aka namespace) returned by __prepare__; this object has the same
> name as the to-be-created class.

It does not raise a NameError because the name was injected via the metaclass __prepare__ method.  (Or did at that time -- I don't think that bit of cleverness made the final cut.)

As far as this enhancement request goes, I think the compatibility break is likely too large to have it in the 3.x series.
msg218783 - (view) Author: Armin Rigo (arigo) * (Python committer) Date: 2014-05-19 07:55
Terry: I meant exactly what I wrote, and not some unrelated examples:

    def f():
        n = 1
        class A: n = n

doesn't work, but the same two lines ("n = 1"; "class A: n = n") work if written at module level instead of in a function.
msg245558 - (view) Author: Nick Coghlan (ncoghlan) * (Python committer) Date: 2015-06-20 11:01
With the fact that the existence of "Python without closures" predates Python 2.2, this now reads like a straight up compiler bug to me.

Compare the behaviour with no local assignment in the class body:

>>> def f():
...     n = 1
...     class C:
...         print(n)
>>> f()

To the behaviour once the local is assigned:

>>> def f():
...     n = 1
...     class C:
...         n = n
...         print(n)
>>> f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f
  File "<stdin>", line 4, in C
NameError: name 'n' is not defined

The issue is that the latter will still emit a LOAD_NAME/STORE_NAME pair, while the former emits LOAD_CLASSDEREF (and has emitted LOAD_DEREF for as long as I can recall hacking on the compiler).

Off the top of my head, I'm not sure our current symbol table analysis pass can actually cope with this idea though - it would require separating "just a class body local, which may or may not first be retrieved as a global or builtin" from "a class body local which is first retrieved from a closure reference".
msg314262 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2018-03-22 14:40
This looks like a duplicate of issue9226.
msg322382 - (view) Author: Tal Einat (taleinat) * (Python committer) Date: 2018-07-25 18:38
This does indeed seem to be a duplicate of issue9226.
Date User Action Args
2022-04-11 14:57:55adminsetgithub: 64178
2018-07-25 18:38:41taleinatsetstatus: pending -> closed

superseder: erroneous behavior when creating classes inside a closure

nosy: + taleinat
messages: + msg322382
resolution: duplicate
stage: test needed -> resolved
2018-03-22 14:40:18serhiy.storchakasetstatus: open -> pending
nosy: + serhiy.storchaka
messages: + msg314262

2015-07-21 07:08:56ethan.furmansetnosy: - ethan.furman
2015-06-20 11:01:47ncoghlansetnosy: + ncoghlan
messages: + msg245558
2014-05-19 07:55:59arigosetmessages: + msg218783
2014-05-19 03:08:20ethan.furmansetmessages: + msg218779
2014-05-19 02:58:18terry.reedysetmessages: + msg218778
2014-05-19 01:28:40ethan.furmansetnosy: + ethan.furman
2013-12-23 21:34:39terry.reedysetversions: + Python 3.5
nosy: + terry.reedy

messages: + msg206875

type: enhancement
stage: test needed
2013-12-13 21:31:17arigocreate