注释

函数注释

【示例】:

1
2
3
4
5
6
7
8
9
10
11
12
@staticmethod
def report_info_add(report_a: dict, report_b: dict, key: str) -> int:
"""
报告信息相加
:param report_a: dict 报告信息字典 A
:param report_b: dict 报告信息字典 B
:param key: str 指定 key 值
:return: int 相加后的数值
"""
report_a_value = report_a[key] if key in report_a else 0
report_b_value = report_b[key] if key in report_b else 0
return report_a_value + report_b_value

在函数声明中 report_a 是参数,: 冒号后 dict 是参数 report_a 的注释,表示参数的数据类型。如果参数有默认值,则可以写成:

1
def sum(a: int = 1)

函数声明头部最后的箭头,则是函数返回值的注释,表明函数返回的数据类型,也可以是其他内容。

这些注释信息都是函数的元信息,保存在 f.annotations 字典中。

【注意】:

  • 元信息(注释信息)的值可以是:
    • object,例如上述示例中的 dict、int,也可以是具体的对象 MyObject;
    • str:通过字符串可以提供更丰富的注释信息,例如 `def sum(a, b) -> “the sum of a and b”:
  • Python 对元信息(注释信息)和 f.annotations 的一致性不做检查。换言之,元信息仅仅只是帮助开发人员理解函数。

奇妙用法

无论是编写插件还是开发项目,我们都需要保证程序的健壮性,在这之中,对输入数据类型的检测为重要。但是,对每个函数都进行输入数据的类型检测是一份非常辛苦的工作,尤其当项目非常庞大时。有没有什么好的办法能够自动对输入数据的类型进行检测呢?上面所讲的函数注释的元信息可以帮助我们达成这个目标。

首先,定义输入数据类型检测函数。

1
2
3
4
def input_check(annotations: dict, **kwargs) -> None:
for param_name in kwargs.keys():
if type(kwargs[param_name]) != annotations[param_name]:
raise TypeError("The param {} must be {}!".format(param_name, annotations[param_name]))
  • annotations:函数注释的元信息。

在其他函数的开始位置调用 input_check 函数。

1
2
3
def read_data(path: str, file_type: str = "txt"):
input_check(read_data.__annotations__, path=path, file_type=file_type)
# 执行其他代码

【示例】:输入类型不匹配。

1
2
3
4
if __name__ == '__main__':
read_data("D://code/name.json", [])

# TypeError: The param file_type must be <class 'str'>!

当然,我们也可以对其进行错误捕获与处理。

1
2
3
4
5
6
7
def read_data(path: str, file_type: str = "txt"):
try:
input_check(read_data.__annotations__, path=path, file_type=file_type)
# 执行其他代码
except TypeError as error:
print(error)
# 错误处理代码

每次在函数开始位置都要调用 input_check() 仍然是一件“麻烦”的事情,有没有更简单易用的方式?毕竟偷懒是推动技术发展的重要动力嘛。当然有,我们可以通过装饰函数来实现。

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
27
28
29
30
def input_check(*args, **kwargs):

def decorate(func):
# 获取函数注释元信息
annotations = func.__annotations__
sig = signature(func)

@wraps(func)
def wrapper(*args, **kwargs):
# 获取执行函数的参数名与值
param_dict = sig.bind(*args, **kwargs).arguments

for param_name, param_value in param_dict.items():
if type(param_value) != annotations[param_name]:
raise TypeError("The param {} must be {}!".format(param_name, annotations[param_name]))

return func(*args, **kwargs)
return wrapper
return decorate


@input_check()
def read_data(path: str, file_type: str = "txt"):
# 执行其他代码


if __name__ == '__main__':
read_data("D://code/name.json", [])

# TypeError: The param path must be <class 'str'>!

除了通过函数注释的元信息来强制规定参数类型外,我们也可以通过给装饰器函数传递参数的形式来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def input_check(*args, **kwargs):

def decorate(func):
# 获取函数规定的参数类型
sig = signature(func)
param_type_dict = sig.bind(*args, **kwargs).arguments

@wraps(func)
def wrapper(*args, **kwargs):
# 获取执行函数的参数名与值
param_values = sig.bind(*args, **kwargs).arguments

for param_name, param_value in param_values.items():
if type(param_value) != param_type_dict[param_name]:
raise TypeError("The param {} must be {}!".format(param_name, param_type_dict[param_name]))

return func(*args, **kwargs)
return wrapper
return decorate


@input_check(str, str)
def read_data(path: str, file_type: str = "txt"):
# 执行其他代码

2024-04-09 更新:GPT-4 编写的使用类型注释和装饰器进行参数校验的代码。这种方式特别适用于需要强制类型检查的情况。下面是一个例子,使用类型注解和一个自定义装饰器来检查函数参数:

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
27
28
29
30
31
from functools import wraps
from typing import Any, Callable

def type_check(func: Callable) -> Callable:
@wraps(func)
def wrapper(*args, **kwargs):
func_annotations = func.__annotations__
all_args = kwargs.copy()
all_args.update(dict(zip(func.__code__.co_varnames, args)))

for arg_name, arg_value in all_args.items():
if arg_name in func_annotations:
expected_type = func_annotations[arg_name]
if not isinstance(arg_value, expected_type):
raise TypeError(f"Argument '{arg_name}' must be {expected_type}")

return func(*args, **kwargs)
return wrapper


@type_check
def my_function(param1: int, param2: float) -> float:
return param1 + param2

# Example usage
try:
result = my_function(5, 3.14) # This should pass
print(result)
result = my_function('5', 3.14) # This should raise a TypeError
except TypeError as e:
print(f"Error: {e}")

在这个例子中,type_check 装饰器会检查 my_function 的参数类型是否与注解相符。如果传递了错误的类型,将会抛出 TypeError

这种方法的优点是类型注解本身就作为文档,说明了函数应该如何被调用。同时,装饰器在运行时强制这些类型规则,确保了类型安全。请注意,Python的类型注解本身不会强制类型检查,它们通常用于文档目的或者使用第三方工具如 mypy 进行静态类型检查。我们在这里通过自定义装饰器在运行时实施类型检查。