This issue tracker has been migrated to GitHub, and is currently read-only.
For more information, see the GitHub FAQs in the Python's Developer Guide.

classification
Title: Unexecuted import in function causes UnboundLocalError
Type: behavior Stage: resolved
Components: Library (Lib) Versions: Python 3.6
process
Status: closed Resolution: not a bug
Dependencies: Superseder:
Assigned To: Nosy List: jhewitt, matrixise, steven.daprano
Priority: normal Keywords:

Created on 2018-10-25 19:28 by jhewitt, last changed 2022-04-11 14:59 by admin. This issue is now closed.

Messages (9)
msg328470 - (view) Author: James Hewitt (jhewitt) Date: 2018-10-25 19:28
Having 'import logging.config' in an if statement in a function causes a namespace issue, despite the fact that the import is not reached.

Example code:

---
#!/usr/bin/env python3

# Test weird import bug

import logging


config = {}
config['log'] = {}
config['log']['log_type'] = 'file'
config['log']['log_file'] = './log'
config['log']['config'] = { 'version' : 1 }


def do_config_logging():
    if config['log']['log_type'] == 'from_config':
        import logging.config
        logging.config.dictConfig(config['log']['config'])
    elif config['log']['log_type'] == 'file':
        logging.basicConfig(filename=config['log']['log_file'])
        logging.info("start logging")


if __name__ == "__main__":
    do_config_logging()

---


This results in:

Traceback (most recent call last):
  File "./bug.py", line 25, in <module>
    do_config_logging()
  File "./bug.py", line 20, in do_config_logging
    logging.basicConfig(filename=config['log']['log_file'])
UnboundLocalError: local variable 'logging' referenced before assignment


Notes:

This was run on Ubuntu Linux 18.04 Intel 64-bit, Python version 3.6.6

The problem does not occur if the branch is actually taken, and it does not occur if the 'if' statement is not in a function.  It also does not occur if 'logging.config' is imported as some other name, eg. 'configlogging'.

virtualenv is installed (via the distribution package) but not in use in the test case.
msg328471 - (view) Author: Stéphane Wirtel (matrixise) * (Python committer) Date: 2018-10-25 19:30
Could you share a pastebin? Thank you

> Le 25 oct. 2018 à 21:28, James Hewitt <report@bugs.python.org> a écrit :
> 
> 
> New submission from James Hewitt <jgh@caurinus.com>:
> 
> Having 'import logging.config' in an if statement in a function causes a namespace issue, despite the fact that the import is not reached.
> 
> Example code:
> 
> ---
> #!/usr/bin/env python3
> 
> # Test weird import bug
> 
> import logging
> 
> 
> config = {}
> config['log'] = {}
> config['log']['log_type'] = 'file'
> config['log']['log_file'] = './log'
> config['log']['config'] = { 'version' : 1 }
> 
> 
> def do_config_logging():
>    if config['log']['log_type'] == 'from_config':
>        import logging.config
>        logging.config.dictConfig(config['log']['config'])
>    elif config['log']['log_type'] == 'file':
>        logging.basicConfig(filename=config['log']['log_file'])
>        logging.info("start logging")
> 
> 
> if __name__ == "__main__":
>    do_config_logging()
> 
> ---
> 
> 
> This results in:
> 
> Traceback (most recent call last):
>  File "./bug.py", line 25, in <module>
>    do_config_logging()
>  File "./bug.py", line 20, in do_config_logging
>    logging.basicConfig(filename=config['log']['log_file'])
> UnboundLocalError: local variable 'logging' referenced before assignment
> 
> 
> Notes:
> 
> This was run on Ubuntu Linux 18.04 Intel 64-bit, Python version 3.6.6
> 
> The problem does not occur if the branch is actually taken, and it does not occur if the 'if' statement is not in a function.  It also does not occur if 'logging.config' is imported as some other name, eg. 'configlogging'.
> 
> virtualenv is installed (via the distribution package) but not in use in the test case.
> 
> ----------
> components: Library (Lib)
> messages: 328470
> nosy: jhewitt
> priority: normal
> severity: normal
> status: open
> title: Unexecuted import in function causes UnboundLocalError
> type: behavior
> versions: Python 3.6
> 
> _______________________________________
> Python tracker <report@bugs.python.org>
> <https://bugs.python.org/issue35069>
> _______________________________________
> _______________________________________________
> New-bugs-announce mailing list
> New-bugs-announce@python.org
> https://mail.python.org/mailman/listinfo/new-bugs-announce
msg328472 - (view) Author: James Hewitt (jhewitt) Date: 2018-10-25 19:36
Sure, it's at https://pastebin.com/L1RMPD7K

-James

On 10/25/2018 12:30 PM, Stéphane Wirtel wrote:
> 
> Stéphane Wirtel <stephane@wirtel.be> added the comment:
> 
> Could you share a pastebin? Thank you
> 
>> Le 25 oct. 2018 à 21:28, James Hewitt <report@bugs.python.org> a écrit :
>>
>>
>> New submission from James Hewitt <jgh@caurinus.com>:
>>
>> Having 'import logging.config' in an if statement in a function causes a namespace issue, despite the fact that the import is not reached.
>>
>> Example code:
>>
>> ---
>> #!/usr/bin/env python3
>>
>> # Test weird import bug
>>
>> import logging
>>
>>
>> config = {}
>> config['log'] = {}
>> config['log']['log_type'] = 'file'
>> config['log']['log_file'] = './log'
>> config['log']['config'] = { 'version' : 1 }
>>
>>
>> def do_config_logging():
>>     if config['log']['log_type'] == 'from_config':
>>         import logging.config
>>         logging.config.dictConfig(config['log']['config'])
>>     elif config['log']['log_type'] == 'file':
>>         logging.basicConfig(filename=config['log']['log_file'])
>>         logging.info("start logging")
>>
>>
>> if __name__ == "__main__":
>>     do_config_logging()
>>
>> ---
>>
>>
>> This results in:
>>
>> Traceback (most recent call last):
>>   File "./bug.py", line 25, in <module>
>>     do_config_logging()
>>   File "./bug.py", line 20, in do_config_logging
>>     logging.basicConfig(filename=config['log']['log_file'])
>> UnboundLocalError: local variable 'logging' referenced before assignment
>>
>>
>> Notes:
>>
>> This was run on Ubuntu Linux 18.04 Intel 64-bit, Python version 3.6.6
>>
>> The problem does not occur if the branch is actually taken, and it does not occur if the 'if' statement is not in a function.  It also does not occur if 'logging.config' is imported as some other name, eg. 'configlogging'.
>>
>> virtualenv is installed (via the distribution package) but not in use in the test case.
>>
>> ----------
>> components: Library (Lib)
>> messages: 328470
>> nosy: jhewitt
>> priority: normal
>> severity: normal
>> status: open
>> title: Unexecuted import in function causes UnboundLocalError
>> type: behavior
>> versions: Python 3.6
>>
>> _______________________________________
>> Python tracker <report@bugs.python.org>
>> <https://bugs.python.org/issue35069>
>> _______________________________________
>> _______________________________________________
>> New-bugs-announce mailing list
>> New-bugs-announce@python.org
>> https://mail.python.org/mailman/listinfo/new-bugs-announce
> 
> ----------
> nosy: +matrixise
> 
> _______________________________________
> Python tracker <report@bugs.python.org>
> <https://bugs.python.org/issue35069>
> _______________________________________
>
msg328475 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2018-10-25 19:58
This is expected behaviour: import is a form of assignment. 

"import logging", like "logging = 1", tells the compiler to treat logging as a local variable (unless you declare logging as global). As the exception says, you are trying to access the logging local variable before it has been assigned to.

You can (and should!) give a SHORT and SIMPLE demonstration, without any excess and irrelevant code:

py> def demo():
...     if False:
...             import logging
...     logging
...
py> demo()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 4, in demo
UnboundLocalError: local variable 'logging' referenced before assignment

So this is expected behaviour.
msg328478 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2018-10-25 20:02
Stéphane, I'm curious why you asked for a pastebin when James already provided the code right here in the tracker? (Your email even included a copy of that code.) Why split the information into another website?
msg328481 - (view) Author: James Hewitt (jhewitt) Date: 2018-10-25 20:14
I don't quite follow...  the 'import logging.config' statement should 
never be executed, and if it is commented out the program works fine as 
written.  It's as if the mere presence of the statement in the code 
causes 'logging' to be shadowed inside the function.

-James

On 10/25/2018 12:58 PM, Steven D'Aprano wrote:
> 
> Steven D'Aprano <steve+python@pearwood.info> added the comment:
> 
> This is expected behaviour: import is a form of assignment.
> 
> "import logging", like "logging = 1", tells the compiler to treat logging as a local variable (unless you declare logging as global). As the exception says, you are trying to access the logging local variable before it has been assigned to.
> 
> You can (and should!) give a SHORT and SIMPLE demonstration, without any excess and irrelevant code:
> 
> py> def demo():
> ...     if False:
> ...             import logging
> ...     logging
> ...
> py> demo()
> Traceback (most recent call last):
>    File "<stdin>", line 1, in <module>
>    File "<stdin>", line 4, in demo
> UnboundLocalError: local variable 'logging' referenced before assignment
> 
> So this is expected behaviour.
> 
> ----------
> nosy: +steven.daprano
> resolution:  -> not a bug
> stage:  -> resolved
> status: open -> closed
> 
> _______________________________________
> Python tracker <report@bugs.python.org>
> <https://bugs.python.org/issue35069>
> _______________________________________
>
msg328487 - (view) Author: Steven D'Aprano (steven.daprano) * (Python committer) Date: 2018-10-25 20:32
Yes, that's exactly right. That's how local variables work in Python:

x = 999  # global x
def demo():
   if False:
       x = 1
   x  # local x has no value

does the same thing. This is standard, documented behaviour, regardless 
of which kind of assignment statement you use.
msg328489 - (view) Author: James Hewitt (jhewitt) Date: 2018-10-25 20:48
So just the fact that somewhere in the function a name is referenced, 
even if that code isn't actually executed, is enough to change the local 
namespace.  I think I knew that, but didn't know that's what it meant :)

I guess the moral is, pay attention to scope when importing submodules 
dynamically.

Thanks for looking at this, sorry it wasn't a bit more interesting :)

-James

On 10/25/2018 01:32 PM, Steven D'Aprano wrote:
> 
> Steven D'Aprano <steve+python@pearwood.info> added the comment:
> 
> Yes, that's exactly right. That's how local variables work in Python:
> 
> x = 999  # global x
> def demo():
>     if False:
>         x = 1
>     x  # local x has no value
> 
> does the same thing. This is standard, documented behaviour, regardless
> of which kind of assignment statement you use.
> 
> ----------
> 
> _______________________________________
> Python tracker <report@bugs.python.org>
> <https://bugs.python.org/issue35069>
> _______________________________________
>
msg328506 - (view) Author: Stéphane Wirtel (matrixise) * (Python committer) Date: 2018-10-25 22:55
Hi Steven,

Sure, I am going to explain, I was at the social event of PyCon Germany without my laptop and the code was not really clear on my smartphone. I wanted to know if there was an indentation issue.

It's also the reason for my big reply with the initial email because I have replied on my phone.

But after that, you have replied.

I am really sorry for the inconvenience.

Thank you
History
Date User Action Args
2022-04-11 14:59:07adminsetgithub: 79250
2018-10-25 22:55:21matrixisesetmessages: + msg328506
2018-10-25 20:48:04jhewittsetmessages: + msg328489
2018-10-25 20:32:45steven.dapranosetmessages: + msg328487
2018-10-25 20:14:47jhewittsetmessages: + msg328481
2018-10-25 20:02:26steven.dapranosetmessages: + msg328478
2018-10-25 19:58:31steven.dapranosetstatus: open -> closed

nosy: + steven.daprano
messages: + msg328475

resolution: not a bug
stage: resolved
2018-10-25 19:36:17jhewittsetmessages: + msg328472
2018-10-25 19:30:24matrixisesetnosy: + matrixise
messages: + msg328471
2018-10-25 19:28:51jhewittcreate