You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何根据子类为父类选择不同数据库模块并实现类型标注?

数据库父类的类型标注优化需求

我希望构建一个父类,让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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 16:11:33