Stefaan
Stefaan

Reputation: 4916

python import inside function hides existing variable

I was wrestling with a weird "UnboundLocalError: local variable referenced before assignment" issue in a multi-submodule project I am working on and slimmed it down to this snippet (using the logging module from standard library):

import logging

def foo():
    logging.info('foo')

def bar():
    logging.info('bar')
    if False:
        import logging.handlers
        # With these alternatives things work:
        # import logging.handlers as logginghandlers
        # from logging.handlers import SocketHandler

logging.basicConfig(level=logging.INFO)
foo()
bar()

Which has this output (I tried python 2.7 and 3.3):

INFO:root:foo
Traceback (most recent call last):
  File "import-test01.py", line 16, in <module>
    bar()
  File "import-test01.py", line 7, in bar
    logging.info('bar')
UnboundLocalError: local variable 'logging' referenced before assignment

Apparently the presence of an import statement inside a function hides already existing variables with the same name in the function scope, even if the import is not executed.

This feels counter-intuitive and non-pythonic. I tried to find some information or documentation about this, without much success thus far. Has someone more information/insights about this behaviour?

thanks

Upvotes: 5

Views: 669

Answers (2)

SingleNegationElimination
SingleNegationElimination

Reputation: 156278

The problem you're having is just a restatement of the same ol' way python deals with locals masking globals.

to understand it, import foo is (approximately) syntactic sugar for:

foo = __import__("foo")

thus, your code is:

x = 1
def bar():
    print x
    if False:
        x = 2

since the name logging appears on the left side of an assignment statement inside bar, it is taken to be a local variable reference, and so python won't look for a global by the same name in that scope, even though the line that sets it can never be called.

The usual workarounds for dealing with globals apply: use another name, as you have found, or:

def bar():
    global logging
    logging.info('bar')
    if False:
        import logging.handlers

so that python won't think logging is local.

Upvotes: 4

yedpodtrzitko
yedpodtrzitko

Reputation: 9359

By that import statement you introduce logging as a local variable in the function and you call that local variable before it's initialized.

def bar():
    import logging.handlers
    print locals()

>>> foo()
{'logging': <module 'logging' from '/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/logging/__init__.pyc'>}

Upvotes: 1

Related Questions