Import a module from both within same package and from outside the package in Python 3
Asked Answered
T

2

25

Okay, the scenario is very simple. I have this file structure:

.
├── interface.py
├── pkg
│   ├── __init__.py
│   ├── mod1.py
│   ├── mod2.py

Now, these are my conditions:

  • mod2 needs to import mod1.
  • both interface.py and mod2 needs to be run independently as a main script. If you want, think interface as the actual program and mod2 as an internal tester of the package.

So, in Python 2 I would simply do import mod1 inside mod2.py and both python2 mod2.py and python2 interface.py would work as expected.

However, and this is the part I less understand, using Python 3.5.2, if I do import mod1; then I can do python3 mod2.py, but python3 interface.py throws: ImportError: No module named 'mod1' :(

So, apparently, python 3 proposes to use import pkg.mod1 to avoid collisions against built-in modules. Ok, If I use that I can do python3 interface.py; but then I can't python3 mod2.py because: ImportError: No module named 'pkg'

Similarly, If I use relative import: from . import mod1 then python3 interface.py works; but mod2.py says SystemError: Parent module '' not loaded, cannot perform relative import :( :(

The only "solution", I've found is to go up one folder and do python -m pkg.mod2 and then it works. But do we have to be adding the package prefix pkg to every import to other modules within that package? Even more, to run any scripts inside the package, do I have to remember to go one folder up and use the -m switch? That's the only way to go??

I'm confused. This scenario was pretty straightforward with python 2, but looks awkward in python 3.

UPDATE: I have upload those files with the (referred as "solution" above) working source code here: https://gitlab.com/Akronix/test_python3_packages. Note that I still don't like it, and looks much uglier than the python2 solution.


Related SO questions I've already read:

Related links:

Torso answered 16/11, 2017 at 0:7 Comment(2)
This is the best (though disappointing) answer I found so far: https://mcmap.net/q/14495/-how-can-i-do-relative-imports-in-pythonTorso
In your mod2.py, instead of from pkg.mod1 import fun, you could use a relative import, such as from .mod1 import fun, or from . import mod1 which would alleviate the need to mention the name of the package. Those work in python2 and 3. Good readup here, relative imports for the billionth timeLavettelavigne
D
17

TLDR:

  • Run your code with python -m pkg.mod2.
  • Import your code with from . import mod1.

The only "solution", I've found is to go up one folder and do python -m pkg.mod2 and then it works.

Using the -m switch is indeed the "only" solution - it was already the only solution before. The old behaviour simply only ever worked out of sheer luck; it could be broken without even modifying your code.

Going "one folder up" merely adds your package to the search path. Installing your package or modifying the search path works as well. See below for details.

But do we have to be adding the package prefix pkg to every import to other modules within that package?

You must have a reference to your package - otherwise it is ambiguous which module you want. The package reference can be either absolute or relative.

A relative import is usually what you want. It saves writing pkg explicitly, making it easier to refactor and move modules.

# inside mod1.py
# import mod2 - this is wrong! It can pull in an arbitrary mod2 module
# these are correct, they uniquely identify the module
import pkg.mod2
from pkg import mod2
from . import mod2
from .mod2 import foo  # if pkg.mod2.foo exists

Note that you can always use <import> as <name> to bind your import to a different name. For example, import pkg.mod2 as mod2 lets you work with just the module name.

Even more, to run any scripts inside the package, do I have to remember to go one folder up and use the -m switch? That's the only way to go??

If your package is properly installed, you can use the -m switch from anywhere. For example, you can always use python3 -m json.tool.

echo '{"json":"obj"}' | python -m json.tool

If your package is not installed (yet), you can set PYTHONPATH to its base directory. This includes your package in the search path, and allows the -m switch to find it properly.

If you are in the executable's directory, you can execute export PYTHONPATH="$(pwd)/.." to quickly mount the package for import.

I'm confused. This scenario was pretty straightforward with python 2, but looks awkward in python 3.

This scenario was basically broken in python 2. While it was straightforward in many cases, it was difficult or outright impossible to fix in any other cases.

The new behaviour is more awkward in the straightforward case, but robust and reliable in any case.

Detach answered 17/5, 2018 at 13:12 Comment(2)
I am hoping you can guide me re. Case 3 from the following link. The author says in Case 3 that solution one (using -m) is not possible. Can you advise me if this is true? chrisyeh96.github.io/2017/08/08/…Who
@Who Sorry, but it is very hard to piece together what you mean. From a quick glance, I'd say it is not true. I recommend you ask a new question on Stackoverflow.com or visit the python chat.Detach
P
-2

I had similar problem. I solved it adding

import sys 
sys.path.insert(0,".package_name")

into the __init__.py file in the package folder.

Pleasure answered 16/2, 2023 at 12:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.