The Python community has some PEPs and a lot of debate over what the 'correct' way to package Python programs is. As of November 2023 I'm not totally sure what the way forward is meant to be for PyPI projects, or if the below information is still true about Python's module handling. Consume appropriate amounts of salt where sus.
In Python, individual source files are referred to as modules. They represent a single collection of code, and can be import
ed. Packages are directories containing an __init__.py
file and other modules. They are comparable to namespaces, and can also be import
ed. A package with an empty __init__.py
file is completely fine; it just won't import any modules when imported.
Here's an example of a package containing modules:
my_package/ __init__.py ui.py attribute.py model.py router.py
There are multiple ways to interact with a package, which are covered below. In every case, it's assumed that the Python interpreter is in the directory above my_package/
, or my_package/
is somewhere in Python's path variable. See Python's documentation on sys.path for information on that.
# Import just the package import my_package
In this case, importing the package will process everything that's in __init__.py
, including any pre-defined import
s. It's considered good practice to define which modules get imported by default when importing a package, to ensure only the necessary modules are loaded. Your modules can individually import
any part of the package they need to get the job done. To do this, your __init__.py
should look somewhat like this:
# Let's say you only need the model and router to get started from . import (model, router)
The .
here means "the current package". If you have sub-packages, you can address them with a dot preceding its name, like .my_subpackage
.
Sometimes, you only need a single module from a package. That's easy:
# Import only the UI from my_package import ui
In this example, only the ui
module is imported. It gets injected into the current environment as ui
, and you can access its members as well, using something like ui.create_box()
.
Lastly, sometimes you need only specific parts of a module. That's easy, too!
# Import only specific things from a given module from my_package.router import do_route
In the above example, do_route
will become available in the current environment, imported from the router
module inside the my_package
package.
If you find yourself with strange NameError
s, AttributeError
s, or ValueError
s, it may point to a name collision. You can rename your imports with the as
keyword:
# Import with a different name from my_package.router import do_route as make_route
This is often caused by importing two modules with the same member name. If you import two things with the same member name, the latter name will take precedence:
# Same name, different structures from os import path print(type(path)) # <class 'module'> from sys import path print(type(path)) # <class 'list'>
For this reason, it's generally better to leave imports alone and go one level deeper only as needed. So if you wanted to use path
from both os
and sys
, just import them on their own, so you're using os.path
and sys.path
. It's more verbose, but also more precise and less likely to break.