发布-订阅模式:为什么用 append 而非直接赋值?

26 阅读4分钟

发布-订阅模式:为什么用 append 而非直接赋值?

解析 publish-subscribe 模式中「追加」与「覆盖」的核心差异

错题回顾

先看一个Python基础错题,帮我们理解「赋值」与「追加」的核心差异:

# 错误写法 list1 = [] list1 = range(1,9) # 这是错的!

❌ 错误原因:

  • 本意大概率是“往空列表 list1 中添加 1~8 的数字”,但直接用 = 赋值会让 list1 从「空列表」直接变成「range 对象」(Python3 中),而非往列表里添加元素;
  • 如果想保留 list1 的列表类型并添加 1~8 的数字,正确写法如下:
# 正确写法1:将 range 转为列表后赋值 list1 = list(range(1,9)) # 正确写法2:往空列表批量追加元素(extend) list1 = [] list1.extend(range(1,9)) # 正确写法3:逐个追加元素(append) list1 = [] for i in range(1,9): list1.append(i)

这个错题的核心逻辑,和我们今天要讲的「发布-订阅模式中 append vs 直接赋值」完全相通:赋值是“替换/覆盖”,而 append/extend 是“往列表里追加元素”。接下来我们就结合发布-订阅模式,深入拆解这个逻辑。

一、核心问题:为什么不用「键=值」而用 append?

在实现发布-订阅(观察者)模式时,很多初学者会疑惑:self.subscribers[event_name].append(callback) 为什么要多此一举用 append?直接写 self.subscribers[event_name] = callback 不行吗?

答案很明确:直接赋值是「覆盖」,append 是「追加」 —— 发布-订阅模式的核心是「一个事件绑定多个回调函数」,直接赋值会彻底破坏这一核心需求。

二、反例:直接赋值的致命问题

如果给事件对应的键直接赋值,新的回调函数会覆盖旧的回调,导致一个事件永远只能有一个响应,完全失去“多订阅者”的意义。

注意:以下是错误写法,仅用于演示问题!
class EventManager: def __init__(self): self.subscribers = {} # 普通字典存储事件-回调映射 # 错误写法:直接赋值(覆盖原有回调) def subscribe(self, event_name, callback): self.subscribers[event_name] = callback # 测试:给 click 事件绑定两个回调 def show_msg(): print("按钮点击了!") def log_click(): print("记录点击日志") # 初始化管理器并订阅 manager = EventManager() manager.subscribe('click', show_msg) # 第一次订阅 manager.subscribe('click', log_click) # 第二次订阅(覆盖掉 show_msg) # 触发事件 def publish(event_name): if event_name in manager.subscribers: manager.subscribers[event_name]() publish('click') # 仅输出「记录点击日志」,show_msg 被覆盖丢失!

运行结果中,只有最后一个订阅的 log_click 执行,第一个 show_msg 被完全覆盖 —— 这在实际场景中(比如按钮点击既要弹提示又要埋点)完全不可用。

三、正确实现:append 追加多个回调

发布-订阅模式的核心是「一对多」(一个事件对应多个订阅者),因此需要用列表存储每个事件的回调函数,append 则是往列表中“追加”新回调,而非替换:

class EventManager: def __init__(self): self.subscribers = {} # 正确写法:先初始化列表,再追加回调 def subscribe(self, event_name, callback): # 若事件不存在,先创建空列表 if event_name not in self.subscribers: self.subscribers[event_name] = [] # 追加回调(不覆盖原有内容) self.subscribers[event_name].append(callback) # 复用上面的 show_msg 和 log_click 函数 manager = EventManager() manager.subscribe('click', show_msg) manager.subscribe('click', log_click) # 触发事件:遍历列表执行所有回调 def publish(event_name): if event_name in manager.subscribers: for callback in manager.subscribers[event_name]: callback() publish('click') # 输出: # 按钮点击了! # 记录点击日志

此时 self.subscribers['click'] 是一个列表 [show_msg, log_click],遍历执行即可触发所有订阅者的回调,完美实现“一个事件多响应”。

四、append 与直接赋值的场景对比

写法 核心效果 适用场景
self.subscribers[event_name] = callback 覆盖原有回调,一个事件仅保留最后一个回调 极端场景:事件永远只需要 1 个响应(失去订阅模式价值)
self.subscribers[event_name].append(callback) 追加新回调,一个事件可绑定 N 个回调 99% 场景:GUI 事件、异步通知、消息订阅、日志监控等

五、核心总结

  • 发布-订阅模式的本质是「一对多」,必须用列表存储每个事件的回调集合;
  • append 是往列表“追加”新回调,保证多订阅者共存;
  • 直接赋值是“替换”列表为单个函数,破坏多订阅者逻辑;
  • 若用普通字典,需先判断事件是否存在,避免 KeyError(也可使用 collections.defaultdict(list) 简化);
  • 底层逻辑和列表操作一致:= 是赋值/覆盖,append/extend 是追加元素。

技术博客 © 2025 发布-订阅模式专题 | 转载请注明出处