You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何更精细控制setuptools包导入?解决其贪婪导入问题

问题背景

我的代码结构如下:

tstpkg/
  pyproject.toml
  tstpkg/
    __init__.py
    modA/
      fnA1
      fnA2
    modB/
      fnB1
      fnB2
    modC/
      fnC1
      fnC2

我希望导入tstpkg后,仅能调用tstpkg.fnA1()和tstpkg.fnC2(),于是做了以下配置:
pyproject.toml内容:

[build-system]
requires = ["setuptools"]
build-backend = "setuptools.build_meta"

[tool.setuptools.packages.find]
namespaces = false

tstpkg/__init__.py内容:

from tstpkg.modA import fnA1
from tstpkg.modC import fnC2

但实际安装后导入tstpkg,却能直接访问tstpkg.modA和tstpkg.modC整个子模块,不符合预期。用del(modA, modC)可以解决但不够优雅,同时我注意到执行from scipy.special import gammaln as sgl时,并不会让整个scipy.special变得可访问,因此有两个疑问:


1. 为什么会出现这种情况?

这是Python模块导入机制的默认行为,和setuptools无关:

  • 当执行from tstpkg.modA import fnA1时,Python会先完整加载tstpkg.modA子模块,再将fnA1的引用挂载到tstpkg的命名空间中。
  • 加载后的子模块会被自动添加到父模块(tstpkg)的命名空间里,作为父模块的属性存在,因此可以通过tstpkg.modA直接访问。

而scipy.special的例子不同:scipy的__init__.py没有触发scipy.special的加载,也没有将其作为属性暴露到自身命名空间中。即便你执行from scipy.special import gammaln加载了子模块,scipy的命名空间里也不会自动生成special属性,自然无法通过scipy.special访问。


2. 是否有更自然的方法实现我想要的行为?

有几种更优雅的方案:

方案一:动态导入避免子模块挂载

使用importlib动态导入函数,不会将子模块添加到父模块命名空间:

from importlib import import_module

fnA1 = import_module("tstpkg.modA").fnA1
fnC2 = import_module("tstpkg.modC").fnC2

方案二:用__all__限制导出范围

在tstpkg/__init__.py中定义__all__,明确指定from tstpkg import *时能导入的名称:

__all__ = ["fnA1", "fnC2"]

from tstpkg.modA import fnA1
from tstpkg.modC import fnC2

注:__all__仅限制import *的行为,用户仍可通过import tstpkg.modA直接导入子模块,但这是Python模块系统的常规开放特性。

方案三:调整代码结构

如果子模块无需独立存在,可将fnA1、fnC2直接写入tstpkg/__init__.py,或放在主包下的单独模块(如tstpkg/core.py)中导入,从根源避免子模块暴露。


内容的提问来源于stack exchange,提问作者user1451632

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 10:43:03