📝Python 的类型注解📝——为什么你应该始终使用它
▶ 类型注解 - 基础知识
▶ 类型注解 - 内置类型
1️⃣ 可选
2️⃣ 任意
3️⃣ 联盟
4️⃣ 系列
5️⃣ 列表
6️⃣ 元组
7️⃣ 词典
8️⃣ 函数执行结果
而不是结论
阅读更多
超棒的 Python 类型

Python是一种动态类型语言,允许我们相当自由地操作不同类型的变量。然而,在编写代码时,我们往往会预先假设将要使用的变量类型(这可能是由于算法或业务逻辑的限制)。为了确保程序正常运行,尽早发现与数据传输类型错误相关的问题至关重要。
在现代版本的Python (3.6+)中保留动态鸭子类型的概念,支持对变量类型、类字段、参数和函数返回值进行注解:
类型注解由 Python 解释器读取,不会以任何方式进行处理,但可供第三方代码使用,主要面向静态分析器设计。
在本文中,我想解释一些基础知识,并举例说明如何使用类型注解,最终展示为什么它让我的 Python 开发工作变得更加轻松🙂。
首先,让我们了解一下什么是类型注解。
▶ 类型注解 - 基础知识
类型本身用于指示变量的基本类型:
strintfloatboolcomplexbytes- ETC。
与旧版本的 Python 不同,类型注解不再写在注释或文档字符串中,而是直接写在代码里。一方面,这会破坏向后兼容性;另一方面,这也明确地表明类型注解是代码的一部分,可以进行相应的处理。
最简单的情况下,注解包含直接期望的类型。更复杂的情况将在下文讨论。如果注解指定了基类,则可以将其子类的实例作为值传递。但是,您只能使用基类中已实现的功能。
变量注解在标识符后加冒号。之后可以进行值初始化。例如:
price: int = 5
title: "str"
函数参数的注解方式与变量相同,返回值位于箭头之后->、冒号之前。下面我举一个在 Python 函数中使用类型注解的例子:
def func(a: int, b: float) -> str:
a: str = f"{a}, {b}"
return a
对于类字段,注解必须在定义类时显式指定。但是,分析器可以根据__init__方法自动推断注解,但这种情况下,注解在运行时将不可用:
class Book:
title: "str"
author: str
def __init__(self, title: "str, author: str) -> None:"
self.title = title
self.author = author
b: Book = Book(title="Fahrenheit 451", author="Bradbury")
▶ 类型注解 - 内置类型
虽然你可以使用标准类型作为注解,但模块中还隐藏着许多有用的东西typing。让我们来看看它的子模块。
1️⃣ 可选
如果给一个变量指定了类型int,然后尝试将其赋值为 None,则会发生错误:
赋值语句中类型不兼容(表达式类型为“None”,变量类型为“int”)
正是为了应对这种情况,类型模块提供了一个注解Optional来指示特定类型。请注意,可选变量的类型用方括号括起来:
from typing import Optional
amount: int
amount: None # Gives "Incompatible types" error
price: Optional[int]
price: None # Will work!
2️⃣ 任意
有时你不想限制变量的类型。例如,如果类型真的无关紧要,或者你打算自己处理不同的类型。在这种情况下,可以使用注解Any。以下代码不会报错:
some_item: Any = 1
print(some_item)
print(some_item.startswith("hello"))
print(some_item // 0)
可能会有人问,为什么不使用呢object?然而,在这种情况下,虽然可以传递任何对象,但它只能被视为一个实例object:
some_object: object
print(some_object)
print(some_object.startswith("hello)) # ERROR: "object" has no attribute "startswith"
print(some_object // 0) # ERROR: Unsupported operand types for // ("object" and "int")
3️⃣ 联盟
对于需要允许使用某些类型而不是所有类型的情况,可以使用typing.Union方括号中的注释来指示类型列表。
def hundreds(x: Union[int, float]) -> int:
return (int(x) // 100) % 100
hundreds(100.0)
hundreds(100)
hundreds("100")
# ERROR: Argument 1 to "hundreds" has incompatible type "str"; expected "Union[int, float]"
顺便一提,这种注解方式Optional[T]等价于Union[T, None],尽管不建议使用这种注解方式。
4️⃣ 系列
类型注解机制支持泛型机制(PEP484 - 泛型,更多详情请参见本文第二部分),该机制允许为容器指定其中存储的元素的类型。
5️⃣ 列表
要表明一个变量包含一个列表,可以使用列表类型作为注解。但是,如果要指定列表包含哪些元素,这种注解就不再有效了。为此,可以使用 `<list>` 标签。typing.List类似于我们指定可选变量类型的方式,我们在方括号中指定列表项的类型。
titles: List[str] = ["hello", "world"]
titles.append(100500)
# ERROR: Argument 1 to "hundreds" has incompatible type "str"; expected "Union[int, float]"
titles = ["hello", 1]
# ERROR: List item 1 has incompatible type "int"; expected "str"
items: List = ["hello", 1]
# Everything is good!
列表假定包含数量不定的相似项。但同时,对注释元素没有任何限制:您可以使用 `<a>` Any、Optional`<b> List`、`<c>` 等。如果未指定元素类型,则假定为 `<a>` Any。
除了列表之外,集合也有类似的注释:typing.Set和typing.FrozenSet。
6️⃣ 元组
与列表不同,元组通常用于存储不同类型的元素。它们的语法类似,区别在于:元组中每个元素的类型需要用方括号分别标明。
如果您计划像使用列表一样使用元组:存储未知数量的相同类型的元素,则可以使用省略号(...)。
不指定元素类型的注解Tuple的工作方式与此相同。Tuple[Any, ...]
price_container: Tuple[int] = (1,)
price_container: ("hello")
# ERROR: Incompatible types in assignment (expression has type "str", variable has type "Tuple[int]")
price_container = (1, 2)
# ERROR: Incompatible types in assignment (expression has type "Tuple[int, int]", variable has type "Tuple[int]")
price_with_title: Tuple[int, str] = (1, "hello")
# Everything is good!
prices: Tuple[int, ...] = (1, 2)
prices: (1,)
prices: (1, "str")
# ERROR: Incompatible types in assignment (expression has type "Tuple[int, str]", variable has type "Tuple[int]")
something: Tuple = (1, 2, "hello")
# Everything is good!
7️⃣ 词典
用于字典typing.Dict。键类型和值类型分别标注:
book_authors: Dict[str, str] = {"Fahrenheit 451": "Bradbury"}
book_authors["1984"] = 0
# ERROR: Incompatible types in assignment (expression has type "int", target has type "str")
book_authors[1984] = "Orwell"
# ERROR: Invalid index type "int" for "Dict[str, str]"; expected type "str"
类似地使用typing.DefaultDict和typing.OrderedDict
8️⃣ 函数执行结果
任何类型注解都可以用来指示函数结果的类型。但也有一些特殊情况。
如果函数不返回任何值(例如,`nil` print),则其结果始终为 `false` None。我们也将其用于注解None。
完成此类函数的正确选项有:显式返回None、不指定值直接返回以及不调用函数直接终止return:
def nothing(a: int) -> None:
if a == 1:
return
elif a == 2:
return
elif a == 3:
return "" # No return value expected
else:
pass
如果函数永远不会返回控制权(例如,如何返回sys.exit),请使用注解NoReturn:
def forever() -> NoReturn:
while True:
pass
如果这是一个生成器函数,也就是说,它的函数体包含 yield 运算符,那么你可以对返回的函数使用注解Iterable[T],方法如下Generator[YT, ST, RT]:
def generate_two() -> Iterable[int]:
yield 1
yield "2"
# ERROR: Incompatible types in "yield" (actual type "str", expected type "int")
而不是结论
在许多情况下,类型模块都有合适的类型,但我不会面面俱到,因为它们的行为与之前描述的情况类似。例如,有Iterator一个通用的版本collections.abc.Iterator,typing.SupportsInt用于表示对象支持某个方法__int__,或者Callable用于表示函数和对象支持某个方法。__call__
该标准还定义了注释的格式,注释以注释和存根文件的形式存在,其中仅包含静态分析器所需的信息。
阅读更多
如果您觉得这篇文章对您有帮助,请点击下方的💚或👏按钮,或在Facebook上分享这篇文章,让您的朋友也能从中受益。
https://subscribe.to/raevskymichail
文章来源:https://dev.to/mikhailraevskiy/python-s-type-annotations-why-you-always-should-use-it-4lh2
