如何在Python中查找任何包的“导入名称”?

33

我想知道是否有一种可靠且一致的方法来获取Python包的“导入名称”/命名空间。例如:

:django-haystack
导入名称:haystack

或者

:ipython
导入名称:IPython

据我所知,PyPi不存储该信息,我已经通过PyPiXmlRpc进行了检查。

我还尝试自动下载软件包、提取并查找.egg-info文件夹,但有些软件包根本没有这个文件夹。

任何帮助将不胜感激,并将用于一个有礼貌的小工具 :)

5个回答

10

轮子

我知道这是一个老问题,但是现在已经发明了轮子包!由于轮子只是一个被解压到 lib/site-packages 目录中的zip文件,因此检查轮子存档的内容可以提供顶级导入。

>>> import zipfile
>>> zf = zipfile.ZipFile('setuptools-35.0.2-py2.py3-none-any.whl')
>>> top_level = set([x.split('/')[0] for x in zf.namelist()])
>>> # filter out the .dist-info directory
>>> top_level = [x for x in top_level if not x.endswith('.dist-info')]
>>> top_level 
['setuptools', 'pkg_resources', 'easy_install.py']

所以setuptools实际上给了你三个顶级导入!

pip download

现在pip有一个下载命令,因此您可以简单地运行pip download setuptools(或其他您喜欢的软件包),然后检查它。

反向查询

不幸的是,我还没有找到一个容易的方法来反向查询。也就是说,给定导入名称,找出包名称。如果您正在查看一些示例代码,或者使用预安装了许多软件包的Anaconda并且想要知道实际的软件包名称,则可能会遇到问题。


1
这很棒,因为它不需要安装发行版就可以检索数据。谢谢! - wim
我有一个改进建议:加入[top_level_fname] = [x for x in zf.namelist() if x.endswith('top_level.txt')],然后只需使用zf.read(top_level_fname).decode('utf-8').splitlines() - wim

9
请注意,这里所说的“package”实际上指的是“distribution”,而非Python中的模块或包。一个distribution可以包含零个或多个模块或包。这意味着distribution和packages之间并不存在一对一的映射关系。
我不确定是否有一种方法可以检测distribution将安装哪些模块和包,除了实际安装并检查新添加的packages、modules和pth文件对文件系统的更改以外。

我明白。我认为主要问题在于发行版和软件包/软件包之间的关系。但是我的问题肯定有答案。我会找到最可靠和一致的答案,并让你们知道。 - kirpit
经过漫长的研究期,我最终得到了你的答案,即任何Python发行版都可能有一个或多个包,例如包本身和一个测试包作为简单示例。无论如何,我已经完成了我的实验性原型(pydoc.net),希望能够提供某种API来一致地解决这个问题。谢谢Wichert。 - kirpit

5

原则上,获取该信息所需的一切都在每个该类软件包中应该有的setup.py文件中。该信息大致为Distribution对象的packages、py_modules、ext_package和ext_modules的并集。实际上,这里有一个小脚本,模拟了distutils.core.setup,仅用于获取该信息。

import distutils.core
distutils.core._setup_stop_after = "config"
_real_setup = distutils.core.setup
def _fake_setup(*args, **kwargs):
    global dist
    dist = _real_setup(*args, **kwargs)

distutils.core.setup = _fake_setup

import sys
setup_file = sys.argv[1]
sys.argv[:] = sys.argv[1:]
import os.path
os.chdir(os.path.dirname(setup_file))

execfile(os.path.basename(setup_file))

cat = lambda *seq: sum((i for i in seq if i is not None), [])
pkgs = set(package.split('.')[0] for package
           in cat(dist.packages,
                  dist.py_modules,
                  [m.name for m in cat(dist.ext_modules)],
                  [m.name for m in cat(dist.ext_package)]))

print "\n".join(pkgs)

对于许多软件包来说,这将像魔法一样运行得很好,但是有一个反例,即numpy。它会出现问题,因为numpy提供了自己的distutils,我无法找到明显的解决方法。


我还没有机会尝试,但会尽快尝试并反馈。提前感谢! - kirpit
是的,它适用于许多软件包版本,但据我所知,它在modulefinder的逻辑中工作,该逻辑为您提供了所有已使用的模块,并从中获取高级命名空间。不幸的是,如果发布使用其他软件包(例如test; check django-uni-form),则会给出多个命名空间。顺便说一下,我稍微修改了您的脚本;https://gist.github.com/1176645 - kirpit
4
Python 应该要求在 PyPI 上“import name”字段是必需的!不可能那么痛苦...卡住了。 - kirpit
2
说真的...我已经想了好一会儿如何从导入名称获取分发名称。目前就是没有办法。我原本希望 setup.py 中的 provides 键可以提供这个信息。我本来还想通过 https://wiki.python.org/moin/PyPIXmlRpc 来查询,但是没有成功 ): - d0c_s4vage

2

我的项目约翰尼·德普有这个功能:

>>> from johnnydep import JohnnyDist
>>> dist = JohnnyDist("django-haystack")
>>> dist.import_names
['haystack']

请注意,一个发行版可能提供多个导入名称:
>>> JohnnyDist("setuptools").import_names
['pkg_resources', 'setuptools']

或者根本不返回任何内容:

>>> JohnnyDist("bs4").import_names
[]

0

在搜索了大量信息后,最终发现唯一能按照我的预期工作的东西。例如 python-dotenv 包,其导入名称为 dotenv

$ cat $(python -c "import pkg_resources; print(pkg_resources.get_distribution('python-dotenv').egg_info)")/top_level.txt
dotenv

(taken from this answer)


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