Python locals() for containing scope
Asked Answered
M

3

12

TL;DR: I want a locals() that looks in a containing scope.

Hi, all.

I'm teaching a course on Python programming to some chemist friends, and I want to be sure I really understand scope.

Consider:

def a():
    x = 1
    def b():
        print(locals())
        print(globals())
    b()

Locals prints an empty environment, and globals prints the usual globals. How do I get access to the environment where x is stored? Clearly the interpreter knows about it because I can refer to it.

Related: When does scoping happen? The following nameErrors on a = x+2 only if x=3 is included:

def a():
    x = 1
    def b():
        a = x+2
        x = 3
    b()

If you comment out x=3, the code works. Does this mean that python makes a lexical-scope pass over the code before it interprets it?

Midstream answered 25/2, 2013 at 22:21 Comment(2)
do you need a list? because you could just do print x after print globals. EDIT: forget to say if you access x with print x then is also locals() filled with x:1.Noahnoak
possible duplicate of Get locals from calling namespace in PythonLouanneloucks
E
7

What is happening in your code is that when python see's the x=3 line in your b() method, it is recreating x with a scope within the b function instead of using the x with it's scope in the a function.

because your code then goes:

    a = x+2
    x = 3

it is saying that you need to define the inner scoped x before referencing it.

however, when you do not have the assigning of x within the b function python does not attempt to make a lower scoped x and will not throw any errors.

The following code will illustrate this:

def a():
    x = 1
    def b():
        x = 3
        print (x)

    def c():
        print (x)

    b()
    c()
    print (x)

a()

However if you were to declare x as global you could use it within a function, like so:

def a():
    global x
    x = 1

    def d():
        global x
        x += 2

    print (x)
    d()
    print (x)

a()

Python 3 has also added a nonlocal keyword that will let you access a variable from an enclosing scope, usage is like:

def a():
    x = 1

    def d():
        nonlocal x
        x += 2

    print (x)
    d()
    print (x)

a()

Will print the same results as the global example.


In case I miss-read the question:

As per the answer to Get locals from calling namespace in Python:

you can use:

import inspect

def a():
    x = 1

    def d():
        frame = inspect.currentframe()
        try:
            print (frame.f_back.f_locals)
        finally:
            del frame
    d()

a()

to get the local scope of the functions caller.

Epigynous answered 25/2, 2013 at 22:36 Comment(1)
It's probably worth noting that Python 3 has added a nonlocal keyword that you can use in the inner function to be able to rebind an existing name from an enclosing scope.Devotee
G
1

The statement print(locals()) refers to nearest enclosing scope, that is the def b(): function. When calling b(), you will print the locals to this b function, and definition of x is outside the scope.

def a():
    x = 1
    def b():
        print(locals())
        print(globals())
    b()
    print(locals())

would print x as a local variable.

For your second question:

def a():
    x = 1
    def b():
        #a = x+2
        x = 3
    b()

>>> a()

gives no error.

def a():
    x = 1
    def b():
        a = x+2
        #x = 3
    b()

>>> a()

gives no error.

And

def a():
    x = 1
    def b():
        a = x+2
        x = 3
    b()

>>> a()

gives following error: UnboundLocalError: local variable 'x' referenced before assignment

IMO (please check), in this third case, first statement in the body of b() looks for value of x in the most enclosing scope. And in this scope, x is assigned on the next line. If you only have statement a = x+2, x is not found in most enclosing scope, and is found in the 'next' enclosing scope, with x = 1.

Gamali answered 25/2, 2013 at 22:27 Comment(0)
M
1

python3 -c "help(locals)" says this about the locals function:

locals()
    Return a dictionary containing the current scope's local variables.

That means that calling locals() in b() will only show you which variables are local to b(). That doesn't mean that b() can't see variables outside its scope -- but rather, locals() only returns information for variables local to b().

What you probably want is to make b() show information on the variables of the calling function. To do that, take this code (which show's b()'s local variables):

def a():
    x = 1
    def b():
        print(locals())
    b()

and replace it with this one (which uses the inspect module to get you the local information of the calling frame):

def a():    
    x = 1
    def b():
        import inspect
        print(inspect.currentframe().f_back.f_locals)
    b()

As for your other issue:

def a():
    x = 1
    def b():
        a = x+2
        x = 3
    b()

The error you get is because Python 3 allows you to set/assign global variables and variables local to outside scopes provided you declare them with the global and nonlocal keywords. (I won't explain them here; I'll just say that you won't have any problem looking them up yourself.)

If you remove the x = 3 line (keeping the a = x+2 line), your code will run because x's value is being used, but not set. If instead you remove the a = x+2 line (keeping the x = 3 line), your code will run because it will create a new x inside b()'s scope.

If you keep both lines, Python doesn't know whether x is supposed to refer to an x outside the current scope (as a = x+2 seems to suggest) or if it's supposed to refer to an x local to b()'s scope (as x = 3 suggests).

If you want x to be local to b(), then you shouldn't have that a = x+2 line there, at least not before x is set with x = 3. But if you want x to be a()'s x, then you should declare x as nonlocal to b(), like this:

def a():
    x = 1
    def b():
        nonlocal x  # use a()'s x
        a = x+2
        x = 3
    b()

If you're confused, remember that Python will let you use global variables and variables created outside of your current scope, but only if you don't assign to them. (It's often acceptable to read global variables, but it's frowned upon to set them.)

If you want to set a global variable or a non-local variable, you will have to declare those variables with global (for global variables) or nonlocal (for non-local variables), as shown in the above code.

I hope this helps.

Moccasin answered 28/2, 2020 at 19:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.