闭包的概念(官方)
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
官方的解释有点迷糊
通俗说法是
定义两层函数 外函数是内函数的返回值 内函数引用外函数的变量
也就是说如果内函数不引用外函数 那就是个普通函数
引用场景
我这几天也一直在想应用场景(除装饰器外的)
看到最多的例子类似于这样
def func(a, b): def result(x): return a * x - b return result result = func(2, 3) print(result(5))
初始化的时候是外部函数 传参a,b
执行的时候运行的是内部函数
我目前感觉最大的用处就是装饰器吧
装饰器的基本用法
假如我需要捕获异常
我们从很简单的装饰一步一步实现出来最复杂的三层带参数的装饰器
先写一个最简单的装饰器捕获异常的
def log_decorator(fun): def wrapper(): try: print("进入wap") fun() except Exception as e: print("异常:{}".format(e)) return wrapper @log_decorator def error_test(): print(5/0) # log_decorator(error_test) error_test()
可以看到我们只是把被装饰的函数 放到了一个新的函数内做了处理@ 是语法糖
其实就是执行了 类似于我注释这行
也可以清晰的看见装饰器所捕获的不能/0异常
进入wap
异常:error_test() missing 1 required positional argument: 'print_str'
可以看到说明我们先进入了装饰器的内函数在打印了 进入wap后 才执行的代码
那如何给他传参是个问题
假如需求是如何传参 num 用num除以0
@log_decorator def error_test(num): print(num) print(num/0)
这时候就报异常处理有问题
异常:error_test() missing 1 required positional argument: 'num'
说缺少一个参数num
那我们可以换一步思考 解包 就是我不管你传什么参数进去 我都能让被装饰的函数完美包容
def log_decorator(fun): def wrapper(*args, **kwargs): try: print("进入wap") fun(*args, **kwargs) except Exception as e: print("异常:{}".format(e)) return wrapper @log_decorator def error_test(num): print(num) print(num/0) # log_decorator(error_test) error_test(5)
此时在做修改 就会发现 依旧是我们5/0的异常说明传参的问题解决了
那么好了 在实战当中其实我们的需求比这个更明显
1在tornado项目中 定义捕获异常装饰器
2 装饰器 要可以自定义返回内容
3 出现错误时记录错误
4 要记录 这是哪个函数
# 日志装饰器 def except_decorator(api_name,result_error='服务器异常'): def wrapper(func): @functools.wraps(func) def inner_wrapper(self, *args, **kwargs): try: return func(self, *args, **kwargs) except Exception as e: Log.error("{}接口出现异常:{}".format(api_name, e)) return self.write({"code": -1, "msg": result_error}) return inner_wrapper return wrapper
之前说过装饰器是闭包
闭包如果不引用外函数变量就是普通函数
由此可以推论出 那第一层的函数 我接受两个参数 就是我自定义的 错误结果 和 接口名称
然后里面这两层函数才是我的装饰器
而self 你必须要传 因为需要调到self的本身
这个最外层的函数其实可以不要api_name的 其实可以 func__name___来记录的
至于@functools.wraps 是将原有的被装饰的名字返回给你
可以单独搜搜这个这个方法的用处 如果不懂
在tornado上可能没啥问题 不用这包也没事 但是在flask上会出现循环导入的错误
因为flask 本身的路由也是装饰器 并且以 是 api_name: function 的方式存储
如果说你没有返回给该函数的原有名字 flask将找不到所以会报错
至于为什么要传self 是因为tornado的返回方法是 self.write()