REPL, global, and local scoping

Chris Angelico rosuav at gmail.com
Tue Mar 19 22:49:21 EDT 2019


On Wed, Mar 20, 2019 at 1:31 PM <adam.preble at gmail.com> wrote:
>
> On Tuesday, March 19, 2019 at 3:48:27 PM UTC-5, Chris Angelico wrote:
> > > I can see in vartest() that it's using a LOAD_GLOBAL for that, yet first() and second() don't go searching upstairs for a meow variable. What is the basis behind this?
> > >
> >
> > Both first() and second() assign to the name "meow", so the name is
> > considered local to each of them. In vartest(), the name isn't
> > assigned, so it looks for an outer scope.
>
> Thanks for the responses. I wanted to poke on this part just a little bit more. I want to mimic the proper behavior without having to go back to it repeatedly.
>
> Let's say I'm parsing this code and generating the byte code for it. I run into meow on the right side of an expression but never the left. At this point, do I always generate a LOAD_GLOBAL opcode? Is that done whether or not I know if the variable is defined in a higher scope? That's what it looks like from renaming it to something I didn't define. I just want to double check.
>
> On the interpreter side seeing the opcode, does that generally mean I walk up the variables I have in higher frames until I either run out of them or find it?
>
> Does that mean defining "global meow" basically states "always use LOAD/STORE_GLOBAL opcodes for this one, even if it shows up on the left side of an assignment first?"
>

I would recommend parsing in two broad steps, as CPython does:

1) Take the source code and turn it into an abstract syntax tree
(AST). This conceptualizes the behaviour of the code more-or-less the
way the programmer wrote it.

2) Implement the AST in byte-code.

The AST for your first() function looks something like this:

FunctionDef(
    name='first',
    args=arguments(args=[], vararg=None, kwonlyargs=[],
kw_defaults=[], kwarg=None, defaults=[]),
    body=[
        Assign(
            targets=[Name(id='x', ctx=Store())],
            value=Constant(value=3, kind=None),
            type_comment=None
        ), Assign(
            targets=[Name(id='meow', ctx=Store())],
            value=Constant(value=11, kind=None),
            type_comment=None
        ), Return(
            value=Name(id='x', ctx=Load())
        )
    ],
    decorator_list=[],
    returns=None,
    type_comment=None
)

(I got this by using ast.parse() and ast.dump() from the CPython 3.8
standard library. If you use a different version or interpreter, it
may look slightly different, but it'll be broadly similar.)

Since there are Assign entries with targets saying Name "x" and Name
"meow", you know that both those names are local to the function. So
you create local-to-function bytecode. But if you had a "global meow"
statement in that function (which translates to an AST node of
Global(names=['meow']) ), you would create STORE_GLOBAL opcodes, and
similarly, any names not assigned in the local scope would be looked
up globally.

Of course, life's never this simple, and there are myriad other
considerations. But hopefully that will help with an understanding of
what Python's doing.

ChrisA



More information about the Python-list mailing list