Running a module in a package, importing a subpack

2019-07-07 19:57发布

问题:

I get the sense this question has been asked many times before, but somehow none of the answers to seemingly related questions get me to fully understand what's going on.

The situation: I create a package and inside that package I have another package and I want to be able to run all of them directly, for testing purposes i.e.:

\main.py
\package_1\__init__.py
\package_1\module_1.py
\package_1\package_2\__init__.py
\package_1\package_2\module_2.py

Where main.py:

from package_1.module_1 import func_1
func_1()

And module_1.py:

from package_1.package_2.module_2 import func_2
def func_1():
    func_2()
if __name__ == "__main__":
    func_1()

And module_2.py:

def func_2:
    print ("This works")
if __name__ == "__main__":
    func_2()

The __init__.py are empty. This allows me to run main.py from the root and it works. I can also run module_2.py from its own folder. But I can't run module_1.py since it complains there is no package_1. That makes some sense, although technically it is itself in package_1. But if I remove that prefix, it breaks main, which also makes some sense.

How to fix this? I tried replacing the import in module_1.py by:

import .package_2.module_2

But that gets me an error I don't fully understand:

ModuleNotFoundError: No module named '__main__.package_2'; '__main__' is not a package

What is the right approach to get all of the packages and modules working as intended? Is it something that should be solved in __init__.py? Or should I simply avoid nesting packages like this and install them all (after providing a setup.py)?

Edit:

@Blckknght suggested running:

python -m package_1.module_1
python -m package_1.package_2.module_1

From the root directory. That works, in that all the code is run as expected. I also updated the import in module_1.py to:

from .package_2.module_2 import func_2

And finally, @jonilyn2730 provided the suggestion to make the main body callable from other scripts by putting it inside a main() function. For example, module_1.py would be:

from package_1.package_2.module_2 import func_2
def func_1():
    func_2()
def main():
    func_1()
if __name__ == "__main__":
    main()

In this trivial example, it makes little difference, but it allows this in other scripts:

from package_1.module_1 import main
main()

So the script no longer has to be called directly and multiple runs can be combined in a single script.

回答1:

There's no good way to run a module in a package by file name without breaking all imports of other parts of the package. Instead, you should run the module it from the top level folder using the -m flag:

python -m package1.module1

When you run the module this way, both relative or absolute imports should work (pick whichever you prefer).

Beware though that if you have circular imports in your project (so that the module you're running as the script also gets imported from somewhere else), the interpreter will end up with two copies of the module, one running as __main__ and the other as its normal name. This can be very awkward and cause confusing bugs.

If you're running the module as a script a lot, you probably should make a new top-level script module that import the module from the package and runs the desired code (like you're already doing in main.py). This prevents the module full of code from potentially existing twice, and also lets you benefit from cached bytecode being loaded from a .pyc file (which may make your program a bit faster to start up).