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