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

ycl3bljg  于 2023-05-19  发布在  Python
关注(0)|答案(5)|浏览(183)

如何在**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起已弃用 *:不需要,因为应该使用加载器来加载模块,并且不推荐使用find_module()。

imp模块文档建议使用importlib

注意新的程序应该使用importlib,而不是这个模块。

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

dgiusagp

dgiusagp1#

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+
UPDATELoader.load_module自Python 3.4起已弃用。使用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'>
z5btuh9x

z5btuh9x2#

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测试了它。
根据评论,它不适用于任意文件扩展名。

j8ag8udp

j8ag8udp3#

类似于@falsetru,但适用于Python 3.5+,并考虑了importlib文档中关于在types.ModuleType上使用importlib.util.module_from_spec的声明:
此函数[importlib.util.module_from_spec]优于使用types.ModuleType创建新模块,因为spec用于在模块上设置尽可能多的导入控制属性。
我们可以通过修改importlib.machinery.SOURCE_SUFFIXES列表导入任何单独使用importlib的文件。

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()
w80xi6nr

w80xi6nr4#

importlib辅助函数

这里有一个方便的、现成的帮助器来替换imp,并提供了一个示例。技术与https://stackoverflow.com/a/19011259/895245相同,这只是提供了更方便的功能。
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)

非主

x = 1

运行:

python3 main.py

输出:

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

我将-替换为_,因为我的可导入Python可执行文件没有扩展名,与my-cmd一样有连字符。这不是强制性的,但会产生更好的模块名称,如my_cmd
此模式也在以下文档中提到: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

我不知道该怎么关掉它,有人问我:

相关:

在Python 3.7.3中测试。

rpppsulh

rpppsulh5#

在许多失败的解决方案之后,这一个对我有效

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)

相关问题