【问题标题】:Create name_space package that contains standalone installable sub-packages创建包含独立可安装子包的 name_space 包
【发布时间】:2020-12-07 09:33:58
【问题描述】:

我目前正在尝试将多个相关包组合在一个父包 (meta_package) 中。在这样做的同时,我还希望其中一个包可以作为独立包安装。为此,我创建了以下文件夹结构:

├── meta_package
│   ├── subpackage1
│   │   ├── module.py
│   │   ├── __init__.py
│   ├── subpackage2
│   │   ├── module.py
│   │   └── __init__.py
│   ├── subpackage3
│   │   ├── module.py
│   │   └── __init__.py
│   └── installable_subpackage
│       ├── README.md
│       ├── __init__.py
│       ├── requirements.txt
│       ├── setup.py
│       ├── installable_subpackage
│       │   ├── __init__.py
│       │   └── submodule
│       │       ├── __init__.py
│       │       └── module.py

虽然上述结构达到了预期的效果,但无论是在将子包定义为namespace packages 还是普通包时,它都引入了一个额外的installable_subpackage 目录。因此,要从installable_subpackage 导入一个类,我必须使用以下导入语句:

from meta_package.installable_subpackage.installable_subpackage.submodule.module import Class

但是,我希望能够使用以下(更短的)导入语句来导入类:

from meta_package.installable_subpackage.submodule.module import Class

我已经尝试过的

使用名称空间包

我尝试将namespace packages 用于子包,而不是使用普通包。不过这并没有解决多余文件夹的问题,还引入了一些python import traps

导入 installable_subpackage.init.py 文件中的冗余文件夹(模块)

我还尝试在installable_subpackage.__init__.py 文件中导入installable_subpackage 子模块:

import meta_package.installable_subpackage.installable_subpackage

但这似乎不起作用,因为meta_package.installable_subpackage.submodule 导入路径不指向meta_package.installable_subpackage.installable_subpackage.submodule 模块。我认为这是因为此方法仅适用于类而不适用于模块。

使用 setuptools 包和 package_dir 参数

最后,根据to this issue,我尝试在meta_package setup.py 中使用setuptools packagespackage_dir 参数摆脱多余的文件夹。为此,我使用了以下 setup.py:

from setuptools import setup, find_namespace_packages

setup(
    name="meta_package",
    ...
    packages=find_namespace_packages(include=["meta_package.*"]),
    package_dir={"meta_package.installable_subpackage": "meta_package/installable_subpackage/installable_subpackage"},
)

然而,这似乎也没有摆脱多余的文件夹。

问题

我试图实现的包装结构是否可行?此外,如果是这样,是否鼓励或不建议使用它?

系统信息

  • Python 版本:Python 3.8.5
  • 虚拟环境:Conda

【问题讨论】:

  • 当使用package_dir时,您不能真正使用find_namespace_packages,您必须手动编写列表或在将其分配给packages之前对其进行修改。 -- 可能是这样的: stackoverflow.com/a/63141103/11138259
  • @sinoroc 非常感谢它解决了我的问题!这确实是对find_namespace_packages 方法和package_dir 参数如何工作的误解。您是否还知道在我的用例中使用namespace 包还是标准包更好?根据我目前的理解,命名空间包主要用于在不同文件夹中分发相关包,同时防止名称冲突 (stackoverflow.com/questions/21819649/…)。
  • @sinoroc 在阅读了python.developreference.com/article/25478339/… 之后,我现在认为使用命名空间包是有意义的。我想让用户有机会只安装小子模块而不必下载整个库。

标签: python namespaces setuptools packaging


【解决方案1】:

正如@sinoroc指出的那样:

使用 package_dir 时,你不能真正使用 find_namespace_packages,你必须手动编写列表或在将其分配给包之前对其进行修改。

因此,为了实现所需的行为,我必须在将包列表提供给setuptools.setup 方法之前对其进行修改。这可以通过多种方式完成。

1。将虚拟缩短包添加到包列表中

我们可以将每个冗余文件夹的额外(缩短)模块条目添加到packages 列表中。这可以通过使用以下 setup.py 文件来完成:

setup.py 文件

from setuptools import setup, find_namespace_packages

# Retrieve package list
PACKAGES = find_namespace_packages(include=["meta_package*"])+["meta_package.installable_subpackage"]

setup(
    name="meta_package",
    ...
    packages=PACKAGES,
    package_dir={
        "meta_package.installable_subpackage": "meta_package/installable_subpackage/installable_subpackage",
    },
)

要为多个子包自动执行此操作,您可以使用以下代码:

# Retrieve package list
PACKAGES = find_namespace_packages(include=["meta_package*"])

# Add extra virtual shortened package for each of namespace_pkgs that contain redundant folders
namespace_pkgs = ["installable_subpackage"]
exclusions = r"|".join(
    [r"\." + item + r"\.(?=" + item + r".)" for item in namespace_pkgs]
)
PACKAGE_DIR = {}
for package in PACKAGES:
    sub_tmp = re.sub(exclusions, ".", package)
    if sub_tmp is not package:
        PACKAGE_DIR[sub_tmp] = package.replace(".", "/")
PACKAGES.extend(PACKAGE_DIR.keys())

setup(
    name="meta_package",
    ...
    packages=PACKAGES,
    package_dir=PACKAGE_DIR,
)

2。修改包列表,使其仅包含缩短的包

或者,如果您想用较短的名称完全替换长模块名称,您可以使用以下setup.py

setup.py 文件

from setuptools import setup, find_namespace_packages

# Retrieve package list
PACKAGES = find_namespace_packages(include=["meta_package*"])

# Remove redundant folders from the package list
PACKAGES = [re.sub(r"\.installable_subpackage\.(?=installable_subpackage.)", ".", package) for package in PACKAGES]

setup(
    name="meta_package",
    ...
    packages=PACKAGES,
    package_dir={
        "meta_package.installable_subpackage": "meta_package/installable_subpackage/installable_subpackage",
    },
)

这也可以使用以下代码为多个子包自动完成:

# Remove redundant folder from package list
red_folders = ["installable_subpackage"]
exclusions = r"|".join(
    [r"\." + item + r"\.(?=" + item + r".)" for item in red_folders]
)
PACKAGE_DIR = {}
for index, package in enumerate(PACKAGES):
    sub_tmp = re.sub(exclusions, ".", package)
    if sub_tmp is not package:
        PACKAGES[index] = sub_tmp

重要说明

  • 在使用上述方法时,需要注意的是,如果在开发模式下安装软件包,它们还不能工作(请参阅this issue)。因此,最好使用第一种方法,因为开发人员仍然可以使用长模块名称,而用户也可以使用较短的模块名称。
  • 请记住,如果您的命名空间包根文件夹中有__init__.py 文件(请参阅setuputils documentation),上述方法将不起作用。

更新

得到这个答案后,我尝试将此逻辑放入setup.cfg 文件中,以便与PEP517/518 兼容。在执行此操作时,我遇到了一些问题。这个问题的解决方案可以在我在the setuptools GitHub page 上创建的这个问题上找到。可以在here 找到示例存储库。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多