10

I have this file structure:

/home/test
         ├── dirA
         │   └── ClassA.py
         └── dirB
             └── Main.py

With the following content in the files:

ClassA.py:

class ClassA:
    def __str__(self):
        return 'Hi'

Main.py:

from dirA.ClassA import ClassA

class Main:

    def main():
        a = ClassA()

if __name__ == '__main__':
    Main.main()

I change the current dir to:

$ cd /home/test/dirB

This works:

$ PYTHONPATH=/home/test python Main.py

This doesn't:

$ python Main.py

Traceback (most recent call last):
  File "Main.py", line 1, in <module>
    from dirA.ClassA import ClassA
ModuleNotFoundError: No module named 'dirA'

Adding this lines in Main.py has no effect:

import os, sys
# Get the top level dir.
path = os.path.dirname(os.path.dirname(__file__)) 
sys.path.append(path)

The module still can't be found! There are plenty of similar questions but I couldn't make this work programmatically (skipping the PYTHONPATH env var.) I understand that dirs are not modules, files are but this works in PyCharm (is the IDE fixing PYTHONPATH?)

John Difool
  • 5,572
  • 5
  • 45
  • 80
  • Are you adding `os.path.append(path)` before or after you call `from dirA.ClassA import ClassA`? – zwer Apr 24 '18 at 23:31
  • Before. I inserted it between the `if` and the call to `Main.main()`. – John Difool Apr 24 '18 at 23:39
  • Oh, I am getting your question. You are saying `from ...` is called even before the Class instance invocation? – John Difool Apr 24 '18 at 23:40
  • Yes. You need to alter your `sys.path` **before** you attempt to import anything that might depend on that path. Whether you're calling something from the imported file is inconsequential - the import itself will fail much before that. – zwer Apr 24 '18 at 23:41
  • I tried to put it on top and check that it's call with the debugger before the `from` import. It still doesn't work . – John Difool Apr 24 '18 at 23:47

3 Answers3

9

You need to make sure that you've altered your sys.path before you attempt to load any package that might depend on the altered path - otherwise your script will fail the moment it encounters and import statement. In other words, make sure your Main.py begins as:

import os
import sys

path = os.path.join(os.path.dirname(__file__), os.pardir)
sys.path.append(path)

from dirA.ClassA import ClassA

To ensure that the last import statement operates on the altered path.

zwer
  • 24,943
  • 3
  • 48
  • 66
1

Thanks for all the help. Per suggestion, I added the appended path as the first statement. That still didn't work. But then I used:

sys.path.append(sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))))

And this suddenly worked. Still not sure why the abspath makes a difference as the printed path I got was already absolute for __file__.

John Difool
  • 5,572
  • 5
  • 45
  • 80
1

Building on the answer given by @zwer, we need to check if the script is being run in interactive mode or not. If it is, then we can use the approach mentioned by @zwer. If it is not, we can use the hard coded way (which is not always foolproof.

# https://stackoverflow.com/questions/16771894/python-nameerror-global-name-file-is-not-defined
if '__file__' in vars():
    print("We are running the script non interactively")
    path = os.path.join(os.path.dirname(__file__), os.pardir)
    sys.path.append(path)    
else:
    print('We are running the script interactively')
    sys.path.append("..")
Nikhil Gupta
  • 1,436
  • 12
  • 15