导入任意 Python 源文件。(Python 3.3+)
- 2025-02-25 09:07:00
- admin 原创
- 23
问题描述:
如何在Python 3.3+.py
中导入任意 python 源文件(其文件名可以包含任何字符,并且并不总是以 结尾)?
我的使用方法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 版起已弃用:不需要,因为应该使用加载器来加载模块,并且 find_module() 已被弃用。
模块imp
文档建议使用importlib
:
注意新程序应该使用 importlib 而不是这个模块。
在不使用弃用函数的情况下,在 Python 3.3+ 中加载任意 Python 源文件的正确方法是什么imp.load_module
?
解决方案 1:
importlib
从测试代码中找到解决方案。
使用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'>
注意:仅适用于Python 3.3+。
从 Python 3.4 开始, UPDATE Loader.load_module
已弃用。请改用Loader.exec_module
:
>>> 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'>
解决方案 2:
针对 Python >= 3.8 进行了更新:
简短版本:
>>> # 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)
完整版本:
>>> 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'>
Python 3.5 和 3.6 的原始答案
@falsetru 解决方案的简短版本:
>>> 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'>
我使用 Python 3.5 和 3.6 对其进行了测试。
根据评论,它不适用于任意文件扩展名。
解决方案 3:
与@falsetru 类似,但适用于Python 3.5+,并考虑了文档importlib
中关于使用importlib.util.module_from_spec
over 的规定types.ModuleType
:
与使用创建新模块相比,此函数 [
importlib.util.module_from_spec
] 更受欢迎,types.ModuleType
因为 spec 用于在模块上设置尽可能多的导入控制属性。
我们可以importlib
通过修改importlib.machinery.SOURCE_SUFFIXES
列表来单独导入任何文件。
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()
解决方案 4:
importlib
辅助函数已在 Python 3.10 上测试
这里有一个方便的、随时可用的助手来替换,并附有示例。该技术与https://stackoverflow.com/a/19011259/895245imp
相同,这只是提供了一个更方便的功能。
主程序
#!/usr/bin/env python3
import os
from importlib import util, machinery
def import_path(path):
module_name = os.path.basename(path).replace('-', '_')
spec = util.spec_from_loader(
module_name,
importlib.machinery.SourceFileLoader(module_name, path)
)
module = 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)
非主要
x = 1
跑步:
python3 main.py
输出:
<module 'not_main' from 'not-main'>
1
我将其替换-
为,_
因为我的可导入 Python 可执行文件没有扩展名,其中有连字符,如my-cmd
。这不是强制性的,但可以产生更好的模块名称,如my_cmd
。
有关的:
如何根据完整路径动态导入模块?
Python 3.7
在那个版本中,我改为使用以下调用来帮助程序,但它们似乎不适用于 Python 3.10
import importlib
spec = importlib.util.spec_from_loader(
module = importlib.util.module_from_spec(
文档中也提到了这种模式: https: //docs.python.org/3.7/library/importlib.html#importing-a-source-file-directly
我最终转向它,因为更新到 Python 3.7 后,import imp
打印:
DeprecationWarning: the imp module is deprecated in favour of importlib; see the module's documentation for alternative uses
我不知道如何关闭它,有人问过这个问题:
imp 模块已弃用
如何忽略 Python 中的弃用警告
解决方案 5:
经过许多失败的解决方案后,这个对我有用
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)
要调用它,只需写下函数名称及其参数_import("function",*args)