问题描述
我正在自学 Python 并试图通过 mypy 的类型检查系统,但我有点迷失在类型、类、抽象类、泛型等之间。
所以,我想制作一个泛型/抽象类型/类来表示日期,指定该类型/类必须具有 year
、month
和 day
属性并且必须支持比较运算符 <
和 <=
,可能还有其他方法。我知道这听起来确实像是抽象类的工作,但我不精通 OO,而且我之前尝试使用抽象类未能通过 mypy 的类型检查。基于通用/抽象模式,我定义了一个将 n 个月添加到日期的函数。接下来,我想根据模块 datetime
中的日期类型定义一个特定/具体的日期类型,并且我重新定义了 add_months
函数以使用该类型进行操作,但这个想法当然是调用为泛型/抽象模式编写的函数,不要重复代码。
希望下面的代码能让我的意图更清晰(我的代码被分成两个文件):
文件dates_generic.py:
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)
文件dates_pylib.py:
import calendar as cal
import dates_generic as dg
import datetime as dt
from dates_generic import Year,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,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)
我的问题是 mypy 仍然使用我的函数 dates_pylib.add_months 提出以下问题:
Value of type variable "Date" of "add_months" cannot be "DateP"
我用作 IDE 的 PyCharm 添加了自己的:
Expected type '(int,int,int) -> Any' (matched generic type '(int,int) -> Date'),got '(y: int,m: int,d: int) -> DateP' instead
从第一条消息中,我似乎明白 mypy 不知道类型 DateP
实现了年、月和日属性和比较运算符。如果我删除了dates_generic.py 中的bound=A
,此消息就会消失,但随后mypy 抱怨无界类型Date
没有year
、month
和day
属性。
第二条消息对我来说意义不大,因为我从 PEP-0484 中读到“每种类型都与 Any 一致”,所以我希望 (int,int) -> Any
可以替换为 (int,int) -> DateP
。
我可能正在寻找类似于 Haskell 的类型类约束的东西,它允许您指定类型必须支持的方法,但我不确定如何在 Python 中模拟它。
解决方法
听起来您想使用协议(昵称为“静态鸭子类型”)来描述您的 Date 实现必须具有的功能。协议完全是另一种打字的东西,一次学习所有这些东西肯定有很多,但是一旦你习惯了它们,它们真的很好。
定义协议告诉类型检查器,“如果你发现任何可以完成所有这些事情的东西,它就是这个协议的实现者,这意味着期望这个协议的实现者的人应该对它感到满意”。它很像一个抽象基类,但也不是——它只是一个接口规范,实际上没有人需要继承它(无论如何,从协议继承基本上是一种无操作——mypy 会弄清楚是否有任何类实现一个协议,无论您是否声明该类继承自该协议)。您可以阅读有关协议 here 的 PEP。
您可能不需要它,或者可能已经找到它,但由于听起来您是在尝试描述和重新实现 datetime 功能的子集,因此 datetime type stubs 可能有用也给你。