osdir.com


[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

REPL, global, and local scoping


On Wed, Mar 20, 2019 at 7:23 AM <adam.preble at gmail.com> wrote:
>
> I got hit on the head and decided to try to write something of a Python interpreter for embedding. I'm starting to get into the ugly stuff like scoping. This has been a good way to learn the real deep details of the language. Here's a situation:
>
> >>> meow = 10
> >>> def vartest():
> ...     x = 1
> ...     y = 2
> ...     def first():
> ...        x = 3
> ...        meow = 11
> ...        return x
> ...     def second():
> ...        y = 4
> ...        meow = 12
> ...        return y
> ...     return first() + second() + x + y + meow
> ...
> >>> vartest()
> 20
>
> first() and second() are messing with their own versions of x and y. Side note: the peephole optimizer doesn't just slap a 3 and 4 on the stack, respectively, and just return that. It'll actually do a STORE_NAME to 0 for each. The meow variable is more peculiar. The first() and second() inner functions are working on their own copy. However, vartest() is using the one from the calling scope, which is the REPL.
>
> 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.

(Side note: the peephole optimizer COULD be written to simplify that
case, and there's no reason it can't in the future. Probably wouldn't
save all that much work though.)

> I tripped on this in my unit tests when I was trying to note that a class' constructor had run successfully without getting into poking the class' variables. I was trying to have it set a variable in the parent scope that I'd just query after the test.
>
> It looks like in practice, that should totally not work at all:
> >>> meow = 10
> >>> class Foo:
> ...    def __init__(self):
> ...       meow = 11
> ...
> >>> f = Foo()
> >>> meow
> 10
>
> ...and it's on me to carve out a throwaway inner meow variable. But hey, let's kick meow up to the class level:

You've made "meow" a local variable here. If you want it to be an
attribute of the object, you would need to apply it to the object
itself ("self.meow = 11").

> >>> meow = 10
> >>> class Foo:
> ...    meow = 11
> ...    def __init__(self):
> ...       pass
> ...
> >>> f = Foo()
> >>> meow
> 10

This has created a class attribute. If you inspect "Foo.meow" (or
"f.meow", because of the way attribute lookup works), you'll see the
11.

> So I guess it's a different ballgame for classes entirely. What are the rules in play here for:
> 1. A first-level function knowing to use a variable globally
> 2. A Second-level function using the name locally and uniquely
> 3. Classes having no visibility to upper-level variables at all by default

In all cases, you can use the declaration "global meow" to affect the
global (module-level) name. In the inner-function case, you can also
use "nonlocal x" to affect the "x" in the outer function. This applies
any time you *assign to* a name; if you just reference it, the lookup
will extend outwards in the most expected way.

ChrisA