0

Can someone explain why the following happens? I had a look at Should I use `import os.path` or `import os`? which is informative, vaguely similar, but didn't really clarify it for me.

If I comment out import os.path or add it directly after import os there's no error.

$ python -V
Python 2.7.2
$ cat min.py
import os

def main():
    os.environ['blah'] = 'bloo'
    import os.path


if __name__ == '__main__':
    main()
$ python min.py
Traceback (most recent call last):
  File "min.py", line 9, in <module>
    main()
  File "min.py", line 4, in main
    os.environ['blah'] = 'bloo'
UnboundLocalError: local variable 'os' referenced before assignment
$
Community
  • 1
  • 1
jones77
  • 505
  • 1
  • 4
  • 16
  • 1
    Not sure, but I suspect 'import os.path' counts as a write to `os` variable in local scope, so, `os` does not exist before that (which seems like a bug to me, that os is counted as an assignment here). `os.path` should be the target of the assignment. – Max Feb 17 '17 at 16:14

3 Answers3

4

Because an import is also an assignment. If you do import os.path in your main method, you're making os a local name, and Python won't look in the global scope to see the os you've already imported.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895
  • 1
    Very succinct. You say in two sentences what took me ten. – Steven Rumbalski Feb 17 '17 at 16:43
  • 1
    Steve's correct that this is the bestest answer, but it took Steve's explanation to make me understand this in my head: "Python has rules about the visibility of names and when they can be referenced. One of the rules is that there cannot be ambiguity in a function as to whether you mean to refer to a local name or a global name. If a local name is created anywhere inside a function that is the same as a global name, Python assumes that all references to that name inside the function refer to the local name (unless you specifically say otherwise)." Thank you both. – jones77 Feb 17 '17 at 16:53
2
# assigns 'os' to global namespace
import os

def main():
    os.environ['blah'] = 'bloo' # <== which 'os'?  python assumes local
    # assigns 'os' to local namespace
    import os.path

import does two things. First, it creates a module object. Second it gives a name to that newly created module object. Giving something a name is assignment, hence the error message "UnboundLocalError: local variable 'os' referenced before assignment".

Python has rules about the visibility of names and when they can be referenced. One of the rules is that there cannot be ambiguity in a function as to whether you mean to refer to a local name or a global name. If a local name is created anywhere inside a function that is the same as a global name, Python assumes that all references to that name inside the function refer to the local name (unless you specifically say otherwise).

Three possible fixes.

Drop the local import:

import os

def main():
    os.environ['blah'] = 'bloo' 

Drop the global import and move local import to the top of the function

def main():
    import os.path 
    os.environ['blah'] = 'bloo'

Declare os global at the beginning of your function so that the first reference uses the global:

import os

def main():
    global os
    os.environ['blah'] = 'bloo'
    import os.path

A note about imports.

# has the same effect as 'import module'
# creates a reference named 'module'
import module.submodule

# imports submodule and creates a reference to it named 'submodule'
from module import submodule

# imports submodule and creates a reference to it named 'x'
import module.submodule as x
Steven Rumbalski
  • 44,786
  • 9
  • 89
  • 119
1

I happens because you are trying to use the library 'os' before importing it. You can do

def main():
    import os
    #now you can use os.environment
    os.environ['blah'] = 'bloo'
    #you don't need to import os.path as os is already imported


if __name__ == '__main__':
    main()
Numlet
  • 819
  • 1
  • 9
  • 21
  • It's the `import os.path` after the assignment that is causing problems. In your example, if I move `import os` to global scope everything's hunky dory. I agree that I don't technically need to `import os.path` anyways, but am curious why it's a problem. – jones77 Feb 17 '17 at 16:51