2 Best Solutions For “ImportError: attempted relative import with no known parent package” [2021 Updated]

Relative imports in python can be frustrated from time to time. Therefore, from time to time, you may meet the mysterious and obscure exception “ImportError: attempted relative import with no known parent package” Let’s see what this exception means, why it is raised. Finally, we see how to get rid of the “ImportError: attempted relative import with no known parent package” exception 

Why “ImportError: attempted relative import with no known parent package” is Raised?

As a rule of thumb – If you try to do relative import in a module that does not belong to a package, you will get the “ImportError: attempted relative import with no known parent package” exception.

Why? The reasons beyond this rule of thumb are related to how relative import works in python.

In PEP 328 (Imports: Multi-Line and Absolute/Relative) that add the support of relative imports, we can find how the python interpreter should resolve the relative modules.

Relative imports use a module’s __name__ attribute to determine that module’s position in the package hierarchy.

PEP 328

First, this statement implies that relative import is relative to the current package. What is the current package? The current package is the package the current module belongs to. However, you probably know that not all modules belong to a package. Therefore, the module where we do the relative import must belong to a package, or otherwise, the python interrupter will scream that you are doing something wrong.

Second, this statement describes how the python interrupter should find the current package when it searches for the imported module. When the Python interrupter tries to find out the current package, It obtains the package information by checking the value of the __name__ variable (the module’s name).

What should the interrupter do when the module’s name does not contain any package information?

If the module’s name does not contain any package information (e.g., it is set to __main__), then relative imports are resolved as if the module were a top-level module, regardless of where the module is actually located on the file system.

PEP 328

If __name__ variable does not have any package information, the python interpreter should treat the module as a top-level module (a module that does not belong to any package). In that case, the current package does not exist. Therefore, the python interpreter can not resolve the location of the imported module and you get “ImportError: attempted relative import with no known parent package

This description shows one of the best example of a module that does not belong to a package : the __main__ module. The __main__ module, the script you invoked the python interrupter with, does not belong to any package. We use this example later to demonstrate how to get rid of “ImportError: attempted relative import with no known parent package” exception.

Avoid ImportError: attempted relative import with no known parent package

As we see, when you try to do relative import in a module that does not belong to a package, you will get the “ImportError: attempted relative import with no known parent package” exception. It is essential to know that the module where you do relative import belongs to a package; otherwise, you get this irritating exception.

2 Ways to Check Whether the module belongs to a package

One way to check if a module belongs to a package is to review the value of __name__ variable. If the __name__ variable contains a dot, the module belongs to a package.

Another way to check if a module belongs to a package is to review the value of __package__ variable. if the __package__ variable do not equal to None, the module belongs to a package.

Sample for “ImportError: attempted relative import with no known parent package”

The following sample demonstrates that the main module (The script you invoked the python interrupter with) does not belong to a package.

Suppose you have a project with the following simple directory structure:

Sample Directory Structure
 root
 ├── config.py
 └── package
     ├── __init__.py
     └── program.py

You are trying access variables defined in the config.py in your program.py. It seems a straightforward task and you choose to use relative import:

root/config.py
count = 5
root/package/program.py
from .. import config
print("config.count => {0}".format(config.count))

However, when invoking program.py script, An exception is raised :

“ImportError: attempted relative import with no known parent package” Is Raised
Y:/root>python package/program.py
Traceback (most recent call last):
  File "package/program.py", line 1, in <module>
    from .. import config
ImportError: attempted relative import with no known parent package

Review the __name__ and __package__ variables

Let’s review the values of the __name__ and __package__ variables by adding some log messages at the top of the above modules:

root/config.py with logs
print('__file__={0:<35} | __name__={1:<25} | __package__={2:<25}'.format(__file__,__name__,str(__package__)))

count = 5
root/package/program.py with logs
print('__file__={0:<35} | __name__={1:<25} | __package__={2:<25}'.format(__file__,__name__,str(__package__)))

from .. import config
print("config.count => {0}".format(config.count))
Invoking program.py
Y:/root>python package/program.py
__file__=package/program.py                  | __name__=__main__                  | __package__=None
Traceback (most recent call last):
  File "package/program.py", line 3, in <module>
    from .. import config
ImportError: attempted relative import with no known parent package

As we can see in the above output, the value of the value of __package__ is None and the value of __name__ variable is __main__.

Since, the value __package__ is None and the value of the variable __name__ does not contains any dot, we can know that module does not belong to any package. Therefore, the python interrupter raises the unclear exception.

How to fix “ImportError: attempted relative import with no known parent package”

Solution 1 : Change Directory Structure

In this solution, we need to change directory structure and create a new script

  • First, create a new directory named new_root and move the root directory to new_root
  • Create main.py in new_root directory
  • Create a new empty __init__.py inside the root directory. This will signal to the python interrupter that this directory is a package.
The sample directory
 new_root
 ├── main.py
 └── root
     ├── __init__.py
     ├── config.py
     └── package
         ├── __init__.py
         └── program.py
Updating new_root/main.py
print('__file__={0:<35} | __name__={1:<25} | __package__={2:<25}'.format(__file__,__name__,str(__package__)))

import root.package.program

When we invoke the new script, we get the following output:

Invoking program.py
Y:/new_root>python program.py
__file__=main.py                             | __name__=__main__                  | __package__=None
__file__=Y:/new_root/root/package/program.py | __name__=root.package.program      | __package__=root.package
__file__=Y:/new_root/root/config.py          | __name__=root.config               | __package__=root
config.count => 5

It works. Let’s see why?

Now, the module root.package.program belongs to root.package.

We can see it in the second line (The print from the top of new_root/root/package/program.py).

__file__=Y:/new_root/root/package/program.py | __name__=root.package.program      | __package__=root.package

Now, when this module belong to a package, the python interpreter has all the information has all the information to resolve the relative import in root/package/program.py successfully.

Solution 2 : Use the -m option

In the first solution, we need to create a new script. In this solution, we use -m option without creating a new script.

Let’s change directory structure and use -m option

  • First, create a new directory named new_root and move the root directory to new_root
  • Create a new empty __init__.py inside the root directory. This will signal to the python interrupter that this directory is a package.
The sample directory
 new_root  
 └── root
     ├── __init__.py
     ├── config.py
     └── package
         ├── __init__.py
         └── program.py

Now, we invoke python with -m option and provide the module root.package.program.

The python -m option allows modules to be located using the Python module namespace for execution as scripts. As the following output demonstrate, It will also set the package information:

Invoking program.py with -m option
Y:/new_root>python -m root.package.program
__file__=Y:/new_root/root/package/program.py | __name__=__main__                  | __package__=root.package
__file__=Y:/new_root/root/config.py          | __name__=root.config               | __package__=root
config.count => 5

It works. Let’s see why?

Now, the module root.package.program belongs to root.package.

We can see it in the first line (The print from the top of new_root/root/package/program.py).

__file__=Y:/new_root/root/package/program.py | __name__=__main__                  | __package__=root.package

Now, when this module belong to a package, the python interpreter has all the information has all the information to resolve the relative import in root/package/program.py successfully.

Note: Unlike PEP 328, the python interpreter do not rely on the value of __name__ to find the package information and uses the value of __package__.

Solution 3 : Workaround Without Changing Directory Structure

In previous solutions, we needed to change the directory structure. Sometimes, you want to avoid changing the directory structure. For example, the framework you use in your code (such as django, pytest, airflow, fastapi or flask) or the environment you work with (such as pycharm, vscode or jupyter notebook) depend on this directory structure. In those cases, you may consider the following workaround.

As we see in solution 2, the python interrupter relies on the __package__ variable to extract the package information. Therefore, when the __package__ is none, the python interrupter will cry that you are doing something wrong.

So, Let’s use this observation and enclose the relative import with the following if statement based on the value of the __package__ variable.

Invoking program.py with -m option
if __package__:
    from .. import config
else:
    sys.path.append(os.dirname(__file__) + '/..')
    import config

It works. Let’s see why?

If the __package__ is not None, we can safely use relative import. However, if __package__ is None, we can not use relative import anymore. Therefore we add the directory of the module to sys.path and import the required module. Since sys.path is a list of paths that specify where the python interrupter should search for imported modules, python interrupter will find the required module.

How to generate the directory?

First, replace the first dot in the relative import with os.dirname(__file__) and then replace each successive dot with “../”. Now append successive part between the dots.

from . import configsys.path.append( os.dirname(__file__) )
import config
from .. import config
sys.path.append( os.path.join( os.dirname(__file__), ‘..’ ) )
import config
from … import config
sys.path.append( os.path.join( os.dirname(__file__), ‘..’ , ‘..’ ))
import config
from ..a.b.c import config sys.path.append( os.path.join( os.dirname(__file__), ‘..’ , ‘a’ ,’b’ ,’c’ ))
import config

OR better (less chance for name clashing)

sys.path.append( os.path.join( os.dirname(__file__), ‘..’ ))
from a.b.c import config
relative import : examples of adding path to sys.path

However, this method is very fragile. If a module with the required name (in our case, config) already exists in the sys path’s previous paths, the python interrupter will import it instead of our module. It can be a source of nasty and mysterious bugs.

So use this workaround with caution and only when necessary.

Summary

We see how the python interrupter resolve relative imports. Then, we see why the “ImportError: attempted relative import with no known parent package” exception is raised. Finally we see how to get rid of “ImportError: attempted relative import with no known parent package”

2 thoughts on “2 Best Solutions For “ImportError: attempted relative import with no known parent package” [2021 Updated]

  1. This is it. This is by far the best article that concisely explain a python project structure. I cannot thank you enough!

    1. I am glad that this tutorial helps you to understand how relative imports works in python.
      Relative imports in python are not intuitive and can be frustrating :-*(

Leave a Reply

Your email address will not be published. Required fields are marked *