修改:File "C:\Users\Administrator\Desktop\project\core\event_center.py", line 114
def subscribe(self, event_type, callback): """订阅事件""" if event_type not in self.subscribers: self.subscribers[event_type] = [] 已重新声明上文定义的无用法的 'subscribe'未使用局部函数 '__init__',从外部作用域隐藏名称 'self'函数中的变量应小写,从外部作用域隐藏名称 'event'代码#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
优化后的事件中心模块 (最终稳定版)
"""
import logging
import threading
import uuid
import time
from enum import Enum, auto
from typing import Tuple, Dict, List, Callable, Optional, Any, Set, DefaultDict, Union
from collections import defaultdict
from dataclasses import dataclass, field
from concurrent.futures import ThreadPoolExecutor
# 初始化日志
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
class EventType(Enum):
"""完整事件类型枚举"""
# 基础事件类型
MODULE_RUN = auto()
ANALYSIS_RESULT = auto()
MODULE_ERROR = auto()
REGISTER_UI = auto()
GET_RESULTS = auto()
# 系统事件
SYSTEM_STARTUP = auto()
SYSTEM_SHUTDOWN = auto()
MODULE_READY = auto()
ERROR = auto()
# 模块特定事件
INPUT_ANALYSIS_START = auto()
INPUT_ANALYSIS_END = auto()
COMBINATION_ANALYSIS_START = auto()
COMBINATION_ANALYSIS_END = auto()
FOLLOW_ANALYSIS_START = auto()
FOLLOW_ANALYSIS_UPDATE = auto()
TREND_ANALYSIS_REQUEST = auto()
TREND_ANALYSIS_REPORT = auto()
NUMBER_GENERATION_START = auto()
NUMBER_GENERATION_FINISH = auto()
# 测试事件
TEST_EVENT = auto()
@dataclass
class Event:
"""事件数据类"""
type: Union[str, EventType] # 必须放在非默认参数位置
source: str # 必须放在非默认参数位置
target: Optional[str] = None
event_id: str = field(default_factory=lambda: str(uuid.uuid4()))
token: Optional[str] = None
data: Optional[Dict[str, Any]] = field(default_factory=dict)
timestamp: float = field(default_factory=time.time)
def __post_init__(self):
"""数据验证和类型转换"""
if isinstance(self.type, EventType):
self.type = self.type.name
if not isinstance(self.type, str) or not self.type:
raise ValueError("type 必须是非空字符串或EventType枚举")
if not isinstance(self.source, str) or not self.source:
raise ValueError("source 必须是非空字符串")
class EventCenter:
"""线程安全的事件中心(最终优化版)"""
_instance = None
_lock = threading.Lock()
_executor = ThreadPoolExecutor(max_workers=10, thread_name_prefix="EventWorker")
def __new__(cls):
with cls._lock:
if cls._instance is None:
cls._instance = super().__new__(cls)
cls._instance.__initialized = False
return cls._instance
def __init__(self):
"""初始化事件中心(单例模式)"""
def __init__(self): self.subscribers = {}
if getattr(self, '__initialized', False):
return
self.__initialized = True
self._subscribers: DefaultDict[str, List[Tuple[Callable[[Event], None], Optional[str]]]] = defaultdict(list)
self._event_history: Dict[str, Event] = {}
self._pending_acks: Set[str] = set()
self._ack_timeout = 5.0
self._stats = {
'total_events': 0,
'failed_events': 0,
'delivered_events': 0
}
def subscribe(self,
event_type: Union[str, EventType],
handler: Callable[[Event], None],
token: Optional[str] = None) -> None:
"""订阅事件(支持token过滤)"""
event_type_str = event_type.name if isinstance(event_type, EventType) else str(event_type)
with self._lock:
self._subscribers[event_type_str].append((handler, token))
logger.debug(f"已订阅事件: {event_type_str}, token: {token}")
def subscribe(self, event_type, callback): """订阅事件""" if event_type not in self.subscribers: self.subscribers[event_type] = [] self.subscribers[event_type].append(callback) def publish(self, event_type, data): """发布事件""" if event_type in self.subscribers: for callback in self.subscribers[event_type]: callback(data)
def unsubscribe(self,
event_type: Union[str, EventType],
handler: Callable[[Event], None]) -> bool:
"""取消订阅事件"""
event_type_str = event_type.name if isinstance(event_type, EventType) else str(event_type)
with self._lock:
if event_type_str not in self._subscribers:
return False
before = len(self._subscribers[event_type_str])
self._subscribers[event_type_str] = [
(h, t) for h, t in self._subscribers[event_type_str] if h != handler
]
removed = before != len(self._subscribers[event_type_str])
if removed:
logger.debug(f"已取消订阅: {event_type_str}")
return removed
def publish(self, event, require_ack=False, async_handle=True) -> bool:
"""发布事件(支持同步/异步处理)"""
# 增强事件验证
try:
# 确保事件有必要的属性
if not hasattr(event, 'type') or not hasattr(event, 'source'):
raise AttributeError("事件缺少必要属性: type或source")
# 自动生成事件ID(如果缺失)
if not hasattr(event, 'event_id') or not event.event_id:
event.event_id = str(uuid.uuid4())
# 执行事件验证
if hasattr(event, '__post_init__'):
event.__post_init__()
else:
# 手动验证基本属性
if not isinstance(event.type, (str, EventType)):
raise TypeError("type必须是字符串或EventType")
if not isinstance(event.source, str) or not event.source:
raise ValueError("source必须是非空字符串")
except (ValueError, TypeError, AttributeError) as e:
# 处理预期的验证错误
logger.error(f"事件验证失败: {e}")
with self._lock:
self._stats['failed_events'] += 1
return False
except Exception as e:
# 记录所有其他异常
logger.exception(f"事件验证过程中发生意外错误: {e}")
with self._lock:
self._stats['failed_events'] += 1
return False
# 主事件处理逻辑
try:
with self._lock:
if event.event_id in self._event_history:
logger.warning(f"重复事件ID: {event.event_id}")
return False
self._event_history[event.event_id] = event
self._stats['total_events'] += 1
if require_ack:
self._pending_acks.add(event.event_id)
handlers = self._get_matching_handlers(event)
if not handlers:
logger.debug(f"没有处理器订阅: {event.type}")
return True
if async_handle:
self._executor.submit(self._dispatch_event, event, handlers, require_ack)
else:
self._dispatch_event(event, handlers, require_ack)
return True
except (KeyError, AttributeError) as e:
# 处理内部数据结构错误
logger.error(f"内部数据结构错误: {e}")
with self._lock:
self._stats['failed_events'] += 1
return False
except Exception as e:
# 捕获所有其他异常
logger.exception(f"发布事件时发生未预期错误: {e}")
with self._lock:
self._stats['failed_events'] += 1
return False
def _get_matching_handlers(self, event: Event) -> List[Callable[[Event], None]]:
"""获取匹配的事件处理器"""
with self._lock:
return [
h for h, t in self._subscribers.get(event.type, [])
if t is None or t == event.token
]
def _dispatch_event(self,
event: Event,
handlers: List[Callable[[Event], None]],
require_ack: bool) -> None:
"""分发事件到处理器"""
for handler in handlers:
try:
handler(event)
with self._lock:
self._stats['delivered_events'] += 1
logger.debug(f"事件处理成功: {event.event_id[:8]}")
except Exception as e:
logger.exception(f"处理器异常: {e}")
if require_ack and event.target:
self._wait_for_ack(event)
def _wait_for_ack(self, event: Event) -> None:
"""等待事件确认"""
start = time.time()
while time.time() - start < self._ack_timeout:
with self._lock:
if event.event_id not in self._pending_acks:
return
time.sleep(0.05)
logger.warning(f"事件确认超时: {event.event_id[:8]}")
def get_stats(self) -> Dict[str, int]:
"""获取事件中心统计信息"""
with self._lock:
return self._stats.copy()
def clear(self):
"""重置事件中心状态(测试专用)"""
with self._lock:
self._subscribers.clear()
self._event_history.clear()
self._pending_acks.clear()
self._stats = {
'total_events': 0,
'failed_events': 0,
'delivered_events': 0
}
# 全局单例实例
event_center = EventCenter()
# ======================== 完整测试套件 ========================
import pytest
from dataclasses import make_dataclass
@pytest.fixture
def event_center_instance():
"""创建独立的EventCenter实例用于测试"""
center = EventCenter()
center.clear()
return center
def test_event_subscription(event_center_instance):
"""测试事件订阅机制"""
handled_events = []
def handler(event: Event):
handled_events.append(event)
# 订阅并发布测试事件
event_center_instance.subscribe(EventType.TEST_EVENT, handler)
test_event = Event(
type=EventType.TEST_EVENT,
source="pytest",
data={"test": "subscription"}
)
assert event_center_instance.publish(test_event) is True
time.sleep(0.1) # 等待异步处理完成
assert len(handled_events) == 1
assert handled_events[0].data["test"] == "subscription"
stats = event_center_instance.get_stats()
assert stats["total_events"] == 1
assert stats["delivered_events"] == 1
assert stats["failed_events"] == 0
def test_event_validation_failure(event_center_instance):
"""测试无效事件处理"""
# 用例1:完全无效的事件对象
class InvalidEvent:
pass
# 用例2:缺少必要属性的事件
PartialEvent = make_dataclass('PartialEvent', [('type', str)], namespace={'source': None})
partial_event = PartialEvent(type="TEST")
# 用例3:属性类型错误的事件
@dataclass
class WrongTypeEvent:
type: int = 123 # 应该是字符串或EventType
source: str = "test"
# 用例4:空source属性的事件
@dataclass
class EmptySourceEvent:
type: str = "TEST"
source: str = "" # 空字符串
for invalid_event in [InvalidEvent(), partial_event, WrongTypeEvent(), EmptySourceEvent()]:
assert event_center_instance.publish(invalid_event) is False
# 验证统计计数
stats = event_center_instance.get_stats()
assert stats["total_events"] == 0
assert stats["failed_events"] == 4
assert stats["delivered_events"] == 0
def test_duplicate_event_id(event_center_instance):
"""测试重复事件ID检测"""
event = Event(
type=EventType.TEST_EVENT,
source="pytest",
event_id="fixed-id-123"
)
# 首次发布应成功
assert event_center_instance.publish(event) is True
# 重复发布应失败
assert event_center_instance.publish(event) is False
stats = event_center_instance.get_stats()
assert stats["total_events"] == 1
assert stats["failed_events"] == 0 # 重复事件不算验证失败
assert stats["delivered_events"] == 0 # 没有订阅者
def test_token_filtering(event_center_instance):
"""测试基于token的事件过滤"""
handled_events = []
def handler(event: Event):
handled_events.append(event)
# 订阅特定token的事件
event_center_instance.subscribe(EventType.TEST_EVENT, handler, token="secret")
# 发布不带token的事件(不应被处理)
event1 = Event(
type=EventType.TEST_EVENT,
source="pytest",
data={"test": "no token"}
)
# 发布带错误token的事件(不应被处理)
event2 = Event(
type=EventType.TEST_EVENT,
source="pytest",
token="wrong",
data={"test": "wrong token"}
)
# 发布正确token的事件(应被处理)
event3 = Event(
type=EventType.TEST_EVENT,
source="pytest",
token="secret",
data={"test": "correct token"}
)
assert event_center_instance.publish(event1) is True
assert event_center_instance.publish(event2) is True
assert event_center_instance.publish(event3) is True
time.sleep(0.1) # 等待异步处理完成
assert len(handled_events) == 1
assert handled_events[0].data["test"] == "correct token"
stats = event_center_instance.get_stats()
assert stats["total_events"] == 3
assert stats["delivered_events"] == 1
assert stats["failed_events"] == 0
def test_async_handling(event_center_instance):
"""测试异步事件处理"""
handled_events = []
def slow_handler(event: Event):
time.sleep(0.2)
handled_events.append(event)
event_center_instance.subscribe(EventType.TEST_EVENT, slow_handler)
# 发布两个事件
event1 = Event(type=EventType.TEST_EVENT, source="pytest", data={"id": 1})
event2 = Event(type=EventType.TEST_EVENT, source="pytest", data={"id": 2})
# 异步发布
assert event_center_instance.publish(event1) is True
assert event_center_instance.publish(event2) is True
# 立即检查(应尚未处理)
assert len(handled_events) == 0
# 等待足够时间
time.sleep(0.3)
assert len(handled_events) == 2
assert {e.data['id'] for e in handled_events} == {1, 2}
def test_ack_mechanism(event_center_instance):
"""测试事件确认机制"""
ack_received = False
def ack_handler(event: Event):
nonlocal ack_received
# 模拟目标模块发送ACK
if event.type == "ACK":
ack_received = True
# 从待确认集合中移除
with event_center_instance._lock:
if event.data.get("ack_for") in event_center_instance._pending_acks:
event_center_instance._pending_acks.remove(event.data["ack_for"])
# 订阅ACK事件(模拟)
event_center_instance.subscribe("ACK", ack_handler)
# 发布需要ACK的事件
event = Event(
type=EventType.TEST_EVENT,
source="pytest",
target="test_target",
data={"require_ack": True}
)
# 发布测试事件(要求ACK)
assert event_center_instance.publish(event, require_ack=True) is True
# 检查事件是否在待确认集合中
with event_center_instance._lock:
assert event.event_id in event_center_instance._pending_acks
# 发布ACK事件(模拟目标模块的响应)
ack_event = Event(
type="ACK",
source="test_target",
data={"ack_for": event.event_id}
)
event_center_instance.publish(ack_event)
# 等待ACK处理
time.sleep(0.1)
assert ack_received is True
# 检查pending_acks应被移除
with event_center_instance._lock:
assert event.event_id not in event_center_instance._pending_acks
def test_event_auto_id_generation(event_center_instance):
"""测试自动生成事件ID"""
# 创建没有event_id的事件
event = Event(
type=EventType.TEST_EVENT,
source="pytest",
data={"auto_id": True}
)
del event.event_id # 移除自动生成的ID
assert event_center_instance.publish(event) is True
assert hasattr(event, 'event_id') and event.event_id
assert len(event.event_id) == 36 # UUID长度验证
def test_concurrent_publishing(event_center_instance):
"""测试并发事件发布"""
from concurrent.futures import ThreadPoolExecutor
handled_events = []
event_count = 50
def handler(event: Event):
handled_events.append(event)
event_center_instance.subscribe(EventType.TEST_EVENT, handler)
def publish_event(i):
event = Event(
type=EventType.TEST_EVENT,
source=f"thread-{i}",
data={"index": i}
)
event_center_instance.publish(event)
# 使用线程池并发发布事件
with ThreadPoolExecutor(max_workers=10) as executor:
executor.map(publish_event, range(event_count))
# 等待所有事件处理完成
time.sleep(0.5)
assert len(handled_events) == event_count
assert len({e.data['index'] for e in handled_events}) == event_count
最新发布