1069

I am running Python 2.5.

This is my folder tree:

ptdraft/
  nib.py
  simulations/
    life/
      life.py

(I also have __init__.py in each folder, omitted here for readability)

How do I import the nib module from inside the life module? I am hoping it is possible to do without tinkering with sys.path.

Note: The main module being run is in the ptdraft folder.

Edward
  • 1,062
  • 1
  • 17
  • 39
Ram Rachum
  • 84,019
  • 84
  • 236
  • 374
  • 1
    What's your PYTHONPATH setting? – S.Lott Apr 03 '09 at 14:18
  • 2
    Ross: I looked there. What should I do about it? I already have a `__init__.py`. S.Lott: I don't know how to check... – Ram Rachum Apr 03 '09 at 14:42
  • 4
    echo $PYTHONPATH from the shell; import sys; print sys.path from within Python. http://docs.python.org/tutorial/modules.html?highlight=pythonpath#modules – S.Lott Apr 03 '09 at 16:27
  • @FlipMcF Google is a bubbled search engine, so the fact that this result is pretty high up for you doesn't matter. Far more important is the fact that the non-bubbled search engine, DuckDuckGo, also ranks this very highly. – ArtOfWarfare May 17 '15 at 15:42
  • @FlipMcF There's nothing wrong with doing an absolute import like in hasen's answer. This is done hundreds of times in Django. It's absolutely acceptable. In some cases doing a relative import is nicer and shorter, so feel free to use these methods interchangeably. – Ram Rachum May 18 '15 at 16:30
  • @RamRachum My opinion has since changed to Absolute Imports. Relative imports are nice, but "Explicit is better than Implicit" and to a lesser extent "Namespaces are a 'honkin great idea" – FlipMcF May 20 '15 at 18:42
  • @FlipMcF but I heard relative imports is considered as bad behavior? – ArtificiallyIntelligence Jun 05 '18 at 00:15
  • 1
    You may want to take a look at this definitive guide https://chrisyeh96.github.io/2017/08/08/definitive-guide-python-imports.html – Kevin Zhu Jun 08 '18 at 07:29
  • 20
    I strongly recommend skipping past all `sys.path` or `PYTHONPATH` answers and checking out [np8's excellent answer](https://stackoverflow.com/a/50194143/1222951). Yes, it's a long read. Yes, it looks like a lot of work. But it's the only answer that actually solves the problem correctly and cleanly. – Aran-Fey Oct 30 '18 at 22:24
  • 8
    Whatever happened to executable pseudocode? Why is it such a pain to import modules from a parent folder in Python? This is absurd. – eric Feb 21 '19 at 05:38
  • 2
    @eric: It’s hard only if you’re being inconsistent: using a file layout appropriate to a **package** (a complicated, reusable library, of which your script is but a client) but trying to run it as a throwaway **script** (without even `python -m`). – Davis Herring Sep 27 '19 at 00:50
  • 12
    Why is this such a pain? After reading through all the discussion and answers, there's still no reasonable simple solution. – Apollys supports Monica Dec 13 '19 at 00:21
  • 1
    I'd argue that [tjk's answer](https://stackoverflow.com/a/64305165/7830459) is the simplest and most pythonic. If you're making a python function that imports other files, you aren't writing one-off scripts, so your 'script files' are actually just module files, and while you can run them as script files, it's presented as more of an additional feature in [the documentation](https://docs.python.org/3/tutorial/modules.html#executing-modules-as-scripts) than the 'correct' way to run them. – Alex Li Feb 08 '22 at 04:22
  • @ApollyssupportsMonica There is no pain. If you want to directly import code from outside of the package, you simply update the `sys.path`. Nothing difficult or hackish about it. Note that the standard way is to not directly import any outside code at all - rather to install that code as a dependency using the `pip` tool. – Jeyekomon Jul 08 '22 at 11:12
  • For pytest I've solved it on the pyproject.toml (details: https://stackoverflow.com/a/76682126/3521881) – Natacha Jul 13 '23 at 18:03

32 Answers32

747

You could use relative imports (Python >= 2.5):

from ... import nib

(What’s New in Python 2.5) PEP 328: Absolute and Relative Imports

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
f3lix
  • 29,500
  • 10
  • 66
  • 86
  • 306
    `ValueError: Attempted relative import in non-package` – endolith Oct 17 '13 at 01:46
  • 24
    @endolith: You have to be in a package, i.e., you must have an __init__.py file. – kirbyfan64sos Oct 17 '13 at 21:59
  • 60
    Attempted relative import beyond toplevel package – User Jun 20 '14 at 15:09
  • 2
    @btk There's nothing wrong with doing an absolute import like in hasen's answer. This is done hundreds of times in Django. It's absolutely acceptable. In some cases doing a relative import is nicer and shorter, so feel free to use these methods interchangeably. – Ram Rachum May 18 '15 at 16:30
  • 25
    See also the following answer, since adding `__init__.py` is not the only thing you have to do: http://stackoverflow.com/questions/11536764/attempted-relative-import-in-non-package-even-with-init-py/27876800#27876800 – Ben Farmer Oct 28 '15 at 07:45
  • 8
    To be *even* more precise, you need an `__init__.py` file. –  Mar 18 '18 at 14:49
  • 3
    @karadeniz Well, that's not more precise. In fact, you omitted the fact that you need a `__init__.py` in the parent directory. – John Strood Jun 26 '18 at 07:27
  • 4
    To summarise [this answer to a related question](http://stackoverflow.com/questions/11536764/attempted-relative-import-in-non-package-even-with-init-py/27876800#27876800): for relative or absolute imports to work (without nasty path hacks, or going to the trouble of installing as an editable package) you simply need to run using `python -m`, e.g. in this case `python -m ptdraft.nib`. All the imports then get correctly resolved, see [PEP338](https://www.python.org/dev/peps/pep-0338/). – TheBamf Oct 17 '19 at 16:28
  • Why can't we just stay with `import nib`? This works for me. – CLOUGH Jan 08 '20 at 06:56
  • 2
    how can we go up and then down? – Victor Ferreira Jan 11 '20 at 03:30
  • 8
    `ImportError: attempted relative import with no known parent package` – Christoph90 May 07 '21 at 07:28
  • @User that is a separate issue; please see https://stackoverflow.com/questions/30669474/beyond-top-level-package-error-in-relative-import . – Karl Knechtel Jun 09 '22 at 00:23
  • @Christoph90 please see https://stackoverflow.com/questions/14132789/relative-imports-for-the-billionth-time similarly. – Karl Knechtel Jun 09 '22 at 00:24
  • @VictorFerreira use the appropriate number of `.`s to go up, then use the folder names to go down. For example, another source file adjacent to `life.py` could import `life.py` like `from . import life`, or like `from ..life import life`, or like `from ...simulations.life import life`. It could not go further than this, unless a folder higher up were established as the package root. – Karl Knechtel Jun 09 '22 at 00:27
512

I posted a similar answer also to the question regarding imports from sibling packages. You can see it here.

Solution without sys.path hacks

Summary

  • Wrap the code into one folder (e.g. packaged_stuff)
  • Create a setup.py script where you use setuptools.setup().
  • Pip install the package in editable state with pip install -e <myproject_folder>
  • Import using from packaged_stuff.modulename import function_name

Setup

I assume the same folder structure as in the question

.
└── ptdraft
    ├── __init__.py
    ├── nib.py
    └── simulations
        ├── __init__.py
        └── life
            ├── __init__.py
            └── life.py

I call the . the root folder, and in my case it is located in C:\tmp\test_imports.

Steps

  1. Add a setup.py to the root folder -- The contents of the setup.py can be simply
    from setuptools import setup, find_packages

    setup(name='myproject', version='1.0', packages=find_packages())

Basically "any" setup.py would work. This is just a minimal working example.

  1. Use a virtual environment

If you are familiar with virtual environments, activate one, and skip to the next step. Usage of virtual environments are not absolutely required, but they will really help you out in the long run (when you have more than 1 project ongoing..). The most basic steps are (run in the root folder)

  • Create virtual env
    • python -m venv venv
  • Activate virtual env
    • . venv/bin/activate (Linux) or ./venv/Scripts/activate (Win)
  • Deactivate virtual env
    • deactivate (Linux)

To learn more about this, just Google out "python virtualenv tutorial" or similar. You probably never need any other commands than creating, activating and deactivating.

Once you have made and activated a virtual environment, your console should give the name of the virtual environment in parenthesis

PS C:\tmp\test_imports> python -m venv venv
PS C:\tmp\test_imports> .\venv\Scripts\activate
(venv) PS C:\tmp\test_imports>
  1. pip install your project in editable state

Install your top level package myproject using pip. The trick is to use the -e flag when doing the install. This way it is installed in an editable state, and all the edits made to the .py files will be automatically included in the installed package.

In the root directory, run

pip install -e . (note the dot, it stands for "current directory")

You can also see that it is installed by using pip freeze

(venv) PS C:\tmp\test_imports> pip install -e .
Obtaining file:///C:/tmp/test_imports
Installing collected packages: myproject
  Running setup.py develop for myproject
Successfully installed myproject
(venv) PS C:\tmp\test_imports> pip freeze
myproject==1.0
  1. Import by prepending mainfolder to every import

In this example, the mainfolder would be ptdraft. This has the advantage that you will not run into name collisions with other module names (from python standard library or 3rd party modules).


Example Usage

nib.py

def function_from_nib():
    print('I am the return value from function_from_nib!')

life.py

from ptdraft.nib import function_from_nib

if __name__ == '__main__':
    function_from_nib()

Running life.py

(venv) PS C:\tmp\test_imports> python .\ptdraft\simulations\life\life.py
I am the return value from function_from_nib!
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Niko Föhr
  • 28,336
  • 10
  • 93
  • 96
  • 1
    Why does this work? Is it changing the PYTHONPATH behind the scenes somehow? – Homero Esmeraldo Jan 22 '19 at 02:00
  • Your PYTHONPATH variable remains the same. You simply install your python code with pip (in an editable state). – Niko Föhr Jan 23 '19 at 14:35
  • 1
    And what does the installation process do? Isn't it including the root folder in the pythonpath? How is it helping python find the module nib.py when referred in life.py? – Homero Esmeraldo Jan 23 '19 at 17:11
  • 14
    Also, why is this better or more elegant than using the sys.path approach? – Homero Esmeraldo Jan 23 '19 at 17:23
  • I think PYTHONPATH is still untouched, since it always includes the installed modules. You can see the added package by using "pip freeze", but I don't know the exact magic behind installing packages in editable state. I cannot answer your questions in greater accuracy than this off the top of my head, sorry. – Niko Föhr Jan 23 '19 at 18:15
  • The elegancy is a matter of taste, I guess ;) I can not think of a situation where I would use the sys.path -append-method instead, but maybe there are proper use cases for that, too. – Niko Föhr Jan 23 '19 at 18:18
  • 12
    @HomeroBarrocasSEsmeraldo Good questions. PYTHONPATH env var is untouched. This installs into [site-packages](https://stackoverflow.com/a/52638888/674039), which is already on `sys.path` and where the code would typically be installed by end users. That's better than `sys.path` hacks (and you can include using PYTHONPATH in that category of path hacks) because it means the code in development should behave the same way for you as it does for other users. In contrast, when using path modifications, import statements would be resolved in a different way. – wim Mar 25 '19 at 21:21
  • 3
    how do I include it in the `requirements.txt`? – Emil Terman Jul 07 '19 at 14:57
  • 18
    This is by far, THE best solution I've read. This creates a link to your current directory so all updates to the code is directly reflected into your working directory. In the event you need to delete this entry, Manually delete the egg file and remove the entry from easy-install in dist-packages (site-packages if you used pip). – Rakshit Kothari Aug 22 '19 at 21:46
  • 2
    Removing the package is also possible with `pip uninstall package-name`, and package name can be read from `pip list`. – Niko Föhr Aug 23 '19 at 10:09
  • 1
    This answer really helped med a lot getting my data analysis app up and running. Thanks for the detailed description and the clear steps. – Jesper Joachim Sørensen Nov 05 '19 at 07:48
  • 1
    How do I bookmark this excellent answer? I frequently have this problem and forget the best practice, and probably on occasion miss this answer since it's not the selected one. Thanks for posting it! – dancow Nov 21 '19 at 22:42
  • @DanNguyen I have found the "share" (text) link below the answers being helpful. It is the direct link to the answer, and it makes bookmarking and sharing answers easier. – Niko Föhr Nov 22 '19 at 08:43
  • 48
    why making the python import headache even more complicated? We have to write a setup file, a virtual env, a pip install? omg! – Emerson Xu May 01 '20 at 09:17
  • This solution gave me a minor problem: https://stackoverflow.com/a/39733705/639739 – Grault Jul 18 '20 at 02:20
  • 1
    it's much better than the workaround sys.path.insert(0,'..'). If you use sys solution in a script, there will be a problem later if you want to import a module from that other file. I had this issue in Jupyter and this solution worked the bets. – mk_sch Jul 20 '20 at 20:48
  • 150
    Is this amount of work to import a module still necessary in 2020 on python 3.8? I cannot believe how complicated this is. – rocksNwaves Aug 05 '20 at 20:06
  • 5
    For smaller projects, sure the path hack works, while for larger ones this answer has helped me a great deal! Can you imaging tracking where you've edited sys.path within your code when you have 7 directories and 50+ files, and pushing it to git and having your teammates reusing your code becomes a disaster! Thank you np8! I believe this is the best solution that is most convenient in long term – Yunkai Xiao Aug 20 '20 at 20:21
  • 2
    [PEP 517](https://www.python.org/dev/peps/pep-0517/) does not support editable installs as noted on [setuptools](https://setuptools.readthedocs.io/en/latest/setuptools.html?highlight=517#setup-cfg-only-projects) . What is the safest, most flexible way of adding directories to python lookup path? I'd like to avoid sys.path and pythonpath hacks, and so far this is the best answer. – DanielTuzes May 12 '21 at 10:21
  • 1
    How would this work if your top level directory name contained dashes? The import with that name will not work seemingly – Fredrik Nilsson Jun 14 '21 at 15:29
  • 3
    @EmilTerman You can add it to `requirements.txt` by adding `-e .` on it's own line. I put this at the top of my `requirements.txt`. – bagelbits Jul 28 '21 at 20:01
  • 1
    Thanks! It's a frustrating how hard it is to import modules from nested packages and sibling directories – Danny B Jan 12 '22 at 18:58
  • 1
    If you're using conda as a package manager, you will need to run `conda develop .` rather than `pip install -e .` – Alex Li Jan 28 '22 at 17:14
  • I followed this approach to check if PyCharm sees the import (similar hierarchical order) : I did not manage to import upper-level modules/scripts : I can see the root directory for the import, but I cannot import "root_directory.package_name.script_X" it did not work in my case. Does this solution work only when inside venv? – Timbus Calin Mar 21 '22 at 08:23
  • Timbus: This works with and without virtual environments. You could make a separate SO question from your case if you wish. – Niko Föhr Mar 21 '22 at 08:52
  • 5
    This answer is *comprehensive*, but it misrepresents a simple task as complex. Virtual environments are something you should have set up anyway (to avoid interfering with a system Python), and installing projects is a fundamental skill. The answer really boils down to: "install the package for your project, and then the package root will be on the path so you can use `import` normally". Once this is set up, both relative (as long as you don't go past the package root) and absolute (starting from the package root) imports work. – Karl Knechtel Jun 09 '22 at 00:09
  • 4
    @KarlKnechtel This is not a simple task being representative as complex. This is complex and frankly awful in 2022. Python should generate a list of available module names from the `__init__.py` files in a small local project and make them available for importing but it doesn't. Making your entire project a package sounds overkill if you just want to reference functions from a few different files in different directories. – Ian Smith Sep 04 '22 at 01:59
  • @IanSmith Making the project a package is an implicit part of the process of *making it possible for other people to use your code*. `__init__.py` files are not required and actually do very little when empty. If the project has a folder structure that makes any of this non-trivial, it is inherently not a "small local project". It would be a very bad idea to make a flat list of "available" module names; that directly contravenes "Explicit is better than implicit" and "Namespaces are a honking great idea - let's do more of those!". The task is not complex and I already explained why. – Karl Knechtel Sep 04 '22 at 02:10
  • 1
    @KarlKnechtel Lol, if we want to quote aphorisms "If the implementation is hard to explain, it's a bad idea." Let's make namespaces easier by default :). Right now they are a pain. I have yet to see a 1 minute method on making them. If they were easy more people would talk about them and use them. Why am I forced to use a virtual environment? Is that necessary in Java, Typescript, Dart? Not really.. – Ian Smith Sep 04 '22 at 02:13
  • 1
    It's not hard to explain at all. You use a dotted path, just like attributes on objects - **because they are**; modules and packages are represented as objects, and modules and sub-packages are attached as attributes to their containing packages. And again - The answer really boils down to: "install the package for your project, and then the package root will be on the path so you can use import normally". That's a one-sentence explanation, and we have to understand `pip` and `venv` and packaging **anyway**. – Karl Knechtel Sep 04 '22 at 02:17
  • 1
    In my `pip freeze` I see a line for `-e git+https://github.com/...' instead of my local directory. How do I fix this? – azizj Dec 25 '22 at 18:31
  • While the idea of this is elegant, huge correction here: installing with pip in editable mode is *substantially different* than installing in the regular mode. It will create a symbolic link, **not** install in the `site-packages` folder! For this reason, while I went with this answer, I am installing it the regular way, not in editable mode. – David Cian Feb 06 '23 at 15:55
471

Relative imports (as in from .. import mymodule) only work in a package. To import 'mymodule' that is in the parent directory of your current module:

import os
import sys
import inspect

currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0, parentdir) 

import mymodule

Note: The __file__ attribute is not always given. Instead of using os.path.abspath(__file__) I suggest using the inspect module to retrieve the filename (and path) of the current file.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Remi
  • 20,619
  • 8
  • 57
  • 41
  • 190
    something a little shorter: `sys.path.insert(1, os.path.join(sys.path[0], '..'))` – b0bz Apr 22 '13 at 00:55
  • 9
    Any reason to avoid sys.path.append() instead of the insert? – Tyler May 19 '13 at 15:34
  • 3
    @Tyler - It can matter if somewhere else then the `parentdir`, but in one of the paths allready specified in `sys.path`, there is another module with the name 'mymodule'. Inserting the `parentdir` as the first element of the `sys.path` list assures that the module from `parentdir` will be imported instead. – Remi May 20 '13 at 18:48
  • 5
    @JHolta If you're not dealing with packages, yours is the best solution. – Jonathon Reinhart Oct 09 '13 at 00:47
  • 9
    @JHolta great idea. `sys.path.insert(1, os.path.realpath(os.path.pardir))` works too. – SpeedCoder5 Aug 07 '18 at 18:28
  • 3
    @SpeedCoder5 Actually, using sys.path[0] is necessary if your working directory is different from it. See https://stackoverflow.com/questions/714063/importing-modules-from-parent-folder/28712742#comment102640654_28712742. – Jimmy He Mar 29 '20 at 07:31
154

It seems that the problem is not related to the module being in a parent directory or anything like that.

You need to add the directory that contains ptdraft to PYTHONPATH

You said that import nib worked with you, that probably means that you added ptdraft itself (not its parent) to PYTHONPATH.

MERose
  • 4,048
  • 7
  • 53
  • 79
hasen
  • 161,647
  • 65
  • 194
  • 231
  • 63
    Having to add everything to pythonpath is not a good solution if you are working with a lot of packages that are never going to be reused. – Jason Cheng Sep 04 '19 at 18:16
  • 5
    @JasonCheng: Then why are they **packages**? – Davis Herring Sep 27 '19 at 00:52
  • 18
    For me this answer has worked out. However, to make it more explicit, the procedure is to `import sys` and then `sys.path.append("..\")` – BCJuan Nov 20 '19 at 16:12
  • 2
    @JasonCheng you don't have to export the modified PYTHONPATH or your shell profile, you can also choose to set it only for a single call to python (so it is only modified on a per-execution basis) or you can put it in some kind of .env file (per-folder basis). – niid Dec 08 '20 at 23:22
136

You can use an OS-dependent path in "module search path" which is listed in sys.path.

So you can easily add the parent directory like the following:

import sys
sys.path.insert(0, '..')

If you want to add the parent-parent directory,

sys.path.insert(0, '../..')

This works both in Python 2 and Python 3.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Shinbero
  • 1,545
  • 1
  • 9
  • 6
91

I don't know much about Python 2.
In Python 3, the parent folder can be added as follows:

import sys
sys.path.append('..')

...and then one is able to import modules from it.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Rob Ellenbroek
  • 1,075
  • 7
  • 3
  • 37
    This only works if your current working directory is such that `'..'` leads to the directory with the module in question. – user5359531 Aug 23 '17 at 21:53
  • @user5359531 is right. You probably want `sys.path.append(os.path.dirname(os.path.abspath(__file__)))` instead. This will work no matter what the working directory is. – Flimm Jan 12 '23 at 17:31
  • 1
    This doesn't work with VS Code @Flimm 's suggestion works with VS code. – Vespa Feb 02 '23 at 08:32
65

If adding your module folder to the PYTHONPATH didn't work, You can modify the sys.path list in your program where the Python interpreter searches for the modules to import, the Python documentation says:

When a module named spam is imported, the interpreter first searches for a built-in module with that name. If not found, it then searches for a file named spam.py in a list of directories given by the variable sys.path. sys.path is initialized from these locations:

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

After initialization, Python programs can modify sys.path. The directory containing the script being run is placed at the beginning of the search path, ahead of the standard library path. This means that scripts in that directory will be loaded instead of modules of the same name in the library directory. This is an error unless the replacement is intended.

Knowing this, you can do the following in your program:

import sys
# Add the ptdraft folder path to the sys.path list
sys.path.append('/path/to/ptdraft/')

# Now you can import your module
from ptdraft import nib
# Or just
import ptdraft
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ricardo Murillo
  • 2,775
  • 1
  • 18
  • 12
  • 8
    Your answer is good, but may not always work and is not very portable. If the program was moved to a new location, `/path/to/ptdraft` would have to be edited. There are solutions that work out the current directory of the file and import it from the parent folder that way as well. – Edward Nov 14 '15 at 10:59
  • 1
    @Edward what are those techniques? – Shuklaswag Sep 16 '18 at 01:37
  • 1
    @Shuklaswag for example, the answer https://stackoverflow.com/questions/714063/importing-modules-from-parent-folder/33532002#33532002. – Edward Sep 22 '18 at 09:38
38

Here is an answer that's simple so you can see how it works, small and cross-platform. It only uses built-in modules (os, sys and inspect), so it should work on any operating system (OS) because Python is designed for that.

Shorter code for answer - fewer lines and variables

from inspect import getsourcefile
import os.path as path, sys
current_dir = path.dirname(path.abspath(getsourcefile(lambda:0)))
sys.path.insert(0, current_dir[:current_dir.rfind(path.sep)])
import my_module  # Replace "my_module" here with the module name.
sys.path.pop(0)

For fewer lines than this, replace the second line with import os.path as path, sys, inspect. Add inspect. at the start of getsourcefile (line 3) and remove the first line.

  • however this imports all of the module so it could need more time, memory and resources.

The code for my answer (longer version)

from inspect import getsourcefile
import os.path
import sys

current_path = os.path.abspath(getsourcefile(lambda:0))
current_dir = os.path.dirname(current_path)
parent_dir = current_dir[:current_dir.rfind(os.path.sep)]

sys.path.insert(0, parent_dir)

import my_module  # Replace "my_module" here with the module name.

It uses an example from a Stack Overflow answer How do I get the path of the current executed file in Python? to find the source (filename) of running code with a built-in tool.

from inspect import getsourcefile
from os.path import abspath

Next, wherever you want to find the source file from you just use:

abspath(getsourcefile(lambda:0))

My code adds a file path to sys.path, the Python path list because this allows Python to import modules from that folder.

After importing a module in the code, it's a good idea to run sys.path.pop(0) on a new line when that added folder has a module with the same name as another module that is imported later in the program. You need to remove the list item added before the import, not other paths.

If your program doesn't import other modules, it's safe to not delete the file path because after a program ends (or restarting the Python shell), any edits made to sys.path disappear.

Notes about a filename variable

My answer doesn't use the __file__ variable to get the file path/filename of running code because users here have often described it as unreliable. You shouldn't use it for importing modules from parent folder in programs used by other people.

Some examples where it doesn't work (quote from this Stack Overflow question):

• it can't be found on some platforms. It sometimes isn't the full file path

  • py2exe doesn't have a __file__ attribute, but there is a workaround
  • When you run from IDLE with execute() there is no __file__ attribute
  • OS X 10.6 where I get NameError: global name '__file__' is not defined
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Edward
  • 1,062
  • 1
  • 17
  • 39
  • 1
    I did this and removed the new path entry with `sys.path.pop(0)` immediately after importing the desired module. However, subsequent imports still went to that location. For example, I have `app/config`, `app/tools` and `app/submodule/config`. From `submodule`, I insert `app/` to import `tools`, then remove `app/` and try to import `config`, but I get `app/config` instead of `app/submodule/config`. – user5359531 Aug 23 '17 at 21:59
  • I figured out that one of `tools`'s imports was also importing `config` from the parent dir. So when I later tried to do `sys.path.pop(0); import config` inside `submodule`, expecting to get `app/submodule/config`, I was actually getting `app/config`. Evidently Python returns a cached version of a module with the same name instead of actually checking the `sys.path` for a module matching that name. `sys.path` in this case was being altered correctly, Python was just not checking it due to the same-named module already having been loaded. – user5359531 Aug 24 '17 at 23:05
  • I think this is the exact reference to the issue I had: https://docs.python.org/3/reference/import.html#the-module-cache – user5359531 Aug 24 '17 at 23:11
  • Some useful information from *5.3.1. The module cache* on the [documentation page](https://docs.python.org/3/reference/import.html#the-module-cache): During import, the module name is looked up in `sys.modules` *...* sys.modules is writable. Deleting a key will invalidate the cache entry for the named module, causing Python to search anew upon its next import. *...* Beware though, as if you keep a reference to the module object, invalidate its cache then re-import, the two objects will be different. By contrast, `importlib.reload()` will reuse and reinitialise the module contents *...* – Edward Aug 25 '17 at 03:30
  • Even shorter: `os.path.realpath(getsourcefile(lambda:0) + "/../..")`. Will get current source file appended with two parent directory symbols. I didn't use `os.path.sep` as `getsourcefile` was returns a string using `/` even on Windows. `realpath` will take care of popping off two directory entries from the full path (in this case, the filename, and then the current directory) which gives the parent directory. – Cobertos Sep 23 '17 at 18:06
37

The pathlib library (included with >= Python 3.4) makes it very concise and intuitive to append the path of the parent directory to the PYTHONPATH:

import sys
from pathlib import Path
sys.path.append(str(Path('.').absolute().parent))
itmatters
  • 665
  • 6
  • 8
34

Here is a more generic solution that includes the parent directory into sys.path (it works for me):

import os.path, sys
sys.path.append(os.path.join(os.path.dirname(os.path.realpath(__file__)), os.pardir))
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Mike
  • 2,065
  • 25
  • 29
  • `import os, sys\n sys.path.insert(0,os.path.pardir)` same thing, but sorther :) (no line feeds in comments) – Anti Veeranna Sep 23 '14 at 09:47
  • @antiveeranna, if you use `os.path.pardir` you won't be getting the realpath of the parent, just relative path from where you're calling the `sys.path.insert()` – alvas Nov 27 '14 at 00:06
  • 2
    This answer uses the `__file__` variable which can be **unreliable** (isn't always the full file path, doesn't work on every operating system etc.) as StackOverflow users have often mentioned. Changing the answer to not include it will cause less problems and be more cross-compatible. For more information, see https://stackoverflow.com/a/33532002/3787376. – Edward Aug 29 '17 at 13:02
33

In a Jupyter Notebook (opened with JupyterLab or Jupyter Notebook)

As long as you're working in a Jupyter Notebook, this short solution might be useful:

%cd ..
import nib

It works even without an __init__.py file.

I tested it with Anaconda 3 on Linux and Windows 7.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Thomas R
  • 1,067
  • 11
  • 17
  • 9
    Wouldn't it make sense to add `%cd -` after the import, so the notebook's current directory stays unchanged. – Dror Apr 17 '20 at 07:24
  • 1
    It helps a lot especially when you need to import modules that also import other modules using relative paths. – Jianyu Oct 26 '21 at 14:22
25

I found the following way works for importing a package from the script's parent directory. In the example, I would like to import functions in env.py from app.db package.

.
└── my_application
    └── alembic
        └── env.py
    └── app
        ├── __init__.py
        └── db
import os
import sys
currentdir = os.path.dirname(os.path.realpath(__file__))
parentdir = os.path.dirname(currentdir)
sys.path.append(parentdir)
Yu N.
  • 1,765
  • 11
  • 9
  • 1
    So, a nice one-liner: `sys.path.append(os.path.dirname(os.path.dirname(os.path.realpath(__file__))))` – juzzlin Sep 12 '19 at 12:26
  • 1
    Modifying `sys.path` at all is usually a bad idea. You need some way (*e.g.*, `PYTHONPATH`) for your library to be accessible to *other* code (or else it’s not really a library); just use that all the time! – Davis Herring Sep 27 '19 at 00:45
17

The previous mentioned solutions are also fine. Another solution to this problem is:

If you want to import anything from top level directory. Then,

from ...module_name import *

Also, if you want to import any module from the parent directory. Then,

from ..module_name import *

Also, if you want to import any module from the parent directory. Then,

from ...module_name.another_module import *

This way you can import any particular method if you want to.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Ravin Gupta
  • 783
  • 7
  • 13
  • 16
    This seems to be like it should be, but for some reason this doesn't work in the same way as the `sys.path.append` hacks above and I cannot import my lib like this. This is what I first tried to do before starting to google :) Ok, it was this `ValueError: attempted relative import beyond top-level package` – juzzlin Sep 12 '19 at 12:30
  • 1
    I did some testing and it seems that `from ... import name` does not mean "import name from top-level", but "import name from the parent's parent". I created a nested directory structure `a1/.../a9` of nine directories where each one contains an `__init__.py` importing the next directory with `from . import next_dir`. The last directory had a file with `from ....... import b` which then imported `a1/a2/a3/b`. (tested with Python 3.8.2) – Cubi73 Aug 16 '20 at 20:49
  • Used this solution for our library tests. The accepted answer is indeed a proper way of doing things but if I understand correctly that would require exposing the modules which we don't want – Higgs Mar 04 '22 at 10:54
  • @juzzlin that is a separate issue. Please see https://stackoverflow.com/questions/30669474/beyond-top-level-package-error-in-relative-import. – Karl Knechtel Jun 09 '22 at 00:22
16

Two line simplest solution

import os, sys
sys.path.insert(0, os.getcwd()) 

If parent is your working directory and you want to call another child modules from child scripts.

You can import all child modules from parent directory in any scripts and execute it as

python child_module1/child_script.py
Vinoj John Hosan
  • 6,448
  • 2
  • 41
  • 37
  • 2
    This is the only one that worked for me and works best for my use-case: importing the main method from a folder of tests – Noah Gary Feb 28 '22 at 07:16
13

I think you can try this in that specific example, but in Python 3.6.3:

Enter image description here

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Gin
  • 629
  • 5
  • 9
  • ok, you can click this link to download 「import_case」to check the source code [case_repo](https://github.com/YaoandShun/SomeTestforHelp.git) – Gin Apr 26 '21 at 03:09
12

For completeness, there one simple solution. It's to run life.py as a module like this:

cd ptdraft
python -m simulations.life.life

This way you can import anything from nib.py as ptdraft directory is in the path.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
tjk
  • 339
  • 3
  • 8
  • This is great approach for those who don't want to mess up `sys.path` or environment variables like `PYTHONPATH` , for more detail about `-m` you can check out [this stackoverflow thread](https://stackoverflow.com/a/7610082/9853105) – Ham Nov 01 '21 at 04:17
  • If you're using vscode, you can do this automatically with the config at https://stackoverflow.com/questions/57455652/vscode-python-launch-current-file-as-module. – Alex Li Feb 06 '22 at 20:53
11
import sys
sys.path.append('../')
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Andong Zhan
  • 12,608
  • 4
  • 24
  • 24
11

For me the shortest and my favorite oneliner for accessing to the parent directory is:

sys.path.append(os.path.dirname(os.getcwd()))

or:

sys.path.insert(1, os.path.dirname(os.getcwd()))

os.getcwd() returns the name of the current working directory, os.path.dirname(directory_name) returns the directory name for the passed one.

Actually, in my opinion Python project architecture should be done the way where no one module from child directory will use any module from the parent directory. If something like this happens it is worth to rethink about the project tree.

Another way is to add parent directory to PYTHONPATH system environment variable.

zviad
  • 586
  • 7
  • 18
  • 2
    +1 for mentioning the possibility of having to restructure the project (as opposed to throwing a hack at the problem) – semore_1267 Apr 19 '18 at 17:00
  • 1
    this doesn't get the parent, it gets the current – Ricky Levi Jun 12 '19 at 10:56
  • This is a bad idea because it depends on the current directory and not on the location of the file, and therefore it will do different things depending on where you happen be running the script from. – jamesdlin Jun 25 '22 at 16:49
6

This is the same sort of style as the past answers, but in fewer lines :P

import os, sys
parentdir = os.path.dirname(__file__)
sys.path.insert(0, parentdir)

file returns the location you are working in.

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
YFP
  • 331
  • 3
  • 8
  • For me __file__ is the filename _without_ the path included. I run it using "ipy filename.py". – Curtis Yallop Mar 04 '14 at 21:26
  • Hi @CurtisYallop in the example above we are adding the dir that contains the file [that we are currently in] that the python file is in. The os.path.dirname call with the file name should return the path of the file and we are adding THAT to to the path and not the file explicitly - HTH's :-) – YFP Mar 11 '14 at 16:46
  • You are assuming that \_\_file\_\_ always contains the path plus the file. Sometimes it contains only the filename without the path. – Curtis Yallop Mar 12 '14 at 22:45
  • @CurtisYallop - not at all sir, I am assuming __file__ is the file name and I am using os.path.dirname() to get the path for the file. Have you got an example of this not working? I would love the steps to reproduce – YFP Mar 14 '14 at 15:27
  • You are assuming \_\_file\_\_ == "C:\dir\file.py". Not "file.py". For me, it is "file.py". os.path.dirname(x) returns the path for "x". It examines the string "x", parses it and pulls out the path at the start of the string. For "C:\dir\file.py", it is "C:\dir". For "file.py" is it blank. If there is no path included in the string, it cannot find any path. If you create file "file.py", insert the exact code from the above comment and execute it like this "python file.py" then parentdir is blank. – Curtis Yallop Mar 18 '14 at 16:48
  • 1
    @CurtisYallop - No I am not! I am saying that: print _/_/file/_/_ will give you the filename in your example above "file.py" - I am then saying that you can use os.path.dirname() to pull the full path from that. If you are calling from some weird location and you would like that relational then you can easily find your working dir through the os module -- Jeez! – YFP Mar 18 '14 at 17:36
  • This answer uses the `__file__` variable which can be **unreliable** (isn't always the full file path, doesn't work on every operating system etc.) as StackOverflow users have often mentioned. Changing the answer to not include it will cause less problems and be more cross-compatible. For more information, see https://stackoverflow.com/a/33532002/3787376. – Edward Aug 29 '17 at 13:09
  • @Edward Do you have an example of when `__file__` is reliable? I have been using it and haven't ran into problems with 3.7. The link you posted is around 7 years old, and was using Python 2.XX. I wonder if changes were made in Python3.5++ or so to make __file__ more reliable. Thank you. – Moondra Dec 09 '19 at 21:14
6

In a Linux system, you can create a soft link from the "life" folder to the nib.py file. Then, you can simply import it like:

import nib
Erel Segal-Halevi
  • 33,955
  • 36
  • 114
  • 183
  • 1
    This answer deserves more attentions. symlinks approach is orders of magnitude cleaner than most of the answers on this thread. yet, this is the first time I see someone suggests it. – Mahmoud Jan 08 '21 at 08:02
  • 4
    I disagree. This is a no-portable solution that also mixes file system layer with python source code. – problemofficer - n.f. Monica Aug 23 '21 at 13:58
6

I have a solution specifically for Git repositories.

First I used sys.path.append('..') and similar solutions. This causes especially problems if you are importing files which are themselves importing files with sys.path.append('..').

I then decided to always append the root directory of the Git repository. In one line it would look like this:

sys.path.append(git.Repo('.', search_parent_directories=True).working_tree_dir)

Or in more details like this:

import os
import sys
import git
def get_main_git_root(path):
    main_repo_root_dir = git.Repo(path, search_parent_directories=True).working_tree_dir
    return main_repo_root_dir
main_repo_root_dir = get_main_git_root('.')
sys.path.append(main_repo_root_dir)

For the original question: Based on what the root directory of the repository is, the import would be

import ptdraft.nib

or

import nib
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Thomas R
  • 1,067
  • 11
  • 17
2

Our folder structure:

/myproject
  project_using_ptdraft/
    main.py
  ptdraft/
    __init__.py
    nib.py
    simulations/
      __init__.py
      life/
        __init__.py
        life.py

The way I understand this is to have a package-centric view. The package root is ptdraft, since it's the top most level that contains __init__.py

All the files within the package can use absolute paths (that are relative to package root) for imports, for example in life.py, we have simply:

import ptdraft.nib

However, to run life.py for package dev/testing purposes, instead of python life.py, we need to use:

cd /myproject
python -m ptdraft.simulations.life.life

Note that we didn't need to fiddle with any path at all at this point.


Further confusion is when we complete the ptdraft package, and we want to use it in a driver script, which is necessarily outside of the ptdraft package folder, aka project_using_ptdraft/main.py, we would need to fiddle with paths:

import sys
sys.path.append("/myproject")  # folder that contains ptdraft
import ptdraft
import ptdraft.simulations

and use python main.py to run the script without problem.

Helpful links:

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Sida Zhou
  • 3,529
  • 2
  • 33
  • 48
2

Using pathlib.Path

import sys
from pathlib import Path
sys.path.append(str(Path(f"{__file__}").parent.parent))
import my_module
Tim
  • 71
  • 6
1

Work with libraries. Make a library called nib, install it using setup.py, let it reside in site-packages and your problems are solved. You don't have to stuff everything you make in a single package. Break it up to pieces.

user3181121
  • 119
  • 1
  • 2
  • 3
    Your idea is good but some people may want to bundle their module in with their code - perhaps to make it portable and not require putting their modules in `site-packages` every time they run it on a different computer. – Edward Nov 04 '15 at 21:11
1

I had a problem where I had to import a Flask application, that had an import that also needed to import files in separate folders. This is partially using Remi's answer, but suppose we had a repository that looks like this:

.
└── service
    └── misc
        └── categories.csv
    └── test
        └── app_test.py
    app.py
    pipeline.py

Then before importing the app object from the app.py file, we change the directory one level up, so when we import the app (which imports the pipeline.py), we can also read in miscellaneous files like a csv file.

import os,sys,inspect
currentdir = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe())))
parentdir = os.path.dirname(currentdir)
sys.path.insert(0,parentdir)

os.chdir('../')
from app import app

After having imported the Flask app, you can use os.chdir('./test') so that your working directory is not changed.

Casper Hansen
  • 443
  • 3
  • 10
1

It's seems to me that you don't really need to import the parent module. Let's imagine that in nib.py you have func1() and data1, you need to use in life.py

nib.py

import simulations.life.life as life
def func1():
   pass
data1 = {}
life.share(func1, data1)

life.py

func1 = data1 = None

def share(*args):
   global func1, data1
   func1, data1 = args

And now you have the access to func1 and data in life.py. Of course you have to be careful to populate them in life.py before you try to use them,

1

After removing some sys path hacks, I thought it might be valuable to add

My preferred solution.

Note: this is a frame challenge - it's not necessary to do in-code.

Assuming a tree,

project
└── pkg
    └── test.py

Where test.py contains

import sys, json; print(json.dumps(sys.path, indent=2)) 

Executing using the path only includes the package directory

python pkg/test.py
[
  "/project/pkg",
 ...
]

But using the module argument includes the project directory

python -m pkg.test
[
  "/project",
  ...
]

Now, all imports can be absolute, from the project directory. No further skullduggery required.

Wyrmwood
  • 3,340
  • 29
  • 33
0

I made this library to do this.

https://github.com/fx-kirin/add_parent_path

# Just add parent path
add_parent_path(1)

# Append to syspath and delete when the exist of with statement.
with add_parent_path(1):
   # Import modules in the parent path
   pass
fx-kirin
  • 1,906
  • 1
  • 20
  • 33
0

This is the simplest solution that works for me:

from ptdraft import nib
mj_whales
  • 124
  • 2
  • 11
0

The only solution that worked for me in the IDE (PyCharm) AND in Docker with the same imports is configuring the root folder in the .ini file, as per pytest official docs

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["."]

Context: project with 2 levels deep testing folders, trying to import main code from root project path (meaning, no src folder or similar). Need to make it work with CLI, OS independent, IDE independent and it needs to be able to import in docker with the same import sentences.

Natacha
  • 1,132
  • 16
  • 23
-1

Although it is against all rules, I still want to mention this possibility:

You can first copy the file from the parent directory to the child directory. Next import it and subsequently remove the copied file:

for example in life.py:

import os
import shutil

shutil.copy('../nib.py', '.')
import nib
os.remove('nib.py')

# now you can use it just fine:
nib.foo()

Of course there might arise several problems when nibs tries to import/read other files with relative imports/paths.

JLT
  • 712
  • 9
  • 15
  • 1
    This is a creative solution! I probably wouldn't use it though. – onewhaleid Nov 17 '20 at 18:17
  • 2
    I would strongly advise not to use this solution, but rather to make the parent directory a package as well or just append the parent directory to your `sys.path` (as suggested in the other answers). Copying/removing the file introduces a very tight dependency on the file location, which may be unnoticed when changing the project in the future (and thus makes the script less robust). Also it prevents the file from being cached and of course also creates slight overhead for the needed IO. – Kim Jan 04 '21 at 14:37
  • 2
    I understand people do downvote this post, since it is indeed bad practice, and I would never use it when writing a package or a reusable script, however it might still be worthwhile in some cases. For example, when you are running an interactive session and you just want to use some functions defined in a script in another location. I believe it is good practice that these kind of solutions are still mentioned on stackoverflow. – JLT Jan 05 '21 at 18:40
  • I understand your remark, @JLT and agree that it may be worth to include this on StackOverflow for reference. So why not update your answer to reflect your reasoning there, and mention some of the considerations/pitfalls which come with it? – Kim Jan 10 '21 at 15:32
-4

This works for me to import things from a higher folder.

import os
os.chdir('..')