Python scoping: understanding LEGB

Paul Craciunoiu

2

Note: this is a repost from my blog. You can find the original post here.
Update: Thanks to Avazu for a clean suggestion and Fred for pointing out I should have indicated a good way to get around this.

Summary

Python scoping fun! Read about LEGB to understand the basics of python scoping.

Beef

I never bothered to read about how python scoping works until I hit this. It’s not exactly something to research until you have issues with it. :)

I had something like this going on:

def func1(param=None):
    def func2():
        if not param:
            param = 'default'
        print param
    # Just return func2.
    return func2


if __name__ == '__main__':
   func1('test')()


Note: Actual code was not as straightforward, func2 was actually a decorator. Admittedly, using the same parameter name is not a must, but it’s still a curiosity. I just wanted to fall back to a default value on run-time.

If you try to run this in python, here’s what you get:

~ $ python test.py 
Traceback (most recent call last):
  File "test.py", line 11, in 
    func1('test')()
  File "test.py", line 3, in func2
    if not param:
UnboundLocalError: local variable 'param' referenced before assignment

If you’re curious, you can read about the principles of LEGB. You have to understand a bit about compilers and the AST to get what’s going on behind the scenes. You might think that replacing lines 3-4 with:

param = param or 'default'

Might work. But no. You can’t assign the same parameter at the local level if the enclosing level defines it. Even this fails:

param = param

Fun, no?

What to do?

There are a few ways to get around this.

  • Assign param outside of func2. This doesn’t work if you need the default value to be dependent on what params func2 receives.
  • Use a second variable, param2 inside of func2 (posted below).

Here is the solution suggested by our commenter Avazu:

def func1(param=None):
    def func2(param2=param):
        if not param2:
            param2 = 'default'
        print param2
    # Just return func2.
    return func2

Read more


2 responses

  1. Fred Wenzel wrote on ::

    You don’t reach a conclusion here ;) I think I do understand why this is happening, but you might want to add a sentence or two next time resolving the issue in plain English — that’ll help people who come here when googling for this problem.

  2. Avazu wrote on ::

    Seems like param is bound to func1 and local, global param raises a SyntaxError. Even func2 is a local function of func1 it seems it doesn´t work like in OOP with inheritance, I would say

    def func1(param=None):
    def func2(param2=param):
    if not param2:
    param2 = ‘default’
    print param2
    # Just return func2.
    return func2

    would be a quick fix here :-)