Question

I've inherited a legacy unit test that attempts to override the name datetime for a custom class nested inside of another class. I'm refactoring it now (I agree this is pretty awful) but I do not understand a particular error I am seeing.

I am seeing an UnboundLocalError and I can reproduce this with a self-contained example:

import datetime
class Foo(object):

    def inner_scope(self):

        real_datetime = datetime
        class datetime(datetime.datetime):
            @staticmethod
            def convert():
                return "blah"

        x = datetime(2012, 1, 1)
        y = x.convert()
        return x, y

f = Foo()
f.inner_scope()

I get this:

In [439]: f.inner_scope()
---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
<ipython-input-439-c5053fb49b64> in <module>()
----> 1 f.inner_scope()

<ipython-input-437-222b3997ec2c> in inner_scope(self)
      3     def inner_scope(self):
      4
----> 5         real_datetime = datetime
      6         class datetime(datetime.datetime):
      7             @staticmethod

UnboundLocalError: local variable 'datetime' referenced before assignment

I've tried adding print statements to print type(datetime) and type(datetime.datetime) immediately inside of the class definition (this works) and immediately inside of the inner_scope definition (this fails).

By virtue of closures, shouldn't the value for datetime be supplied by the module that is imported, as it is checked for at higher and higher levels of scope from the inner_scope function?

I'm not trying to modify it either, as far as I understand, because I've seen other UnboundLocalError questions relating to attempts to modify variables that can be accessed via closures but not changed unless first made as local variables.

Was it helpful?

Solution

This case is no different from the others you've seen related to UnboundLocalError. Since your inner-class is also called datetime Python's bytecode compiler will mark the name datetime as a local variable. Since you try to assign real_datetime = datetime before the class declaration you get the error.

This might be clearer if you consider that

class datetime(datetime.datetime):
    ...

is an equivalent statement to:

datetime = type('datetime', (datetime.datetime), {...class members...})

As a workaround to this sort of thing, you could always use a different name from datetime for the mock class. It probably doesn't matter what its name is (and if it really does matter you could still override that by assigning to its __name__ attribute after the class is created).

In Python 3 you would just declare nonlocal datetime like you would any other non-local variable.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top