如何为多Mixin应用ABC或Protocol解决类型提示与Lint问题
解决DRF ModelSerializer扩展的类型提示与Lint问题
核心思路
结合ABC(强制实现校验)和Protocol(类型提示)的优势,避免重复定义:
- 用ABC确保子类在定义阶段就暴露未实现的属性/方法
- 用Protocol整合所有必要接口(包括ABC要求、ModelSerializer方法、其他Mixin方法),给Mixin提供完整类型提示
- 统一类型注解,消除跨Mixin/父类的Lint错误
修正后的代码
from abc import ABC, abstractmethod from typing import Protocol, type from dataclasses import dataclass from rest_framework.serializers import ModelSerializer @dataclass class Constraints: width: int height: int # ABC:强制子类必须实现的属性与方法 class ThingsToImplement(ABC): my_number: int constraints: Constraints @abstractmethod def get_da_name(self, s: str) -> str: pass # Protocol:整合ABC要求 + ModelSerializer核心方法 + 跨Mixin方法 class SerializerProtocol(Protocol, ThingsToImplement): # 继承ABC的所有约束 # 添加ModelSerializer的核心成员 instance: object def to_representation(self, instance) -> dict: ... # 添加其他Mixin的方法,供类型识别 def get_number(self) -> int: ... def get_foo(self) -> str: ... class ConstraintsMixin: def __init_subclass__(cls, **kwargs): if not hasattr(cls, 'constraints'): raise NotImplementedError('请添加constraints属性') @classmethod def print_constraints(cls: type[SerializerProtocol]) -> None: print(cls.constraints.width, cls.constraints.height) class FooMixin: def get_foo(self: SerializerProtocol) -> str: return self.get_da_name("test") if self.constraints.width > 123 else 'Too small to be named' def get_bar(self: SerializerProtocol) -> str: # 可正常识别ModelSerializer的to_representation和instance return str(self.to_representation(self.instance)) def from_another(self: SerializerProtocol) -> str: return f"from {self.get_number()}" class AnotherMixin: def get_number(self: SerializerProtocol) -> int: return self.my_number def from_foo(self: SerializerProtocol) -> str: return "from " + self.get_foo() # 调整基类顺序,解决元类冲突(ModelSerializer放最后,DRF元类优先级更高) class AbstractSerializer(ConstraintsMixin, FooMixin, AnotherMixin, ThingsToImplement, ModelSerializer): def implemented_method_1(self): return 1 def implemented_method_2(self): return 2 # ... def implemented_method_30(self): return 30 class MySerializer(AbstractSerializer): my_number = 7 constraints = Constraints(1, 2) def get_da_name(self, s: str) -> str: # 修复笔误:number改为my_number self.my_number += 1 return 'hi ' + s
关键调整说明
- Protocol继承ABC:让
SerializerProtocol自动包含ThingsToImplement的所有约束,避免重复定义,同时补充ModelSerializer和其他Mixin的方法,给Lint提供完整类型信息 - 统一类型注解:所有Mixin方法都用
self: SerializerProtocol,Lint可识别跨Mixin方法(如get_number、get_foo)和ModelSerializer成员(to_representation、instance) - 调整基类顺序:将
ModelSerializer放在基类列表最后,解决DRF元类与ABC元类的冲突问题 - 修复代码笔误:
MySerializer中self.number改为self.my_number,消除不存在的属性错误
方案优势
- ABC实现定义阶段校验:子类未实现要求的属性/方法时,定义类时直接报错,无需等到运行时
- Protocol提供完整类型提示:消除所有跨类成员的Lint警告
- 两者结合既满足提前校验需求,又避免重复代码,兼顾类型安全与开发效率
内容的提问来源于stack exchange,提问作者run_the_race
相关产品推荐
相关产品推荐

