ref:
https://xinetzone.github.io/zh-CN/55e01cf0.html
https://docs.python.org/zh-cn/3/library/typing.html
[typing](https://docs.python.org/zh-cn/3/library/typing.html#module-typing)
—- 类型标注支持
3.5 新版功能.
源码: Lib/typing.py
注解
The Python runtime does not enforce function and variable type annotations. They can be used by third party tools such as type checkers, IDEs, linters, etc.
This module provides runtime support for type hints as specified by PEP 484, PEP 526, PEP 544, PEP 586, PEP 589, and PEP 591. The most fundamental support consists of the types Any
, Union
, Tuple
, Callable
, TypeVar
, and Generic
. For full specification please see PEP 484. For a simplified introduction to type hints see PEP 483.
函数接受并返回一个字符串,注释像下面这样:
def greeting(name: str) -> str:
return 'Hello ' + name
在函数 greeting
中,参数 name
预期是 str
类型,并且返回 str
类型。子类型允许作为参数。
类型注解
类型注解通过将类型分配给别名来定义。在这个例子中, Vector
和 List[float]
将被视为可互换的同义词:
from typing import List
Vector = List[float]
def scale(scalar: float, vector: Vector) -> Vector:
return [scalar * num for num in vector]
# typechecks; a list of floats qualifies as a Vector.
new_vector = scale(2.0, [1.0, -4.2, 5.4])
类型注解可用于简化复杂类型签名。例如:
from typing import Dict, Tuple, Sequence
ConnectionOptions = Dict[str, str]
Address = Tuple[str, int]
Server = Tuple[Address, ConnectionOptions]
def broadcast_message(message: str, servers: Sequence[Server]) -> None:
...
# The static type checker will treat the previous type signature as
# being exactly equivalent to this one.
def broadcast_message(
message: str,
servers: Sequence[Tuple[Tuple[str, int], Dict[str, str]]]) -> None:
...
请注意,None
作为类型提示是一种特殊情况,并且由 type(None)
取代。
0 引言:定义一个线段
假设我们使用 Python 实现数学中的整点线段的概念,即 $L = [a,b]$, 且 $a,b \in Z$。使用 Python 可以这样写:
class LineSegment:
def __init__(self, start, end):
'''
整点线段
=======
start 和 end 均是整数
'''
assert start <= end, "线段的方向错误"
self.start = start # 线段的起点
self.end = end # 线段的终点
def __len__(self):
'''
获取线段的长度
'''
return self.end - self.start + 1
Python
类 LineSegment 模拟了线段的形态与长度。但是,这样的写法是不是有点繁琐?为了更加人性化,Python 提供了 typing 模块,让我们可以对代码进行注解。
1 typing 注解 Python
如果想要声明函数参数和返回值的类型,您可以这样做:
def add(x:int, y:int=2) -> int:
return x + y
这就是被称为 function annotation 的写法。使用冒号 : 加类型名来代表参数的类型,使用箭头 -> 加类型表示返回值的类型。注解部分不会被 Python 解析器所解析。只是一种注解的方式,类似于:
def add(x, y):
'''
x: int
y: int
return int
'''
return x + y
注意:由于 Python 是动态语言,所以注解是对函数参数和返回值的“注释”,没有强制定义的作用。
比如,您像这样 add(1.2, 3.0) 传入参数,Python 解释器并不会报错。
print("print 'add(1.2, 3.0)', result is", add(1.2, 3.0))
output:
print 'add(1.2, 3.0)', result is 4.2
1.1 使用 inspect 检查 python 对象的类型
如果您想要让 Python 对类型进行检查,可以借助模块 inspect。比如:
# 1、类型检查
import inspect
inspect.isfunction(add) # 判断add是否是函数
inspect.ismethod(add) # 判断add是否是类的方法
inspect.isgenerator(add) # 判断add是否是生成器对象
inspect.isclass(add) # 判断add是否是类
如果对函数的参数进行检查呢?这个需要借助 sig=inspect.signature
:
from inspect import signature
# 获得函数的签名
sig = signature(add)
sig
output
<Signature (x: int, y: int = 2) -> int>
接着,可以直接获取函数的信息:
params = sig.parameters # 获取函数的参数信息
params
output:
mappingproxy({'x': <Parameter "x: int">, 'y': <Parameter "y: int = 2">})
Parameter 是 inspect 下的一个类,可以把它看做是一个有序字典,里面存放了函数的参数和参数类型,遍历 params.values() 的 annotation 属性会得到参数的注解类型:
for v in params.values():
print(v, 'annotation is', v.annotation)
output:
x: int annotation is <class 'int'>
y: int = 2 annotation is <class 'int'>
可以这样定义类型检查函数:
from inspect import signature
def checkParameterType(fn):
def wrapper(*args, **kwargs):
sig = signature(fn)
# 获取函数的参数信息
params = sig.parameters
values = params.values()
for p, v in zip(args, values):
print(p, v)
# 判断传入参数的类型和参数注解类型是否相符
if not isinstance(p, v.annotation):
print('TypeWrong')
for k, v in kwargs.items():
if not isinstance(v, params[k].annotation):
print('TypeWrong')
return fn(*args, **kwargs)
return wrapper
@checkParameterType
def add(x: int, y: int = 2) -> int:
return x + y
1.2 利用 python 对象的 annotations 属性检查类型
下面以定义一个商品对象:
from typing import Any
def goods(name: str, price: float) -> Any:
return goods
fish = goods('fish', 10.0)
fish.__annotations__
输出结果是(Any
代表 Python 很难表达的形式或者类型):
{'name': str, 'price': float, 'return': typing.Any}
可以看出,annotations 属性与 1.1 中的 params.values()[k].annotations 很相似。
这样,可以改写其类型检查函数为:
def checkParameterType(fn):
def wrapper(*args, **kwargs):
annotations = fn.__annotations__
for k, v in zip(annotations.keys(), args):
if not isinstance(v, annotations[k]):
print('TypeWrong')
for k, v in kwargs.items():
if not isinstance(v, annotations[k]):
print('TypeWrong')
return fn(*args, **kwargs)
return wrapper
2 dataclass 提供强大的类型注解机制
dataclass 的定义位于 PEP-557,一个 dataclass 是指“一个带有默认值的可变的 namedtuple”,广义的定义就是有一个类,它的属性均可公开访问,可以带有默认值并能被修改,而且类中含有与这些属性相关的类方法,那么这个类就可以称为 dataclass,再通俗点讲,dataclass 就是一个含有数据及操作数据方法的容器。
我们先看看 dataclass 的参数:
key | 含义 |
---|---|
init |
指定是否自动生成__init__ ,如果已经有定义同名方法则忽略这个值,也就是指定为True 也不会自动生成 |
repr |
同init ,指定是否自动生成__repr__ ;自动生成的打印格式为class_name(arrt1:value1, attr2:value2, ...) |
eq |
同init ,指定是否生成__eq__ ;自动生成的方法将按属性在类内定义时的顺序逐个比较,全部的值相同才会返回True |
order |
自动生成__lt__ ,__le__ ,__gt__ ,__ge__ ,比较方式与eq 相同;如果order 指定为True 而eq 指定为False ,将引发ValueError ;如果已经定义同名函数,将引发TypeError |
frozen |
设为True 时对field 赋值将会引发错误,对象将是不可变的,如果已经定义了__setattr__ 和__delattr__ 将会引发TypeError |
参数 unsafehash
的使用比较复杂,当设置为unsafehash=True
时将会根据类属性自动生成__hash__
,然而这是不安全的,因为这些属性是默认可变的,这会导致hash
的不一致,所以除非能保证对象属性不可随意改变,否则应该谨慎地设置该参数为True
。对于 unsafehash=False
的情况将根据eq
和frozen
参数来生成__hash__
,下面单独列出:
参数设置 | 含义 |
---|---|
eq=True,frozen=True |
__hash__ 将会生成 |
eq=True,frozen=False |
__hash__ 将被设为None |
eq=False,frozen=True |
__hash__ 将使用超类(object )的同名属性(通常就是基于对象id 的hash ) |
注意:最好去掉了init方法,以确保 dataclass 装饰器可以添加它生成的对应方法。
如果要覆盖 init,我们将失去 dataclass 的优势,因此,如果要处理任何附加功能可以使用新的 dunder 方法:post_init,让我们看看post_init方法对于我们的包装类来说是什么样子的。
由于 Python 支持中文作为变量名、类名、函数名等,所以,我可以这样定义一个 Python 类:
from dataclasses import dataclass
@dataclass
class BookList:
name: str
price: float
num: int=0
def __post_init__(self) -> float:
self.sum = self.price * self.num
book1 = BookList('书籍', 10.0, 7)
print(f'{book1},花费的成本{book1.sum}元')
BookList(name='书籍', price=10.0, num=7),花费的成本70.0元
上面的代码模拟了出售商品的清单,整个代码看起来都很清爽。