19

I know that there are lots of questions about the same import issues in Python but it seems that nobody managed to provide a clear example of correct usage.

Let's say that we have a package mypackage with two modules foo and bar. Inside foo we need to be able to access bar.

Because we are still developing it, mypackage is not in sys.path.

We want to be able to:

  • import mypackage.foo
  • run foo.py as a script and execute the sample usage or tests from the __main__ section.
  • use Python 2.5

How do we have to do the import in foo.py in order to be sure it will work in all these cases.

# mypackage/__init__.py
...

# mypackage/foo/__init__.py
...

# mypackage/bar.py  
def doBar()
    print("doBar")

# mypackage/foo/foo.py
import bar # fails with module not found
import .bar #fails due to ValueError: Attempted relative import in non-package

def doFoo():
    print(doBar())

if __name__ == '__main__':
    doFoo()
Ani Menon
  • 27,209
  • 16
  • 105
  • 126
sorin
  • 161,544
  • 178
  • 535
  • 806
  • I believe you have to distinguish importable modules from scripts. I'm not sure I agree with that design, but that's my understanding of it. – Thomas K Nov 28 '11 at 17:59
  • Usually, when you need to import packages from upper level, you are doing something wrong: would you instead want to use `bar` as a library rather than as a submodule to mypackage? I.e. could you refactor mypackage into mypackage1 and mypackage2 where mypackage2 (with foo) imports mypackage1 (with bar)? – jsalonen Nov 28 '11 at 19:01

2 Answers2

32

Take a look at the following info from PEP 328:

Relative imports use a module's __name__ attribute to determine that module's position in the package hierarchy. 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.

When you run foo.py as a script, that module's __name__ is '__main__', so you cannot do relative imports. This would be true even if mypackage was on sys.path. Basically, you can only do relative imports from a module if that module was imported.

Here are a couple of options for working around this:

1) In foo.py, check if __name__ == '__main__' and conditionally add mypackage to sys.path:

if __name__ == '__main__':
    import os, sys
    # get an absolute path to the directory that contains mypackage
    foo_dir = os.path.dirname(os.path.join(os.getcwd(), __file__))
    sys.path.append(os.path.normpath(os.path.join(foo_dir, '..', '..')))
    from mypackage import bar
else:
    from .. import bar

2) Always import bar using from mypackage import bar, and execute foo.py in such a way that mypackage is visible automatically:

$ cd <path containing mypackage>
$ python -m mypackage.foo.foo
Andrew Clark
  • 202,379
  • 35
  • 273
  • 306
  • Thanks! The only thing that is still unclear is the detection of the path to the package. I will check tomorrow and be back with the results. – sorin Nov 28 '11 at 21:33
  • @sorin - I have just edited my answer so you can get the path to mypackage regardless of where you call foo.py from. – Andrew Clark Nov 28 '11 at 22:53
  • On Windows I found few cases where `getcwd()` is not providing the script directory. There are at least 3 ways of calling a python script: "python a.py", "a.py", and running from Windows Explorer (eventually via pythonw) – sorin Nov 28 '11 at 22:59
  • @sorin - Unfortunately I am only able to test on Linux, the behavior I see is that `__file__` is either an absolute path, or the path to `foo.py` from `os.getcwd()`. In both of these cases, `foo_dir` is an absolute path to the directory containing `foo.py`. Not sure how it needs to change if this isn't the case for Windows. – Andrew Clark Nov 28 '11 at 23:03
  • @fj I discovered that your approach doesn't work if inside foo.py I need to use something from bar, take a look at the updated example. – sorin Nov 29 '11 at 11:03
  • 1
    @sorin - If you need to use `doBar` from bar, you either need to import it to foo's namespace with `from mypackage.bar import doBar` or use `from mypackage import bar` and call it as `bar.doBar()`. – Andrew Clark Nov 29 '11 at 17:24
7

My solution looks a bit cleaner and can go at the top, with all the other imports:

try:
   from foo import FooClass
except ModuleNotFoundError:
   from .foo import FooClass
Riley Martine
  • 193
  • 2
  • 9