如何让mypy知道现有类型支持某些属性和方法



我正在自学Python,并试图通过mypy的类型检查系统,但我有点迷失在类型、类、抽象类、泛型等中。

因此,我想创建一个通用/抽象类型/类来表示日期,指定该类型/类必须具有yearmonthday属性,并且必须支持比较运算符<<=,以及可能的其他方法。我知道这听起来真的像是一个抽象类的工作,但我不精通OO,我以前尝试过一个抽象的类,但都没有通过mypy的类型检查。基于通用/抽象模式,我定义了一个在日期上添加n个月的函数。接下来,我想根据模块datetime中的日期类型定义一个特定/具体的日期类型,并重新定义add_months函数以使用该类型进行操作,但其想法当然是调用为泛型/抽象模式编写的函数,而不是重复代码。

希望下面的代码能让我的意图更清晰(我的代码分为两个文件(:

文件日期_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)

文件日期_pylib.py:


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)

我的问题是,mypy仍然对我的函数dates_pylib.add_months提出以下问题:

Value of type variable "Date" of "add_months" cannot be "DateP"

PyCharm,我使用它作为IDE,添加了自己的:

Expected type '(int, int, int) -> Any' (matched generic type '(int, int, int) -> Date'), got '(y: int, m: int, d: int) -> DateP' instead

从第一条消息中,我似乎了解到mypy不知道类型DateP实现了年、月、日属性和比较运算符。如果我删除dates_generic.py中的bound=A,则此消息将消失,但mypy随后抱怨无边界类型Date没有yearmonthday属性。

第二条消息对我来说意义不大,因为我从PEP-0484中读到;每种类型都与Any一致&";,所以我期望CCD_ 14可以用CCD_。

我可能正在寻找类似于Haskell的typeclass约束的东西,它允许您指定类型必须支持的方法,但我不确定如何在Python中模拟这一点。

听起来您想要描述Date实现必须使用协议(昵称为"静态鸭子键入"(的功能。协议完全是另一件打字的事情,同时了解所有这些东西肯定有很多,但一旦你习惯了,它们真的很好。

定义协议告诉类型检验器;如果你发现任何能做所有这些事情的东西,它就是这个协议的实现者,这意味着期望实现这个协议的人应该对此感到满意";。它很像抽象基类,但也不是-它只是一个接口规范,实际上没有人需要从它继承(无论如何,从协议继承基本上是不可行的-mypy会判断是否有任何类实现了协议,无论你是否声明该类从该协议继承(。您可以在此处阅读有关协议的PEP。

您可能不需要它,或者可能已经找到了它,但由于听起来您正试图描述和重新实现datetime功能的一个子集,因此datetime类型的存根也可能对您有用。

最新更新