3
def Test(value):
    def innerFunc():
        print value
    innerFunc()

def TestWithAssignment(value):
    def innerFunc():
        print value
        value = "Changed value"
    innerFunc()

Test("Hello 1")
# Prints "Hello 1"

TestWithAssignment("Hello 2")
# Throws UnboundLocalError: local variable 'value' referenced before assignment
# on the "print value" line

Why does the second one fail, given that the only difference is an assignment which comes after the print statement? I am pretty confused about this.

combatdave
  • 765
  • 7
  • 17

2 Answers2

9

The issue is that Python wants you to be explicit and you want to be implicit. The execution model that Python uses binds names to the nearest available enclosing scope.

def Test(value):
    # Local Scope #1
    def innerFunc():
        # Local Scope #2
        print value 
        # No immediate local in Local Scope #2 - check up the chain
        # First find value in outer function scope (Local Scope #1).
        # Use that.
    innerFunc()

def TestWithAssignment(value):
    # Local Scope #1
    def innerFunc():
        # Local Scope #2
        print value 
        # Immediate local variable found in Local Scope #2.
        # No need to check up the chain.
        # However, no value has been assigned to this variable yet.
        # Throw an error.
        value = "Changed value"
    innerFunc()

There is not (as far as I know) a way to walk up the scope in Python 2.x - you have globals() and locals() - but any scopes between the global and the local scope cannot be accessed (if this is not true, I'd love to be corrected).

However, you can pass the local variable value into your inner local scope:

def TestWithAssignment(value):
    def innerFunc(value):
        print value 
        # Immediate local **and assigned now**.
        value = "Changed value"
        # If you need to keep the changed value
        # return value
    innerFunc(value)
    # If you need to keep the changed value use:
    # value = innerFunc(value)

In Python 3 you have the new nonlocal statement that can be used to refer to the containing scope (Thanks @Thomas K).

def TestWithAssignment(value):
    def innerFunc():
        nonlocal value
        print value 
        value = "Changed value"
    innerFunc()
Sean Vieira
  • 155,703
  • 32
  • 311
  • 293
  • Why/how does python know about the immediate local even though it is assigned later on in the function? This seems like pretty bizarre behavior to me - if "value" is in scope in the first function, I would expect it to be in scope in the second. – combatdave Jul 26 '11 at 16:00
  • 1
    @Sean: From Python 3, you can use `nonlocal value` to bind to its name in the outer scope. But I don't know of any way to access outer scopes like globals() and locals(). – Thomas K Jul 26 '11 at 16:09
  • @combatdave - it is scoped in both - the scoping is done first. The key is in the link I provided "A *block* is a piece of Python program text that is executed as a unit. The following are blocks: a module, a **function body**, and a class definition ... If a local variable is defined in a block, its scope includes that block. If the definition occurs in a function block, the scope extends to any blocks contained within the defining one, **unless a contained block introduces a different binding for the name**." – Sean Vieira Jul 26 '11 at 16:09
0

This should fix it:

def Test(value):
    def innerFunc(value):
        print value
    innerFunc(value)
inspectorG4dget
  • 110,290
  • 27
  • 149
  • 241
  • 3
    I don't think he's asking how to fix it, but rather why it's broken. Also, you'd have to update `innerFunc` to take value as a parameter for this to work. – thegrinner Jul 26 '11 at 15:52
  • Yeah, there are a few ways that this could be fixed, but I'd like to know how/why the first one works fine and the second one fails. – combatdave Jul 26 '11 at 15:54