How can I use relative importing in Python3 with an if __name__='__main__' block?
According to the Module documentation, for __main__
modules, you have to use absolute imports.
Note that relative imports are based on the name of the current module. Since the name of the main module is always "main", modules intended for use as the main module of a Python application must always use absolute imports.
So just change the import line in module1.py
to:
from mypackage import module2
Everything else remains the same.
A Python package isn't just a folder you stick your code in, and import behavior depends on more than just what folder you've stuck your code in.
When you run your file directly, you're not running it as part of a package. Package-level initialization doesn't run, and Python doesn't even recognize the package's existence. On Python 2, the existence of implicit relative imports meant that a bare import module2
would resolve to either an absolute import or an implicit relative import, hiding the problem, but the import structure is still broken. On Python 3, implicit relative imports are gone (for good reason), so the problem is immediately visible.
Running a submodule of a package directly by filename just doesn't work very well. These days, I believe the standard is to either use -m
, or use a top-level entry point script that invokes the submodule's functionality.
There's sort of a way to get run-by-filename working anyway, but it's a lot of boilerplate. The designers of PEP 366 seem to have intended for a __package__ = 'appropriate.value'
assignment to make relative imports work properly, but that's not actually enough, even if you fix the import path. You also have to initialize the parent package manually, or you'll get a "SystemError: Parent module 'foo' not loaded, cannot perform relative import" as soon as you try to run a relative import. The full boilerplate looks more like
import os.path
import sys
if __name__ == '__main__' and __package__ is None:
__package__ = 'mypackage'
right_import_root = os.path.abspath(__file__)
for i in range(__package__.count('.') + 2):
right_import_root = os.path.dirname(right_import_root)
# sys.path[0] is usually the right sys.path entry to replace, but this
# may need further refinement in the presence of anything else that messes
# with sys.path
sys.path[0] = right_import_root
__import__(__package__)
This goes after stuff like future imports, but before any import that depends on your package.
I would wrap this boilerplate in a reusable function (using stack manipulation to access the caller's globals), except that if you try to put that function somewhere in your project, you won't be able to import the function until you've fixed your import situation, which you need the function to do. It might work as an installable dependency.
I ended up in similar scenario and it troubled me a alot until I realised how module and package import is supposed to work.
Consider the following structure
mydir
- project
- __init__.py
- module1.py
- module2.py
Contents of module1
and module2
looks like below
module1.py
print("moudule1")
module2.py
from . import module1
print("Module 2")
if __name__ == '__main__':
print("Executed as script")
Now if i open a repl outside the package directory and try to make imports it works
Python 3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from package import module2
Module 1
Module 2
>>> sys.path
['', '/usr/lib/python36.zip', '/usr/lib/python3.6', '/usr/lib/python3.6/lib-dynload', '/home/rbhanot/.local/lib/python3.6/site-packages', '/usr/local/lib/python3.6/dist-packages', '/usr/lib/python3/dist-packages']
Take a note at sys.path
, as you can see it contains the current directory I am in as the first item, which means all my imports are going to be first searched in my current directory.
Now if i go into the package directory and then open a repl, and try making same imports see what happens
Python 3.6.7 (default, Oct 22 2018, 11:32:17)
[GCC 8.2.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from . import module2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ImportError: cannot import name 'module2'
>>> import module2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
File "/home/rbhanot/python-dotfiles/python3/modules-packages/mydir/package/module2.py", line 1, in <module>
from . import module1
ImportError: attempted relative import with no known parent package
>>> import module1
Module 1
>>>
As you can see, the imports fail, the reason for failure is that when i try to import module from package python searches in sys.path
to find any package with name package
, since I could not find any , hence import fails. But importing the module1 works because it is found in the current directory.
Outside the package i can execute the script as
python3 -m package.module2 2 ↵
Module 1
Module 2
Executed as script
Though I can execute the script but this is not how its supposed to be used. Remember packages are library of code that needs to shared across and should not have any code that is directly executable via command line. Packages and modules inside packages are meant to be just imported and then after importing you can write your scripts which execute via command line by putting __name__
clause in it.