运行时实现@typing.overload。
项目描述
超越
如何安装?
pip install overtake
超越相当自包含。默认情况下,唯一的依赖项是typing-extensions
。
什么是超越?
你有没有想过多次声明同一个函数?
def count_words(arg: str) -> int:
return len(arg.split())
def count_words(arg: list[str]) -> int:
return sum(len(text.split()) for text in arg)
print(count_words("one two three!"))
# 3
print(count_words(["one two", "three four five six"]))
# 6
那么你就在正确的位置!
超越是一个小型库,旨在将@typing.overload
推得更远。@typing.overload
仅定义签名,以便类型检查器知道在调用函数时有哪些类型提示可用。使用超越,您还可以在运行时调用这些函数,类似于C++或Julia等语言。
举例说明胜过千言万语
from typing import overload
# if you are using Python <3.11, you'll need instead
# from typing_extensions import overload
from overtake import overtake
@overload
def count_words(arg: str) -> int:
return len(arg.split())
@overload
def count_words(arg: list[str]) -> int:
return sum(len(text.split()) for text in arg)
@overtake(runtime_type_checker="beartype")
def count_words(arg):
...
print(count_words("one two three!"))
# 3
print(count_words(["one two", "three four five six"]))
# 6
超越将分析类型和提供的参数以调用正确的实现。
它支持@typing.overload
支持的每个签名@typing.overload
此模式由IDE(Pycharm、VSCode等)支持,因此自动补全将工作得很好。它也受到类型检查器(Mypy、Pyright等)的支持,因此您无需在类型安全方面做出妥协 😁
超越紧密遵循Mypy关于@typing.overload
的指南:https://mypy.readthedocs.io/en/stable/more_types.html#function-overloading
更多高级示例。
我们可以在这里向您展示更多可能的模式。基本上,if isinstance(..., ...)
可能是您使用overtake
来编写更清晰代码的提示。
递归
让我们编写一个返回自1月1日起天数数量的函数
from typing import overload
from datetime import date
from overtake import overtake
@overload
def days_this_year(current_date: date) -> int:
delta = current_date - date(2023, 1, 1)
return delta.days
@overload
def days_this_year(current_date: str) -> int:
return days_this_year(date.fromisoformat(current_date))
@overtake
def days_this_year(current_date):
...
print(days_this_year(date(2023, 8, 15)))
# 226
print(days_this_year("2023-08-15"))
# 226
您可以使用递归方式调用您的函数,以消除代码重复。我们实际上可以像这样重写我们的第一个例子
from typing import overload
from overtake import overtake
@overload
def count_words(input_value: str) -> int:
return len(input_value.split())
@overload
def count_words(input_value: list[str]) -> int:
return sum(count_words(text) for text in input_value)
@overtake(runtime_type_checker="beartype")
def count_words(input_value):
...
print(count_words("one two three!"))
# 3
print(count_words(["one two", "three four five six"]))
# 6
不同的输出类型
也可以有不同的输出类型,比如使用@overload
from typing import overload
from overtake import overtake
@overload
def convert_to_int(input_value: str) -> int:
return int(input_value)
@overload
def convert_to_int(input_value: list[str]) -> list[int]:
return [int(x) for x in input_value]
@overtake(runtime_type_checker="beartype")
def convert_to_int(input_value):
...
print(convert_to_int("88"))
# 88 (an integer)
print(convert_to_int(["88", "42", "84"]))
# [88, 42, 84] (a list of integers)
利用可选参数
它可以避免一些令人讨厌的if ... is None:
使用,您可以指定不同数量的参数(但顺序必须匹配!)。
对于这个示例,假设您想让用户能够在任何类型的文件中写入一些文本。如果没有提供文件,我们将创建一个临时文件。我们必须接受对文件的任何输入。可以是 str
、pathlib.Path
,也可以是文件对象。或者什么都不提供(随机文件)。
from typing import overload
from pathlib import Path
import io
import random
from overtake import overtake
@overload
def write_text_to_file(text: str, file: io.TextIOBase) -> None:
file.write(text)
@overload
def write_text_to_file(text: str, file: Path) -> Path:
file.write_text(text)
return file
@overload
def write_text_to_file(text: str, file: str) -> Path:
return write_text_to_file(text, Path(file))
@overload
def write_text_to_file(text: str) -> Path:
random_file_name = f"/tmp/{random.randint(0, 10)}.txt"
return write_text_to_file(text, random_file_name)
@overtake(runtime_type_checker="beartype")
def write_text_to_file(text, file=None):
...
print(write_text_to_file("hello world"))
# /tmp/4.txt
print(write_text_to_file("hello world", "/tmp/some-file.txt"))
# /tmp/some-file.txt
print(write_text_to_file("hello world", Path("/tmp/some-file.txt")))
# /tmp/some-file.txt
print(write_text_to_file("hello world", io.StringIO()))
# None (we didn't write in a file on disk)
利用关键字参数
您可以使用这种范式来强制用户使用比函数中定义的更少的参数。这里有一个例子,您正在寻找用户的余额。您可以提供用户ID或用户名,但不能两者都提供。
from overtake import overtake
from typing import overload
@overload
def find_user_balance(name: str) -> int:
return 40
@overload
def find_user_balance(user_id: int) -> int:
return 50
@overtake
def find_user_balance(*, user_id=None, name=None):
...
print(find_user_balance(name="Julie"))
# 40
print(find_user_balance(user_id=14))
# 50
在这种情况下,Overtake可以为您节省很多时间。您不需要检查user_id
和name
是否都提供,也不需要检查它们中是否没有任何一个提供。Overtake会为您做这些!
建议
我们建议使用您选择的类型检查器(Mypy、Pyright等),以便类型检查器可以捕获对@overload
的无效使用。尽管这不是强制的,但早期捕获@overload
的错误是很有帮助的。
runtime_type_checker
参数
Python中的运行时类型检查很困难。实际上,非常困难。一些库提供了这项功能,如beartype或pydantic。
为了避免安装任何依赖项,overtake
可以使用isinstance
作为默认的"basic"
类型检查器。请注意,这仅适用于类类型。例如,它不适用于list[str]
,但适用于list
或datetime
。
要处理更复杂的数据类型,您应该使用
@overtake(runtime_type_checker="beartype")
def find_user_balance(*, user_id=None, name=None):
...
或
@overtake(runtime_type_checker="pydantic")
def find_user_balance(*, user_id=None, name=None):
...
我需要什么样的 runtime_type_checker
?有这么多选择!!!
首先,不要选择任何(默认使用"basic"
),看看它是否工作。如果您使用的类型太复杂(如果您正在使用泛型或协议),Overtake将引发错误,并告诉您使用另一个runtime_type_checker
。
然后,您可以选择使用"beartype"
或"pydantic"
。问问自己,“我是否需要beartype特定的类型?比如beartype的验证器?” 如果是,请使用beartype。
如果不是,问问自己“我是否需要Pydantic特定的类型?比如Pydantic的urls或自定义类型?” 如果是,请使用pydantic。
如果您在做出决定后仍然犹豫,您应该使用beartype,因为它更快(验证时间复杂度为O(1)),但请注意,您可能会遇到不支持类型的静默错误,这可能会非常不愉快。请参阅这个问题。Pydantic可能较慢,但在使用不支持类型时您会收到错误。因此,它更安全。
这两个库都非常酷,我鼓励任何好奇的心去阅读那些文档!我们建议您使用pip install overtake[beartype]
或pip install overtake[pydantic]
安装这些库。
我可以用Beartype类型检查器做些什么有趣的事情?
我一直在等你问。看看!
from overtake import overtake
from typing import overload, Annotated
from beartype.vale import Is
# Type hint matching only strings with lengths ranging [4, 40].
LengthyString = Annotated[str, Is[lambda text: 4 <= len(text) < 40]]
# Type hint matching only strings with lengths ranging [0, 4].
ShortString = Annotated[str, Is[lambda text: 0 <= len(text) < 4]]
@overload
def is_this_string_big(arg: ShortString) -> str:
return "This is a short string!"
@overload
def is_this_string_big(arg: LengthyString) -> str:
return "This is a very long string"
@overtake(runtime_type_checker="beartype")
def is_this_string_big(arg):
...
print(is_this_string_big("Hi!"))
# This is a short string!
print(is_this_string_big("No one expects the spanish inquisition!"))
# This is a very long string
您还需要if
语句吗?人们可能会想。
我可以用Pydantic类型检查器做些什么有趣的事情?
您可以玩他们的自定义类型!
from typing import overload
from overtake import overtake
from pydantic import MongoDsn, RedisDsn
@overload
def connect(arg: RedisDsn) -> str:
return "Connected to redis!"
@overload
def connect(arg: MongoDsn) -> str:
return "Connected to MongoDB!"
@overtake(runtime_type_checker="pydantic")
def connect(arg):
...
print(connect("rediss://:pass@localhost"))
# Connected to redis!
print(connect("mongodb://mongodb0.example.com:27017"))
# Connected to MongoDB!
与Pyright的兼容性
Pyright有一个小的兼容性问题,您可能会遇到以下错误
error: "my_function" is marked as overload, but it includes an implementation
The body of a function overload should be "..."
这可以通过在重载函数签名旁边添加# type: ignore
来解决,而无需禁用调用和函数体的类型检查。这里有一个小示例
from datetime import date
from overtake import overtake
from typing_extensions import overload
@overload
def days_this_year(current_date: date) -> int: # type: ignore
delta = current_date - date(2023, 1, 1)
return delta.days
@overload
def days_this_year(current_date: str) -> int: # type: ignore
return days_this_year(date.fromisoformat(current_date))
@overtake
def days_this_year(current_date):
...
为了长期解决这个限制(Mypy有一天可能会引发相同的错误),已经制定了一个PEP草案。不要犹豫,在讨论中提出反馈!
项目详情
下载文件
下载适用于您平台的文件。如果您不确定选择哪个,请了解更多关于安装包的信息。
源代码分发
构建分发
overtake-0.4.0.tar.gz 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | 28ed16d04eb8e59e58d43e3812d915f737df9163e1245432f063e87918e5cb42 |
|
MD5 | eacb3a0ea3158027f4d784719684ad80 |
|
BLAKE2b-256 | 10a51efee0aa7a687192fa692f4a273ecbe841b115b857fe827bba31e04b674a |
overtake-0.4.0-py3-none-any.whl 的哈希值
算法 | 哈希摘要 | |
---|---|---|
SHA256 | ad53c852c292bdba27bff1b6044e880217c378f52e859b6f6154250e37c53254 |
|
MD5 | ce01c14fe618fcddafe3170c4431673e |
|
BLAKE2b-256 | 8ea2013a22b6f8aebe1411d47caee13b9f2c91a301142f34c35310f768549714 |