First things first! Let’s get the unnecessary memory concerns out of the way. Run Python interpreter. Type the following snippet and execute it.
import sys print(sys.modules)
As you know Python adds imported modules to
sys.modules dict to keep track of them. Thus, when we import a module, it’s used from
sys.modules without a lookup, if it’s previously imported. Let’s execute the following snippet.
print(sys.modules.get('pprint')) # None
None as expected. Let’s import
pprint module, like so;
print(sys.modules.get('pprint')) # <module 'pprint' from '<PATH>/pprint.py'>
pprint module and it’s in
sys.modules as expected. Now launch a new Python interpreter and execute the following snippet.
from pprint import pprint
Now it’s time to check
sys.modules again, like so;
import sys print(sys.modules.get('pprint')) # <module 'pprint' from '<PATH>/pprint.py'>
The result, without a surprise is the same. When we use
from X import Y statement, we simply get a “handle” to the object we “import” and the entire module, which contains the object itself actually gets imported. Just because we use
from X import Y statement to “import” an object into local scope doesn’t mean that the entire Python module doesn’t get imported. It’s quite the contrary as we’ve just seen.
In a nutshell, Python doesn’t support importing only an object without importing the entire module, which the object belongs to. Therefore, using
from x import y statement compare to
import x doesn’t really save memory at all. So using
from statement “only” with memory concerns is not a valid argument.
Conflicts and Confusing Hacks
from X import Y statement will cause other issues such as conflicts. Say we have two classes with the same name,
AssetVersion in different modules/packages. I do not even get into why in the world there would be two classes with the exact same name particularly in a development department where
from x import y is furiously promoted to be used instead of plain old
import statement, but this is truly a real world scenario. For argument sake, we assume that one of them a model class, which interacts with database.
In a scenario where we need to use both classes with
from X import Y statement will present a conflict as expected, like so;
from assetSystem.asset import AssetVersion from assetDatabase.asset import AssetVersion # BOOM!
Well, here we go! If you’ve been a Python developer for some time, you would know how common this situation may become. But wait! There is a solution for that, isn’t there? Of course there is and it’s…
from assetSystem.asset import AssetVersion from assetDatabase.asset import AssetVersion as AssetVersionModel
Do I even need to say don’t do this? It’s obvious why we shouldn’t do that but I’ll mention about it in the next section.
Importing from init Module
Importing every available object of a package into it’s
__init__.py “may” appear logical thing to do for some people since when it comes to importing objects from another package will arguably be shorter. Say we have the following;
from assetSystem import AssetBundle
Imagine there is no HTML document for
assetSystem package, which we would refer and see where
AssetBundle object comes from. It might be buried anywhere in the package. So we have to navigate to
__init__.py module to see where the object is imported from. This is additionally painful if we need to fix a bug, where we need to dig into plenty of imported references in
__init__.py modules in different packages in order to understand what’s going on. Again, this is a real world problem, and it gets worse when you need to clone those dependent packages or search through them in repositories to track the issue.
Give it a thought for a second. Would that help you to understand the architecture of the packages you work with? Wouldn’t it waste your precious time? Please encourage your colleagues to search a job in another field if they say something like “Who cares understanding the architecture of the packages/APIs I use, I only work here (9 to 6)”.
Seeing is Understanding
Needless to say, understanding the libraries, frameworks that we use is important. Their inner mechanics and architecture are also greatly represented by the organization of the packages/modules. A great way to understand them is to use them in our code like so;
import sDocument.doxygen.packagePython import sDocument.doxygen.packageCPP
Apart from seeing the structure when we see these imports, we can also see the consistency, which obviously makes us more productive in our development work. Imagine sDocument.doxygen package contains a C# related module. What would the name of that module be? Easy to guess, isn’t it?
Take a look at the following snippet, where we import
createFromNode function from a package’s
from assetSystem import createFromNode
As normal as it looks, this import doesn’t give us a clue in terms of context.
createFromNode of what? From a Maya node? From a Nuke node? We may also consider it as a generic function that we can use in any DCC, which I’d highly doubt about. Now consider the following snippet.
import assetSystem.maya.createFromNode import assetSystem.nuke.createFromNode import assetSystem.generic.createFromNode
Doesn’t it appear clearer? I can clearly see that
createFromNode functions are imported from their respective modules, and of course if we had a generic
createFromNode function, it would be imported from its respective module too.
So, seeing the entire import path helps you understanding the architecture, if this is of course something you’d care about.
I wasted many precious hours of tracking what a generically named function like
createFromNode would do and where it comes from. I’ve seen other developers wasting their precious hours because of same issue too. I don’t know what you would think but I prefer to spend my precious time to produce best work I can produce instead of tracking/debugging things, which shouldn’t be a problem to begin with.
Generating HTML documentation for Python code via Doxygen offers amazing navigation capabilities. The objects we use for our method/function arguments, inherited classes, class members and returns are used by Doxygen to generate navigation automatically. Thus, you can navigate one object/package/module to another in the HTML docs and it’s without a doubt a huge time saver.
For instance, as you can see in the screenshot below
mFileSystem.jsonFileLib.JSONFile class inherits
mFileSystem.fileLib.File class, which you can click on and navigate to.
setPreBuild method in the screenshot below accepts two arguments,
envEntryContainer that expect
mMeco.libs.entryLib.EnvEntryContainer class instances respectively. You can click on them to see their API reference documentation. It’s literally “what you see is what you get”.
What About from x import *
import statement is better than using
from x import y (as z) for the reasons I’ve stated above.
- It doesn’t cost more memory
- No conflicts, therefore no confusing hacks
- What you see is what you get, understanding is easier
- Productivity and consistency reflects on documentation as well
I can hear someone saying “But wait! Typing takes a bit longer with
import statement, doesn’t it?”
I thought autocomplete feature was for that…