切片的 Python 类型注释?

Nay*_*uki 0 python type-hinting

我有这个通用功能:

def slice(seq):
    return seq[1:3]

assert slice("abcde") == "bc"
assert slice(b"xyz") == b"yz"
assert slice([2,7,1,8]) == [7,1]
Run Code Online (Sandbox Code Playgroud)

我们可以看到slice有类型str -> str, bytes -> bytes, List[int] -> List[int]

如何为函数编写类型注释?这是一个错误的尝试(TypeVar文档):

from typing import *

E = TypeVar("E")
T = TypeVar("T", bytes, str, Sequence[E])

def slice(seq: T) -> T:
    return seq[1:3]
Run Code Online (Sandbox Code Playgroud)

它产生以下错误消息:

error: Type variable "mymodule.E" is unbound
note: (Hint: Use "Generic[E]" or "Protocol[E]" base class to bind "E" inside a class
note: (Hint: Use "E" in function signature to bind "E" inside a function)
Run Code Online (Sandbox Code Playgroud)

我正在运行 Python 3.9.6 和 mypy 0.910。

Sam*_*ord 5

你想TypeVarboundSequence

from typing import Sequence, TypeVar, cast

_T = TypeVar("_T", bound=Sequence)


def slice(seq: _T) -> _T:
    return cast(_T, seq[1:3])


assert slice("abcde") == "bc"
assert slice(b"xyz") == b"yz"
assert slice([2, 7, 1, 8]) == [7, 1]
Run Code Online (Sandbox Code Playgroud)

注意cast内部slice是必要的,因为实际上并不保证切片运算符(至少从 Python 3.8 开始,根据Sequence抽象类)返回与原始对象相同的类型。

你可以通过定义你自己的Slicable协议来解决这个问题并拥有更强的类型(你需要停止隐藏内置slice类型):

from typing import Protocol, TypeVar

_S = TypeVar('_S', bound='Slicable')


class Slicable(Protocol):
    def __getitem__(self: _S, i: slice) -> _S: ...


_T = TypeVar('_T', bound=Slicable)


def my_slice(seq: _T) -> _T:
    return seq[1:3]


assert my_slice("abcde") == "bc"
assert my_slice(b"xyz") == b"yz"
assert my_slice([2, 7, 1, 8]) == [7, 1]
Run Code Online (Sandbox Code Playgroud)

使用这种类型不需要强制转换,并且my_slice只允许其类型实现返回相同类型对象的切片操作的参数。例如,如果我定义一个__getitem__返回任意字符串的类:

class TrickySlicable:
    def __getitem__(self, i: slice) -> str:
        return "foo"

my_slice(TrickySlicable())  # error: Value of type variable "_T" of "my_slice" cannot be "TrickySlicable"
Run Code Online (Sandbox Code Playgroud)

dict 参数(实现__getitem__但不使用切片作为索引)和根本没有实现的类型会产生类似的失败__getitem__