A question about import styles on the Python-Dev mailing list asked about imports like this:
import os as _os
Understanding why people do this is an interesting lesson in how modules work. A module is nothing more than a collection of names. When you define a name in a .py file, it becomes an attribute of the module, and is then importable from the module.
An underlying simplicity in Python is that many statements are really just assignment statements in disguise. All of these define the name X:
X = 17
def X(): print("look!")
When you create a module, you can make the name “X” importable from that module by assigning to it, or defining it as a function. You can also make it importable by importing it yourself.
Suppose your module looks like this:
This module has two names defined in it: “doit”, and “os”. Someone else can now do this:
from yourmodule import os
# or worse, this imports os and doit:
from yourmodule import *
This bothers some people. “os” is not part of the actual interface of yourmodule. That first import I showed prevents this leaking of your imports into your interface. Importing star doesn’t pull in names starting with underscores. (Another solution is to define __all__ in your module.)
Most people though, don’t worry about this kind of name leaking. Import-star is discouraged anyway, and people know not to import os from other modules. The solution of renaming os to _os just makes your code ugly for little benefit.
The part of the discussion thread that really caught my eye was Daniel Holth’s winking suggestion of the “evil ninja mode pattern” of module initialization:
What’s going on here!? Remember that def is an assignment statement like any other. When used inside a function, it defines a local name, as assignment always does. But an assignment in a function can define a global name if the name is declared as global. It’s a little unusual to see a global statement without an explicit assignment at the top-level, but it works just fine. The def statement defines a global “exported” function, because the global statement told it to. “os” is now a local in our function, because again, the import statement is just another form of assignment.
So we define ninja(), and then execute it immediately. This defines the global “exported”, and doesn’t define a global “os”. The only problem is the name “ninja” has been defined, which we can clean up with a del statement.
Please don’t ever write code this way. It’s a kind of over-defensiveness that isn’t needed in typical Python code. But understanding what it does, and why it does it, is a good way to flex your understanding of Python workings.
For more about how names (and values) work in Python, people seem to like my PyCon talk, Python Names and Values.