0

I have a directory structure with 2 basic python files inside seperate directories:

├── package
│   ├── subpackage1
│   │   └── module1.py
    └── subpackage2
        └── module2.py

module1.py:

def module1():
    print('hello world')

module2.py:

from ..subpackage1.module1 import module1

module1()

When running python3 module2.py I get the error: ImportError: attempted relative import with no known parent package

However when I run it with the imports changed to use sys.path.append() it runs successfully

import sys
sys.path.append('../subpackage1/')
from module1 import module1

module1()

Can anyone help me understand why this is and how to correct my code so that I can do this with relative imports?

kmurp62rulz
  • 169
  • 2
  • 9

3 Answers3

4

To be considered a package, a Python directory has to include an __init__.py file. Since your module2.py file is not below a directory that contains an __init__.py file, it isn't considered to be part of a package. Relative imports only work inside packages.

UPDATE:

I only gave part of the answer you needed. Sorry about that. This business of running a file inside a package as a script is a bit of a can of worms. It's discussed pretty well in this SO question:

Relative imports in Python 3

The main take-away is that you're better off (and you're doing what Guido wants you to) if you don't do this at all, but rather move directly executable code outside of any module. You can usually do this by adding an extra file next to your package root dir that just imports the module you want to run.

Here's how to do that with your setup:

.
├── package
│   ├── __init__.py
│   ├── subpackage1
│   │   └── module1.py
│   └── subpackage2
│       └── module2.py
└── test.py

test.py:

import package.subpackage2.module2

You then run test.py directly. Because the directory containing the executed script is included in sys.path, this will work regardless of what the working directory is when you run the script.

You can also do basically this same thing without changing any code (you don't need test.py) by running the "script" as a module.

python3 -m package.subpackage2.module2

If you have to make what you're trying to do work, I think I'd take this approach:

import os, sys

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))

from subpackage1.module1 import module1

module1()

So you compute in a relative way where the root of the enclosing package is in the filesystem, you add that to the Python path, and then you use an absolute import rather than a relative import.

There are other solutions that involve extra tools and/or installation steps. I can't think why you could possibly prefer those solutions to the last solution I show.

CryptoFool
  • 21,719
  • 5
  • 26
  • 44
1

By default, Python just considers a directory with code in it to be a directory with code in it, not a package/subpackage. In order to make it into a package, you'll need to add an __init__.py file to each one, as well as an __init__.py file to within the main package directory.

Miguel Guthridge
  • 1,444
  • 10
  • 27
  • I have added 3 blank `__init__.py` files to each directory but it is still not working, do I need to add anything inside these? – kmurp62rulz Jan 11 '23 at 05:36
1

Even adding the __init__.py files won't be enough, but you should. You should also create a setup.py file next to your package directory. Your file tree would look like this:

├── setup.py
└── package
    ├── __init__.py   
    └── subpackage1
    │   ├── __init__.py 
    │   └── module1.py
    └── subpackage2
        ├── __init__.py 
        └── module2.py

This setup.py file could start off like this:

from setuptools import setup

setup(
    name='package',
    packages=['package'],
)

These configurations are enough to get you started. Then, on the root of your directory (parent folder to package and setup.py), you will execute next command in you terminal pip install -e . to install your package, named package, in development mode. Then you'll be able to navigate to package/subpackage2/ and execute python module2.py having your expected result. You could even execute python package/subpackage2/module2.py and it works.

The thing is, modules and packages don't work the same way they work in another programming languages. Without the creation of setup.py if you were to create a program in your root directory, named main.py for example, then you could import modules from inside package folder tree. But if you're looking to execute package\subpackage2\module2.py.

ccolin
  • 304
  • 2
  • 7