您的位置:首页 > 编程语言 > Python开发

Python,从 package name 得到 module name

2016-06-03 14:05 405 查看

探索

怎样从包名得到模块名?

可以把 pypi 上的所有包爬下来,然后解析出对应的模块就可以了。

其中解析模块显然是一个难点。因为我要的不仅仅是模块,还包括子模块。这会涉及到文件结构和安装逻辑,工作量比较大。

另一个能想到的思路是研究 pip。因为 pip 本身做的就是包名到模块名的过程。

pip

pip 命令有哪些参数?

E:\workspace>pip -h

Usage:
pip <command> [options]

Commands:
install                     Install packages.
uninstall                   Uninstall packages.
freeze                      Output installed packages in requirements format.
list                        List installed packages.
show                        Show information about installed packages.
search                      Search PyPI for packages.
wheel                       Build wheels from your requirements.
zip                         DEPRECATED. Zip individual packages.
unzip                       DEPRECATED. Unzip individual packages.
bundle                      DEPRECATED. Create pybundles.
help                        Show help for commands.

General Options:
-h, --help                  Show help.
-v, --verbose               Give more output. Option is additive, and can be
used up to 3 times.
-V, --version               Show version and exit.
-q, --quiet                 Give less output.
--log-file <path>           Path to a verbose non-appending log, that only
logs failures. This log is active by default at
C:\Users\Administrator\pip\pip.log.
--log <path>                Path to a verbose appending log. This log is
inactive by default.
--proxy <proxy>             Specify a proxy in the form
[user:passwd@]proxy.server:port.
--timeout <sec>             Set the socket timeout (default 15 seconds).
--exists-action <action>    Default action when a path already exists:
(s)witch, (i)gnore, (w)ipe, (b)ackup.
--cert <path>               Path to alternate CA bundle.


list 比较有意思,能列出所有的包名

show 能看到指定包名的信息

可是还是没能到达模块名这一步,import 的时候应该 import 什么?

突然想到 import 的机制中,是根据 sys.path 中的路径来查找的。

那 list 命令是通过什么来找到的包呢?感觉这个点可能成为突破口。

研究下 pip list 的运行过程。

win 下是 pip.exe,不好研究,祭出我的 msys2

$ cat /usr/bin/pip
#!/usr/bin/python2
# EASY-INSTALL-ENTRY-SCRIPT: 'pip==8.1.2','console_scripts','pip'
__requires__ = 'pip==8.1.2'
import sys
from pkg_resources import load_entry_point

if __name__ == '__main__':
sys.exit(
load_entry_point('pip==8.1.2', 'console_scripts', 'pip')()
)


load_entry_point
又是什么啊?

In [1]: from pkg_resources import load_entry_point

In [2]: load_entry_point?
Signature: load_entry_point(dist, group, name)
Docstring: Return `name` entry point of `group` for `dist` or raise ImportError
File:      d:\python27\lib\site-packages\pkg_resources\__init__.py
Type:      function


大概是一个模块实例化的函数

感觉这个模块会对我们的目标有帮助?

赶紧补一下

看源码了解到,它是解析了包中的
entry_points.txt
文件

(group, name) 对应了文件中的 (section, key)

值是一个函数名

2016年6月12日 13:05:50 更新

跟踪
pip list
命令到
pip/util.py:get_installed_distributions


代码如下

def get_installed_distributions(local_only=True,
skip=('setuptools', 'pip', 'python', 'distribute'),
include_editables=True,
editables_only=False):
"""
Return a list of installed Distribution objects.

If ``local_only`` is True (default), only return installations
local to the current virtualenv, if in a virtualenv.

``skip`` argument is an iterable of lower-case project names to
ignore; defaults to ('setuptools', 'pip', 'python'). [FIXME also
skip virtualenv?]

If ``editables`` is False, don't report editables.

If ``editables_only`` is True , only report editables.

"""
if local_only:
local_test = dist_is_local
else:
local_test = lambda d: True

if include_editables:
editable_test = lambda d: True
else:
editable_test = lambda d: not dist_is_editable(d)

if editables_only:
editables_only_test = lambda d: dist_is_editable(d)
else:
editables_only_test = lambda d: True

return [d for d in pkg_resources.working_set
if local_test(d)
and d.key not in skip
and editable_test(d)
and editables_only_test(d)
]


发现其中的关键正是
pkg_resources.working_set
,运行一下,发现里面存储了当前环境的所有包信息。

继续跟踪,找到
pkg_resources.py:find_on_path
,大部分的 list 功能应该是由这个函数完成的。

def find_on_path(importer, path_item, only=False):
"""Yield distributions accessible on a sys.path directory"""
path_item = _normalize_cached(path_item)

if os.path.isdir(path_item) and os.access(path_item, os.R_OK):
if path_item.lower().endswith('.egg'):
# unpacked egg
yield Distribution.from_filename(
path_item, metadata=PathMetadata(
path_item, os.path.join(path_item,'EGG-INFO')
)
)
else:
# scan for .egg and .egg-info in directory
for entry in os.listdir(path_item):
lower = entry.lower()
if lower.endswith('.egg-info') or lower.endswith('.dist-info'):
fullpath = os.path.join(path_item, entry)
if os.path.isdir(fullpath):
# egg-info directory, allow getting metadata
metadata = PathMetadata(path_item, fullpath)
else:
metadata = FileMetadata(fullpath)
yield Distribution.from_location(
path_item,entry,metadata,precedence=DEVELOP_DIST
)
elif not only and lower.endswith('.egg'):
for dist in find_distributions(os.path.join(path_item, entry)):
yield dist
elif not only and lower.endswith('.egg-link'):
entry_file = open(os.path.join(path_item, entry))
try:
entry_lines = entry_file.readlines()
finally:
entry_file.close()
for line in entry_lines:
if not line.strip(): continue
for item in find_distributions(os.path.join(path_item,line.rstrip())):
yield item
break


看一遍代码,问题的答案就呼之欲出了:

怎样得到已经安装的第三方库信息呢?

答:都写在 EGG INFO / DIST INFO!

但这和我们的需求还不太一样,现在看来 pip 处理的是包名。它的搜索、安装、存储都是通过包名来完成的。要想获得包名对应的模块名,还需要自己的操作。

我们可以发现,同目录下还有别的文件,这些文件中的信息都很有用

比如 top_level.txt 中存储了顶级模块名,配合 pkgutil,基本可以解决我们的问题了

现在我们可以做到:对于已经安装的包,通过包名找到所有模块

总结

以为很复杂的一个功能,到头来才发现都写到了包的声明文件里。

再来一遍的话,什么样的建议可以避免或者快速解决这个问题呢?

答:如果我提交过自己的 python 第三方库,肯定就不会有这些疑问了。

还是要多探索,多经历!
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  python package module