Ganesh P
Ganesh P

Reputation: 1085

Unresolved reference in a python local function

Usually in a local function I should be able to access any variable declared in enclosing scope. But I'm getting Unresolved reference error. Here is the code snippet.

import time
def make_timer():
    last_called = None
    def elapsed():
        now = time.time()
        print(last_called)
        # nonlocal last_called
        if last_called is None:
            last_called = now
        return None
        elapsed_time = now - last_called
        last_called = now
        return elapsed_time
    return elapsed

Accessing last_called gives unresolved reference error.

Upvotes: 2

Views: 2902

Answers (2)

pfranjic
pfranjic

Reputation: 36

The error arises because of the assignments to last_called inside the elapsed function. A variable that isn't declared as global or nonlocal and gets assigned a value inside the function is a local variable. At the point you're accessing the variable it hasn't been assigned a value yet which causes the error.

import time
def make_timer():
    last_called = None
    def elapsed():
        now = time.time()
        print(last_called)
        if last_called is None:
            last_called = now # <- 
            return None
        elapsed_time = now - last_called
        last_called = now # <- 
        return elapsed_time
    return elapsed

More info can be found here: Why can functions in Python print variables in enclosing scope but cannot use them in assignment?

Upvotes: 1

michael_heath
michael_heath

Reputation: 5372

last_called = None

The object named last_called is immutable. To change the content of the object will release the object and assign a new object.

print(last_called)

Looks like debugging code. last_called is seen as the immutable object which has not been declared as a nonlocal name yet.

# nonlocal last_called

Even if you uncommented this line, it is after the access of lasted_call by the use of the previous print. This is too late to change the scope.

last_called = now

This is assignment. The print(last_called) references None in a different scope and the object is immutable and now attempt to assign a value to it.

The elapsed function wants to access the immutable object named last_called. The interpreter progresses to the assignment and cannot continue. Exception is raised.

Possible options for the behavior of last_called within elapsed:

  1. If nonlocal last_called is used, then declare before access and assignment.

  2. if nonlocal last_called is not declared, then access before assignment causes an exception.

  3. if nonlocal last_called is not declared, access after assignment uses a local name last_called.

Your code order may work better as:

import time
def make_timer():
    last_called = None
    def elapsed():
        now = time.time()
        nonlocal last_called             # declare scope
        print(last_called)               # access
        if last_called is None:          # access
            last_called = now            # assign
        return None
        elapsed_time = now - last_called # access
        last_called = now                # assign
        return elapsed_time
    return elapsed

nonlocal last_called is uncommented as it is required for option 1.

Upvotes: 3

Related Questions