如何让mypy知道现有的类型支持某些属性和方法?
如何让mypy知道现有的类型支持某些属性和方法?
我正在自学Python,试图逐步理解mypy的类型检查系统,但在类型、类、抽象类、泛型类型等概念中有些迷茫。\n因此,我想创建一个泛型/抽象类型/类来表示日期,指定该类型/类必须具有“年”、“月”和“日”的属性,并且必须支持比较运算符“<”和“<=”,可能还要支持其他方法。我知道这听起来确实适合使用抽象类,但我对面向对象不熟练,我之前尝试使用抽象类未能通过mypy的类型检查。基于泛型/抽象模式,我定义了一个函数,用于在日期上添加n个月。接下来,我想基于模块“datetime”的日期类型定义一个特定/具体的日期类型,并重新定义“add_months”函数以操作该类型,但当然是要调用为泛型/抽象模式编写的函数,而不是复制代码。\n希望下面的代码能更清楚地表达我的意图(我的代码分为两个文件):\n文件dates_generic.py:\n[apcode language="python"]
from typing import Callable, TypeVar Year = int Month = int Day = int class A: year: Year month: Month day: Day def __lt__(self: A, other: A) -> bool: ... def __le__(self: A, other: A) -> bool: ... Date = TypeVar('Date', bound=A) def add_months(x: Date, n: int, days_in_month: Callable[[Year, Month], int], date_make: Callable[[Year, Month, Day], Date]) -> Date: r = (x.month + n - 1) % 12 q = (x.month + n - 1) // 12 y = x.year + q m = r + 1 d = min(x.day, days_in_month(y, m)) return date_make(y, m, d)
[/apcode]\n文件dates_pylib.py:\n
import calendar as cal import dates_generic as dg import datetime as dt from dates_generic import Year, Month, Day from typing import NewType DateP = NewType('DateP', dt.date) def date_make(y: Year, m: Month, d: Day) -> DateP: return DateP(dt.date(y, m, d)) def days_in_month(y: Year, m: Month): return cal.monthrange(y, m)[1] def add_months(x: DateP, n: int) -> DateP: return dg.add_months(x, n, days_in_month, date_make)
\n我的问题是,mypy仍然对我的函数`dates_pylib.add_months`提出以下问题:\n
类型变量"Date"的值不能为"DateP"
\n而我使用的IDE PyCharm还添加了以下提示:\n预期类型“(int, int, int) -> Any”(匹配的泛型类型“(int, int, int) -> Date”),得到的却是“(y: int, m: int, d: int) -> DateP”\n
\n根据第一条消息,我似乎理解mypy并不知道类型`DateP`实现了年、月和日的属性以及比较运算符。如果我在`dates_generic.py`中删除`bound=A`,这条消息就会消失,但是mypy会抱怨无界类型`Date`没有`year`、`month`和`day`属性。\n第二条消息对我来说不太明白,因为我从PEP-0484中读到“每种类型都与Any类型兼容”,所以我期望`(int, int, int) -> Any`可以替换为`(int, int, int) -> DateP`。\n我可能正在寻找类似于Haskell的类型类约束的东西,它允许您指定类型必须支持的方法,但我不确定如何在Python中模拟这个。
如何让mypy知道现有类型支持某些属性和方法?
问题的出现原因:用户想要描述自己的Date实现必须具备的功能,使用Protocol(通俗地称为“静态鸭子类型”)。Protocol是一种全新的类型定义方式,一下子要学习这么多东西确实很多,但一旦你习惯了它们,就会发现它们非常好用。
解决方法:定义一个Protocol,告诉类型检查器“如果你找到任何实现了这些功能的东西,它就是这个Protocol的实现者,这意味着那些期望实现该Protocol的人应该对它满意”。它很像抽象基类,但又不完全一样——它只是一个接口规范,实际上没人需要从它继承(而且从Protocol继承基本上是一个空操作——mypy会确定任何类是否实现了Protocol,无论你是否声明该类继承自该Protocol)。你可以在这里阅读有关Protocols的PEP。
你可能不需要这个,或者可能已经找到了,但由于你似乎正试图描述和重新实现datetime的某些功能子集,datetime的类型存根可能也对你有用。
感谢你提供的有用链接。我之前从未听说过Protocols,但标题中的“静态鸭子类型”听起来很有趣,因为我的问题是关于一个已有类型(datetime.date)是否符合我定义的模式。看来在修复我的代码之前,我有一些阅读材料要研究,但你给了我一个有希望的方向。