导入任意的python源文件。(Python 3.3以上版本)


72

如何.pyPython 3.3+中导入任意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而不是此模块。

在不使用不推荐使用的imp.load_module功能的情况下,在Python 3.3+中加载任意python源文件的正确方法是什么?


4
请问您为什么要这样做?我是importlib的维护者,我一直在尝试从人们那里获得答案,以了解他们为什么使用imp.load_module()直接的import语句。您是否希望以后通过名称导入模块(例如import a_b)?您是否担心这种方法不会使用任何自定义进口商?您是否希望该模块具有全功能(例如define__name____loader__)?
Brett Cannon 2014年

3
@BrettCannon,一个第三方程序定期(一个小时)修改一个包含python语句(主要THIS='blah'类似于行)的文本文件。文件名不以结尾.py。我的程序读取了该文件。
falsetru 2014年

1
@BrettCannon,我不知道自定义进口商。我不在乎模块是否功能齐全。
falsetru 2014年

1
IOW使用Python作为一种非常简单的数据结构格式。谢谢(你的)信息!
Brett Cannon 2014年

1
@BrettCannon —我只是遇到一种情况,我需要从名为版本号(例如,“ v1.0.2”)的目录中导入一些Python代码。在可能的情况下,重命名目录是非常不希望的。我在下面使用了Stefan-Scherfke的解决方案。
Andrew Miner

Answers:


80

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+

Loader.load_module从Python 3.4开始不推荐使用UPDATELoader.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'>

23
Downvoter:如何改善答案?如果您有更好的方法来实现我的目标,请告诉我。
falsetru

3
有一个有用的警告,它会load_module忽略via warnings.catch_warnings。如果改为使用mod = imp.load_source('a_b', '/tmp/a-b.txt'),它提出了以下警告(使用-WallDeprecationWarning: imp.load_source() is deprecated; use importlib.machinery.SourceFileLoader(name, pathname).load_module() instead
Eryk Sun 2014年

1
@eryksun,你是对的。感谢您的评论。顺便说一句,Python 3.4(rc1)不会像Python 3.3.x那样显示替代用法。
falsetru

1
@ihavenoidea,请发布一个单独的问题,以便其他人可以回答您,其他用户也可以阅读答案。
falsetru

1
@falsetru实际上我在这里,到目前为止没有答案。我在此处发表评论是因为发布问题后碰到了您的答案。如果您知道该怎么做,我将不胜感激!
ihavenoidea

25

@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进行了测试。

根据评论,它不适用于任意文件扩展名。


2
importlib.util.spec_from_file_location(..)None给我回来 导致以下importlib.util.module_from_spec(..)调用发生异常。(见i.imgur.com/ZjyFhif.png
falsetru

3
importlib.util.spec_from_file_location适用于已知的文件扩展名(.py.so..),但不适用于其他文件扩展名(.txt...)
falsetru

哦,我仅将其与Python文件一起使用,但修改了示例以使其看起来像上面的示例,并且未对其进行测试……我对其进行了更新。
Stefan Scherfke,

10

与@falsetru相似,但适用于Python 3.5+,并说明importlib文档使用importlib.util.module_from_specover的状态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()

1
有趣的是,虽然这种将空字符串附加到源后缀列表的技巧非常适合导入已重命名的Python源模块,但是等效于导入已重命名的扩展模块的工作……也就是说,使用importlib.machinery.EXTENSION_SUFFIXES.append('')仍然会使importlib.util.spec_from_file_locationreturn None。
mxxk

大概,importlib.util.spec_from_file_location如果您指定了加载程序,它应该仍然可以与扩展一起使用
Alex Walczak

4

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可执行文件带有连字符。这不是强制性的,但会产生更好的模块名称。

在以下文档中也提到了这种模式:https : //docs.python.org/3.7/library/importlib.html#importing-a-source-file-direct

我最终转向它,因为在更新到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中测试。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.