'ModuleNotFoundError' when trying to import module from imported package
Asked Answered
I

6

50

This is my directory structure:

man/                          
  Mans/                  
          man1.py
  MansTest/
          SoftLib/
                  Soft/
                      SoftWork/
                              manModules.py
          Unittests/
                    man1test.py

man1.py contains the following import statement, which I do not want to change:

from Soft.SoftWork.manModules import *

man1test.py contains the following import statements:

from ...MansTest.SoftLib import Soft
from ...Mans import man1

I need the second import in man1test.py because man1test.py needs access to a function in man1.py.

My rationale behind the first import (Soft) was to facilitate the aforementioned import statement in man1.py.

Contrary to my expectation, however, the import statement in man1.py gives rise to:

ModuleNotFoundError: No module named 'Soft'

when I run

python3 -m man.MansTest.Unittests.man1test

from a directory above man/.

Is there any way to resolve this error without changing the import statement in man1.py and without adding anything to sys.path?

Edit: python3 -m man.ManTest.Unittests.man1test from the original version of the question changed to python3 -m man.MansTest.Unittests.man1test

Isola answered 8/2, 2019 at 18:29 Comment(1)
You can hack __path__ of imported modules. It's not sys.path but is it OK for you?Contemporaneous
C
89

FIRST, if you want to be able to access man1.py from man1test.py AND manModules.py from man1.py, you need to properly setup your files as packages and modules.

Packages are a way of structuring Python’s module namespace by using “dotted module names”. For example, the module name A.B designates a submodule named B in a package named A.

...

When importing the package, Python searches through the directories on sys.path looking for the package subdirectory.

The __init__.py files are required to make Python treat the directories as containing packages; this is done to prevent directories with a common name, such as string, from unintentionally hiding valid modules that occur later on the module search path.

You need to set it up to something like this:

man
|- __init__.py
|- Mans
   |- __init__.py
   |- man1.py
|- MansTest
   |- __init__.py
   |- SoftLib
      |- Soft
         |- __init__.py
         |- SoftWork
            |- __init__.py
            |- manModules.py
      |- Unittests
         |- __init__.py
         |- man1test.py

SECOND, for the "ModuleNotFoundError: No module named 'Soft'" error caused by from ...Mans import man1 in man1test.py, the documented solution to that is to add man1.py to sys.path since Mans is outside the MansTest package. See The Module Search Path from the Python documentation. But if you don't want to modify sys.path directly, you can also modify PYTHONPATH:

sys.path is initialized from these locations:

  • The directory containing the input script (or the current directory when no file is specified).
  • PYTHONPATH (a list of directory names, with the same syntax as the shell variable PATH).
  • The installation-dependent default.

THIRD, for from ...MansTest.SoftLib import Soft which you said "was to facilitate the aforementioned import statement in man1.py", that's now how imports work. If you want to import Soft.SoftLib in man1.py, you have to setup man1.py to find Soft.SoftLib and import it there directly.

With that said, here's how I got it to work.

man1.py:

from Soft.SoftWork.manModules import *
# no change to import statement but need to add Soft to PYTHONPATH

def foo():
    print("called foo in man1.py")
    print("foo call module1 from manModules: " + module1())

man1test.py

# no need for "from ...MansTest.SoftLib import Soft" to facilitate importing..
from ...Mans import man1

man1.foo()

manModules.py

def module1():
    return "module1 in manModules"

Terminal output:

$ python3 -m man.MansTest.Unittests.man1test
Traceback (most recent call last):
  ...
    from ...Mans import man1
  File "/temp/man/Mans/man1.py", line 2, in <module>
    from Soft.SoftWork.manModules import *
ModuleNotFoundError: No module named 'Soft'

$ PYTHONPATH=$PYTHONPATH:/temp/man/MansTest/SoftLib
$ export PYTHONPATH
$ echo $PYTHONPATH
:/temp/man/MansTest/SoftLib
$ python3 -m man.MansTest.Unittests.man1test
called foo in man1.py
foo called module1 from manModules: module1 in manModules 

As a suggestion, maybe re-think the purpose of those SoftLib files. Is it some sort of "bridge" between man1.py and man1test.py? The way your files are setup right now, I don't think it's going to work as you expect it to be. Also, it's a bit confusing for the code-under-test (man1.py) to be importing stuff from under the test folder (MansTest).

Commove answered 10/2, 2019 at 3:27 Comment(3)
Thank you for the elaborate answer. I am currently busy with a different project and haven't had time to implement your solution, which is why I haven't marked the answer as "accepted" yet. I did upvote it, but it's not going to show because I am new here and have less than 15 reputations. In any case, in reference to how you got it to work, is there an alternative to modifying PYTHONPATH? I want to solve this problem without modifying any "path" so the code can be transported as it is and used by others without making any modification to their "paths". Thanks again!Isola
@Isola Hmm.. I don't think there is any other way. If you read the Python docs for the the Module Search Path, that's how the interpreter finds the files you import. If you want to make the code portable and avoid others doing extra steps, you can modify sys.path internally inside man1test.py, before you import man1.py. The path modification will only take effect until your program is running, it's not going to affect your user's env. The user just needs to make sure the folders are copied correctly.Commove
Is there any reason why, in your example module structure, |- Soft has an |- init.py' but |- SoftLib does not?Tirado
L
10

I had a similar problem, although not the same.

My folders and files had the structure (GUI_ML is the main folder):

GUI_ML
\ Views \ login.py
\ Views \ __ init __.py
\ Controllers \ Control_login.py
\ Controllers \ __ init __.py

I needed to import Control_login.py from login.py. The following code solved my problem:

import sys
import os
myDir = os.getcwd()
sys.path.append(myDir)

from pathlib import Path
path = Path(myDir)
a=str(path.parent.absolute())

sys.path.append(a)

from Controllers.Control_login import Control_login
Liscomb answered 7/12, 2021 at 19:10 Comment(0)
K
3

For me when I created a file and saved it as python file, I was getting this error during importing. I had to create a filename with the type ".py" , like filename.py and then save it as a python file. post trying to import the file worked for me.

Kennel answered 3/7, 2020 at 8:51 Comment(0)
E
1

some package name and module name might be the same. this conflict is also responded with this error.

Elastic answered 19/11, 2021 at 11:53 Comment(0)
B
0

I was having a similar issue where I forgot to put a __init__.py in the module and spend a lot of time wasting trying to understand what's wrong.

Barbet answered 3/2, 2022 at 21:27 Comment(0)
D
0

Another solution depends on where you run this code from.

Assuming the OP's directory structure:

man/                          
  Mans/                  
          man1.py
  MansTest/
          SoftLib/
                  Soft/
                      SoftWork/
                              manModules.py
          Unittests/
                    man1test.py

If you try running python Mans/man1.py (from the man directory), you would have to do the following two things for this to work:

  1. Change the import line in man1.py to from ManTest.SoftLib.Soft.SoftWork.manModules import *
  2. If not already there, add to your system environment variable. Key PYTHONPATH, value .
Diffusion answered 5/7, 2022 at 15:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.