Python Submodule Importing Madness

2020-03-31 08:53发布

问题:

I'm banging my head against the wall with some basic Python importing. I have simplified the problem as much as possible hoping I'd be able to expand this to a larger scale if I understand how this works

Here is the dilemma -
run.py from inside the submodule p1 works, but NOT when it's at the top level. Why?

(version Python 3.6.3)


Structure:

/sandbox
    __init__.py
    /p1
        __init__.py
        file1.py
        run.py
    run.py

/sandbox/p1/__init__.py

__all__ = ["file1", "file2"]

/sandbox/p1/file1.py

from file2 import B
class A(object):
    pass

/sandbox/p1/file2.py

class B(object):
    pass

/sandbox/p1/run.py

from file1 import A
a = A()

/sandbox/run.py

from p1 import file1
a = file1.A()

Doing:

python p1/run.py (works fine)
python run.py

Traceback (most recent call last): File "run.py", line 2, in from p1 import file1 File ".../sandbox/p1/file1.py", line 1, in from file2 import B ModuleNotFoundError: No module named 'file2'

回答1:

(I) no module named 'file2'

You encountered No module named 'file2' because you run run.py outside a package, when file1 import file2, python cannot find file2 as the module directory not in module search path.

For your scenario, file1 & file2 are 2 modules which in the same package, for this situation, suggest you use relative import, this is the best practice.

file1.py

from .file2 import B
class A(object):
    pass

Then, python run.py works.

(II) attempted relative import with no known parent package

As you mentioned you will see next if execute python p1/run.py:

attempted relative import with no known parent package

What does it mean?

This because relative import can just be supported in the module which in package.

You may say file1.py is in package p1, why still see the error?

This is because python use __name__ to determine if this module in package or not, not by the position of it.

Change file1.py as follows:

print(__name__)
from .file2 import B
class A(object):
    pass

Next is the output:

python run.py

p1.file1

python p1/run.py

file1

For python p1/run.py, because it runs in the package, python will fail to know it is in a package, so output is file1.

If __name__ does not has ., python will think it's not in package, so relative import failure.

(III) Finally, what if you really want to execute python p1/run.py, e.g. unittest your package?

Use python -m p1.run, this should works to execute top script which inside a package.



回答2:

Python makes it intentionally difficult to mix scripts in a modules. The way this can be handled is like this:

/project
    /scripts
        run.py
    /sandbox
        __init__.py
        /p1
            __init__.py
            file1.py
            file2.py

/project/sandbox/p1/file1.py

from .file2 import B
class A(object):
    pass

/project/sandbox/p1/file2.py

class B(object):
    pass

/project/scripts/run.py

from sandbox.p1.file1 import A
a = A()

I make sure that the path to /project is in a .pth file in the site-packages of the virtual environment I want to use it from.

conda

If you use conda, you can use conda develop