8

if there is a directory /home/project/aaa. and I know that it is a Python package.

so, how can i import this module by just knowing its path.

means that, i hope that code is worked:

aaa = load_module("/home/project/aaa")

the only way i knew is that, adding /home/project to sys.path

but it maybe incur some problem:

if i add /home/project to sys.path

and if there a dir pytest in path /home/project.

then the official pytest package would not work.

i try importlib yet. but it seems that importlib can just import a file as a module, not a path.

so, i try it:

aaa = importlib.import_module("aaa", "/home/project")

or

aaa = importlib.import_module("aaa", "/home/project/aaa")

both of them are not work.

so, is there any other way to do what i want?

oh, i am using Python3.6


UPDATED AT 20170628

(NOTICE: there is a __init__.py in folder /home/project/aaa)

all solutions i known is that import module from a single file.

if there is bbb.py file in folder /home/project/aaa

then, add /home/project/aaa to sys.path or __path__ (whatever)

import bbb is worked, but not the import aaa

what i wanna ask is how to import folder (or directory) as a module.

for my example, folder aaa is consided as a module.

i wanna use import aaa not import {SOMETHING IN AAA}

Kilerd Chan
  • 151
  • 1
  • 2
  • 5
  • LMGTFY: https://askubuntu.com/questions/470982/how-to-add-a-python-module-to-syspath/471168 – Egor Biriukov Jun 27 '17 at 17:32
  • @EgorBiriukov: The questioner already knows how to add a directory to `sys.path`, and they have given a good reason to want a different option. – user2357112 Jun 27 '17 at 17:58

1 Answers1

14

Here are five different ways of how to accomplish that task.

For the following considerations I refer to Python 3.5+.


Register a custom Finder

Python uses finders for when importing modules. If a finder knows how to deal with a particular requested module then it returns a corresponding module spec and otherwise None. Python has three different finders already registered which can be found in sys.meta_path:

>>> import sys
>>> sys.meta_path
[<class '_frozen_importlib.BuiltinImporter'>, <class '_frozen_importlib.FrozenImporter'>, <class '_frozen_importlib_external.PathFinder'>]

The first one handles built-in modules, the second one frozen modules (some kind of "self-contained" Python scripts, see the wiki) and the last one handles everything which can be found on sys.path. So if we modified sys.path by appending '/home/project' then it would be this finder which provides the corresponding spec.

Instead of modifying sys.path we can register our own finder which uses the functionality of PathFinder:

import importlib.machinery

class CustomFinder(importlib.machinery.PathFinder):
    _path = ['/home/project']

    @classmethod
    def find_spec(cls, fullname, path=None, target=None):
        return super().find_spec(fullname, cls._path, target)

Here we explicitly tell the PathFinder to look into the /home/project when importing modules.

We can register the finder as follows:

import sys
sys.meta_path.append(CustomFinder)

Then we can import the package aaa which will be found by the CustomFinder:

import aaa

For more information see PEP-302.

Extend sys.path

We can modify sys.path in order to put the required package on the path:

import sys

sys.path.append('/home/project')
import aaa
from aaa import whatever
# Optionally remove the added path.
sys.path.pop()

Appending this directory to the path won't block "existing" (e.g. built-in packages) with the same name due to the order of searching that is performed during an import.

Add a local module containing a __path__

You can add a local module aaa.py (in fact you can add it to any location which is on the Python path) which contains the following code:

__path__ = ['/home/project/aaa']

Then you can perform import statements which will refer to the package that you referred to with the __path__ variable:

from aaa import whatever

If you want to import aaa you can mimic this by applying the same method one level up in the directory hierarchy. Add a local module project.py (for example) with the following code:

__path__ = ['/home/project']

Then you can do

from project import aaa

which is very much similar import aaa if aaa was on the path (provided that no other module named project has precedence on the path).

Create a symlink pointing to the package

You can create a symlink that points to the package's directory. For example on Unix:

ln -s /home/project/aaa aaa

Then you can import the package via import aaa, given you're executing this in the directory where you placed the symlink.

The symlink can also be created within your program via

import os

package = '/home/project/aaa'
target = os.path.split(package)[-1]  # For example.
if not os.path.exists(target):
    # `target_is_directory=True` is needed for Windows platform.
    os.symlink(package, target, target_is_directory=True)

# Now import the package.
aaa = __import__(target)

Install the package via setuptools

You can add a /home/project/setup.py script which contains (for example) the following code:

from setuptools import setup

setup(
    name='aaa',
    packages=[
        'aaa',
        # Add any sub-packages that `aaa` contains here.
    ]
)

Then you can install the package via cd /home && pip install -e project and you can readily import it in your other Python files:

import aaa
from aaa import whatever

By using virtualenv you can keep your installed packages in a clean state.

a_guest
  • 34,165
  • 12
  • 64
  • 118
  • `/home/project/aaa` is a directory, not a single file. – Kilerd Chan Jun 28 '17 at 03:55
  • @KilerdChan I updated my answer in order to clarify. All three methods work for your scenario (`aaa` being a directory). Modifying `sys.path` is probably the simplest one. I consider installing the package via `setuptools` & `pip` the cleanest method. You mentioned in your question that you are worried about modifying `sys.path` shadowing existing (e.g. built-in packages) but it won't due to the [search order](https://docs.python.org/3/reference/import.html#searching) during an import. If you _append_ to `sys.path` then the added location has the least priority. – a_guest Jun 28 '17 at 12:48
  • Thank you for such a detailed answer. I'm trying to figure out how to edit a project I cloned from github, and this was the missing piece! – vorpal Oct 11 '17 at 00:33
  • @a_guest, Thank you! This is wonderfully complete and detailed. :) Regarding the 3rd option (*Add a local module containing a\ __path\__*), does the 2nd half of that approach (beginning with "If you want to import aaa you can mimic...") depend upon the `aaa` directory containing a `__init__.py` file? – dbanas Sep 21 '19 at 13:48
  • @dbanas If you want to mimic `import aaa` via `from project import aaa` then `aaa` can be really anything: 1) it can be module `aaa.py` 2) it can be a directory containing an `__init__.py` 3) it can be a directory without `__init__.py`. For case 1) and 2) `aaa` will be loaded as a module and for case 3) it will be loaded as a [namespace package](https://www.python.org/dev/peps/pep-0420/) (see also [this answer](https://stackoverflow.com/a/27586272/3767239)). – a_guest Sep 21 '19 at 23:20