【问题标题】:Get module name programmatically with only PyPI package name仅使用 PyPI 包名称以编程方式获取模块名称
【发布时间】:2018-09-20 17:34:58
【问题描述】:

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

但是,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)?

【问题讨论】:

    标签: python python-import python-packaging


    【解决方案1】:

    在模块名称上使用distlib (pip install distlib) 和一个骇人听闻的“猜测”(这可以改进,但我想在我必须回到其他事情之前给你我想出的东西!)

    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 的 windows)。

    样本输出:

    $ 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
    

    【讨论】:

    • 请注意distlib 这样做的方式是读取安装元数据的RECORD 文件(通常在site-packages 内的XXX.dist-info 目录中找到)
    • 圣烟。我试图根据您的功能创建一个更通用的解决方案,但存在很多问题:windows(有时文件包含绝对路径,有时斜线指向一个方向,有时另一个与posix,大型复杂包有很多模块我不知道要导入哪个。感谢您的帮助。我在下面添加我的解决方案。
    【解决方案2】:

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

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

    1. os.sep 不会根据分发类型正确拆分文件名。 (当轮子是 posix 方向时,鸡蛋去 os.sep。)
    2. 对于某些发行版,您可以获得完整的路径(看起来像鸡蛋)。这会导致疯狂的模块名称猜测(例如,'C:.Users.Username.AppData.RestOfPath.File')。

    这会搜索第一个__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
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-03-07
      • 2023-04-04
      • 2018-09-15
      • 1970-01-01
      • 2014-01-17
      • 1970-01-01
      • 2018-03-12
      • 1970-01-01
      相关资源
      最近更新 更多