如何在不导入模块的情况下检查Python模块是否存在

271

如何在不导入模块的情况下知道Python模块是否存在?

导入可能不存在的内容(不是我想要的)会导致:

try:
    import eggs
except ImportError:
    pass

7
我很好奇,使用import有什么缺点? - Chuck
33
如果你的模块具有副作用,调用 import 可能会产生意想不到的后果。因此,如果你想首先检查要运行哪个文件的版本,你可以使用以下答案进行检查,稍后再进行 import。我并不建议编写具有副作用的模块,但我们都是成年人,可以自己决定编码的危险程度。 - yarbelk
1
可能是重复的问题:如何检查Python模块是否存在并可以导入 - ArtOfWarfare
2
@ArtOfWarfare,我刚刚关闭了你链接的那个问题,并将其作为这个问题的重复。因为这个问题更清晰,而且这里提出的解决方案比所有其他列出的解决方案都要好。我宁愿指向那些想要答案的人这个更好的解决方案,而不是让他们远离它。 - Bakuriu
10
此模块可能存在,但本身可能包含导入错误。像上面的代码中捕获ImportError可能会导致表明模块不存在,而实际上它是存在的,只是有错误而已。 - Michael Barton
显示剩余4条评论
14个回答

304

简短概述) 使用 importlib.util.find_spec(module_name) (Python 3.4+)。

Python2: imp.find_module

为了检查在Python 2中是否可以找到某个东西,使用imp

import imp
try:
    imp.find_module('eggs')
    found = True
except ImportError:
    found = False

要查找点式导入,您需要做更多的工作:

import imp
try:
    spam_info = imp.find_module('spam')
    spam = imp.load_module('spam', *spam_info)
    imp.find_module('eggs', spam.__path__) # __path__ is already a list
    found = True
except ImportError:
    found = False

你也可以使用 pkgutil.find_loader(与 Python 3 部分更或少相同):
import pkgutil
eggs_loader = pkgutil.find_loader('eggs')
found = eggs_loader is not None

Python 3

Python 3 ≤ 3.3: importlib.find_loader

你应该使用importlib。我通常这样做:

import importlib
spam_loader = importlib.find_loader('spam')
found = spam_loader is not None

我的期望是,如果你能找到一个加载器,那么它就存在。你也可以更加聪明地处理它,比如过滤掉你不接受的加载器。例如:

import importlib
spam_loader = importlib.find_loader('spam')
# only accept it as valid if there is a source file for the module - no bytecode only.
found = issubclass(type(spam_loader), importlib.machinery.SourceFileLoader)

Python 3 ≥ 3.4: importlib.util.find_spec

在Python 3.4中,importlib.find_loaderPython documentation已被弃用,推荐使用importlib.util.find_spec。建议使用importlib.util.find_spec方法。还有其他方法,如importlib.machinery.FileFinder,如果您需要加载特定文件,则非常有用。但是,如何使用它们超出了本文的范围。

import importlib.util
spam_spec = importlib.util.find_spec("spam")
found = spam_spec is not None

这也适用于相对导入,但您必须提供起始包,因此您也可以这样做:

import importlib.util
spam_spec = importlib.util.find_spec("..spam", package="eggs.bar")
found = spam_spec is not None
spam_spec.name == "eggs.spam"

我相信有一个原因做这个 - 不过,我不确定是什么原因。
警告
在试图找到子模块时,它将导入父模块(对于上述所有方法)!
food/
  |- __init__.py
  |- eggs.py

## __init__.py
print("module food loaded")

## eggs.py
print("module eggs")

were you then to run
>>> import importlib
>>> spam_spec = importlib.util.find_spec("food.eggs")
module food loaded
ModuleSpec(name='food.eggs', loader=<_frozen_importlib.SourceFileLoader object at 0x10221df28>, origin='/home/user/food/eggs.py')

欢迎评论如何解决这个问题

致谢

  • @rvighne 提供 importlib
  • @lucas-guido 因 Python 3.3+ 废弃 find_loader
  • @enpenax 因 Python 2.7 中的 pkgutils.find_loader 行为

4
这仅适用于顶级模块,而不适用于 eggs.ham.spam - hemflit
3
如果您想在eggs.ham中查找 spam,您需要使用imp.find_module('spam', ['eggs', 'ham']) - gitaarik
5
在Python 3中,imp被废弃,推荐使用importlib代替。+1。 - rvighne
4
如果导入的模块出现了实际的“ImportError”,会怎样呢?这就是我的情况。那么这个模块虽然存在,但是无法被“找到”。 - enpenax
2
如果你遇到了“importlib”没有“util”属性的错误,你可以按照这个答案进行修复。 - ashes999
显示剩余9条评论

46

Python 3 >= 3.6: 模块未发现错误

Python 3.6中引入了ModuleNotFoundError,可用于此目的:

try:
    import eggs
except ModuleNotFoundError:
    # Error handling
    pass

当无法找到一个模块或其父模块时,就会引发该错误。

try:
    import eggs.sub
except ModuleNotFoundError as err:
    # Error handling
    print(err)

如果找不到eggs模块,它将打印类似于No module named 'eggs'的消息;但是,如果只有sub模块找不到,但可以找到eggs包,则会打印类似于No module named 'eggs.sub'的消息。

有关ModuleNotFoundError的更多信息,请参见导入系统文档


26
这并没有回答问题,因为它会导入该包(库)如果它存在。 - divenex
2
如果另一个模块在导入期间引发了此异常,您将无法轻松地区分 - 您的模块不存在还是它的某些依赖项不存在。 - frnhr

12

不依赖于 ImportError 的 Python 2

在更新当前答案之前,这是适用于Python 2的方法。

import pkgutil
import importlib

if pkgutil.find_loader(mod) is not None:
    return importlib.import_module(mod)
return None

为什么需要另一个答案?

很多答案使用捕获 ImportError 的方法,但问题在于我们无法知道是什么抛出了 ImportError

如果您导入已存在的模块,而该模块发生了 ImportError(例如,在第1行打字错误),结果将是您的模块不存在。

这将需要花费相当长的时间去跟踪,才能发现您的模块存在,但 ImportError 被捕获并使事情悄无声息地失败了。


可能不太清楚,但除了第一个代码块外,其他代码块都不依赖于“ImportError”- 如果您觉得不清楚,请编辑。 - yarbelk
我看到你在前两个Python 2的例子中使用了ImportError异常捕获,那它们为什么会在那里呢? - enpenax
3
如果 mod == 'not_existing_package.mymodule',则会引发 ImportError。请参阅下面的完整解决方案 - Marcin Raczyński
1
当然会抛出导入错误。如果模块不存在,它应该抛出导入错误。这样,如果需要,您就可以捕获它。其他解决方案的问题在于它们掩盖了其他错误。 - enpenax
Try/except并不意味着你不需要记录或确保事情。你可以完全捕获任何潜在的回溯信息,并做任何你想做的事情。 - Zulu
关于“当前答案”:您是指“被接受”的答案吗? - Peter Mortensen

12

使用yarbelk的回答后,我做了这个,所以不必导入imp

try:
    __import__('imp').find_module('eggs')
    # Make things with a supposed existing module
except ImportError:
    pass

例如,在 Djangosettings.py 文件中非常有用。


6
我进行了负投票,因为这会掩盖模块中的导入错误,使得很难发现错误。 - enpenax
1
Downvote 不是一个好主意,一个好的做法是“始终记录捕获的错误”。这是一个示例,在你写下你想要的内容之后。 - Zulu
2
如果导入的模块在第一行出现ImportError错误且您的try catch使其无声地失败,您会如何记录错误? - enpenax
我在实际工作中遇到了掩码导入错误问题,情况很糟糕(导致本应该失败的测试通过了!)。 - David Given
我遇到了这种情况,有人在使用该错误来触发对不同模块的 monkeypatching...... 这是疯狂的找到。 - yarbelk

7

go_as的答案可以简化为一行代码:

 python -c "help('modules');" | grep module

5

以下是检查模块是否从命令行加载的方法:

Linux/UNIX 脚本文件方法:创建一个名为module_help.py的文件:

#!/usr/bin/env python

help('modules')

然后确保它是可执行的:chmod u+x module_help.py

并使用管道调用它到grep

./module_help.py | grep module_name

调用内置的帮助系统。(此函数仅供交互使用。)如果没有给出参数,则交互式帮助系统将在解释器控制台上启动。如果参数是字符串,则该字符串将被查找为模块、函数、类、方法、关键字或文档主题的名称,并在控制台上打印帮助页面。如果参数是任何其他类型的对象,则会生成有关该对象的帮助页面。 交互方法:在控制台中加载python
>>> help('module_name')

如果找到了,输入q退出阅读。 要退出Python解释器交互会话,请按Ctrl+D

Windows脚本文件方法,也适用于Linux/UNIX,并且整体效果更好:

#!/usr/bin/env python

import sys

help(sys.argv[1])

从命令行中调用:

python module_help.py site

会输出:

模块 site 的帮助信息:

名称 site - 将第三方包的模块搜索路径添加到 sys.path。

文件 /usr/lib/python2.7/site.py

模块文档 http://docs.python.org/library/site

描述 ...

您需要按下 q 退出交互模式。

对于未知的模块,例如:

python module_help.py lkajshdflkahsodf

将输出:

'lkajshdflkahsodf' 的 Python 文档未找到

并退出。


不错的想法。我喜欢它。缺点是:至少在我的Win10上,help('modules')命令需要大约10秒钟的时间来收集所有可用的模块。如果这不是问题,那么这个命令很酷。 - Markus-Hermann

5

使用pkgutil中的一个函数,例如:

pkgutil.iter_modules
from pkgutil import iter_modules

def module_exists(module_name):
    return module_name in (name for loader, name, ispkg in iter_modules())

5

我写了这个帮助函数:

def is_module_available(module_name):
    if sys.version_info < (3, 0):
        # python 2
        import importlib
        torch_loader = importlib.find_loader(module_name)
    elif sys.version_info <= (3, 3):
        # python 3.0 to 3.3
        import pkgutil
        torch_loader = pkgutil.find_loader(module_name)
    elif sys.version_info >= (3, 4):
        # python 3.4 and above
        import importlib
        torch_loader = importlib.util.find_spec(module_name)

    return torch_loader is not None

需要解释一下。例如,这个想法/要点是什么?它与之前的答案相比如何?来自帮助中心“...始终解释为什么您提出的解决方案是合适的以及它是如何工作的”。请通过编辑(更改)您的答案进行回复,而不是在此处进行评论(不包括“Edit:”,“Update:”或类似内容 - 答案应该看起来像是今天写的)。 - Peter Mortensen

3
你可以编写一个小脚本,尝试导入所有模块,并告诉你哪些模块失败了,哪些模块正常工作:
import pip


if __name__ == '__main__':
    for package in pip.get_installed_distributions():
        pack_string = str(package).split(" ")[0]
        try:
            if __import__(pack_string.lower()):
                print(pack_string + " loaded successfully")
        except Exception as e:
            print(pack_string + " failed with error code: {}".format(e))

输出:

zope.interface loaded successfully
zope.deprecation loaded successfully
yarg loaded successfully
xlrd loaded successfully
WMI loaded successfully
Werkzeug loaded successfully
WebOb loaded successfully
virtualenv loaded successfully
...

警告:这将尝试导入所有内容,因此你会看到一些像PyYAML failed with error code: No module named pyyaml这样的东西,因为实际的导入名称只是yaml。所以只要你知道你的导入,这应该对你有帮助。


1

你也可以直接使用 importlib.util

import importlib.util    

def module_exists_without_import(module_name):    
    spec = importlib.util.find_spec(module_name)
    return spec is not None

2
这个问题在于实际导入它。副作用和一切。 - yarbelk

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接