1

I use Python 3.5 and my project structure is like this:

Project/
    App/
        myApp.py
    Libraries/
        myLibA.py
        myLibB.py

I want to import myLibA.py in myApp.py

If I simply write from Libraries import myLibA I end up with this error :

ImportError: No module named Libraries.

I found the Q/A Importing files from different folder in Python, from which I adapted my own solution, adding this at the beginning of myApp.py in order to add my Project folder to the Python Path :

import sys
sys.path.insert(0, sys.path[0] + "..")

This worked well on Windows, but when I run myApp.py from the same project on OSX (10.9) I see the same error message, my module is not found.

To reproduce my issue it's very simple. Just fill the Python files like this :

myApp.py :

import sys
sys.path.insert(0, sys.path[0] + "..")

from Libraries import myLibA

if __name__ == '__main__':
    myLibA.print_hello()

myLibA.py :

def print_hello():
    print("Hello")

I don't understand why the Python Path method doesn't work here. Anyway, I'm looking for a solution that keeps the Python file compatible with Windows and that is contained in the sources (in the Python files). I've seen a few console hooks but I'm not satisfied with that because I want to be able to clone the project on any OSX/Windows PC with Python 3.5, and just run myApp.py. I'm looking for a solution that doesn't involve any library not natively present in Python 3.5.

Is there another way to achieve this ?

(If not, is it because it is somehow not pythonic to organize a project like this? As a C developer I might have the wrong approach)

Community
  • 1
  • 1
Tim
  • 1,853
  • 2
  • 24
  • 36
  • Does `Libraries` has an `__init__.py` in it? If not then an empty one will do: `touch __init__.py`. Second: don't use relative path names (..), you are asking for trouble, use absolute path names, or use environment variables (like `os.environ['HOME']`). – cdarke Nov 30 '16 at 10:37
  • @cdarke they don't, I thought it was a Python 2 thing. The problem with an absolute path is that I don't know in advance at which location the project will be cloned. – Tim Nov 30 '16 at 10:39
  • You also will not know the current directory of the process running `python` either. Should it be relative to the directory that your script resides in, for example? As a C programmer, you wouldn't do a `#include('../fred.h')` would you? – cdarke Nov 30 '16 at 10:41
  • @cdarke That's right I assumed the current process directory would be the one containing myApp.py. As a C programmer I'd just include "fred.h" and tell the linker to look for the files in all the project folders. – Tim Nov 30 '16 at 11:25

3 Answers3

4

Add __init__.py to your Libraries directory. That file can be empty, and it magically turns the Libraries directory into a package. Now:

import sys
import os.path
libdir = os.path.dirname(__file__)
sys.path.append(os.path.split(libdir)[0])   

from Libraries import myLibA

if __name__ == '__main__':
    myLibA.print_hello()

The __file__ special variable gives the filename of the current script. os.path.dirname() gives the directory it resides in, and os.path.split(libdir)[0] gives its parent directory. This should work wherever the script is called from.

I am using append rather than insert. It is generally advised that user directories are searched last, and also append is more efficient than insert in the C Python implementation.

cdarke
  • 42,728
  • 8
  • 80
  • 84
  • Thank you, this works fine both on Windows and OSX. I was sure that the __init__.py file was only required in Python 2, and that Python 3 wasn't using it. Was I wrong about this ? – Tim Nov 30 '16 at 11:39
  • Generally `__init__.py` is used on both. At 3.3 *Namespace Packages* were introduced, but those are for splitting a package across directories. I have not seen them used much and I don't think that is appropriate in your case. See https://www.python.org/dev/peps/pep-0420/. The point about `__init__.py` is that it can contain initialisation code for the *package*, including defining functions. – cdarke Nov 30 '16 at 14:05
  • Thank you for this explanation :-) – Tim Nov 30 '16 at 14:29
2

You should have __init__.py inside every folder that you want to import. Also a common project structure for a python project is this:

Project/ 
Libraries/         <-- this will contain app specific code (Libraries)
    __init__.py
    myLibA.py
    myLibB.py
main.py

Where main.py will contain the entry code of you app, just like C's main(), for example

from Libraries import myLibA

if __name__ == '__main__':
    myLibA.print_hello()

No need to tinker with sys.path in runtime.

giorgosp
  • 426
  • 4
  • 11
  • Thank you, that's the correct solution, though someone posted it a few seconds before you ;) Just correct me if I'm wrong, but if I don't modify that Path at runtime I have the risk that someone who clones the project on a location that is not in the Path will have an import error as long has he doesn't add the project location to the path, is that right ? – Tim Nov 30 '16 at 11:42
  • With the above structure you won't have any issues, there is no need to add anything to path. Just run `python /path/to/Project/main.py`. The "path" we refer to in python is the path where python looks for packages (folders with __init__py inside), and the folder of the main.py ("Project" in this case) is already in the python path. – giorgosp Nov 30 '16 at 13:37
  • Oh that's right thanks. But my files will not be at the same level than Libraries. I think that's why I need to modify the path. – Tim Nov 30 '16 at 13:38
  • if you insist to put them in an "app" directory, then go ahead, modify the path but I dont think it 's worth it. But instead of adding '..' in the path, search google for getting the folder of a file in python – giorgosp Nov 30 '16 at 13:45
  • The thing is that I oversimplified my current Project structure for asking this question, but I actually have a high amount of myApp.py files, all with a main function, etc. I put them in a subfolder to keep the Project folder as clean as possible. – Tim Nov 30 '16 at 13:47
2

This Question is somewhat similar. My favourite answer suggests a project structure like this in your case:

Project/
    myApp.py
    Libraries/
        __init__.py
        myLibA.py
        myLibB.py

With the library code in myLibA.py unchanged the app code and import would look like this:

from Libraries import myLibA

if __name__ == "__main__":
    myLibA.print_hello()

Edit 1:

When the requested folder structure is necessary, this change to the myApp.py file:

import sys
import os
sys.path.append(os.path.abspath(".."))

works too.

Community
  • 1
  • 1
Lukisn
  • 190
  • 8
  • Thanks for the answer. I think it would work, but I want to put myApp.py in a subfolder. I simplified my usecase a lot for asking the question, but actually I have plenty of files such as myApp1.py, myApp2.py, etc, so I prefer to put them all in a subfolder. – Tim Nov 30 '16 at 11:27