什么是高阶函数?
-- 把函数名当做参数传给另外一个函数,在另外一个函数中通过参数调用执行
- #!/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 的值
如何解决这个问题?
- #!/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()
案例:
实现一个能将函数调用信息记录到日志的装饰器
需求:
如何解决这个问题?
为了装饰器的灵活性,定义一个装饰类,把这个类的实例方法当做装饰器,在类中装饰器方法持有实例对象,便于修改属性和扩展功能
- #!/usr/bin/python3
- __author__ = 'beimenchuixue'
- __blog__ = 'http://www.cnblogs.com/2bjiujiu/'
- import logging
- from time import time, strftime, localtime, sleep
- from random import choice
- from functools import wraps
- class ToLog():
- def __init__(self, name):
- log = logging.getLogger(name)
- log.setLevel(logging.INFO)
- # 日志保存文件名字
- file_name = logging.FileHandler(name + '.log')
- # 添加日志文件
- log.addHandler(file_name)
- # 日志格式
- log.info('start'.center(50, '-'))
- self.log = log
- self.temp = '%(func)s -> [%(start_time)s - %(used_time)s - %(naclls)s]'
- def go_log(self, func):
- @wraps(func)
- def wrapper(*args, **kwargs):
- # 函数每调用一次加1
- wrapper.naclls += 1
- start_time = time()
- res = func(*args, **kwargs)
- used_time = time() - start_time
- info = {}
- info['func'] = func.__name__
- info['start_time'] = start_time
- info['used_time'] = used_time
- info['naclls'] = wrapper.naclls
- msg = self.temp % info
- # 把日志按格式写入文件
- self.log.info(msg)
- return res
- # 初始化调用次数参数
- wrapper.naclls = 0
- return wrapper
- # 重新定义日志记录模版
- def set_log_temp(self, temp):
- self.temp = temp
- # 关闭日志功能
- def log_off(self):
- self.log.setLevel(logging.WARN)
- # 打开日志功能
- def log_on(self):
- self.log.setLevel(logging.INFO)
- # 实例化出两个装饰器对象
- log_one = ToLog('one')
- log_two = ToLog('two')
- # 修改实例2的日志模版,去掉执行时间
- log_two.set_log_temp('%(func)s -> [%(start_time)s - %(naclls)s]')
- # 关闭log_two中记录日志功能
- log_two.log_off()
- @log_one.go_log
- def func_one():
- print('one')
- @log_one.go_log
- def func_two():
- print('two')
- @log_two.go_log
- def func_three():
- print('three')
- if __name__ == '__main__':
- for _ in range(50):
- choice([func_one, func_two, func_three])()
- sleep(choice([0.5, 1, 1.5]))
如何逻辑整理?
-- 3 层 :一层获得 value,二层偷梁换柱,三层逻辑处理
-- 2 层:一层偷梁换柱,二层逻辑处理
来源: http://www.cnblogs.com/2bjiujiu/p/7355266.html