Loading...
墨滴

超人不会飞

2021/06/05  阅读:24  主题:默认主题

Python函数注释

Python 函数注释是在PEP 3107引入的,其主要目的是给函数形参和返回值添加可选元数据。

两点重要的信息

  1. 函数注释可选
  2. 可添加任意 Python 表达式,但是 Python 本身并不会对表达式做处理,也不具有任何意思

函数注释语法如下:

>>> def annotation(param1: "第一个函数注释", param2: "第二个函数注释") -> "返回值函数注释":
...     pass
...
>>> annotation.__annotations__
{'param1''第一个函数注释''param2''第二个函数注释''return''返回值函数注释'}

我们可以通过函数对象的__annotation__ 属性来获取所有的注释,其中有一个特别的 key 是 return 专门用来存放返回值函数注释。

函数注释可以被用来实现很多功能:

  • 类型检测
  • 谓词逻辑
  • IDE 类型辅助

下面我们利用函数注释实现一个简单的谓词逻辑功能。

我们先创建一个函数,并给两个参数添加_谓词注释_。

def repeat(times: 'times > 0 and times < 10', name: 'len(name) > 5') -> str:
    return name * times
  1. times 必须大于 0 并且小于 10
  2. name 长度必须大于 5

我们可以直接运行该函数,但是 Python 不会解释注释,所以注释并没有效果。接下来我们来学习如何优雅的访问这些注释。

from inspect import signature

sig = signature(repeat)
for param_name in sig.parameters:
    print(f"{sig.parameters[param_name]}")

# times: 'times > 0 and times < 10'
# name: 'len(name) > 5'

我们可以利用signature 来访问函数参数,注意signature.parameters 是一个有序字典,按照参数列表的顺序访问参数。

接下来我们要使用装饰器语法来定义一个可以再运行时访问实参的 wrapper 函数。

def parameter_predicate(f):
    def wrapper(*args, **kwargs):
        for arg in args:
            print(arg)
        for key, value in kwargs:
            print(key, value)

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

@parameter_predicate
def repeat(times: 'times > 0 and times < 10', name: 'len(name) > 5') -> str:
    return name * times

repeat(1"abcde")
# 1
# abcde

现在唯一欠缺的就是如何执行注释中的 Python 表达式,我们使用eval内置函数

globals = {}
locals = {"name""Hello, World"}
eval("print(name)", globals, locals)

eval函数可以执行任意的Python 片段,其中出现的变量会通过globalslocals 变量表查询,如果没有查询到则会报错。

现在我们有了实现谓词解释器的所有工具。我们可以组合这些代码如下所示。

由于 Python 中参数可以通过位置或者关键字的形式传入,所以argskwargs都需要处理,我们将实参值做为locals变量表传给eval,并且将失败的失败的谓词逻辑放入failures数组。

from inspect import signature


def parameter_predicate(f):
    sig = signature(f)

    def wrapper(*args, **kwargs):
        params = [param for param in sig.parameters]
        failures = []
        for i in range(0, len(args)):
            param = sig.parameters[params[i]]
            value = args[i]
            locals = {param.name: value}
            globals = {}
            if not eval(param.annotation, globals, locals):
                failures.append({
                    "name": param.name,
                    "condition": param.annotation.format(param.name),
                    "context": {param.name: value}
                })
        for i in range(len(args), len(args) + len(kwargs)):
            param = sig.parameters[params[i]]
            value = kwargs[param.name]
            locals = {param.name: value}
            globals = {}
            if not eval(param.annotation, globals, locals):
                failures.append({
                    "name": param.name,
                    "condition": param.annotation.format(param.name),
                    "context": {param.name: value}
                })
        if len(failures) > 0:
            raise RuntimeError(f"{failures}")
        return f(*args, **kwargs)

    return wrapper

@parameter_predicate
def repeat(times: 'times > 0 and times < 10', name: 'len(name) > 5') -> str:
    return name * times

repeat(1"abcde")
# RuntimeError: [{'name': 'name', 'condition': 'len(name) > 5', 'context': {'name': 'abcde'}}]
print(repeat(0"abcdef"))
# RuntimeError: [{'name': 'times', 'condition': 'times > 0 and times < 10', 'context': {'times': 0}}]

通过定义并处理函数注释,我们实现了一个简单的谓词解释器,帮助我们附加一些谓词条件到形参上。当然通过自定义一些语法,我们可以是谓词更加复杂具有更多功能。

超人不会飞

2021/06/05  阅读:24  主题:默认主题

作者介绍

超人不会飞