lesson-9-a-story-on-cyclic-imports

news/2025/2/15 14:26:45/文章来源:https://www.cnblogs.com/super-zzh/p/18716855

前言

模块(Module)是我们用来组织 Python 代码的基本单位。很多功能强大的复杂站点,都由成百上千个独立模块共同组成。

虽然模块有着不可替代的用处,但它有时也会给我们带来麻烦。比如,当你接手一个新项目后,刚展开项目目录。第一眼就看到了攀枝错节、难以理解的模块结构,那你肯定会想: “这项目也太难搞了。” 😂

在这篇文章里,我准备了一个和模块有关的小故事与你分享。

一个关于模块的小故事

小 R 是一个刚从学校毕业的计算机专业学生。半个月前,他面试进了一家互联网公司做 Python 开发,负责一个与用户活动积分有关的小项目。项目的主要功能是查询站点活跃用户,并为他们发送有关活动积分的通知: “亲爱的用户,您好,您当前的活动积分为 x”

项目主要由 notify_users.py 脚本和 fancy_site 包组成,结构与各文件内容如下:

├── fancy_site
│   ├── __init__.py
│   ├── marketing.py        # 与市场活动有关的内容
│   └── users.py            # 与用户有关的内容
└── notify_users.py     # 脚本:发送积分通知

文件 notify_users.py

from fancy_site.users import list_active_users
from fancy_site.marketing import query_user_pointsdef main():"""获取所有的活跃用户,将积分情况发送给他们"""users = list_active_users()points = query_user_points(users)for user in users:user.add_notification(... ...)#  <... 已省略 ...>

文件 fancy_site/users.py

from typing import Listclass User:# <... 已省略 ...>def add_notification(self, message: str):"""为用户发送新通知"""passdef list_active_users() -> List[User]:"""查询所有活跃用户"""pass

文件:fancy_site/marketing.py

from typing import List
from .users import Userdef query_user_points(users: List[User]) -> List[int]:"""批量查询用户活动积分"""def send_sms(phone_number: str, message: str):"""为某手机号发送短信"""

只要在项目目录下执行 python notify_user.py,就能实现给所有活跃用户发送通知。

需求变更

但有一天,产品经理找过来说,光给用户发站内信通知还不够,容易被用户忽略。除了站内信以外,我们还需要同时给用户推送一条短信通知。

琢磨了五秒钟后,小 R 跟产品经理说:“这个需求可以做!”。毕竟给手机号发送短信的 send_sms() 函数早就已经有人写好了。他只要先给 add_notification 方法添加一个可选参数 enable_sms=False,当传值为 True 时调用 fancy_site.marketing 模块里的 send_sms 函数就行。

一切听上去根本没有什么难度可言,十分钟后,小 R 就把 user.py 改成了下面这样:

# 导入 send_sms 模块的发送短信函数
from .marketing import send_smsclass User:# <...> 相关初始化代码已省略def add_notification(self, message: str, enable_sms=False):"""为用户添加新通知"""if enable_sms:send_sms(user.mobile_number, ... ...)

但是,当他修改完代码,再次执行 notify_users.py 脚本时,程序却报错了:

Traceback (most recent call last):File "notify_users.py", line 2, in <module>from fancy_site.users import list_active_usersFile .../fancy_site/users.py", line 3, in <module>from .marketing import send_smsFile ".../fancy_site/marketing.py", line 3, in <module>from .users import User
ImportError: cannot import name 'User' from 'fancy_site.users' (.../fancy_site/users.py)

错误信息说,无法从 fancy_site.users 模块导入 User 对象。

解决环形依赖问题

小 R 仔细分析了一下错误,发现错误是因为 usersmarketing 模块之间产生的环形依赖关系导致的。

当程序在 notify_users.py 文件导入 fancy_site.users 模块时,users 模块发现自己需要从 marketing 模块那里导入 send_sms 函数。而解释器在加载 marketing 模块的过程中,又反过来发现自己需要依赖 users 模块里面的 User 对象。

如此一来,整个模块依赖关系成为了环状,程序自然也就没法执行下去了。

modules_before

不过,没有什么问题能够难倒一个可以正常访问 Google 的程序员。小 R 随便上网一搜,发现这样的问题很好解决。因为 Python 的 import 语句非常灵活,他只需要 把在 users 模块内导入 send_sms 函数的语句挪到 add_notification 方法内,延缓 import 语句的执行就行啦。

class User:# <...> 相关初始化代码已省略def add_notification(self, message: str, send_sms=False):"""为用户添加新通知"""# 延缓 import 语句执行from .marketing import send_sms

改动一行代码后,大功告成。小 R 简单测试后,发现一切正常,然后把代码推送了上去。不过小 R 还没来得及为自己点个赞,意料之外的事情发生了。

这段明明几乎完美的代码改动在 Code Review 的时候被审计人小 C 拒绝了。

小 C 的疑问

小 R 的同事小 C 是一名有着多年经验的 Python 程序员,他对小 R 说:“使用延迟 import,虽然可以马上解决包导入问题。但这个小问题背后隐藏了更多的信息。比如,你有没有想过 send_sms 函数,是不是已经不适合放在 marketing 模块里了?”

被小 C 这么一问,聪明的小 R 马上意识到了问题所在。要在 users 模块内发送短信,重点不在于用延迟导入解决环形依赖。而是要以此为契机,发现当前模块间依赖关系的不合理,拆分/合并模块,创建新的分层与抽象,最终消除环形依赖。

认识清楚问题后,他很快提交了新的代码修改。在新代码中,他创建了一个专门负责通知与消息类的工具模块 msg_utils,然后把 send_sms 函数挪到了里面。之后 users 模块内就可以毫无困难的从 msg_utils 模块中导入 send_sms 函数了。

from .msg_utils import send_sms

新的模块依赖关系如下图所示:

modules_afte

在新的模块结构中,整个项目被整齐的分为三层,模块间的依赖关系也变得只有单向流动。之前在函数内部 import 的“延迟导入”技巧,自然也就没有用武之地了。

小 R 修改后的代码获得了大家的认可,很快就被合并到了主分支。故事暂告一段落,那么这个故事告诉了我们什么道理呢?

总结

模块间的循环依赖是一个在大型 Python 项目中很常见的问题,越复杂的项目越容易碰到这个问题。当我们在参与这些项目时,如果对模块结构、分层、抽象缺少应有的重视。那么项目很容易就会慢慢变得复杂无比、难以维护。

所以,合理的模块结构与分层非常重要。它可以大大降低开发人员的心智负担和项目维护成本。这也是我为什么要和你分享这个简单故事的原因。“在函数内延迟 import” 的做法当然没有错,但我们更应该关注的是:整个项目内的模块依赖关系与分层是否合理。

最后,让我们再尝试从 小 R 的故事里强行总结出几个道理吧:

  • 合理的模块结构与分层可以降低项目的开发维护成本
  • 合理的模块结构不是一成不变的,应该随着项目发展调整
  • 遇到问题时,不要选“简单但有缺陷”的那个方案,要选“麻烦但正确”的那个
  • 整个项目内的模块间依赖关系流向,应该是单向的,不能有环形依赖存在

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/884246.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

KUKA库卡机器人KRC5控制器维修全过程

库卡工业机器人作为现代工业中的重要设备,KUKA机械手KRC5控制柜作为其核心部件,对机器人的正常运行起着至关重要的作用。 【一】KUKA机械手KRC5控制柜的结构分析 库卡工业机器人控制柜主要由电源模块、控制模块、驱动模块、传感器模块等组成。这些模块协同工作,确保机器人的…

萌新刚入坑

markdddown学习 标题 三级标题 四级标题 字体 Hello,World! Hello,World! Hello,World! Hello,World! 引用 选择狂神说java,走上人生巅峰 分割线图片超链接 点击跳转到狂神博客 列表A CABC表格名字 性别 生日张三 男 1997.1.1代码 hello public

Retrieval-Augmented Generation

大模型的商业化落地挑战 更好地控制大模型生成:生成优点:内容的多样性、创造性 缺点:存在不可控制的问题检索优点:可控 缺点:内容具有局限性结合两者:检索增强生成(Retrieval-Augmented Generation, RAG) 案例:金融智能客服系统的几种思路专家系统 生成式模型 大模型检索…

沁恒CH32V307EVT-R1开发板使用说明

首先下载官方的资料:CH32V307EVT.ZIP - 南京沁恒微电子股份有限公司 解压后CH32V307EVT\EVT\EXAM里的就是官方的示例代码,CH32V307EVT\EVT\PUB下面的是文档 CH32V307EVT/EVT/PUB/CH32V30x评估板说明书.pdf:是各个开发板的介绍 CH32V307EVT/EVT/PUB/CH32V30xSCH.pdf:是开发板…

YASKAWA机器人NX100控制柜无法启动维修

当YASKAWA机器人NX100控制柜无法启动时,这无疑是一个典型的机器人故障。首先要做的是对故障进行初步的排查。检查电源连接是否正常,是否存在松动或者断电的情况。这可能是最常见的原因,但也容易被忽视。同时,观察控制柜的指示灯状态,如果指示灯完全不亮,可能是电源供应模…

微信小程序ReferenceError:xxx is not defined报错解决办法

我在开发的过程中想要打印adressInfo的数据信息,却发现一直报错,而AppData里显示adressInfo是被成功赋值的。 那么问题就出现在了"console.log(adressInfo)"这条语句中 只需要把"adressInfo"改为"this.data.adressInfo"报错问题就能够成功解决…

蒙德里安的梦想(状态压缩)

1.首先就是每列状态的表示,如果该列有横着的方块就用1,表示否则就用零,一共n行最多有n个一就是2^n-1最少0个一就是0,所以0<=i<n;j表示i位移的个数,i最多n位所以最多位移n-1位来检验i每一位,如果是1就检验cnt来检验是否有连续奇数个0,最后还要再检验一次来检验高位…

原子物理之光电效应

光电效应的原理、实验细节和动态分析普朗克能量子 定义 普朗克认为,带电微粒(高中阶段主要研究电子)辐射或吸收能量的时候,只能辐射或吸收某个最小能量值的整数倍。这个不可再分的最小能量值就叫做能量子。 通常的,我们认为光子也是一种能量子。 能量子大小 公式: \[ε=…

钜泉代理商,HT6453钜泉M4电能专用MCU,集成1M Flash、32位定时器、CAN总线

HT6453微控制器内部集成了:32位ARMCortex-M4F处理器,多个16位和32位的定时器,DMA控制器,SPI通信接口,I2C通信接口,USART/UART通信接口,SDIO接口,CAN总线控制器,外部存储控制器XMC,USB2.0全速设备接口,HICK自动时钟校准ACC,12位ADC,12位DAC和PVM模块等外设。大量的…

项目管理5阶段

目录背景和价值参考资料 背景和价值 项目的生命周期共包括5个部分,因为每个部分都会包含至少两个相对独立又相互联系的过程,所以又称“过程组”​。每个过程组的主要工作如下: 启动:确立项目的合法地位和总体要求(目标)​,宣布项目正式立项(上马)​。 规划:编制项目计…

图解支付账务系统核心设计

在金融科技领域,支付账务系统的设计和实现是构建高效、安全支付平台的关键。本文深入探讨了支付账务系统的核心设计,从账户管理、记账处理到清结算与会计服务,为读者揭示了支付账务系统设计的复杂性和重要性。通过详细的图解和案例分析,文章为支付系统设计提供了宝贵的理论…

前端初探 Vue.js 第 1 期:创建第一个Vue项目

Vue.js入门第一期Vue.js 作为前端三大框架之一,一直享有盛誉。本文我们将来实现第一个Vue项目。 准备安装 Node.js https://nodejs.org/ 安装 VSCode https://code.visualstudio.com/Download输入npm -v以检查是否安装成功,建议下载最新LTS版本即可。笔者所用为10.8.2 配置镜…