5

I've got a following code snippet:

def isolation_level(level):
    def decorator(fn):
        def recur(level, *args, **kwargs):
            if connection.inside_block:
                if connection.isolation_level < level:
                    raise IsolationLevelError(connection)
                else:
                    fn(*args, **kwargs)
            else:
                connection.enter_block()
                try:
                    connection.set_isolation_level(level)
                    fn(*args, **kwargs)
                    connection.commit()
                except IsolationLevelError, e:
                    connection.rollback()
                    recur(e.level, *args, **kwargs)
                finally:
                    connection.leave_block()
        def newfn(*args, **kwargs):
            if level is None: # <<<< ERROR MESSAGE HERE, Unbound local variable `level`
                if len(args):
                    if hasattr(args[0], 'isolation_level'):
                        level = args[0].isolation_level
                elif kwargs.has_key('self'):
                    if hasattr(kwargs['self'], 'isolation_level'):
                        level = kwargs.pop('self', 1) 
            if connection.is_dirty():
                connection.commit()
            recur(level, *args, **kwargs)
        return newfn
    return decorator

It really doesn't matter what it does, however I post it in its original form, as I was unable to recreate the situation with anything simpler.

The problem is that when I call isolation_level(1)(some_func)(some, args, here) I get Unbound local variable exception in line 21 (marked on the listing). I don't understand why. I tried recreating the same structure of functions and function calls that wouldn't contain all the implementation details to figure out what is wrong. However I don't get the exception message then. For example the following works:

def outer(x=None):
    def outer2(y):
        def inner(x, *args, **kwargs):
            print x
            print y
            print args
            print kwargs
        def inner2(*args, **kwargs):
            if x is None:
                print "I'm confused"
            inner(x, *args, **kwargs)
        return inner2
    return outer2

outer(1)(2)(3, z=4)

Prints:

1
2
(3,)
{'z': 4}

What am I missing??

EDIT

Ok, so the problem is that in the first version I actually perform an assignment to the variable. Python detects that and therefore assumes the variable is local.

julx
  • 8,694
  • 6
  • 47
  • 86
  • 3
    The error message can be reproduced in a few lines, and it's easy if one knows what causes it. See http://codepad.org/nI0vCx4L –  Apr 29 '11 at 22:36
  • @delnan: the shortest code that causes the error is using `x = x` inside the inner function :) – Sven Marnach Apr 29 '11 at 22:49
  • @Sven: Initially, it was this, but I opted to make it look more like OP's code. –  Apr 29 '11 at 23:02

1 Answers1

9

Local variables are determined at compile time: the assignments to the variable level a few lines below the line the error occurs in make that variable local to the inner function. So the line

if level is None:

actually tries to access a variable level in the innermost scope, but such a variable does not exist yet. In Python 3.x, you can solve this problem by declaring

nonlocal level

at the beginning of the inner function, if you actually want to alter the variable of the outer function. Otherwise, you can simply use a different variable name in the inner function.

Sven Marnach
  • 574,206
  • 118
  • 941
  • 841
  • Look, I do the same thing in the listing below that I posted. And it doesn't crash. I don't understand what is the difference. – julx Apr 29 '11 at 22:38
  • @julkiewicz: No, you don't do the same thing. There are no *assignments* at all in your second snippet. (An assignment is a line like `a = 2`. This line anywhere inside a function would make `a` a local variable anywhere inside this function.) – Sven Marnach Apr 29 '11 at 22:41
  • Oh, ok, so it detects the assignment. Stupid me. Thanks. I'll accept and add another edit. Thanks. – julx Apr 29 '11 at 22:42