6.3 KiB
事件绑定与属性监听技术文档
概述
本文档介绍一个基于 EventDispatcher 的事件系统扩展,通过 EventProperty 类实现可观察的类属性。当属性值发生变化时,会自动触发对应的事件,并通知所有注册的监听器。
该模块结合了事件分发机制和 Python 描述符协议,实现了简洁、灵活的状态变化响应模式,适用于 GUI 编程、状态管理、MVVM 架构等场景。
依赖模块
eventpy.eventdispatcher.EventDispatcher:基础事件分发类。appPublic.dictObject.DictObject:用于封装事件数据的对象,支持点语法访问属性。
扩展方法:bind 和 unbind
为了简化事件监听器的注册与移除操作,为 EventDispatcher 类动态添加了两个便捷方法:
bind(self, eventname, handler)
将指定处理器绑定到某个事件名上。
| 参数 | 类型 | 说明 |
|---|---|---|
self |
object | EventDispatcher 实例 |
eventname |
str | 事件名称(字符串标识) |
handler |
callable | 事件触发时调用的函数或方法 |
等价于调用
appendListener(eventname, handler)
unbind(self, eventname, handler)
从指定事件中移除处理器。
| 参数 | 类型 | 说明 |
|---|---|---|
self |
object | EventDispatcher 实例 |
eventname |
str | 事件名称 |
handler |
callable | 要移除的处理器函数 |
等价于调用
removeListener(eventname, handler)
::: warning 注意
在当前实现中,unbind 方法可能存在未正确移除监听器的问题(见主程序注释),需确保 EventDispatcher 原生的 removeListener 方法功能正常。
:::
EventDispatcher.bind = bind
EventDispatcher.unbind = unbind
核心类:EventProperty
EventProperty 是一个描述符类,用于定义能够触发事件的类级属性。
初始化
def __init__(self, event_name, initial_value=None)
| 参数 | 类型 | 说明 |
|---|---|---|
event_name |
str | 属性变更时触发的事件名称 |
initial_value |
any | 属性的初始值(默认为 None) |
描述符行为
__get__(self, instance, owner)
返回属性当前值。
- 若通过类访问(
instance为None),返回描述符实例本身。 - 若通过实例访问,返回内部存储的
_value。
__set__(self, instance, value)
设置新值并触发事件(仅当值发生改变时)。
- 比较新旧值,若相同则不执行任何操作。
- 更新内部
_value。 - 创建一个
DictObject实例封装事件数据:.target: 触发事件的实例对象.data: 新值.event: 事件名称
- 调用
instance.dispatch(event_name, data)分发事件。
使用示例
以下代码演示如何使用 EventProperty 和事件绑定机制:
class SomeClass(EventDispatcher):
state = EventProperty('onstate', 0) # 初始值为 0,变化时触发 'onstate'
age = EventProperty('onage', 20) # 初始值为 20,变化时触发 'onage'
def __init__(self):
super().__init__() # 初始化父类事件系统
定义监听器
def observer1(data):
print(f"Observer 1 received: {data}")
def observer2(data):
print(f"Observer 2 received: {data}")
def observer3(data):
print(f"Observer 3 received: {data}")
每个监听器接收一个 data 参数,其类型为 DictObject,包含:
data.target: 发生变化的实例data.data: 新的属性值data.event: 事件名称(如'onstate')
绑定事件监听
si = SomeClass()
si.bind('onstate', observer1)
si.bind('onstate', observer2)
si.bind('onage', observer3)
属性赋值触发事件
si.state = 10
# 输出:
# Observer 1 received: <DictObject target=... data=10 event='onstate'>
# Observer 2 received: <DictObject target=... data=10 event='onstate'>
si.state = 20
# 输出:
# Observer 1 received: ...
# Observer 2 received: ...
si.age = 30
# 输出:
# Observer 3 received: <DictObject target=... data=30 event='onage'>
每次修改 state 或 age,只要值不同,就会广播对应事件。
注意事项与限制
-
单例值共享问题
多个实例共享同一个EventProperty描述符中的_value?
❌ 实现中存在潜在 bug:_value是描述符实例变量,如果多个实例共用同一描述符,则可能造成状态污染。
✅ 正确做法应将值存储在实例字典中(例如使用instance.__dict__或弱引用映射)。当前实现是非实例安全的!⚠️ 当前实现中,所有实例共享
_value,这是严重缺陷!建议改进如下:def __set__(self, instance, value): if getattr(instance, f"_{self.event_name}_value") != value: setattr(instance, f"_{self.event_name}_value", value) # ... dispatch event -
unbind可能无效 示例中注释指出unbind存在错误,可能是removeListener实现问题或函数引用不一致导致无法匹配。 -
事件数据结构 推荐统一规范
DictObject的字段命名,便于前端或其他模块解析。
改进建议
| 项目 | 建议 |
|---|---|
| 状态隔离 | 将 _value 存储在实例上而非描述符中,避免跨实例污染 |
| 类型提示 | 添加类型注解提升可维护性 |
| 文档字符串 | 为类和方法补充 docstring |
| 单元测试 | 添加测试用例验证绑定、解绑、事件触发逻辑 |
总结
本模块通过组合 EventDispatcher 和描述符机制,提供了一种声明式的方式来实现属性变化监听。尽管当前实现存在一些设计缺陷(特别是状态共享问题),但整体架构清晰,易于扩展。
适用于需要轻量级响应式编程能力的 Python 应用,经过适当优化后可用于生产环境。
✅ 提示:建议重构
EventProperty以支持实例级状态存储,确保线程安全和多实例兼容性。