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

ABC与typing:能否让函数参数实现真正的可选性?

优化带可选参数的抽象类方法设计

当子类重写基类带可选默认参数的抽象方法时,比如下面的代码,pylance、mypy这类类型检查器会因为参数数量不匹配报错(基类request方法包含self、url、query_params三个参数,子类仅定义self和url两个):

from abc import ABC, abstractmethod
from typing import TypedDict, Optional

class QueryParams(TypedDict):
    param_name: str
    param_value: str

class ApiClient(ABC):

    @abstractmethod
    def request(self, url: str, query_params: Optional[QueryParams] = None) -> None:
        raise NotImplementedError

class ConcreteApiClient(ApiClient):
    def request(self, url: str):
        print("I don't need query params to make request!")

类型检查器会强制开发者补全缺失的参数,改成如下代码:

class ConcreteApiClient(ApiClient):
    def request(self, url: str, query_params: Optional[QueryParams] = None):
        print("I don't need query params to make request!")

但这种设计很不合理——这个参数在子类里完全没被用到。虽然这是里氏替换原则的要求,但优化这段代码的方式不止一种,你提到的“基类仅保留非可选参数,子类按需定义额外参数”是可行方案之一,下面是几种更具体的优化思路:

方案1:按功能拆分抽象接口(推荐)

基于接口隔离原则,把不同需求的客户端抽象拆分成层级化的接口,避免强制子类实现不需要的参数:

from abc import ABC, abstractmethod
from typing import TypedDict, Optional

class QueryParams(TypedDict):
    param_name: str
    param_value: str

# 基础接口,仅定义核心请求能力
class BaseApiClient(ABC):
    @abstractmethod
    def request(self, url: str) -> None:
        raise NotImplementedError

# 扩展接口,增加查询参数支持
class QuerySupportApiClient(BaseApiClient):
    @abstractmethod
    def request(self, url: str, query_params: Optional[QueryParams] = None) -> None:
        raise NotImplementedError

# 不需要查询参数的子类,继承基础接口
class ConcreteApiClient(BaseApiClient):
    def request(self, url: str):
        print("I don't need query params to make request!")

# 需要查询参数的子类,继承扩展接口
class QueryEnabledApiClient(QuerySupportApiClient):
    def request(self, url: str, query_params: Optional[QueryParams] = None):
        if query_params:
            print(f"Making request with params: {query_params}")
        else:
            print("Making request without params")

这种方式既符合里氏替换原则,又让子类只需要实现自己需要的接口,完全避免冗余参数。

方案2:用可变关键字参数兼容(适合简单场景)

如果不想拆分接口,可以让基类用**kwargs接收所有可选参数,子类按需处理:

from abc import ABC, abstractmethod
from typing import TypedDict, Optional, Any

class QueryParams(TypedDict):
    param_name: str
    param_value: str

class ApiClient(ABC):
    @abstractmethod
    def request(self, url: str, **kwargs: Any) -> None:
        raise NotImplementedError

class ConcreteApiClient(ApiClient):
    def request(self, url: str):
        print("I don't need query params to make request!")

class QueryEnabledApiClient(ApiClient):
    def request(self, url: str, query_params: Optional[QueryParams] = None):
        if query_params:
            print(f"Making request with params: {query_params}")
        else:
            print("Making request without params")

缺点是基类失去了严格的类型检查能力,无法明确声明可选参数的类型,适合参数简单、类型要求不高的场景。

方案3:封装可选参数为配置类

把所有可选的请求配置(比如查询参数、请求头、超时时间等)封装成一个单独的配置类,基类只接收这个配置参数,子类可以选择忽略或使用:

from abc import ABC, abstractmethod
from typing import TypedDict, Optional
from dataclasses import dataclass

class QueryParams(TypedDict):
    param_name: str
    param_value: str

@dataclass
class RequestConfig:
    query_params: Optional[QueryParams] = None
    # 后续可以添加其他可选配置,比如headers: Optional[dict] = None

class ApiClient(ABC):
    @abstractmethod
    def request(self, url: str, config: Optional[RequestConfig] = None) -> None:
        raise NotImplementedError

class ConcreteApiClient(ApiClient):
    def request(self, url: str, config: Optional[RequestConfig] = None):
        # 直接忽略配置参数
        print("I don't need query params to make request!")

class QueryEnabledApiClient(ApiClient):
    def request(self, url: str, config: Optional[RequestConfig] = None):
        if config and config.query_params:
            print(f"Making request with params: {config.query_params}")
        else:
            print("Making request without params")

这种方式既保留了类型安全,又具备扩展性——后续新增可选参数时,只需要修改配置类,不需要修改方法签名,子类也可以灵活选择是否处理配置。

内容的提问来源于stack exchange,提问作者barciewicz

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.17 04:32:08