Python 装饰器
1、闭包
自由变量:未在本地作用域中定义的变量。例如定义在内层函数外的外层函数的作用域中的变量。
闭包:就是一个概念,出现在嵌套函数中,指的是内层函数引用到了外层函数的自由变量,就形成了闭包。很多语言都有这个概念,最熟悉就是 JavaScript。
实例:
# python 2 实现闭包 def counter(): c = [0] def inc(): c[0] += 1 # 是赋值即定义嘛?不是!是修改值 return c[0] return inc # 返回标识符,即函数对象 m = counter() m() # 调用函数 inc(),但是 c 消亡了嘛?没有,内层函数没有消亡,c 不消亡(闭包) m() m() print(m())
# 不推荐使用 global! def counter(): global c c = 0 def inc(): global c c += 1 # 不是闭包! return c return inc m = counter() m() m() m() print(m())
# 推荐使用 nonlocal,python3 实现闭包 def counter(): c = 0 def inc(): nonlocal c # 非当前函数的本地变量,当前函数之外的任意层函数的变量,绝非 global c += 1 # 是闭包吗?是! return c return inc m = counter() m() m() m() print(m())
nonlocal 语句: 将变量标记为不在本地作用域定义,而是在上级的某一级局部作用域中定义,但不能是全局作用域中。
2、柯里化
指的是将原来接受两个参数的函数变成新的接受一个参数的函数的过程,新的函数返回一个以原有第二个参数为参数的函数。
实例: z = f(x, y) 转换成 z = f(x)(y) 的形式.
def add(x, y): return x + y print(add(4, 5)) # 柯里化 def add1(x): # 步骤1:先定义一个函数(一参) def func(y): # 步骤3:定义返回函数(一参) return x + y return func # 步骤2:返回函数 fn = add1(4) # 返回函数 print(fn(5)) # 9 print(add1(4)(5)) # 9
建议: 逆推!原来函数调用为 add(4, 5)
,柯里化目标是 add(4)(5)
,两个括号两次函数调用,第一次函数调用返回仍然是函数。
add 函数再拓展:
# 已知函数 add(x, y, z) 求柯里化后,使其可以如下 add(4)(5, 6) 的形式: def add(x): def func(y, z): return x + y + z return func print(add(4)(5, 6)) # 15 # 已知函数 add(x, y, z) 求柯里化后,使其可以如下 add(4)(5)(6) 的形式: def add(x): def func(y): def func1(z): return x + y + z return func1 return func print(add(4)(5)(6)) # 15 # 已知函数 add(x, y, z) 求柯里化后,使其可以如下 add(4, 5)(6) 的形式: def add(x, y): def func(z): return x + y + z return func print(add(4, 5)(6)) # 15
3、日志记录实现
3.1 日志功能分析
现有一个加法函数:
def add(x, y): return x + y
想增强它的功能,能够输出加法函数的日志信息:
def add(x, y): print("call add,x + y") #日志输出到控制台 return x + y
上面的加法函数是完成了需求,但是有以下的缺点:
打印日志信息是一个功能,这条语句和 add 函数耦合太高;
加法函数属于业务功能,而输出日志信息的功能,属于非业务功能代码,不该放在业务函数 add 中。
3.2 业务功能分离
下面代码做到了业务功能分离,但是 fn 函数调用传参是个问题:
def add(x, y): return x + y def logger(fn): print('before') print('add function:{} {}'.format(4, 5)) ret = fn(4, 5) print('after') return ret print(logger(add))
为了解决传参的问题,进一步改变:
def add(x, y): return x + y def logger(fn, *args, **kwargs): print('before') print('add function:{} | {}'.format(args, kwargs)) ret = fn(*args, **kwargs) # 参数解构 print('after') return ret print(logger(add, 4, 5)) print(logger(add, x=4, y=6)) print(logger(add, 4, y=15))
柯里化:
def add(x, y): return x + y def logger(fn): def wrapper(*args, **kwargs): print('before') print('add function:{} | {}'.format(args, kwargs)) ret = fn(*args, **kwargs) # 参数解构 print('after') return ret return wrapper print(logger(add)(4, 5)) # 换一种写法: ''' add = logger(add) print(add(x=5, y=10)) ''' # 1.先算等式右边,logger(add) # 2.add函数对象被fn记住 # 3.logger函数返回 wrapper,即add = wrapper # 4.调用add(4, 5)即wrapper(4, 5) # 5.ret = fn(*args, **kwargs),此处的fn记住的是最原始的add函数对象
3.3 装饰器语法糖
def logger(fn): def wrapper(*args, **kwargs): print('before') print('add function:{} | {}'.format(args, kwargs)) ret = fn(*args, **kwargs) # 参数解构 print('after') return ret return wrapper @logger # 等价于add = logger(add) def add(x, y): return x + y print(add(20, 30)) ''' 可以采用逆推思维: 1.add(20, 30) 2.包装add(20, 30)为:wrapper(20, 30) 3.实现日志增强功能:logger(add)(20, 30) 即:add(20, 30) => wrapper(20, 30) => logger(add)(20, 30) def logger(fn): def wrapper(*args, **kwargs): val = fn(*args, **kwargs) return val return wrapper @logger def add(x, y): return x + y '''
@logger是什么?这就是装饰器语法
装饰器
它是一个函数,函数作为它的形参,无参装饰器实际上就是一个单形参函数
返回值也是一个函数
可以使用 @functionname 方式,简化调用
3.4 装饰器和高阶函数
装饰器可以是高阶函数,但装饰器是对传入函数的功能的装饰(功能增强)
''' 设定logger()增强日志功能: 1、增强谁,就将谁作为参数传入:即logger(add) add = logger(add) add(4, 5) = logger(add)(4, 5) —>柯里化wrapper(4, 5) 2、def logger(fn): def wrapper(*args, **kwargs): print("before") ret = fn(*args, **kwargs) print("alter") return wrapper @logger # add(4, 5) = logger(add)(4, 5) add(4, 5) ''' import datetime import time def logger(fn): def wrapper(*args, **kwargs): print('begin to work') start = datetime.datetime.now() ret = fn(*args, **kwargs) # 参数解构 stop = (datetime.datetime.now() - start).total_seconds() print('{} took {:.2f}s.'.format(fn.__name__, stop)) return ret return wrapper @logger # 等价于add = logger(add) def add(x, y): time.sleep(2) return x + y print(add(20, 30))
总结: 怎么理解装饰器?非侵入式装饰
4、文档字符串
python 文档字符串 Documentation Strings,在函数语句块的第一行,且习惯是多行的文本,多使用三引号。
惯例是首字母大写,第一行写概述,空一行,第三行写详细描述。
可以使用特殊属性 __doc__
访问这个文档字符串。
实例:
def add(x,y): """This is a function of addition""" return x + y print("function name: {}\nfunction doc: {}".format(add.__name__, add.__doc__)) print(help(add)) # 输出结果: function name: add function doc: This is a function of addition Help on function add in module __main__: add(x, y) This is a function of addition
4.1 日志增强业务
import datetime import time def logger(fn): def wrapper(*args, **kwargs): """this is an function of wrapper""" print("begin to work...") start = datetime.datetime.now() ret = fn(*args, **kwargs) delta = (datetime.datetime.now() - start).total_seconds() print("function {},took {:.2f}s.".format(fn.__name__, delta)) return ret return wrapper @logger def add(x, y): """this is an function of add""" time.sleep(2) return x + y print(add.__name__, add.__doc__) # 执行结果: wrapper this is an function of wrapper
@logger 装饰器语法,即:add = logger(add) = wrapper,所以此处的 add.__name__, add.__doc__
为 wrapper 增强函数的函数名及文档字符串。
4.2 赋值更改文档字符串
import datetime import time def logger(fn): def wrapper(*args, **kwargs): """this is an function of wrapper""" print("begin to work...") start = datetime.datetime.now() ret = fn(*args, **kwargs) delta = (datetime.datetime.now() - start).total_seconds() print("function {},took {:.2f}s.".format(fn.__name__, delta)) return ret wrapper.__name__ = fn.__name__ # 赋值更改 wrapper.__doc__ = fn.__doc__ return wrapper @logger def add(x, y): """this is an function of add""" time.sleep(2) return x + y print(add.__name__, add.__doc__) # 输出结果: add this is an function of add
4.3 函数调用实现
import datetime import time def copy_properties(src, dest): # 函数实现 dest.__name__ = src.__name__ dest.__doc__ = src.__doc__ def logger(fn): def wrapper(*args, **kwargs): """this is an function of wrapper""" print("begin to work...") start = datetime.datetime.now() ret = fn(*args, **kwargs) delta = (datetime.datetime.now() - start).total_seconds() print("function {},took {:.2f}s.".format(fn.__name__, delta)) return ret copy_properties(fn, wrapper) return wrapper @logger def add(x, y): """this is an function of add""" time.sleep(2) return x + y print(add.__name__, add.__doc__)
4.4 柯里化再改进
import datetime import time def copy_properties(src): # 柯里化 def _copy(dest): dest.__name__ = src.__name__ dest.__doc__ = src.__doc__ return _copy def logger(fn): def wrapper(*args, **kwargs): """this is an function of wrapper""" print("begin to work...") start = datetime.datetime.now() ret = fn(*args, **kwargs) delta = (datetime.datetime.now() - start).total_seconds() print("function {},took {:.2f}s.".format(fn.__name__, delta)) return ret copy_properties(fn)(wrapper) # 柯里化调用 return wrapper @logger def add(x, y): """this is an function of add""" time.sleep(2) return x + y print(add.__name__, add.__doc__)
4.5 装饰器语法再改进
import datetime import time def copy_properties(src): # fn def _copy(dest): # wrapper dest.__name__ = src.__name__ dest.__doc__ = src.__doc__ return dest # 有下面返回None分析得,此处缺少返回值 return _copy def logger(fn): # 带参装饰器 @copy_properties(fn) # wrapper = copy_properties(fn)(wrapper) ——> 返回 None def wrapper(*args, **kwargs): """this is an function of wrapper""" print("begin to work...") start = datetime.datetime.now() ret = fn(*args, **kwargs) delta = (datetime.datetime.now() - start).total_seconds() print("function {},took {:.2f}s.".format(fn.__name__, delta)) return ret #copy_properties(fn)(wrapper) return wrapper @logger def add(x, y): """this is an function of add""" time.sleep(2) return x + y print(add.__name__, add.__doc__)
通过 copy_properties 函数将被包装函数的属性覆盖掉包装函数,凡是被装饰的函数都需要复制这些属性,这个函数很通用,可以将复制属性的函数构建成装饰器函数,带参装饰器。
带参装饰器:
它是一个函数,函数作为它的形参,返回值是一个不带参的装饰器函数
使用 @functionname(参数列表) 方式调用
可以看做在装饰器外层又加了一层函数,这个函数可以多参数