如何检查一个模块/库/包是否属于Python标准库?

21

我使用pip安装了很多库/模块/包,现在无法区分哪些是Python标准库,哪些不是。这会导致我的代码在我的电脑上工作正常,但在其他地方却无法运行。

我该如何检查我在代码中导入的模块/库/包是否来自Python标准库?

假设检查是在具有所有外部库/模块/包的机器上完成的,否则我可以在没有它们的其他机器上进行try-except导入操作。

例如,我确信这些导入在我的机器上可以工作,但当它在只有普通Python安装的机器上运行时,它就会出现问题:

from bs4 import BeautifulSoup
import nltk
import PIL
import gensim

我猜你可以从这里开始:http://docs.python.org/2/library/index.html - Jayanth Koushik
1
使用 virtualenv。它专门构建以避免这些问题。 - Jayanth Koushik
这不是一个答案,但如果你遵循了PEP-0008,就更容易看出哪些import是第三方的。 - jonrsharpe
“Native Python”通常指的是“纯Python”,例如不使用任何C或其他非Python语言扩展。您所说的是标准库与外部依赖项之间的区别。 - Martijn Pieters
啊,@martijnpieters,谢谢你提到“本地Python”的注释。=) - alvas
3个回答

9

您需要检查所有已导入的模块,看看是否有任何一个位于标准库之外。

以下脚本不是百分百可靠的,但可以作为起点:

import sys
import os

external = set()
exempt = set()
paths = (os.path.abspath(p) for p in sys.path)
stdlib = {p for p in paths
          if p.startswith((sys.prefix, sys.real_prefix)) 
          and 'site-packages' not in p}
for name, module in sorted(sys.modules.items()):
    if not module or name in sys.builtin_module_names or not hasattr(module, '__file__'):
        # an import sentinel, built-in module or not a real module, really
        exempt.add(name)
        continue

    fname = module.__file__
    if fname.endswith(('__init__.py', '__init__.pyc', '__init__.pyo')):
        fname = os.path.dirname(fname)

    if os.path.dirname(fname) in stdlib:
        # stdlib path, skip
        exempt.add(name)
        continue

    parts = name.split('.')
    for i, part in enumerate(parts):
        partial = '.'.join(parts[:i] + [part])
        if partial in external or partial in exempt:
            # already listed or exempted
            break
        if partial in sys.modules and sys.modules[partial]:
            # just list the parent name and be done with it
            external.add(partial)
            break

for name in external:
    print name, sys.modules[name].__file__

将这个模块放在一个新的模块中,在脚本中所有导入之后导入它,它会打印所有它认为不是标准库的模块。


5
标准库在Python的文档中定义。您可以在那里搜索,或将模块名称放入列表中并使用程序进行检查。
此外,在Python 3.4中,有一个新的隔离模式,允许忽略某些用户定义的库路径。在以前的Python版本中,您可以使用-s来忽略每个用户的环境,使用-E来忽略系统定义的变量。
在Python2中,检查模块是否属于标准库的一种非常简单的方法是清除sys.path
>>> import sys
>>> sys.path = []
>>> import numpy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named numpy
>>> import traceback
>>> import os
>>> import re

然而,这在Python3.3+中不起作用:
>>> import sys
>>> sys.path = []
>>> import traceback
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named 'traceback'
[...]

这是因为从python3.3开始,导入机制发生了改变,并且导入标准库使用与导入任何其他模块相同的机制(请参阅文档)。
在python3.3中,确保仅限于stdlib导入成功的唯一方法是将只有标准库路径添加到sys.path中,例如:
>>> import os, sys, traceback
>>> lib_path = os.path.dirname(traceback.__file__)
>>> sys.path = [lib_path]
>>> import traceback
>>> import re
>>> import numpy
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ImportError: No module named 'numpy'

我使用了traceback模块来获取库路径,因为这应该在任何系统上都有效。
对于内置模块,它们是标准库模块的子集,您可以检查sys.builtin_module_names

但这不是关于标准库对第三方库的争论,而是非纯的第三方库与纯 Python 的第三方库之间的区别。 - tripleee
@tripleee:我不同意;我认为OP在这里混淆了术语。bs4是纯Python,而PIL显然不是。两者都不会在新机器上安装。 - Martijn Pieters
@triplee,这是关于标准库和第三方库的问题。因为第三方库没有安装在我的合作者的机器上,所以他们非常生气...让我产生了一些疑问,比如“你怎么能用两行代码就做到X?”...哈哈 - alvas

2

@Bakuriu的回答对我非常有用。唯一遇到的问题是,如果您想检查特定模块是否为stdlib,但已经被导入。在这种情况下,sys.modules只会为其设置一个条目,因此即使剥离了sys.path,导入也会成功:

In [1]: import sys

In [2]: import virtualenv

In [3]: sys.path = []

In [4]: try:
    __import__('virtualenv')
except ImportError:
    print(False)
else:
    print(True)
   ...:
True

vs

In [1]: import sys

In [2]: sys.path = []

In [3]: try:
    __import__('virtualenv')
except ImportError:
    print(False)
else:
    print(True)
   ...:
False

我想出了以下解决方案,它似乎在Python2和Python3中都可以使用:
from __future__ import unicode_literals, print_function
import sys
from contextlib import contextmanager
from importlib import import_module


@contextmanager
def ignore_site_packages_paths():
    paths = sys.path
    # remove all third-party paths
    # so that only stdlib imports will succeed
    sys.path = list(filter(
        None,
        filter(lambda i: 'site-packages' not in i, sys.path)
    ))
    yield
    sys.path = paths


def is_std_lib(module):
    if module in sys.builtin_module_names:
        return True

    with ignore_site_packages_paths():
        imported_module = sys.modules.pop(module, None)
        try:
            import_module(module)
        except ImportError:
            return False
        else:
            return True
        finally:
            if imported_module:
                sys.modules[module] = imported_module

您可以在此处跟踪源代码:这里

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