是否可以在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 没有任何问题,您完全按照预期使用它。问题出在协议定义中:intComparableProto 不兼容,因为它有 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 不需要从协议继承以与其兼容。例如,来自 typingSized(或者来自 collections.abc,如果你足够现代并且使用 python 3.9+)是一个定义 __len__(self) -> int 的协议。任何实现 __len__、returns int 的 class 都与 Sized 兼容(读作:子类型)Sized,尽管没有从它继承。请参阅 PEP544 了解更正式的规则(请注意,使用术语“子类型”而不是“subclass”以避免误解)。