如何根据子类为父类选择不同数据库模块并实现类型标注?
数据库父类的类型标注优化需求
我希望构建一个父类,让Hana和Oracle两个子类数据库复用相同操作逻辑,同时要给父类做正确的类型标注。目前有两段示例代码能运行,但存在类型标注问题:
- 模块不能直接作为类型使用
T.Connection这种语法在类型标注里不合法
另外,第二种传入Connection类的方案,如果后续需要同时处理多个对象(比如Connection、Cursor),会导致泛型和参数重复定义,想找个简洁的实现方式。
示例代码1
from typing import Type, cast, get_args import oracledb # want to use oracledb.Connection from hdbcli import dbapi # want to use dbapi.Connection class Parent[T: oracledb | dbapi]: _db_type: Type[T] def __init_subclass__(cls) -> None: # 获取运行时的泛型参数T cls._db_type = cast(Type[T], get_args(cls.__orig_bases__[0])[0]) def conn(self, **kwargs) -> T.Connection: return self._db_type.Connection(**kwargs) class Hana(Parent[dbapi]): ... class Oracle(Parent[oracledb]): ...
示例代码2
class Parent[T: dbapi.Connection]: _conn: Type[T] def __init__(self, conn: Type[T]): self._conn = conn def conn(self, **kwargs) -> T: return self._conn(**kwargs) class Hana(Parent[dbapi.Connection]): def __init__(self): super().__init__(dbapi.Connection)
优化方案
1. 用Protocol定义数据库模块接口
通过typing.Protocol定义数据库模块必须具备的属性(如Connection、Cursor),既解决模块不能作为类型的问题,又支持T.Connection这类合法的类型标注,同时实现静态类型约束。
from typing import Type, cast, get_args, Protocol, TypeVar # 定义Protocol,描述数据库模块的核心结构 class DBModule(Protocol): class Connection: def __init__(self, **kwargs): ... def cursor(self): ... # 可根据实际需求添加更多方法签名 class Cursor: def execute(self, sql: str, *args): ... # 可扩展Cursor的其他方法 # 定义受Protocol约束的类型变量 T = TypeVar("T", bound=DBModule) class Parent[T]: _db_type: Type[T] def __init_subclass__(cls) -> None: cls._db_type = cast(Type[T], get_args(cls.__orig_bases__[0])[0]) def get_connection(self, **kwargs) -> T.Connection: return self._db_type.Connection(**kwargs) def get_cursor(self, conn: T.Connection) -> T.Cursor: return conn.cursor() # 子类直接传入对应数据库模块即可 class Hana(Parent[dbapi]): ... class Oracle(Parent[oracledb]): ...
2. 解决多对象场景的冗余问题
Protocol将所有数据库相关类型(Connection、Cursor等)打包在一个接口中,父类仅需一个泛型参数T就能访问所有关联类型,无需为每个对象单独定义泛型或参数。后续扩展其他类型(如连接池Pool)时,只需在Protocol中添加对应的类定义,父类代码无需大幅修改。
3. 运行时与静态类型安全说明
__init_subclass__中的get_args逻辑可正确获取子类传入的泛型参数,结合cast做类型转换,保证运行时能正确调用数据库模块的类;静态类型检查时,Protocol会约束T必须包含指定属性,提前规避类型错误。
替代方案:类型集合封装(可选)
若不想使用Protocol,也可通过数据类封装所需类型,但这种方式无法利用静态类型检查的接口约束,可读性与扩展性弱于Protocol方案:
from typing import TypeVar, Generic from dataclasses import dataclass @dataclass class DBTypes: Connection: type Cursor: type T = TypeVar("T", bound=DBTypes) class Parent(Generic[T]): def __init__(self, db_types: T): self.db_types = db_types def get_connection(self, **kwargs): return self.db_types.Connection(**kwargs) # 子类传入封装后的类型集合 class Hana(Parent[DBTypes]): def __init__(self): super().__init__(DBTypes(Connection=dbapi.Connection, Cursor=dbapi.Cursor))
内容的提问来源于stack exchange,提问作者Guillaume
相关产品推荐
相关产品推荐

