2

I define parameters for my programs in a python module for simplicity. These parameters are then loaded using import. Therefore, I have to make sure to always load from the working directory, and nowhere else (independent form the location of the executed script or available modules in python path).

I found two solutions. First modifying the path:

import sys
from os import getcwd

import_path = sys.path
sys.path = [str(getcwd(), ]
import xxx
sys.path = import_path

or using importlib

from pathlib import Path
from importlib.util import module_from_spec, spec_from_file_location

spec = spec_from_file_location('xxx', str(Path('.').expanduser()/'xxx.py'))
xxx = module_from_spec(spec)
spec.loader.exec_module(xxx)

Of course this can be wrapped into a context manager or a function respectively.

What would the pythonic way be to do this? Do those two approaches have advantages and disadvantages?


I checked How can I import a Python library located in the current working directory? as well as Import python package from local directory into interpreter, but they lack the focus on robustness.

DerWeh
  • 1,721
  • 1
  • 15
  • 26
  • 1
    Can you give an explanation for "I have to make sure to always load from the working directory, and nowhere else"? There should be several other ways to load parameters without messing with the imports. – Klaus D. Feb 15 '19 at 10:41
  • Of course there are other ways, but this is simply the most convenient and comfortable one. I define my parameters and models in a script, and start the calculations with an `if __main__` guard. Afterwards I can just import the script to get the information. Else I would have to think of a good by to write the parameter to a different format and who to read it again. This is not very flexible. – DerWeh Feb 15 '19 at 11:56

1 Answers1

0

Local imports can be achieved by modifying the path. A context manager is a suitable solution:

import sys
from pathlib import Path
from contextlib import contextmanager


@contextmanager
def local_import(dir_=None):
    """Only import modules within `dir_` (default: cwd)."""
    if dir_ is None:
        dir_ = Path.cwd()
    else:
        dir_ = Path(dir_).absolute().resolve(strict=True)
    import_path0 = sys.path[0]
    sys.path[0] = str(dir_)
    try:
        yield
    finally:
        sys.path[0] = import_path0

Then the local import can be done using the standard import syntax

with local_import():
    import xxx

This solution relys on the order, in which the paths are scanned, thus we temporarily replace sys.path[0]. We replace it, instead of prepending to avoid import conflicts with the script directory.

Note

You have to be careful to avoid name conflicts, as the import statement is used, modules with identical names will be imported only once. Thus if different modules with the same name exist in the working directory and in the original sys.path[0], only one of them will be imported. Thus, local_import should only be used for scripts, that only use the standard library or installed third party libraries, but not for scripts that import other scripts from the directory. For the unlikely case that you want to import different files with the same name, the following function can be used:

import uuid
from importlib.util import module_from_spec, spec_from_file_location


def import_file(file, content=None):
    """Try importing `file` as module avoiding name clashes.

    If `content` is given `content = import_file('file.py', 'content')`
    roughly corresponds to `from file import content`
    else `file = import_file('file.py')`
    roughly corresponds to `import file`.

    Parameters
    ----------
    file : str or Path
        The Python file corresponding to the module.
    content : str, optional
        What to import from the module (optional).

    """
    file = Path(file).expanduser().resolve(strict=True)
    print(file)
    spec = spec_from_file_location(file.stem + str(uuid.uuid4()), str(file))
    module = module_from_spec(spec)
    spec.loader.exec_module(module)
    if content:
        print(module)
        return getattr(module, content)
    else:
        return module
DerWeh
  • 1,721
  • 1
  • 15
  • 26