我有一个名为foobar的文件(没有.py扩展名)。在同一目录中,我有另一个试图导入它的python文件:
import foobar
但这只有在我将文件重命名为foobar.py时才有效。是否可以导入一个没有.py扩展名的python模块?
更新:文件没有扩展名,因为我也将它用作独立脚本,我不想输入.py扩展名来运行它。
Update2:我将选择下面提到的符号链接解决方案。
答案 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 ==========================================================================