如何使用变量类型正确地类型注释函数?

How to correctly type-annotate functions with variable types?

我正在尝试向文件系统相关库添加类型提示,其中许多函数采用 strbytes 类型的路径。我可以通过使用重载来处理我自己的函数,但我很难处理简单的操作或在内部使用任一类型的参数调用的标准库函数。这是一个简化的例子:

@overload
def join_paths(s1: str, s2: str) -> str: ...


@overload
def join_paths(s1: bytes, s2: bytes) -> bytes: ...


def join_paths(s1: Union[str, bytes],
               s2: Union[str, bytes]) -> Union[str, bytes]:
    return s1 + s2

如果我想从其他地方调用这个函数,重载工作正常,但我的问题是 s1 + s2 语句,它导致 mypy 发出警告:

example.py:74: error: Unsupported operand types for + ("str" and "bytes")  [operator]
example.py:74: error: Unsupported operand types for + ("bytes" and "str")  [operator]

我想表达的是,要么两个操作数都是str类型,要么都是bytes类型,类似于我自己的函数使用重载所做的事情。

我没有太多打字经验,所以我可能会错过明显的解决方案,但到目前为止我还没有找到如何调整它以避免警告。

使用 TypeVar:

from typing import TypeVar

T = TypeVar('T', str, bytes)


def join_paths(s1: T, s2: T) -> T:
    return s1 + s2


join_paths("foo", "bar")    # fine
join_paths(b"foo", b"bar")  # fine
join_paths(1, 2)            # error: T can't be int
join_paths("foo", b"bar")   # error: T can't be object

当您无法通过 TypeVars 和泛型表达类型关系时,重载更像是一种不得已的工具——有效地使用重载通常涉及大量运行时类型断言(或 #type: ignores)松散类型实现的主体。

typing.AnyStr 最适合这种特定情况。

来自文档:

It is meant to be used for functions that may accept any kind of string without allowing different kinds of strings to mix. For example:

def concat(a: AnyStr, b: AnyStr) -> AnyStr:
    return a + b

concat(u"foo", u"bar")  # Ok, output has type 'unicode'
concat(b"foo", b"bar")  # Ok, output has type 'bytes'
concat(u"foo", b"bar")  # Error, cannot mix unicode and bytes

因此,您可以这样修改代码:

from typing import AnyStr


def join_paths(s1: AnyStr, s2: AnyStr) -> AnyStr:
    return s1 + s2

join_paths("s1", "s2")  # OK
join_paths(b"s1", b"s2")  # OK
join_paths("s1", b"s2")  # error: Value of type variable "AnyStr" of "join_paths" cannot be "object"