如何在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

