Ask for help about class variable scope (Re: Why doesn't a dictionary work in classes?)

Chris Angelico rosuav at gmail.com
Thu Dec 27 17:21:15 EST 2018


On Fri, Dec 28, 2018 at 8:47 AM eryk sun <eryksun at gmail.com> wrote:
>
> On 12/27/18, Chris Angelico <rosuav at gmail.com> wrote:
> >
> > Class scope is special, and a generator expression within that class
> > scope is special too. There have been proposals to make these kinds of
> > things less special, but the most important thing to remember is that
> > when you create a generator expression, it is actually a function.
> > Remember that a function inside a class statement becomes a method,
> > and that inside the method, you have to use "self.X" rather than just
> > "X" to reference class attributes. That's what's happening here.
>
> A generator expression is implemented internally as a generator
> function that takes an iterator as its only parameter (named ".0") and
> gets called immediately to get a generator object. There's some inline
> bytecode in the defining scope that sets this up.
>
> A generator object has iterator methods (__iter__, __next__) and
> close, send, and throw methods. Its code and execution state in
> CPython uses a code object and a frame object:

A generator expression is an expression that creates and then calls a
function to create a generator object. This is exactly the same as any
other generator function being called to create a generator object.
Yes, the generator function and the generator object are very
different beasts, and yes, they're both called "a generator", and yes,
this is confusing. But it's the same whether it's a genexp or an
explicit function.

> Unlike a function object, a generator object is not a descriptor (i.e.
> it has no __get__ method) that could in principle be bound as either a
> class or instance method. Anyway, since the class doesn't exist yet,
> trying to bind and call a method at this point can't work.

This is correct. A genexp created at class scope would be
approximately equivalent to a generator function called as "Foo.f()"
not as "Foo().f()" - called on the class, not an instance of it.

> This is straying off topic, but note that a consequence of late
> binding is that super() can be broken by rebinding __class__:
>
>     class C:
>         def f(self):
>             nonlocal __class__
>             __class__ = None
>             super()
>
>     >>> C().f()
>     Traceback (most recent call last):
>       File "<stdin>", line 1, in <module>
>       File "<stdin>", line 5, in f
>     RuntimeError: super(): __class__ is not a type (NoneType)
>
> It's not a bug or design flaw; just an example of code shooting itself
> in the foot by stepping on an informally reserved dunder name.

Technically only the no-arg form of super() can be broken that way,
because it's actually a bit of magic syntax that becomes
"super(__class__, self)", except that it doesn't actually use "self"
but "whatever the first argument to this function is". You can break
it in an even simpler way:

>>> class C:
...     def f():
...         super()
...
>>> C.f()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 3, in f
RuntimeError: super(): no arguments

"But Mum, I thought you said you wanted NO ARGUMENTS while you were out...."

ChrisA


More information about the Python-list mailing list