导入任意的 Python 源文件(Python 3.3 +)

如何在 Python 3.3 + 中导入任意的 python 源文件(其文件名可以包含任何字符,并且不总是以 .py结尾) ?

我使用 imp.load_module如下:

>>> import imp
>>> path = '/tmp/a-b.txt'
>>> with open(path, 'U') as f:
...     mod = imp.load_module('a_b', f, path, ('.py', 'U', imp.PY_SOURCE))
...
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

它仍然可以在 Python 3.3中使用,但是根据 imp.load_module文档,它已经被弃用了:

由于版本3.3 已经废弃: 不需要,因为应该使用加载程序 Load 模块和 find _ module ()不推荐使用。

建议使用 importlibimp模块文档:

注意 新程序应该使用 import lib 而不是这个模块。

在 Python 3.3 + 中加载任意的 Python 源文件而不使用废弃的 imp.load_module函数的正确方法是什么?

45662 次浏览

Found a solution from importlib test code.

Using importlib.machinery.SourceFileLoader:

>>> import importlib.machinery
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> mod = loader.load_module()
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

NOTE: only works in Python 3.3+.

UPDATE Loader.load_module is deprecated since Python 3.4. Use Loader.exec_module instead:

>>> import types
>>> import importlib.machinery
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> mod = types.ModuleType(loader.name)
>>> loader.exec_module(mod)
>>> mod
<module 'a_b'>

>>> import importlib.machinery
>>> import importlib.util
>>> loader = importlib.machinery.SourceFileLoader('a_b', '/tmp/a-b.txt')
>>> spec = importlib.util.spec_from_loader(loader.name, loader)
>>> mod = importlib.util.module_from_spec(spec)
>>> loader.exec_module(mod)
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

Updated for Python >= 3.8:

Short version:

>>> # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
>>> import importlib.util, sys
>>> spec = importlib.util.spec_from_file_location(modname, fname)
>>> module = importlib.util.module_from_spec(spec)
>>> sys.modules[modname] = module
>>> spec.loader.exec_module(module)

Full version:

>>> import importlib.util
>>> import sys
>>> from pathlib import Path
>>> from typing import TYPE_CHECKING
>>>
>>>
>>> if TYPE_CHECKING:
...     import types
...
...
>>> def import_source_file(fname: str | Path, modname: str) -> "types.ModuleType":
...     """
...     Import a Python source file and return the loaded module.


...     Args:
...         fname: The full path to the source file.  It may container characters like `.`
...             or `-`.
...         modname: The name for the loaded module.  It may contain `.` and even characters
...             that would normally not be allowed (e.g., `-`).
...     Return:
...         The imported module


...     Raises:
...         ImportError: If the file cannot be imported (e.g, if it's not a `.py` file or if
...             it does not exist).
...         Exception: Any exception that is raised while executing the module (e.g.,
...             :exc:`SyntaxError).  These are errors made by the author of the module!
...     """
...     # https://docs.python.org/3/library/importlib.html#importing-a-source-file-directly
...     spec = importlib.util.spec_from_file_location(modname, fname)
...     if spec is None:
...         raise ImportError(f"Could not load spec for module '{modname}' at: {fname}")
...     module = importlib.util.module_from_spec(spec)
...     sys.modules[modname] = module
...     try:
...         spec.loader.exec_module(module)
...     except FileNotFoundError as e:
...         raise ImportError(f"{e.strerror}: {fname}") from e
...     return module
...
>>> import_source_file(Path("/tmp/my_mod.py"), "my_mod")
<module 'my_mod' from '/tmp/my_mod.py'>

Original answer for Python 3.5 and 3.6

Shorter version of @falsetru 's solution:

>>> import importlib.util
>>> spec = importlib.util.spec_from_file_location('a_b', '/tmp/a-b.py')
>>> mod = importlib.util.module_from_spec(spec)
>>> spec.loader.exec_module(mod)
>>> mod
<module 'a_b' from '/tmp/a-b.txt'>

I tested it with Python 3.5 and 3.6.

According to the comments, it does not work with arbitrary file extensions.

Similar to @falsetru but for Python 3.5+ and accounting for what the importlib doc states on using importlib.util.module_from_spec over types.ModuleType:

This function [importlib.util.module_from_spec] is preferred over using types.ModuleType to create a new module as spec is used to set as many import-controlled attributes on the module as possible.

We are able to import any file with importlib alone by modifying the importlib.machinery.SOURCE_SUFFIXES list.

import importlib


importlib.machinery.SOURCE_SUFFIXES.append('') # empty string to allow any file
spec = importlib.util.spec_from_file_location(module_name, file_path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
# if desired: importlib.machinery.SOURCE_SUFFIXES.pop()

importlib helper function

Here is a convenient, ready-to-use helper to replace imp, with an example. The technique is the same as that of https://stackoverflow.com/a/19011259/895245 , this is just providing a more convenient function.

main.py

#!/usr/bin/env python3


import os
import importlib


def import_path(path):
module_name = os.path.basename(path).replace('-', '_')
spec = importlib.util.spec_from_loader(
module_name,
importlib.machinery.SourceFileLoader(module_name, path)
)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
sys.modules[module_name] = module
return module


notmain = import_path('not-main')
print(notmain)
print(notmain.x)

not-main

x = 1

Run:

python3 main.py

Output:

<module 'not_main' from 'not-main'>
1

I replace - with _ because my importable Python executables without extension have hyphens as in my-cmd. This is not mandatory, but produces better module names like my_cmd.

This pattern is also mentioned in the docs at: https://docs.python.org/3.7/library/importlib.html#importing-a-source-file-directly

I ended up moving to it because after updating to Python 3.7, import imp prints:

DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses

and I don't know how to turn that off, this was asked at:

Tested in Python 3.7.3.

after many failure solutions this one works for me

def _import(func,*args):
import os
from importlib import util
module_name = "my_module"
BASE_DIR = "wanted module directory path"
path =  os.path.join(BASE_DIR,module_name)
spec = util.spec_from_file_location(func, path)
mod = util.module_from_spec(spec)
spec.loader.exec_module(mod)
return getattr(mod,func)(*args)

and to call it just write the function name and it's parameters _import("function",*args)