Is it bad form putting a main function in my package files?
It is not necessarily bad for your submodules to have a main
function defined, but if you want the logger names to be something other than "__main__"
then you'll want to avoid executing the submodules as top-level code. Do not try to fight or fiddle the __main__
machinery, this part of the language is inflexible and it's unlikely to change because Guido considers executing submodules directly as scripts an anti-pattern.
Instead, you want the top-level code environment to be a wrapper script which imports main
and executes it. This way, the normal pattern of logger = logging.getLogger(__name__)
will work as desired, creating a proper hierarchy of named loggers for your subpackages. The logging tree heirarchy is important so that logging handlers can be configured recursively, and loggers can propagate up to a single node (i.e. the root logger).
There is a Python packaging feature called Entry Points which can be used to generate these wrapper scripts automatically. I'll demonstrate how to define them with setuptools, but all other major build systems support them nowadays since they were specified years ago in PEP 621 – Storing project metadata in pyproject.toml.
In your submodules, you will have something like (simplified):
# package_name/functionality_1.py
import logging
log = logging.getLogger(__name__)
def main():
logging.basicConfig(format="%(name)s:%(message)s", level=logging.INFO)
log.info("hello")
And in your pyproject.toml
file, add a section with:
[project.scripts]
functionality-1 = "package_name.functionality_1:main"
functionality-2 = "package_name.functionality_2:main"
The installer (usually pip) will autogenerate wrapper scripts and put them on the $PATH. These scripts are what you should invoke instead of executing submodules directly as __main__
, i.e. instead of using:
python3 -m package_name.functionality_1
python3 -m package_name.functionality_2
You will call:
functionality-1
functionality-2
You can remove all the if __name__ == "__main__:
blocks from the source code, they are not necessary. For development/testing, install your package as editable by using pip install -e .
(it is this installation command which will generate the wrapper scripts).
Take a look at the contents of the wrapper scripts if you like, to see how it works. You'll find them at the location specified by sysconfig.get_path("scripts")
. They'll have executable mode bits set, and if you're on macOS/Linux they'll have a #!shebang pointing at the corresponding Python environment (i.e. the runtime used for the installation). It is these scripts which execute as the top-level code, and they'll just import the main
function(s) from your package code and call them.