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

如何在Python中结合__instancecheck__使用类型提示?

问题描述

我有一个roast(obj)函数,用来处理所有具备quack方法的“鸭子”对象:

def roast(obj):
    print(f"<< {obj.quack()} >> the duck said, before being roasted\n - The (sad) end.")

为了校验传入对象是否符合鸭子类型,我用元类实现了自定义的isinstance判断逻辑:

from abc import ABCMeta

class DuckMeta(ABCMeta):
    @classmethod
    def __instancecheck__(cls, instance):
        return callable(getattr(instance, "quack", None))

class Duck(metaclass=DuckMeta):
    def quack(self):
        return "Quack!"

之后更新了roast函数,加入类型校验:

def roast(obj):
    if not isinstance(obj, Duck):
        raise TypeError(f"Object of type {type(obj)} is not a duck!")
    print(f"<< {obj.quack()} >> the duck said, before being roasted\n - The (sad) end.")

运行时,不管是继承自Duck的Pato类,还是没有继承但实现了quack方法的Canard类,都能正常调用roast:

class Pato(Duck):
    def quack(self):
        return "¡Cua, cua, cua!"

class Canard:
    # 有意不继承Duck
    def quack(self):
        return "Coin-coin!"
>>> roast(Pato())
<< ¡Cua, cua, cua! >> the duck said, before being roasted
 - The (sad) end.
>>> roast(Canard())
<< Coin-coin! >> the duck said, before being roasted
 - The (sad) end.

但如果给roast加上类型提示def roast(obj: Duck),IDE(PyCharm)会在调用roast(Canard())时提示:

Expected type 'Duck', got 'Canard' instead

显然这种类型提示方式不对,该怎么正确添加?

解决方案

要给鸭子类型的对象正确添加类型提示,应该使用Protocol(协议),它支持结构子类型(即鸭子类型的类型检查),不需要强制继承。

步骤1:导入Protocol

Python 3.8及以上版本,Protocol在typing模块中可用;更早版本需要从typing_extensions导入。

步骤2:定义鸭子协议

创建一个继承自Protocol的抽象协议类,仅声明需要的方法签名即可:

from typing import Protocol

class DuckProtocol(Protocol):
    def quack(self) -> str:
        ...  # 无需实现逻辑,仅定义方法结构

步骤3:更新函数的类型提示

把roast函数的参数类型改成DuckProtocol:

def roast(obj: DuckProtocol) -> None:
    if not isinstance(obj, Duck):
        raise TypeError(f"Object of type {type(obj)} is not a duck!")
    print(f"<< {obj.quack()} >> the duck said, before being roasted\n - The (sad) end.")

这样修改后,IDE会自动识别所有实现了quack方法的对象(包括Canard)符合DuckProtocol类型,不会再抛出类型不匹配的提示,同时运行时的isinstance校验逻辑依然有效。

可选:统一类型提示与运行时校验

如果希望直接用协议做运行时校验,可以给协议加上@runtime_checkable装饰器:

from typing import Protocol, runtime_checkable

@runtime_checkable
class DuckProtocol(Protocol):
    def quack(self) -> str:
        ...

此时可以把原来的isinstance(obj, Duck)替换成isinstance(obj, DuckProtocol),让类型提示和运行时校验统一基于协议标准。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 14:25:20