导入不带.py扩展名的python模块


74

我有一个名为foobar的文件(没有.py扩展名)。在同一目录中,我还有另一个python文件试图将其导入:

import foobar

但这仅在我将文件重命名为foobar.py时有效。是否可以导入不带.py扩展名的python模块?

更新:该文件没有扩展名,因为我也将其用作独立脚本,并且我不想键入.py扩展名来运行它。

Update2:我将使用下面提到的symlink解决方案。


1
我很感兴趣 为什么会有没有py扩展名的python文件?
EstebanKüber2010年

3
有时,将python用于配置文件(扩展名为.conf)或表示一种特殊类型的文件是很好的。就我而言,这对于管理员来说更加方便。
NuclearPeon 2013年

1
我有一个配置文件,既可以用作python文件,又可以用作bash脚本。我给了它一个pysh扩展名...
Sergey Orshanskiy 2014年

如果那是与配置有关的事情,我建议使用ConfigParserwiki.python.org/moin/ConfigParserExamples
Chemical Programmer

3
@voyager原因之一是带有.cgi扩展名而不是.py扩展名的python脚本
repzero

Answers:


53

您可以使用该imp.load_source功能(来自imp模块)从给定的文件系统路径动态加载模块。

import imp
foobar = imp.load_source('foobar', '/path/to/foobar')

SO讨论还显示了一些有趣的选项。


固定。[建议编辑,但更具建设性]
Eli Bendersky,2015年

2
'foobar'如果在返回值中分配了第一个参数,则有什么用?
Anmol Singh Jaggi

2
@AnmolSinghJaggi设置模块__name__属性;通常这是由文件名确定的,但是由于您使用的是非标准文件名(它甚至可能根本不包含任何有效的python标识符),因此您必须指定模块名称。存储对创建的模块对象的引用的变量名称无关紧要,就像您import foo.bar as baz通过该变量引用的模块baz仍然具有原始名称一样__name__

3
从3.4开始不推荐使用。任何想法如何从3.4+起没有.py扩展名的文件导入?
exhuma

2
对于Python 3.4+:此答案更为准确:stackoverflow.com/questions/2601047/…–
tres.14159

23

这是Python 3.4+的解决方案:

from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import SourceFileLoader 

spec = spec_from_loader("foobar", SourceFileLoader("foobar", "/path/to/foobar"))
foobar = module_from_spec(spec)
spec.loader.exec_module(foobar)

使用spec_from_loader并明确指定aSourceFileLoader将迫使机器将文件作为源加载,而无需尝试从扩展名中找出文件的类型。这意味着即使文件未在中列出,您也可以加载该文件importlib.machinery.SOURCE_SUFFIXES

如果要在第一次加载后继续按名称导入文件,请将模块添加到sys.modules

sys.modules['foobar'] = foobar

2
这个模块急需一个foobar = importlib.nice_import('foobar')助手。
西罗Santilli郝海东冠状病六四事件法轮功

@Ciro。它已经有。我认为那不算/some/arbitrary/file.weird_extension是“不错”。话虽这么说,一旦发现这一点,我就开始对所有配置文件使用python代码。真方便。
疯狂物理学家

如果我要导入*怎么办?
亚历克斯·哈维

1
@AlexHarvey。这为您提供了一个模块对象。您可以做类似的事情globals().update(foobar.__dict__),但我建议不要这样做。
疯狂物理学家

17

就像其他人提到的那样,您可以使用imp.load_source,但是这会使您的代码更难以阅读。我真的只建议您在需要导入其名称或路径在运行时才知道的模块时使用。

您不想使用.py扩展名的原因是什么?不想使用.py扩展名的最常见情况是,因为python脚本也作为可执行文件运行,但是您仍然希望其他模块能够导入它。如果是这种情况,将功能移动到具有类似名称的.py文件中,然后foobar用作包装器可能会有所帮助。


14
或者,而不是包装,只需将foobar.py链接到foobar(假设您不在Windows上)
whaley 2010年

@whaley,是的,这要干净得多。您可以对Windows使用.bat来完成相同的操作。

我有一个简洁的用例-一个自述文件,其中包含示例,我希望doctest进行验证。我希望制作一个行之有效的doctest markdown文档...
Danny Staple

答案是(针对该用例)-使用doctest.loadfile!
Danny Staple

包装器的问题在于,如果有人天真地将包装器仅复制到bin/目录,则从路径运行该程序将无法工作。
cjs

14

imp.load_source(module_name, path)应该这样做,或者imp.load_module(module_name, file_handle, ...)如果您有文件句柄,则可以执行更详细的路由


7

importlib 辅助功能

这里是一个方便,随时可以使用的辅助来代替imp,用一个例子,基于什么在提到:https://stackoverflow.com/a/43602645/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中测试。


2

如果使用程序包管理器(deb或类似程序)安装脚本,则另一种选择是使用setuptools:

“ ...在Windows和POSIX平台上,没有一种简单的方法来使脚本的文件名与本地约定相匹配。另一方面,当实际的“ main”是一个“ main”脚本时,通常必须为“ main”脚本创建一个单独的文件。在某处的模块中起作用... setuptools通过自动为您生成具有正确扩展名的脚本来解决所有这些问题,在Windows上它甚至会创建一个.exe文件...”

https://pythonhosted.org/setuptools/setuptools.html#automatic-script-creation


-1

我已经尝试了上面的所有建议,但在我想对没有.py扩展名的cli脚本进行pytest的简单情况下,找不到解决方案。

为什么spec.loader.exec_module()在“ .”上引发IsADirectoryError ?

我在目录中有两个文件:

ls

cli_with_no_dot_py      test_cli_with_no_dot_py.py

cat cli_with_no_dot_py

#!/usr/bin/env python3

cool_thing = True
print("cool_thing is", cool_thing)

./cli_with_no_dot_py

cool_thing is True

cat test_cli_with_no_dot_py.py

#!/usr/bin/env python3

from importlib.util import spec_from_loader, module_from_spec
from importlib.machinery import SourceFileLoader
spec = spec_from_loader("cli_with_no_dot_py",
    SourceFileLoader("cli_with_no_dot_py", "."))
print("spec:", spec)
cli_with_no_dot_py = module_from_spec(spec)
print("cli_with_no_dot_py):", cli_with_no_dot_py)
print("dir(cli_with_no_dot_py:", dir(cli_with_no_dot_py))
print("spec.loader:", spec.loader)
spec.loader.exec_module(cli_with_no_dot_py)  # !!! IsADirectoryError !!!

def test_cool_thing():
    print("cli_with_no_dot_py.cool_thing is", cli_with_no_dot_py.cool_thing)
    assert cli_with_no_dot_py.cool_thing

pytest

======================================================================== test session starts ========================================================================
platform darwin -- Python 3.8.2, pytest-5.4.1, py-1.8.1, pluggy-0.13.1
rootdir: /Users/cclauss/Python/pytest_a_cli_tool
collected 0 items / 1 error

============================================================================== ERRORS ===============================================================================
____________________________________________________________ ERROR collecting test_cli_with_no_dot_py.py ____________________________________________________________
test_cli_with_no_dot_py.py:11: in <module>
    spec.loader.exec_module(cli_with_no_dot_py)
<frozen importlib._bootstrap_external>:779: in exec_module
    ???
<frozen importlib._bootstrap_external>:915: in get_code
    ???
<frozen importlib._bootstrap_external>:972: in get_data
    ???
E   IsADirectoryError: [Errno 21] Is a directory: '.'
-------------------------------------------------------------------------- Captured stdout --------------------------------------------------------------------------
spec: ModuleSpec(name='cli_with_no_dot_py', loader=<_frozen_importlib_external.SourceFileLoader object at 0x10e106f70>, origin='.')
cli_with_no_dot_py: <module 'cli_with_no_dot_py' from '.'>
dir(cli_with_no_dot_py): ['__doc__', '__file__', '__loader__', '__name__', '__package__', '__spec__']
spec.loader: <_frozen_importlib_external.SourceFileLoader object at 0x10e106f70>
====================================================================== short test summary info ======================================================================
ERROR test_cli_with_no_dot_py.py - IsADirectoryError: [Errno 21] Is a directory: '.'
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
========================================================================= 1 error in 0.09s ==========================================================================

这是一个问题而不是答案。
jamesdlin
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.