传入相等参数初始化的Python Frame类实例为何不相等?
问题原因
1. 默认__eq__方法的身份判断逻辑
Python自定义类的默认__eq__方法仅判断对象身份(即is比较),只有当两个变量指向同一个实例时才返回True。哪怕两个实例初始化参数一致、业务逻辑等价,只要是不同的实例,默认==比较就会返回False。
2. 传入的Playwright Frame实例被污染
从测试输出frame0 == frame1为True可知,这两个是Playwright的同一个Frame实例。在初始化new_frame0时,代码直接修改了传入的frame0的方法(如frame.query_selector = self.query_selector),将其替换为当前自定义Frame实例的方法。当创建new_frame1时,传入的frame1(即同一个Frame实例)的方法已经被new_frame0篡改,导致new_frame1中保存的_origin_xxx属性是new_frame0的绑定方法,而new_frame0的_origin_xxx是原始Playwright Frame的方法——这直接造成两个实例的内部状态(__dict__)完全不一致,进一步导致==比较失败。
解决方法
1. 重写__eq__定义业务相等逻辑
根据实际业务需求,自定义两个Frame实例的相等判断规则(比如基于内部封装的_frame和_page是否相等),同时重写__hash__保证哈希一致性:
class Frame: # 保留原有__init__及其他方法 def __eq__(self, other: object) -> bool: if not isinstance(other, Frame): return False # 以封装的原始frame和page相等作为实例相等的依据 return self._frame == other._frame and self._page == other._page def __hash__(self) -> int: # 基于相等判断属性生成哈希值 return hash((self._frame, self._page))
2. 采用包装器模式,避免修改原Frame实例
当前代码直接修改传入的Playwright Frame实例方法,会污染原对象并引发连锁问题。建议改为封装原方法调用,不修改原对象:
class Frame: def __init__(self, frame: PlaywrightFrame, page: Page) -> None: super().__init__(frame) self._impl_obj = frame._impl_obj self._page = page self._frame = frame self._parent_frame = frame.parent_frame # 无需保存_origin_xxx或替换原frame的方法 def query_selector(self, selector: str): # 插入自定义逻辑 # ... # 调用原frame的方法 return self._frame.query_selector(selector) # 其他方法同理,直接封装原frame的调用,不修改原对象
如果需要拦截原方法添加逻辑,优先使用装饰器或继承Playwright的Frame类(需确认Playwright类允许继承扩展),避免直接修改实例方法。
3. 确保传入未被修改的原始实例(不推荐)
如果必须保留修改原Frame实例的逻辑,创建多个自定义Frame实例时必须传入原始未被修改的Playwright Frame实例。但该方式风险极高,容易出现对象被意外篡改的情况,仅作为临时 workaround 使用。
内容的提问来源于stack exchange,提问作者cfvWEW

