Python,从 package name 得到 module name

来源:互联网 发布:淘宝服装抠图 编辑:程序博客网 时间:2024/06/06 01:39

探索

怎样从包名得到模块名?
可以把 pypi 上的所有包爬下来,然后解析出对应的模块就可以了。
其中解析模块显然是一个难点。因为我要的不仅仅是模块,还包括子模块。这会涉及到文件结构和安装逻辑,工作量比较大。
另一个能想到的思路是研究 pip。因为 pip 本身做的就是包名到模块名的过程。

pip

pip 命令有哪些参数?

E:\workspace>pip -hUsage:  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 sysfrom pkg_resources import load_entry_pointif __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_pointIn [2]: load_entry_point?Signature: load_entry_point(dist, group, name)Docstring: Return `name` entry point of `group` for `dist` or raise ImportErrorFile:      d:\python27\lib\site-packages\pkg_resources\__init__.pyType:      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 第三方库,肯定就不会有这些疑问了。
还是要多探索,多经历!

0 0
原创粉丝点击