什么是高阶函数?
-- 把函数名当做参数传给另外一个函数,在另外一个函数中通过参数调用执行
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' def func_x(x): return x * 2 def func_y(y): return y * 3 def func_z(x, y): # 等价于 return func_x(5) + func_y(3) return x(5) + y(3) if __name__ == '__main__': # 把函数当做参数,本质上是把函数的内存地址当做参数传递过去, result = func_z(func_x, func_y) print(result)
什么是装饰器?
-- 在不改变源代码的基础上扩展新需求,装饰器本身也是函数,应用高阶函数实现
-- 把被装饰的函数内存地址当参数传入装饰器函数体,通过参数调用被装饰的函数
装饰器原则:
-- 不改变源代码 - 因为函数可能在其他地方各种调用,一改动全身
-- 不改变原函数调用顺序 - 源代码有自己的逻辑处理
-- 装饰器又叫做语法糖
装饰器逻辑上格式?
- 高阶函数+嵌套函数
需求:
给某个函数增加一个计算运行时间功能
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' import time def total_time(func): # func = hell_word def wrapper(): # 等价于hell_word() start_time =time.time() func() end_time = time.time() print(end_time - start_time) # 打印统计时间 return wrapper # 通过装饰器给hell_word函数装上了统计时间的功能,功能逻辑在装饰器中实现 @total_time def hell_word(): time.sleep(0.5) print('hello word') if __name__ == '__main__': hell_word()
相当于下面的函数逻辑
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' import time
# 装饰器函数 def total_time(func): def wrapper(): start_time =time.time() func() end_time = time.time() print(end_time - start_time) return wrapper
def hell_word(): time.sleep(0.5) print('hello word') if __name__ == '__main__': # 把函数当做参数传入装饰器函数,然后装饰器函数返回包裹函数wrapper地址,执行装饰器函数本质上执行包裹函数wrapper中逻辑 total_time(hell_word)()
假如传入的函数中有参数如何?
-- 需要在wrapper和func中加入收集参数(*args)或收集字典参数(**kwargs),
-- warps和func可自定义名字,默认如此命名
如果原函数有个返回值,该如何?
-- 如果到func结束 直接在func()前面加return
-- 到func未结束,可以func()结果赋值给一个变量,res = func(*args,**kwargs)到新增逻辑结束后加上 return res
需求:
计算出斐波那契数列中第n个数的值?
求一个共有10个台阶的楼梯,从下走到上面,一次只能迈出1~3个台阶,并且不能后退,有多少中方法?
要求:
通过装饰器实现剪枝函数
如何逻辑整理这个需求?
斐波那契数列(黄金分割数列),从数列的第3项开始,每一项都等于前两项之和
每次迈出都是 1~3 个台阶,剩下就是 7~9 个台阶
如果迈出1个台阶,需要求出后面9个台阶的走法
如果迈出2个台阶,需要求出后面8个台阶的走法
如果迈出3个台阶,需要求出后面7个台阶的走法
此3种方式走法,通过递归方式实现,递归像树,每次递归都生成子节点函数
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' def jian_zhi(func): # 中间字典,判断已经是否求解过 median = {} def wrapper(*args): # 假如不在中间字典中,说明没有求解过,添加到字典中去,在的话,直接返回, 将不在递归下去,保证每次递归的唯一性 if args not in median: median[args] = func(*args) return median[args] return wrapper @jian_zhi def fibonacci(n): if n <= 1: return 1 return fibonacci(n - 1) + fibonacci(n - 2) @jian_zhi def climb(n, steps): count = 0 # 当最后台阶为0的时候,说明最后只是走了一次 if n == 0: count = 1 # 当最后台阶不为0的时候,说明还需要走至少一次 elif n > 0: # 对三种情况进行分别处理momo for step in steps: count += climb(n - step, steps) # 返回每次递归的计数 return count if __name__ == '__main__': print(climb(10, (1, 2, 3))) print(fibonacci(20))
需求:
实现在装饰器函数中,保留 被装饰函数 的元数据
那,什么是函数的元数据?
在函数对象中保存着一些函数的元数据,如:
f.__name__ 函数名
f.__doc__ 函数文档
f.__moudle__ 函数所属模块名
f.__dict__ 属性字典
f.__defaults__ 默认参数组
……
在使用装饰器后,在装饰器里访问以上属性时,我们看到的是装饰器函数的元数据
那,如何解决这个需求?
通过 functools中的wraps或update_wrapper方法实现,其中每个方法都可单独实现
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' import time from functools import (wraps, update_wrapper, WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES) def count_time(func): """ 给目标函数加上计算运行时间统计 """ # 这个装上器和update_wrapper一样,默认参数WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES @wraps(func) def wrapper(*args, **kwargs): start_time = time.time() # 定义result接收函数返回值,并且在装饰函数最后返回回去 resutl = func(*args, **kwargs) print('运行时间:', time.time() - start_time) return resutl # 其中默认参数 WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES # update_wrapper(wrapper, func) return wrapper @count_time def add(num=100): """ 计算 0~num 累加值,默认num=100 """ time.sleep(1) return sum([x for x in range(num + 1)]) if __name__ == '__main__': print('函数名:', add.__name__) print('属性字典:', add.__dict__) print('函数默认参数:', add.__defaults__) print('函数所在模块:', add.__module__) print('函数文档:', add.__doc__) # 打印两个默认参数 # WRAPPER_ASSIGNMENTS :__module__', '__name__', '__qualname__', '__doc__', '__annotations__ # WRAPPER_UPDATES:__dict__ print(WRAPPER_ASSIGNMENTS, WRAPPER_UPDATES) result = add() print(result)
需求:
实现一个装饰器,用它来检查被装饰函数的参数类型,装饰器可以通过函数,指明函数参数类型,进行函数调用的时候,传入参数,检测到不匹配时,抛出异常
那,如何解决这个需求?
-- 先要获取函数的签名,并且获得装饰器中参数,然后把函数签名和装饰器中参数对应绑定
-- 把调用函数时候传入的参数和函数签名进行绑定
-- 把实参和装饰器中定义的数据进行类型比较,不匹配抛出异常
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' from inspect import signature def check_type(*ty_args, **ty_kwargs): def out_wrapper(func): # 通过signature方法,获取函数形参:name, age, height sig = signature(func) # 获得装饰器传来的参数, 函数签名与之绑定,字典类型 bind_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments print(bind_types) def wrapper(*args, **kwargs): # 给执行函数中具体的实参进行和形参进行绑定,形成字典的形式 func_type = sig.bind(*args, **kwargs).arguments.items() print(func_type) # 循环形参和实参字典的items()形式 for name, obj in func_type: if name in bind_types: # 判断实参是否是指定类型数据 if not isinstance(obj, bind_types[name]): raise TypeError('%s must be %s' % (name, bind_types[name])) # 假如函数有返回值,通过此方法返回函数的返回值 res = func(*args, **kwargs) return res return wrapper return out_wrapper # 通过装饰器实现对函数参数进行类型检查 @check_type(str, int, float) def func(name, age, height): print(name, age, height) if __name__ == '__main__': # 正常数据 func('bei_men', 18, 1.75) # 错误数据 func('bei_men', '18', 1.75)
案例:
为分析程序内哪些函数执行时间开销较大,我们需定义一个带timeout参数的装饰器
需求:
统计被装饰函数的运行时间
时间大于timeout时,将此次函数调用记录到log日志中
运行时可以修改timeout的值
如何解决这个问题?
- 定义一个装饰器,计算函数执行时间,并与timeout比较,当大于timeout时候,通过logging模块打印出日志信息
- 在包裹函数中添加一个函数,通过这个函数来修改timeout变量
- 在python3中用nonlocal来声明嵌套作用域中的变量引用,在python2中可以通过把timeout参数变成列表,通过列表索引来进行改值
#!/usr/bin/python3 __author__ = 'beimenchuixue' __blog__ = 'http://www.cnblogs.com/2bjiujiu/' import time import logging from random import randint def run_time(timeout): """ 定义检查函数运行时间,并打印对应函数运行时间超出设定时间日志,并支持更改timeout """ # python2 # timeout = [timeout] # 真正包裹函数 def out_wrapper(func): def wrapper(*args, **kwargs): start_time = time.time() result = func(*args, **kwargs) used_time = time.time() - start_time # 对于超出timeout的函数进行日志打印 if used_time > timeout: msg = '%s: %s > %s' % (func.__name__, used_time, timeout) logging.warn(msg) # python2 # if used_time > timeout[0]: # msg = '%s: %s > %s' % (func.__name__, used_time, timeout[0]) # logging.warn(msg) # return result return result # 设置timeout参数值 def set_timeout(value): # 声明嵌套域变量,可以更改,python2通过把列表形式进行更改 nonlocal timeout timeout = value # 定义接口 wrapper.set_timeout = set_timeout # python2 # def set_timeout(value): # timeout[0] = value # wrapper.set_timeout = set_timeout return wrapper return out_wrapper @run_time(1.5) def func(): # 随机有50%的几率程序沉睡1秒 while randint(0, 1): time.sleep(1) print('func_run') if __name__ == "__main__": for _ in range(10): func() print('_' * 50) # 更改run_time装饰器中timeout参数 func.set_timeout(2) for _ in range(10): func()
如何逻辑整理?
-- 3层 :一层获得value,二层偷梁换柱,三层逻辑处理
-- 2层:一层偷梁换柱,二层逻辑处理