栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 面试经验 > 面试问答

在不修改sys.path或第三方软件包的情况下,在Python软件包中导入供应商依赖性

面试问答 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

在不修改sys.path或第三方软件包的情况下,在Python软件包中导入供应商依赖性

首先,我建议不要出售。一些主要软件包以前曾使用过供应商,但是为了避免不得不处理供应商的痛苦,已经放弃了。

requests
图书馆就是一个这样的例子。如果您依靠
pipinstall
用来安装软件包的人员,则 只需使用依赖项
并向人们介绍虚拟环境。不要假设您需要承担使依赖关系不复杂的负担,也不必阻止人们在全局Python
site-packages
位置中安装依赖项。

同时,我知道第三方工具的插件环境有所不同,并且如果对该工具使用的Python安装添加依赖项很麻烦或无法进行商贩销售,则是可行的选择。我看到Anki在

.zip
不支持setuptools的情况下将扩展名作为文件分发,因此肯定是这种环境。

因此,如果您选择供应商依赖性,则可以使用脚本来管理依赖性并更新其导入。这是您的选择#1,但 自动化

这是

pip
项目选择的路径,有关其自动化的信息,请参见其
tasks
子目录,该子目录建立在
invoke
库上。请参阅pip项目供应商README,以了解其政策和原理(其中之一是
pip
需要
自举 ,例如,可以使用其依赖项来安装任何东西)。

您不应使用任何其他选项;您已经列举了#2和#3的问题。

使用自定义导入程序的选项#4的问题在于, 您仍然需要重写import
。换句话说,所使用的自定义导入器钩子

setuptools
根本无法解决供应商名称空间的问题,而是可以在缺少供应商软件包的情况下动态导入顶级软件包(
pip
通过
手动
分包过程解决的问题)。
setuptools
实际上使用选项#1,在那里他们重写供应商软件包的源代码。例如,在
packaging
项目的
setuptools
供应商子包中查看这些行;该
setuptools.extern
命名空间是由自定义导入钩,然后重定向要么处理
setuptools._vendor

或顶级名称(如果从供应商化的软件包导入失败)。

pip
自动化更新vendored包采取以下步骤:

  • 删除 所有
    _vendor/
    子目录,除了文档的
    __init__.py
    文件和要求的文本文件。
  • 用于使用
    pip
    名为的专用需求文件将所有供应商的依赖项安装到该目录中,
    vendor.txt
    避免编译
    .pyc
    字节缓存文件并忽略瞬时依赖项(假定
    vendor.txt
    已在其中列出);使用的命令是
    pip install -t pip/_vendor -r pip/_vendor/vendor.txt --no-compile --no-deps
  • 这是由安装删除一切
    pip
    ,但在vendored环境中不需要的,即
    *.dist-info
    *.egg-info
    bin
    目录,并从已安装的依赖关系的几件事情
    pip
    永远不会使用。
  • 收集所有已安装的目录,并添加没有
    .py
    扩展名的文件(因此白名单中没有任何内容);这是
    vendored_libs
    清单。
  • 重写进口;这仅仅是一系列的正则表达式的,其中每一个名字
    vendored_lists
    被用来替换
    import <name>
    发生与
    import pip._vendor.<name>
    一位
    from <name>(.*) import
    有发生
    from pip._vendor.<name>(.*) import
  • 应用一些补丁以清除所需的其余更改;从供应商的角度来看,这里只有
    pip
    补丁
    requests
    是有趣的,因为它
    requests
    requests
    库已删除的供应商软件包更新了库的向后兼容性层。这个补丁是相当元的!

因此,从本质上讲,该方法最重要的部分是

pip
重写供应商的程序包导入非常简单;为了简化逻辑并删除
pip
特定部分,它的解释过程很简单:

import shutilimport subprocessimport refrom functools import partialfrom itertools import chainfrom pathlib import PathWHITELIST = {'README.txt', '__init__.py', 'vendor.txt'}def delete_all(*paths, whitelist=frozenset()):    for item in paths:        if item.is_dir(): shutil.rmtree(item, ignore_errors=True)        elif item.is_file() and item.name not in whitelist: item.unlink()def iter_subtree(path):    """Recursively yield all files in a subtree, depth-first"""    if not path.is_dir():        if path.is_file(): yield path        return    for item in path.iterdir():        if item.is_dir(): yield from iter_subtree(item)        elif item.is_file(): yield itemdef patch_vendor_imports(file, replacements):    text = file.read_text('utf8')    for replacement in replacements:        text = replacement(text)    file.write_text(text, 'utf8')def find_vendored_libs(vendor_dir, whitelist):    vendored_libs = []    paths = []    for item in vendor_dir.iterdir():        if item.is_dir(): vendored_libs.append(item.name)        elif item.is_file() and item.name not in whitelist: vendored_libs.append(item.stem)  # without extension        else:  # not a dir or a file not in the whilelist continue        paths.append(item)    return vendored_libs, pathsdef vendor(vendor_dir):    # target package is <parent>.<vendor_dir>; foo/_vendor -> foo._vendor    pkgname = f'{vendor_dir.parent.name}.{vendor_dir.name}'    # remove everything    delete_all(*vendor_dir.iterdir(), whitelist=WHITELIST)    # install with pip    subprocess.run([        'pip', 'install', '-t', str(vendor_dir),        '-r', str(vendor_dir / 'vendor.txt'),        '--no-compile', '--no-deps'    ])    # delete stuff that's not needed    delete_all(        *vendor_dir.glob('*.dist-info'),        *vendor_dir.glob('*.egg-info'),        vendor_dir / 'bin')    vendored_libs, paths = find_vendored_libs(vendor_dir, WHITELIST)    replacements = []    for lib in vendored_libs:        replacements += ( partial(  # import bar -> import foo._vendor.bar     re.compile(r'(^s*)import {}n'.format(lib), flags=re.M).sub,     r'1from {} import {}n'.format(pkgname, lib) ), partial(  # from bar -> from foo._vendor.bar     re.compile(r'(^s*)from {}(.|s+)'.format(lib), flags=re.M).sub,     r'1from {}.{}2'.format(pkgname, lib) ),        )    for file in chain.from_iterable(map(iter_subtree, paths)):        patch_vendor_imports(file, replacements)if __name__ == '__main__':    # this assumes this is a script in foo next to foo/_vendor    here = Path('__file__').resolve().parent    vendor_dir = here / 'foo' / '_vendor'    assert (vendor_dir / 'vendor.txt').exists(), '_vendor/vendor.txt file not found'    assert (vendor_dir / '__init__.py').exists(), '_vendor/__init__.py file not found'    vendor(vendor_dir)


转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/455766.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号