导入没有.py扩展名的python模块

时间:2010-04-08 15:11:22

标签: python import

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

import foobar

但这只有在我将文件重命名为foobar.py时才有效。是否可以导入一个没有.py扩展名的python模块?

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

Update2:我将选择下面提到的符号链接解决方案。

7 个答案:

答案 0 :(得分:40)

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

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

SO discussion也显示了一些有趣的选项。

答案 1 :(得分:15)

与其他人提到的一样,你可以使用imp.load_source,但它会让你的代码更难以阅读。如果你需要在运行时之前导入名称或路径未知的模块,我真的只会推荐它。

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

答案 2 :(得分:14)

如果您有文件句柄,

imp.load_source(module_name, path)应该这样做,或者您可以执行更详细的imp.load_module(module_name, file_handle, ...)路由

答案 3 :(得分:11)

这是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并明确指定SourceFileLoader会强制machinery将文件作为源加载,而不会尝试从扩展名中找出文件的类型。这意味着您可以加载文件,即使它未在importlib.machinery.SOURCE_SUFFIXES中列出。

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

sys.modules['foobar'] = foobar

答案 4 :(得分:1)

如果您使用软件包管理器(deb或类似软件)安装脚本,则另一个选项是使用setuptools:

  

“...没有简单的方法让脚本的文件名与Windows和POSIX平台上的本地约定相匹配。另一方面,你经常需要为”主“脚本创建一个单独的文件,当你的实际”主“ “是某个模块中的一个函数... setuptools通过使用正确的扩展名为您自动生成脚本来修复所有这些问题,而在Windows上它甚至会创建一个.exe文件......”

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

答案 5 :(得分:0)

importlib辅助功能

这是一个方便的,随时可用的帮助程序,它基于一个示例,基于https://stackoverflow.com/a/43602645/895245

来替换imp

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

答案 6 :(得分:-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 ==========================================================================