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.

Author steven.daprano
Recipients eryksun, qpeter, steven.daprano
Date 2021-12-23.13:17:48
SpamBayes Score -1.0
Marked as misclassified Yes
Message-id <20211223131418.GR6272@ando.pearwood.info>
In-reply-to <1640238453.32.0.417281119585.issue46153@roundup.psfhosted.org>
Content
On Thu, Dec 23, 2021 at 05:47:33AM +0000, Eryk Sun wrote:
> 
> Eryk Sun <eryksun@gmail.com> added the comment:
> 
> > That's taken straight out of the documentation.
> 
> Yes, but it's still a misleading comparison.

I asked how you would re-word the docs, but you haven't responded.

The description given in the docs exactly explains the observed 
behaviour. Without recognising that, the observed behaviour is 
perplexing to the point that it suggested to at least one person that it 
was a bug in the language.

If you're not prepared to suggest an improvement to the documentation, 
then I don't think that this conversation is going anywhere and maybe 
we should just let the discussion die.

But for the record, in case you, or anyone else, does want to continue 
the discussion in the hope of reaching additional insight to the 
problem, my further comments are below.

[...]
> Saying that code will be "executed as if it were embedded in a class 
> definition" is correct only so far as the fact that globals and locals 
> are different in this case. 

So it's correct in all the ways that matter:

- different globals and locals;
- and the behaviour is different.

and incorrect in no ways at all (see below). I don't think that supports 
a charge of "misleading".

The bottom line here is that the description in the docs that you call 
"misleading" did not mislead me, but lead me directly to the solution of 
why the code behaved as it did, and why that was the intentional 
behaviour rather than a bug.

So un-misleading, if you will.

> But it's also misleading because the code 
> gets compiled as module-level code, not as class code.

Obviously there is no actual "class code" involved. That is why the 
description says that it is executed *as if* it were embedded inside a 
class statement, rather than by having an actual class statement added 
to your source string.

I don't understand your comment about "compiled as module-level ... not 
as class code". What's class code? (Aside from the actual class 
statement itself, which is a red herring.)

If you look at the disassembly of the following two snippets:

    dis.dis("""
    a = 1
    def f():
        return a
    print(f())
    """)

and 

    dis.dis("""
    class C:
        a = 1
        def f():
            return a
        print(f())
    """)

the generated bytecode for the lines `a = 1` etc is the same, putting 
aside the code for the actual class statement part. You get the same 
code for `a = 1`

    LOAD_CONST                (1)
    STORE_NAME                (a)

the same code for both the body of the function:

    LOAD_GLOBAL               (a)
    RETURN_VALUE

and the `def f()` statement:

    LOAD_CONST                (<code object ...>)
    LOAD_CONST                ('f')
    MAKE_FUNCTION
    STORE_NAME

and the same code for the call to print:

     LOAD_NAME                (print)
     LOAD_NAME                (f)
     CALL_FUNCTION
     CALL_FUNCTION
     POP_TOP
     LOAD_CONST               (None)
     RETURN_VALUE

Obviously the offsets and locations of constants will be different, but 
aside from those incidental details, the code generated for the block is 
the same whether it is inside a class statement or not.

So I don't understand what you consider to be the difference between 
code compiled at module-level and code compiled at class-level. They 
seem to me to be identical (aside from the incidentals).

The visible difference in behaviour relates to the *execution* of the 
code, not to whether (quote):

"the code gets compiled as module-level code [or] as class code".

There is no distinct "class code". The difference in behaviour is in the 
execution, not to the compilation.

> It should be pretty obvious why the following fails:
> 
>     exec("a = 1\ndef f(): return a\nprint(f())", {}, {})

Yes, it is obvious why it fails, in the same sense as the maths joke 
about the professor who stares at the equations on the blackboard for 
twenty minutes before exclaiming "Yes, it is obvious!".

It takes a sophisticated understanding of Python's scoping rules to 
understand why that fails when the other cases succeed.

> Assignment is local by default, unless otherwise declared. Function 
> f() has no access to the local scope where `a` is defined

With the same dict used for globals and locals, execution runs the 
statements `a = 1`, the `def f` and the print in the same scope, which 
is *both* global and local. This is what happens when you run code at 
the module level: locals is globals.

Consequently, the statement `a = 1` assigns a to the local namespace, 
which is the global namespace. And the call to f() retrieves a from the 
global namespace, which is the local namespace.

This is what happens when you execute the code at module-level.

With different dicts, the three statements still run in the same scope, 
the local scope, but the call to f() attempts to retrieve a from the 
global namespace, which is distinct from local namespace.

This is what happens when you execute code inside a class body, just as 
the docs suggest.

> > because a class definition intentionally supports nonlocal closures, 
> >
> >I don't know what you mean by that. Classes are never closures. Only 
> >functions can be closures.
> 
> I didn't say that a class can be a closure. That's never the case 
> because a class uses non-optimized locals. But a class definition does 
> support free variables that are bound to an enclosing scope.

Right -- but that's not the same as a closure.

A class with free variables bound to an enclosing scope is not a 
closure, nor is it a class with a closure. I don't think we have 
terminology for it, other than the mouthful "a class with free variables 
bound to an enclosing scope", or perhaps "a class with nonlocal 
variables".

In any case, whatever we want to call it, it has nothing to do with this 
bug report. Its a distraction.

> To implement this different behavior, the code object for a class 
> definition uses bytecode operations such as COPY_FREE_VARS and 
> LOAD_CLASSDEREF, which are never used for module-level code. For 
> example, from the original example, here's the class definition code:

None of this is relevant to the original examples in this bug report, 
which does not involve a class statement, let alone a class statement 
involving nonlocals.

You seem to be arguing that a description in the docs is "misleading", 
not because it misleads, but because it don't describe a situation which 
has nothing to do with the situation that the docs are describing.

Anyway, if anyone is still reading this far, I think that the 
documentation is correct, but if anyone wants to suggest an improvement 
which doesn't over-complicate the description by involving scenarios 
which are irrelevant to exec(), please do so.
History
Date User Action Args
2021-12-23 13:17:48steven.dapranosetrecipients: + steven.daprano, eryksun, qpeter
2021-12-23 13:17:48steven.dapranolinkissue46153 messages
2021-12-23 13:17:48steven.dapranocreate