I often end up in a situation where one package needs to use a sibling package. I want to clarify that I'm not asking about how Python allows you to import sibling packages, which has been asked many times. Instead, my question is about a best practice for writing maintainable code.
Let's say we have a
tools
package, and the functiontools.parse_name()
depends ontools.split_name()
. Initially, both might live in the same file where everything is easy:# tools/__init__.py from .name import parse_name, split_name # tools/name.py def parse_name(name): splits = split_name(name) # Can access from same file. return do_something_with_splits(splits) def split_name(name): return do_something_with_name(name)
Now, at some point we decide that the functions have grown and split them into two files:
# tools/__init__.py from .parse_name import parse_name from .split_name import split_name # tools/parse_name.py import tools def parse_name(name): splits = tools.split_name(name) # Won't work because of import order! return do_something_with_splits(splits) # tools/split_name.py def split_name(name): return do_something_with_name(name)
The problem is that
parse_name.py
can't just import the tools package which it is part of itself. At least, this won't allow it to use tools listed below its own line intools/__init__.py
.The technical solution is to import
tools.split_name
rather thantools
:# tools/__init__.py from .parse_name import parse_name from .split_name import split_name # tools/parse_name.py import tools.split_name as tools_split_name def parse_name(name): splits = tools_split_name.split_name(name) # Works but ugly! return do_something_with_splits(splits) # tools/split_name.py def split_name(name): return do_something_with_name(name)
This solution technically works but quickly becomes messy if more than just one sibling packages are used. Moreover, renaming the package tools
to utilities
would be a nightmare, since now all the module aliases should change as well.
It would like to avoid importing functions directly and instead import packages, so that it is clear where a function came from when reading the code. How can I handle this situation in a readable and maintainable way?