Python随笔-5

本文最后更新于:2022年9月30日 晚上

文章未完成

__future__模块

说白了就是把新版本的特性导入到当前Python版本。
然后程序里是这个东西:from __future__ import annotations
额,这条语句其实搞不是很懂,参考了下别人的博文,大概有这么两种解释得好的:

加入以下语句,解释器将不再构造这些复合类型。
from __future__ import annotations
一旦解释器解析脚本语法树后,它会识别类型提示并绕过评估它,将其保留为原始字符串。这种机制使得类型提示发生在需要它们的地方:由linter来进行类型检查。 在Python 4中,这种机制将成为默认行为。
原文链接:全面理解Python中的类型提示(Type Hints)
还有一个是代码例子:(原文链接:Python Class 定义时声明当前 Class 类型)

1
2
3
4
5
6
7
class Foo:

def __init__(self: Foo, bar: str) -> None:
self.bar = bar

if __name__ == '__main__':
Foo('bar')

但是上面的代码是会报错的,原因就在于self: Foo。因为在类Foo定义前就已经被自己的方法__init__使用了,解释器无法理解,报出NameError: name 'Foo' is not defined错误。
这个时候这条语句就有用了:

1
2
3
4
5
6
from __future__ import annotations

class Foo:

def __init__(self: Foo, bar: str) -> None:
self.bar = bar

typing模块——类型注解支持

这个模块提供对类型提示的运行时支持。下边详细介绍一下程序里边的从typing导入的各种东西:

TypeVar

类型变量的缩写。类型变量主要是为静态类型检查器提供支持,用于泛型类型与泛型函数定义的参数。有关泛型类型,详见Generic
其实就是定义一个泛型变量,然后你就能把这个泛型变量用在泛型类型或者泛型函数定义的参数中了。直接上例子:

1
2
3
4
5
T = TypeVar('T')  # Can be anything

def repeat(x: T, n: int) -> Sequence[T]: # 这里的 Sequence[T] 就是把泛型变量用在泛型类型中,x: T 就是把泛型变量用在泛型函数定义的参数中。
"""Return a list containing n references to x."""
return [x]*n

你也可以对类型变量进行绑定(bound),或者约束类型变量,比如:

1
2
S = TypeVar('S', bound=str)  # Can be any subtype of str
A = TypeVar('A', str, bytes) # Must be exactly str or bytes

但是要注意的是,类型变量不能既是被绑定的又是被约束的。

Generic

用于泛型类型的抽象基类。
泛型类型一般通过继承含一个或多个类型变量的类实例进行声明。官方文档给出的例子是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
class Mapping(Generic[KT, VT]):
def __getitem__(self, key: KT) -> VT:
...
# Etc.

X = TypeVar('X')
Y = TypeVar('Y')

def lookup_name(mapping: Mapping[X, Y], key: X, default: Y) -> Y:
try:
return mapping[key]
except KeyError:
return default

这里还给出了另一篇文章中的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
from typing import TypeVar, Union, Generic, List

# 通过TypeVar限定为整数型的列表和浮点数的列表
T = TypeVar("T", bound=Union[int, float])

class MyList(Generic[T]):

def __init__(self, size: int) -> None:
self.size = size
self.list: List[T] = []

def append(self, e: T):
self.list.append(e)
print(self.list)

# 适用于整数型
intList = MyList[int](3) # 通过[int]进行类型提示!
intList.append(101)

# 也适用于浮点数
floatList = MyList[float](3)
floatList.append(1.1)

# 但不适用于字符串,以下代码通过mypy检查会报错!
strList = MyList[str](3)
strList.append("test")

或许可以理解为,如果你定义的一个类中使用了某种类型变量,那么你可以通过Generic来对你的类声明。(如果你创建的类中想要使用某种类型变量,如T,那么你就通过继承Generic[T]类来使用。)

Protocol

官方文档是这么描述的:协议类的基类。呃,那么问题来了,我也不知道什么是协议类啊。。。所以我直接使用搜索引擎,找到了点零零散散的东西。
在Python中,协议就是一个或一组方法(接口),那协议类很明显就是包含协议的类,就是在这个类里边放一些不实现的方法,是不是像那啥,抽象基类啊?
比如下面的代码,__eq____lt__不就pass了吗,这俩就能看成协议。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# ... 表示函数没有实现任何代码,跟 pass 的作用类似
class Comparable(Protocol):
def __eq__(self, other: Any) -> bool:
...

def __lt__(self: C, other: C) -> bool:
...

def __gt__(self: C, other: C) -> bool:
return (not self < other) and self != other

def __le__(self: C, other: C) -> bool:
return self < other or self == other

def __ge__(self: C, other: C) -> bool:
return not self < other

然后官方文档接着说了:这些类主要与静态类型检查器搭配使用,用来识别结构子类型(静态鸭子类型)。啊,好的,我也不知道啥是鸭子类型,所以我又去找搜索引擎帮忙了,然后我找到这么一篇文章:你知道什么是Python里的鸭子类型和猴子补丁吗?
文章里是这么说的:

鸭子类型是对Python中数据类型本质上是由属性和行为来定义的一种解读。
Python是一种动态语言,不像Java和C++这种强类型语言,Python里实际上没有严格的类型检查。
只要某个对象具有鸭子的方法,可以像鸭子那样走路和嘎嘎叫,那么它就可以被其它函数当做鸭子一样调用。
啥意思呢,举个栗子:
假设我们现在有两个类,一个Duck类,一个Goose类,两个类都实现了同样的方法bark()
然后我们自定义了一个函数test,这个函数接受一个Duck对象,并调用该对象的bark()方法。在其它语言中,我们需要指定传入形参duck的类型,得这么写test(Duck duck),但是在Python中不需要,直接这么写:test(duck)
但由于Python是动态语言,没有严格类型检查,这就导致你把一个Goose类的对象塞进去也不会报错,因为Goose类也实现了bark()方法。
卸载再来看下官方文档给出的例子:

1
2
3
4
5
6
7
8
9
10
11
12
class Proto(Protocol):
def meth(self) -> int:
...

class C:
def meth(self) -> int:
return 0

def func(x: Proto) -> int:
return x.meth()

func(C()) # Passes static type check

上面的Proto类即是协议类,里面只有一个协议(方法)meth(),而另一个则是C,它实现了协议meth(),那么它就可以看作是鸭子类型。
这里的func()函数用了类型检查,虽然指定了参数x的类型是Proto,但是由于Proto继承了Protocol,所以传入鸭子类型C的时候,跳过了静态类型检查。

Protocol 类可以是泛型,例如:

1
2
3
class GenProto(Protocol[T]):
def meth(self) -> T:
...

Callable


这里有一只爱丽丝

希望本文章能够帮到您~


Python随笔-5
https://map1e-g.github.io/2022/09/22/python-essay-5/
作者
MaP1e-G
发布于
2022年9月22日
许可协议