Joril
Joril

Reputation: 20576

How can I do relative imports in Python?

Imagine this directory structure:

app/
   __init__.py
   sub1/
      __init__.py
      mod1.py
   sub2/
      __init__.py
      mod2.py

I'm coding mod1, and I need to import something from mod2. How should I do it?

I tried from ..sub2 import mod2, but I'm getting an "Attempted relative import in non-package".

I googled around, but I found only "sys.path manipulation" hacks. Isn't there a clean way?


All my __init__.py's are currently empty

I'm trying to do this because sub2 contains classes that are shared across sub packages (sub1, subX, etc.).

The behaviour I'm looking for is the same as described in PEP 366 (thanks John B).

Upvotes: 629

Views: 434194

Answers (17)

alelom
alelom

Reputation: 3018

I wrote a package to solve this nasty issue, Sysappend:

https://pypi.org/project/sysappend/

This appends every folder in your repository to the sys.path variable, so Python is able to import any folder.

If you use this package, you don't need to pollute your project with __init__.py files, and you will unlock relative imports (both parent and child folders) from anywhere, without all the fuss. You will get correctly working debug, test and deploy features without going mad. At the same time, you get correct type-hinting, syntax highlighting, and module highlights.

This package does require you to add a short one-liner to the top of all your Python files (it doesn't need to be all files -- but it's convenient and can be enforced with CI/CD, GitHub actions or simply added via a quick copy-paste).

In my opinion, this solves the super-nasty import and relative package management in an developer-friendly way which basic Python fails to deliver.

How to use it

  1. pip install sysappend
  2. Add an if True: import sysappend; sysappend.all() statement at the top of every python file in your repo. (It doesn't need to be every file, but it's just easier for convenience, and the function caches results avoiding redundant computation, so it doesn't slow the code down).

Recommended folder referencing when importing

You should always try to reference the folders in the same way.
For example, use one (or few) agreed-upon primary source code folder from which to reference the sub-directories and stick to that convention.

E.g., if your directory looks like the below, you could pick src to be the primary source code folder:

sample_project/
    src/
        sub_folder_1/
            utils.py
        sub_folder_2/
            models.py
    app.py

So, when you write imports, they should start from the primary src folder.

E.g., in your app.py file, you should do:

from src.sub_folder_1.utils import somefunction

and you should do the same thing in models.py:

from src.sub_folder_1.utils import somefunction

Do not use a sub_folder as a starting name for an import, i.e., do not do from sub_folder_1.utils import somefunction. Although it will may still work in most cases, it may fail when you do type comparisons or deserialization, as Python looks at the import path to compare/deserialize types.

Quality of life setting for editors like VSCode

If you're using an editor like VSCode, you may want to add the main primary source code folder(s) to your settings.json file, like:

    "python.autoComplete.extraPaths": [
        "./src",
    ]

This will help the autocomplete to reference the folders always starting from src, in the same way as explained above -- i.e., you won't get automatic completion that attempt to do imports from sub folders.

Upvotes: 0

John B
John B

Reputation: 4216

The problem is that you're running the module as '__main__' by passing the mod1.py as an argument to the interpreter.

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.

In Python 2.6, they're adding the ability to reference modules relative to the main module. PEP 366 describes the change.

Upvotes: 388

Clark
Clark

Reputation: 42

What a debate!

I am a relative newcomer to Python (but years of programming experience, and dislike of Perl) and am a relative layperson when it comes to the dark art of Apache setup, but I know what I (think I) need to get my little experimental projects working at home.

Here is my summary of what the situation seems to be.

If I use the -m 'module' approach, I need to:

  1. dot it all together;
  2. run it from a parent folder;
  3. lose the '.py';
  4. create an empty (!) __init__.py file in every subfolder.

How does that work in a CGI environment, where I have aliased my scripts directory, and want to run a script directly as /dirAlias/cgi_script.py??

Why is amending sys.path a hack? The Python documentation page states: "A program is free to modify this list for its own purposes." If it works, it works, right? The bean counters in Accounts don't care how it works.

I just want to go up one level and down into a 'modules' directory:

.../py
      /cgi
      /build
      /modules

So my 'modules' can be imported from either the CGI world or the server world.

I've tried the -m/modules approach but I think I prefer the following (and am not confused how to run it in CGI space):

  1. Create XX_pathsetup.py in the /path/to/python/Lib directory (or any other directory in the default sys.path list). 'XX' is some identifier that declares an intent to set up my path according to the rules in the file.

  2. In any script that wants to be able to import from the 'modules' directory in above directory config, simply import XX_pathsetup.py.

And here's my really simple XX_pathsetup.py file:

import sys, os
pypath = sys.path[0].rsplit(os.sep, 1)[0]
sys.path.insert(0, pypath + os.sep + 'modules')

It is not a 'hack', IMHO. It is one small file to put in the Python 'Lib' directory and one import statement which declares intent to modify the path search order.

Upvotes: -3

suhailvs
suhailvs

Reputation: 21730

Explanation of nosklo's answer with examples:

Note: all __init__.py files are empty.

main.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       fun_a.py
    package_b/ ->
       __init__.py
       fun_b.py

app/package_a/fun_a.py

def print_a():
    print 'This is a function in dir package_a'

app/package_b/fun_b.py

from app.package_a.fun_a import print_a
def print_b():
    print 'This is a function in dir package_b'
    print 'going to call a function in dir package_a'
    print '-'*30
    print_a()

main.py

from app.package_b import fun_b
fun_b.print_b()

If you run python main.py it returns:

This is a function in dir package_b
going to call a function in dir package_a
------------------------------
This is a function in dir package_a
  • main.py does: from app.package_b import fun_b
  • fun_b.py does from app.package_a.fun_a import print_a

so file in folder package_b used file in folder package_a, which is what you want. Right??

Upvotes: 29

LondonRob
LondonRob

Reputation: 78923

As EvgeniSergeev says in the comments to the OP, you can import code from a .py file at an arbitrary location with:

import imp

foo = imp.load_source('module.name', '/path/to/file.py')
foo.MyClass()

This is taken from this SO answer.

Upvotes: 8

Gabriel
Gabriel

Reputation: 1106

On top of what John B said, it seems like setting the __package__ variable should help, instead of changing __main__ which could screw up other things. But as far as I could test, it doesn't completely work as it should.

I have the same problem and neither PEP 328 nor PEP 366 solve the problem completely, as both, by the end of the day, need the head of the package to be included in sys.path, as far as I could understand.

Upvotes: 2

Andrew_1510
Andrew_1510

Reputation: 13556

I found it's easier to set the "PYTHONPATH" environment variable to the top folder:

bash$ export PYTHONPATH=/PATH/TO/APP

Then:

import sub1.func1
# ...more imports

Of course, PYTHONPATH is "global", but it didn't raise trouble for me yet.

Upvotes: 1

iElectric
iElectric

Reputation: 5819

Use:

def import_path(fullpath):
    """ 
    Import a file with full path specification. Allows one to
    import from anywhere, something __import__ does not do. 
    """
    path, filename = os.path.split(fullpath)
    filename, ext = os.path.splitext(filename)
    sys.path.append(path)
    module = __import__(filename)
    reload(module) # Might be out of date
    del sys.path[-1]
    return module

I'm using this snippet to import modules from paths.

Upvotes: 24

F.M.F.
F.M.F.

Reputation: 2300

A hacky way to do it is to append the current directory to the PATH at runtime as follows:

import pathlib   
import sys
sys.path.append(pathlib.Path(__file__).parent.resolve())
import file_to_import  # the actual intended import

In contrast to another solution for this question this uses pathlib instead of os.path.

Upvotes: -1

This is solved 100%:

  • app/
    • main.py
  • settings/
    • local_setings.py

Import settings/local_setting.py in app/main.py:

main.py:

import sys
sys.path.insert(0, "../settings")


try:
    from local_settings import *
except ImportError:
    print('No Import')

Upvotes: 48

lesnik
lesnik

Reputation: 2757

"Guido views running scripts within a package as an anti-pattern" (rejected PEP-3122)

I have spent so much time trying to find a solution, reading related posts here on Stack Overflow and saying to myself "there must be a better way!". Looks like there is not.

Upvotes: 55

Pankaj
Pankaj

Reputation: 3712

Here is the solution which works for me:

I do the relative imports as from ..sub2 import mod2 and then, if I want to run mod1.py then I go to the parent directory of app and run the module using the python -m switch as python -m app.sub1.mod1.

The real reason why this problem occurs with relative imports, is that relative imports works by taking the __name__ property of the module. If the module is being directly run, then __name__ is set to __main__ and it doesn't contain any information about package structure. And, thats why python complains about the relative import in non-package error.

So, by using the -m switch you provide the package structure information to python, through which it can resolve the relative imports successfully.

I have encountered this problem many times while doing relative imports. And, after reading all the previous answers, I was still not able to figure out how to solve it, in a clean way, without needing to put boilerplate code in all files. (Though some of the comments were really helpful, thanks to @ncoghlan and @XiongChiamiov)

Hope this helps someone who is fighting with relative imports problem, because going through PEP is really not fun.

Upvotes: 170

milkypostman
milkypostman

Reputation: 3043

Let me just put this here for my own reference. I know that it is not good Python code, but I needed a script for a project I was working on and I wanted to put the script in a scripts directory.

import os.path
import sys
sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))

Upvotes: 10

Garrett Berg
Garrett Berg

Reputation: 2635

This is unfortunately a sys.path hack, but it works quite well.

I encountered this problem with another layer: I already had a module of the specified name, but it was the wrong module.

what I wanted to do was the following (the module I was working from was module3):

mymodule\
   __init__.py
   mymodule1\
      __init__.py
      mymodule1_1
   mymodule2\
      __init__.py
      mymodule2_1


import mymodule.mymodule1.mymodule1_1  

Note that I have already installed mymodule, but in my installation I do not have "mymodule1"

and I would get an ImportError because it was trying to import from my installed modules.

I tried to do a sys.path.append, and that didn't work. What did work was a sys.path.insert

if __name__ == '__main__':
    sys.path.insert(0, '../..')

So kind of a hack, but got it all to work! So keep in mind, if you want your decision to override other paths then you need to use sys.path.insert(0, pathname) to get it to work! This was a very frustrating sticking point for me, allot of people say to use the "append" function to sys.path, but that doesn't work if you already have a module defined (I find it very strange behavior)

Upvotes: 13

jung rhew
jung rhew

Reputation: 870

From Python doc,

In Python 2.5, you can switch import‘s behaviour to absolute imports using a from __future__ import absolute_import directive. This absolute- import behaviour will become the default in a future version (probably Python 2.7). Once absolute imports are the default, import string will always find the standard library’s version. It’s suggested that users should begin using absolute imports as much as possible, so it’s preferable to begin writing from pkg import string in your code

Upvotes: 2

mossplix
mossplix

Reputation: 3865

Take a look at http://docs.python.org/whatsnew/2.5.html#pep-328-absolute-and-relative-imports. You could do

from .mod1 import stuff

Upvotes: 8

nosklo
nosklo

Reputation: 223102

main.py
setup.py
app/ ->
    __init__.py
    package_a/ ->
       __init__.py
       module_a.py
    package_b/ ->
       __init__.py
       module_b.py
  1. You run python main.py.
  2. main.py does: import app.package_a.module_a
  3. module_a.py does import app.package_b.module_b

Alternatively 2 or 3 could use: from app.package_a import module_a

That will work as long as you have app in your PYTHONPATH. main.py could be anywhere then.

So you write a setup.py to copy (install) the whole app package and subpackages to the target system's python folders, and main.py to target system's script folders.

Upvotes: 133

Related Questions