是否可以在python中指定一个泛型类型有一些属性和方法
Is it possible in python to specify that a generic type has some attributes and methods
在 python 中,我想创建一个接受泛型参数的函数,并且 return 一个相同类型的值,我想以某种方式指定泛型有一些属性和方法。我认为将 TypeVar
绑定到 Protocol
就可以完成这项工作,但我大错特错了
仅举一个此类函数的示例,假设该函数对某些对象的输入数组进行排序,并return对这些对象的排序数组进行排序。对数组元素的唯一限制是它们必须实现 __lt__
from __future__ import annotations
from typing import List, Protocol, TypeVar
class ComparableProto(Protocol):
def __lt__(self, other: ComparableProto) -> bool: ...
Comparable = TypeVar("Comparable", bound=ComparableProto)
def sort(arr: List[Comparable]) -> List[Comparable]: ...
if __name__ == "__main__":
sort([1, 2, 3]) # mypy will say: Value of type variable "Comparable" of "sortarr" cannot be "int" [type-var]
我明白上面的例子是行不通的,因为 Comparable
类型必须是 ComparableProto
的子类型,只是因为某些类型实现 __lt__
并不能使它成为 __lt__
的子类型ComparableProto
.
我注意到 typing
模块实现了几个名为 Supports*
的通用协议类型,但它们与我想要的不同
有什么方法可以实现我想要的吗?
您的代码几乎没问题。 TypeVar
绑定到 Protocol
没有任何问题,您完全按照预期使用它。问题出在协议定义中:int
与 ComparableProto
不兼容,因为它有 def __lt__(self, __other: int) -> bool
。请注意 int
只能与另一个 int
进行比较,而您的协议需要更宽的类型 ComparableProto
。你可以用一个更简单的程序来验证:
from __future__ import annotations
from typing import Protocol
class ComparableProto(Protocol):
def __lt__(self, other: ComparableProto) -> bool: ...
x: ComparableProto = 1
mypy
将解释发生了什么,并附注:
main.py:7: error: Incompatible types in assignment (expression has type "int", variable has type "ComparableProto")
main.py:7: note: Following member(s) of "int" have conflicts:
main.py:7: note: Expected:
main.py:7: note: def __lt__(self, ComparableProto) -> bool
main.py:7: note: Got:
main.py:7: note: def __lt__(self, int) -> bool
我建议您尝试一下 playground 中的这段代码,以便更好地理解这个不太明显的部分。
现在事情清楚了。如果你定义你的协议仅可比较(阅读:至少)具有相同类型,错误将消失:
from __future__ import annotations
from typing import List, Protocol, TypeVar
_T = TypeVar('_T')
class ComparableProto(Protocol):
def __lt__(self: _T, __other: _T) -> bool: ... # Only same type allowed
Comparable = TypeVar("Comparable", bound=ComparableProto)
def sort(arr: List[Comparable]) -> List[Comparable]:
return arr
sort([1, 2, 3])
协议中的 Dunder 名称用于明确说明参数名称不重要(因此协议不允许调用 obj.__lt__(other=something)
,但可以允许任何具有此名称的子 class名为 other
的参数)。 __lt__
显然是这种情况,因为它旨在以运算符形式使用。
I understand that the above example can't work, because Comparable
type must be subtype of ComparableProto
and just because some type implements __lt__
doesn't make it subtype of ComparableProto
.
你的理解是错误的,每个 class 实现 __lt__
并带有正确的签名 是 ComparableProto
的 子类型(和“subclass" 术语对于协议来说并不那么重要)。协议用于表示结构子类型,因此另一个 class 不需要从协议继承以与其兼容。例如,来自 typing
的 Sized
(或者来自 collections.abc
,如果你足够现代并且使用 python 3.9+)是一个定义 __len__(self) -> int
的协议。任何实现 __len__
、returns int
的 class 都与 Sized
兼容(读作:子类型)Sized
,尽管没有从它继承。请参阅 PEP544 了解更正式的规则(请注意,使用术语“子类型”而不是“subclass”以避免误解)。
在 python 中,我想创建一个接受泛型参数的函数,并且 return 一个相同类型的值,我想以某种方式指定泛型有一些属性和方法。我认为将 TypeVar
绑定到 Protocol
就可以完成这项工作,但我大错特错了
仅举一个此类函数的示例,假设该函数对某些对象的输入数组进行排序,并return对这些对象的排序数组进行排序。对数组元素的唯一限制是它们必须实现 __lt__
from __future__ import annotations
from typing import List, Protocol, TypeVar
class ComparableProto(Protocol):
def __lt__(self, other: ComparableProto) -> bool: ...
Comparable = TypeVar("Comparable", bound=ComparableProto)
def sort(arr: List[Comparable]) -> List[Comparable]: ...
if __name__ == "__main__":
sort([1, 2, 3]) # mypy will say: Value of type variable "Comparable" of "sortarr" cannot be "int" [type-var]
我明白上面的例子是行不通的,因为 Comparable
类型必须是 ComparableProto
的子类型,只是因为某些类型实现 __lt__
并不能使它成为 __lt__
的子类型ComparableProto
.
我注意到 typing
模块实现了几个名为 Supports*
的通用协议类型,但它们与我想要的不同
有什么方法可以实现我想要的吗?
您的代码几乎没问题。 TypeVar
绑定到 Protocol
没有任何问题,您完全按照预期使用它。问题出在协议定义中:int
与 ComparableProto
不兼容,因为它有 def __lt__(self, __other: int) -> bool
。请注意 int
只能与另一个 int
进行比较,而您的协议需要更宽的类型 ComparableProto
。你可以用一个更简单的程序来验证:
from __future__ import annotations
from typing import Protocol
class ComparableProto(Protocol):
def __lt__(self, other: ComparableProto) -> bool: ...
x: ComparableProto = 1
mypy
将解释发生了什么,并附注:
main.py:7: error: Incompatible types in assignment (expression has type "int", variable has type "ComparableProto")
main.py:7: note: Following member(s) of "int" have conflicts:
main.py:7: note: Expected:
main.py:7: note: def __lt__(self, ComparableProto) -> bool
main.py:7: note: Got:
main.py:7: note: def __lt__(self, int) -> bool
我建议您尝试一下 playground 中的这段代码,以便更好地理解这个不太明显的部分。
现在事情清楚了。如果你定义你的协议仅可比较(阅读:至少)具有相同类型,错误将消失:
from __future__ import annotations
from typing import List, Protocol, TypeVar
_T = TypeVar('_T')
class ComparableProto(Protocol):
def __lt__(self: _T, __other: _T) -> bool: ... # Only same type allowed
Comparable = TypeVar("Comparable", bound=ComparableProto)
def sort(arr: List[Comparable]) -> List[Comparable]:
return arr
sort([1, 2, 3])
协议中的 Dunder 名称用于明确说明参数名称不重要(因此协议不允许调用 obj.__lt__(other=something)
,但可以允许任何具有此名称的子 class名为 other
的参数)。 __lt__
显然是这种情况,因为它旨在以运算符形式使用。
I understand that the above example can't work, because
Comparable
type must be subtype ofComparableProto
and just because some type implements__lt__
doesn't make it subtype ofComparableProto
.
你的理解是错误的,每个 class 实现 __lt__
并带有正确的签名 是 ComparableProto
的 子类型(和“subclass" 术语对于协议来说并不那么重要)。协议用于表示结构子类型,因此另一个 class 不需要从协议继承以与其兼容。例如,来自 typing
的 Sized
(或者来自 collections.abc
,如果你足够现代并且使用 python 3.9+)是一个定义 __len__(self) -> int
的协议。任何实现 __len__
、returns int
的 class 都与 Sized
兼容(读作:子类型)Sized
,尽管没有从它继承。请参阅 PEP544 了解更正式的规则(请注意,使用术语“子类型”而不是“subclass”以避免误解)。