类型模块中的重载装饰器似乎没有按预期运行

Posted

技术标签:

【中文标题】类型模块中的重载装饰器似乎没有按预期运行【英文标题】:Overload decorator in typings module doesn't seem to behave as expected 【发布时间】:2019-02-01 17:22:20 【问题描述】:
>>> from typing import overload

>>> @overload
... def hello(s: int):
...     return "Got an integer!"

>>> def hello(s: str):
...     return "Got a string"

为什么调用hello(1) 调用带有字符串参数的函数?理想情况下,@overload 运算符应该处理它,对吧?

【问题讨论】:

你需要装饰这两个定义,否则你只是用普通函数覆盖hello 准确地说,应该只修饰存根函数。调用修饰函数将引发NotImplementedErroroverload 的想法是用实际实现覆盖存根 【参考方案1】:

不幸的是,python 不允许函数重载。每次你认为你正在重载函数时,你只是在覆盖之前的函数声明。引用自docs:

@overload 装饰器允许描述函数和方法 支持多种不同的参数类型组合。一系列的 @overload-decorated 定义必须紧跟一个 非@overload-decorated 定义(对于相同的函数/方法)。 该 @overload-decorated 定义是为了类型的利益 仅检查器,因为它们将被 非@overload-decorated 定义,而后者用于 运行时,但应该被类型检查器忽略。在运行时,调用 @overload-decorated 函数直接会引发 NotImplementedError。

typing.overload的正确用法如下:

from typing import overload


@overload
def hello(s: int) -> str:
    ...


@overload
def hello(s: str) -> str:
    ...


def hello(s):
    if isinstance(s, int):
        return "Got an integer!"
    if isinstance(s, str):
        return "Got a string"
    raise ValueError('You must pass either int or str')


if __name__ == '__main__':
    print(hello(1))

为了显示typing.overload 的实际好处,我们将def hello(s: int) 更改为返回int 而不是str

from typing import overload


@overload
def hello(s: int) -> int:
    ...


@overload
def hello(s: str) -> str:
    ...


def hello(s):
    if isinstance(s, int):
        return "Got an integer!"
    if isinstance(s, str):
        return "Got a string"
    raise ValueError('You must pass either int or str')


if __name__ == '__main__':
    print(hello(1))
    a = hello(1) + 1
    b = hello(1) + 'a'

注意,实际实现仍然返回str - python 在这里不执行任何检查。然而,PyCharm 提出了一个警告:

mypy 还抱怨无效类型:

➜ mypy test.py 
test.py:25: error: Unsupported operand types for + ("int" and "str")

typing 模块的目的是允许第三方工具对您的代码执行静态检查。这里没有魔法——所有类型在运行时都会被忽略。

【讨论】:

那么如果我们要检查类型,@overload 的意义何在?编辑:没有意义。明白了。 我怀疑的原因是 Pycon 的演讲之一,演讲者完全按照问题中提到的内容做了。很奇怪。 @saruftw 请检查更新的答案。简而言之 - typing 模块(尤其是 @overload)的重点是允许第三方工具(例如 mypy)运行静态检查 知道了。感谢您的解释。 为什么不直接使用Union[str, int] 来说明函数可以获取int 或str?【参考方案2】:
# tested in Python 3.8.5 32-bit
# overloads the method
# imports libraries from the base distribution 
# confuses some linters
# undermines type-hinting by documenting *kwargs or dispatch signature

from functools import singledispatch

class Car:
    def __init__(self, color: str, brand: str) -> None:
        self.color = color
        self.brand = brand


@singledispatch
def describe_car(color: str, kind: str) -> str:
    return "Little " + color + " " + kind

@describe_car.register(Car)
def _(car: Car) -> str:
        return describe_car(car.color, car.brand)


newcar = Car("red", "corvette")

print(describe_car("green", "pinto"))
print(describe_car(newcar))

pythonout> 小绿斑图 小红色护卫舰

【讨论】:

以上是关于类型模块中的重载装饰器似乎没有按预期运行的主要内容,如果未能解决你的问题,请参考以下文章

Python装饰器AOP 不定长参数 鸭子类型 重载

Python 没有函数重载?如何用装饰器实现函数重载?

Python 中的装饰器类

为什么 Python 没有函数重载?如何用装饰器实现函数重载?

Python 没有函数重载?如何用装饰器实现函数重载?

使用 TypeVar 在 MyPy 中使用参数键入装饰器会产生预期的无人居住类型