class name spaces inside an outer function

I filed bug http://bugs.python.org/issue17853 last night. If somebody could point me in the right direction (mainly which files to look in), I'd be happy to attempt a patch. -- ~Ethan~

On Sat, Apr 27, 2013 at 2:27 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Wow. I had no idea Python actually did this (override class-local references with ; I'd have expected your code to work. I was even more surprised to find that the same thing happens all the way back to Python 2.3. Guess I'm not nearly the wizard of abusing scope rules that I thought I was. ;-) About the only workaround I can see is to put "Season = Season" at the top of a class that uses this inside a function definition, or else to define a special name 'enum' instead and hope that nobody ever tries to define an enumeration inside a function with a local variable named 'enum'. ;-)

PJ Eby wrote:
On Sat, Apr 27, 2013 at 2:27 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
I filed bug http://bugs.python.org/issue17853 last night.
About the only workaround I can see is to put "Season = Season" at the top of a class that uses this inside a function definition,
This whole business can be avoided by doing things differently in the first place. Instead of initialising the enum items by calling the class, just assign a tuple of args to the name and have the metaclass make the constructor call. class Planet(Enum): MERCURY = (3.303e+23, 2.4397e6) VENUS = (4.869e+24, 6.0518e6) EARTH = (5.976e+24, 6.37814e6) MARS = (6.421e+23, 3.3972e6) JUPITER = (1.9e+27, 7.1492e7) SATURN = (5.688e+26, 6.0268e7) URANUS = (8.686e+25, 2.5559e7) NEPTUNE = (1.024e+26, 2.4746e7) def __init__(self, mass, radius): self.mass = mass self.radius = radius I think that's better anyway, since it avoids aggravated violation of DRY by repeating the class name umpteen times. -- Greg

On 04/27/2013 07:01 PM, Greg Ewing wrote:
You certainly have a point about DRY, and generally I agree with you, but given the nature of Enums I can see a little extra RY being useful. Regardless of the outcome for Enums, I can see another metaclass doing the same kind of thing and having it work just fine until an unsuspecting soul tries to reuse an inserted name further down the function and suddenly the whole thing blows up on him. Now, I'll grant you it's not like a seg fault, but it would be nice if Python followed its own lookup rules. -- ~Ethan~

On 04/27/2013 09:20 PM, Guido van Rossum wrote:
If I'm saying what you already know I apologize now, but this thread is about what happens when: class InsertsName(type): @classmethod def __prepare__(metacls, cls, bases): classdict = {'new_name': lambda: 'haha!'} return classdict def test(): new_name = 'Jose' # if here will result in str not callable error class SomeClass(metaclass=InsertsName): surprise = new_name() new_name = 'Clara' # if here will result in NameError: free variable... However, if that class definition is either top level, or if the function itself does not define nor use the 'new_name', there is no problem. Enum was being used in the example because that's what I was toying with when I found the problem. -- ~Ethan~

On 04/27/2013 09:20 PM, Guido van Rossum wrote:
To answer your question: Somewhere in the previous threads about enums a couple people had use-cases for an enum with extra attributes. So, while you don't /have/ to enherit from Enum, if Enum provides the basics of what you need, and you can extend it with the extra functionality that you need, why shouldn't you? (Not a rhetorical question -- I'm happy to learn something I don't know.) -- ~Ethan~

On 28 Apr 2013 04:30, "Ethan Furman" <ethan@stoneleaf.us> wrote:
I filed bug http://bugs.python.org/issue17853 last night.
If somebody could point me in the right direction (mainly which files to
look in), I'd be happy to attempt a patch. Hmm, interesting challenge. A key part of the problem is that the 3.x compiler assumes there's no way to inject names it doesn't know about into code inside a function - we missed the fact that you could still do it with a nested class and a metaclass __prepare__ method. I suspect resolving it sensibly will require a new opcode that tries a local-only load and then falls back to loading from a cell rather than from a global/builtins lookup. Cheers, Nick.
http://mail.python.org/mailman/options/python-dev/ncoghlan%40gmail.com

On Sun, Apr 28, 2013 at 11:38 AM, Benjamin Peterson <benjamin@python.org> wrote:
Yeah, what I wrote didn't quite capture what I meant: - in Python 2.x, using LOAD_DEREF when a nested class body references a lexically scoped name is correct - in Python 3.x, it is no longer correct, because __prepare__ may inject additional names that the compiler doesn't know about Previously, the compiler new just as much about the nested class namespaces as it did about the function locals. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia

On Sat, Apr 27, 2013 at 2:27 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
Wow. I had no idea Python actually did this (override class-local references with ; I'd have expected your code to work. I was even more surprised to find that the same thing happens all the way back to Python 2.3. Guess I'm not nearly the wizard of abusing scope rules that I thought I was. ;-) About the only workaround I can see is to put "Season = Season" at the top of a class that uses this inside a function definition, or else to define a special name 'enum' instead and hope that nobody ever tries to define an enumeration inside a function with a local variable named 'enum'. ;-)

PJ Eby wrote:
On Sat, Apr 27, 2013 at 2:27 PM, Ethan Furman <ethan@stoneleaf.us> wrote:
I filed bug http://bugs.python.org/issue17853 last night.
About the only workaround I can see is to put "Season = Season" at the top of a class that uses this inside a function definition,
This whole business can be avoided by doing things differently in the first place. Instead of initialising the enum items by calling the class, just assign a tuple of args to the name and have the metaclass make the constructor call. class Planet(Enum): MERCURY = (3.303e+23, 2.4397e6) VENUS = (4.869e+24, 6.0518e6) EARTH = (5.976e+24, 6.37814e6) MARS = (6.421e+23, 3.3972e6) JUPITER = (1.9e+27, 7.1492e7) SATURN = (5.688e+26, 6.0268e7) URANUS = (8.686e+25, 2.5559e7) NEPTUNE = (1.024e+26, 2.4746e7) def __init__(self, mass, radius): self.mass = mass self.radius = radius I think that's better anyway, since it avoids aggravated violation of DRY by repeating the class name umpteen times. -- Greg

On 04/27/2013 07:01 PM, Greg Ewing wrote:
You certainly have a point about DRY, and generally I agree with you, but given the nature of Enums I can see a little extra RY being useful. Regardless of the outcome for Enums, I can see another metaclass doing the same kind of thing and having it work just fine until an unsuspecting soul tries to reuse an inserted name further down the function and suddenly the whole thing blows up on him. Now, I'll grant you it's not like a seg fault, but it would be nice if Python followed its own lookup rules. -- ~Ethan~

On 04/27/2013 09:20 PM, Guido van Rossum wrote:
If I'm saying what you already know I apologize now, but this thread is about what happens when: class InsertsName(type): @classmethod def __prepare__(metacls, cls, bases): classdict = {'new_name': lambda: 'haha!'} return classdict def test(): new_name = 'Jose' # if here will result in str not callable error class SomeClass(metaclass=InsertsName): surprise = new_name() new_name = 'Clara' # if here will result in NameError: free variable... However, if that class definition is either top level, or if the function itself does not define nor use the 'new_name', there is no problem. Enum was being used in the example because that's what I was toying with when I found the problem. -- ~Ethan~

On 04/27/2013 09:20 PM, Guido van Rossum wrote:
To answer your question: Somewhere in the previous threads about enums a couple people had use-cases for an enum with extra attributes. So, while you don't /have/ to enherit from Enum, if Enum provides the basics of what you need, and you can extend it with the extra functionality that you need, why shouldn't you? (Not a rhetorical question -- I'm happy to learn something I don't know.) -- ~Ethan~

On 28 Apr 2013 04:30, "Ethan Furman" <ethan@stoneleaf.us> wrote:
I filed bug http://bugs.python.org/issue17853 last night.
If somebody could point me in the right direction (mainly which files to
look in), I'd be happy to attempt a patch. Hmm, interesting challenge. A key part of the problem is that the 3.x compiler assumes there's no way to inject names it doesn't know about into code inside a function - we missed the fact that you could still do it with a nested class and a metaclass __prepare__ method. I suspect resolving it sensibly will require a new opcode that tries a local-only load and then falls back to loading from a cell rather than from a global/builtins lookup. Cheers, Nick.
http://mail.python.org/mailman/options/python-dev/ncoghlan%40gmail.com

On Sun, Apr 28, 2013 at 11:38 AM, Benjamin Peterson <benjamin@python.org> wrote:
Yeah, what I wrote didn't quite capture what I meant: - in Python 2.x, using LOAD_DEREF when a nested class body references a lexically scoped name is correct - in Python 3.x, it is no longer correct, because __prepare__ may inject additional names that the compiler doesn't know about Previously, the compiler new just as much about the nested class namespaces as it did about the function locals. Cheers, Nick. -- Nick Coghlan | ncoghlan@gmail.com | Brisbane, Australia
participants (6)
-
Benjamin Peterson
-
Ethan Furman
-
Greg Ewing
-
Guido van Rossum
-
Nick Coghlan
-
PJ Eby