以编程方式获取模块名称仅使用PyPI包名称

时间:2018-04-11 00:47:38

标签: python python-import python-packaging

我想以编程方式根据包名列表安装和导入包。对于大多数软件包,这是没有问题的,因为软件包和模块名称是相同的。

但是,PyYAML包是一个例外,因为它的模块只被调用yaml,并且可能有更多例外。

这是我用来安装和导入软件包/模块的python函数:

def install_and_import(package):
    import importlib
    try:
        importlib.import_module(package) #needs module name!
    except ImportError:
        import pip
        pip.main(['install', package]) #needs package name
    finally:
        globals()[package] = importlib.import_module(package)

为此列表中的每个包调用函数['backoff', 'pyyaml'](从requirements.txt解析),我得到:

Collecting backoff
Installing collected packages: backoff
Successfully installed backoff-1.4.3
Collecting pyyaml
Installing collected packages: pyyaml
Successfully installed pyyaml-3.12
[...Trackback...]
ModuleNotFoundError: No module named 'pyyaml'

有没有办法,只给出包名称(例如pyyaml),找出我实际需要导入的模块的名称(例如yaml)?

2 个答案:

答案 0 :(得分:2)

使用distlibpip install distlib)和hacky"猜测"在模块名称(这可以改进,但希望在我必须回到其他东西之前给你我想出的东西!)

import os.path
import sys

import distlib.database


def to_module(s):
    parts = os.path.splitext(s)[0].split(os.sep)
    if s.endswith('.py'):
        if parts[-1] == '__init__':
            parts.pop()
    elif s.endswith('.so'):
        parts[-1], _, _ = parts[-1].partition('.')
    return '.'.join(parts)


def main():
    dp = distlib.database.DistributionPath()
    dist = dp.get_distribution(sys.argv[1])
    for f, _, _ in dist.list_installed_files():
        if f.endswith(('.py', '.so')):
            print(to_module(f))


if __name__ == '__main__':
    exit(main())

to_module非常自我解释,我使用DistributionPath()(表示"已安装"模块)来查询已安装的特定包。从那里我列出了文件,如果它们看起来像模块将它们转换为模块。请注意,这不会捕捉six(动态添加six.moves模块)之类的内容,但它是一个非常好的一阶近似值。

我也在这里对posix做出假设,对于你想要调整的其他平台(例如我认为会使用.pyd的窗口。)

示例输出:

$ python test.py pyyaml
_yaml
yaml
yaml.composer
yaml.constructor
yaml.cyaml
yaml.dumper
yaml.emitter
yaml.error
yaml.events
yaml.loader
yaml.nodes
yaml.parser
yaml.reader
yaml.representer
yaml.resolver
yaml.scanner
yaml.serializer
yaml.tokens
$ python test.py coverage
coverage.pickle2json
coverage.execfile
coverage.python
coverage.summary
coverage.html
coverage.plugin
coverage.pytracer
coverage.config
coverage.__main__
coverage.data
coverage.debug
coverage.annotate
coverage.backward
coverage.parser
coverage.misc
coverage.files
coverage.multiproc
coverage.backunittest
coverage.env
coverage
coverage.control
coverage.cmdline
coverage.results
coverage.version
coverage.plugin_support
coverage.templite
coverage.collector
coverage.xmlreport
coverage.report
coverage.phystokens
coverage.bytecode
coverage.tracer
coverage.fullcoverage.encodings

答案 1 :(得分:0)

基于Anthony Sottile的excellent answer,我创建了一个简化版本,从包中提供一个模块。我的情况的大多数包都有一个主要模块。 (当然,使用多个“主”模块处理更复杂的包会很好。)

Windows上进行测试,我发现了.list_installed_files()的一些问题(其中一些问题在此“解决方案”中得到了解决):

  1. os.sep无法正确分割文件名,具体取决于分发的类型。 (当轮子朝向posix方向时,鸡蛋进入os.sep。)
  2. 对于某些发行版,您会获得完整的路径(看起来像鸡蛋)。这导致疯狂的模块名称猜测(例如,'C:.Users.Username.AppData.RestOfPath.File')。
  3. 这将搜索第一个__init__.py以通知模块名称。如果找不到,它只返回包名(包含90%的情况)。

    def package_to_module(package):
        dp = distlib.database.DistributionPath(include_egg=True)
        dist = dp.get_distribution(package)
        if dist is None:
            raise ModuleNotFoundError
        module = package # until we figure out something better
        for filename, _, _ in dist.list_installed_files():
            if filename.endswith(('.py')):
                parts = os.path.splitext(filename)[0].split(os.sep)
                if len(parts) == 1: # windows sep varies with distribution type
                    parts = os.path.splitext(filename)[0].split('/')
                if parts[-1].startswith('_') and not parts[-1].startswith('__'):
                    continue # ignore internals
                elif filename.endswith('.py') and parts[-1] == '__init__':
                    module = parts[-2]
                    break
        return module
    

    一些例子:

    >>> package_to_module("pyyaml")
    'yaml'
    >>> package_to_module("click")
    'click'
    >>> package_to_module("six")
    'six'
    >>> package_to_module("pip")
    'pip'
    >>> package_to_module("doesntexist")
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
      File "<stdin>", line 5, in package_to_module
    ModuleNotFoundError
    
相关问题