什么是高阶函数?
-- 把函数名当做参数传给另外一个函数,在另外一个函数中通过参数调用执行
#!/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()
案例:
实现一个能将函数调用信息记录到日志的装饰器
需求:
- 把每次函数的调用时间,执行时间,调用次数写入日志
- 可以对被装饰函数分组,调用信息记录到不同日志
- 动态修改参数,比如日志格式
- 动态打开关闭日志输出功能
如何解决这个问题?
为了装饰器的灵活性,定义一个装饰类,把这个类的实例方法当做装饰器,在类中装饰器方法持有实例对象,便于修改属性和扩展功能
#!/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层:一层偷梁换柱,二层逻辑处理
北门吹雪: https://www.cnblogs.com/2bjiujiu/
python_装饰器_语法糖的更多相关文章
-
day13 装饰器与语法糖
day13 装饰器与语法糖 一.装饰器 1.什么是装饰器 装饰器就是装饰别人的工具,具体是指为被装饰者添加新功能 装饰器->函数 被装饰者->函数 2.为何要用装饰器 装饰器的核心思想:( ...
-
PYTHON-有参装饰器,无参装饰器,语法糖
装饰器 装饰器就是闭包函数的一种应用场景 一 为何要用装饰器 #开放封闭原则:对修改封闭,对扩展开放 二 什么是装饰器 装饰器他人的器具,本身可以是任意可调用对象,被装饰者也可以是任意可调用对象. 强 ...
-
Python学习——装饰器/decorator/语法糖
装饰器 定义:本质是函数,为其他函数添加附加的功能. 原则:1.不能修改原函数的源代码 2.不能修改被原函数的调用方式 重点理解: 1.函数即“变量” 2.高阶函数:返回值中包含函数名 3.嵌套函数 ...
-
python中装饰器(语法糖)概念
“”“” 什么是装饰器? """ 还是通过一个例子来慢慢说明 先看下面的例子 def func_1(x): return x*2 def fun_2(x): return ...
-
python3.5-day5_迭代器_生成器_装饰器_模块
笔者QQ 360212316 迭代器&生成器 生成器: 一个函数调用返回一个迭代器,那这个函数叫做生成器,如果函数中包含yield语法,那么这个函数就会变成生成器 生成器的特点: 1.生成器必 ...
-
python_装饰器
越来越觉得写一点技术博客是有多么重要了,明日复明日,现在就开始写吧! 1. 普通装饰器 装饰器的写法是一种语法糖,装饰器也还是一个函数而已,它接收一个函数对象作为参数,并返回一个新函数,主要是拓展原函 ...
-
Python_装饰器_29
# 装饰器形成的过程 : 最简单的装饰器 有返回值的 有一个参数 万能参数 # 装饰器的作用 # 原则 :开放封闭原则 # 语法糖 :@ # 装饰器的固定模式 import time # print( ...
-
Python_装饰器函数
楔子 作为一个会写函数的python开发,我们从今天开始要去公司上班了.写了一个函数,就交给其他开发用了. def func1(): print('in func1') 季度末,公司的领导要给大家发绩 ...
-
python_装饰器模式
装饰器模式定义:动态地给一个对象添加一些额外的职责. 在Python中Decorator mode可以按照像其它编程语言如C++, Java等的样子来实现,但是Python在应用装饰概念方面的能力上远 ...
随机推荐
-
MsSQLserver中修改字段值系统自动生成的脚本
主要参考一下脚本的实现思路: ①创建临时表 ②创建临时表 ③向临时表插入数据 ④删除原表 ⑤临时表改名 /* 为了防止任何可能出现的数据丢失问题,您应该先仔细检查此脚本,然后再在数据库设计器的上下文之 ...
-
mysql锁SELECT FOR UPDATE【转】
MySQL 使用SELECT ... FOR UPDATE 做事务写入前的确认 以MySQL 的InnoDB 为例,预设的Tansaction isolation level 为REPEATABLE ...
-
身份证校验(c++实现)
描述: 我国国标[GB 11643-1999]中规定:公民身份号码是18位特征组合码,由十七位数字本体码和一位数字校验码组成.排列顺序从左至右依次为:六位数字地址码,八位数字出生日期码,三位数字顺序码 ...
-
jQuery 数据 DOM 元素 核心 属性
jQuery 参考手册 - 数据 .clearQueue() 从序列中删除仍未运行的所有项目 .clearQueue(queueName) $("div").clearQueue( ...
-
NYOJ-571 整数划分(三)
此题是个非常经典的题目,这个题目包含了整数划分(一)和整数划分(二)的所有情形,而且还增加了其它的情形,主要是用递归或者说是递推式来解,只要找到了递推式剩下的任务就是找边界条件了,我觉得边界也是非常重 ...
-
如何解决ORA-12547: TNS:lost contact错
执行环境:ubuntu+oracle 11.2.0 为了启动oracle时间,出现ORA-12547: TNS:lost contact错误. 中午好好的纳,下午就无论了.以为是链接失效,关机重新启动 ...
-
微信小程序支付
@Controllerpublic class UserPayToMerchantController { public static final String appid="******* ...
-
MT【193】三面角的正余弦定理
(原题为浙江名校新高考研究联盟2018届第三次联考选择压轴题) 在平面$\alpha$内,已知$AB\perp BC$,过直线$AB,BC$分别作平面$\beta,\gamma$,使得锐二面角$\al ...
-
用PopupWindow实现弹出菜单(弹出的菜单采用自定义布局)
用PopupWindow实现弹出菜单是一个比较好的方式.当然我们还有一个类PopupMenu也能实现弹出菜单,但那个太过于局限了,所以不是很推荐. 这个实例的效果是这样的:点击按钮后,一个菜 ...
-
Codeforces Round #106 (Div. 2) D. Coloring Brackets 区间dp
题目链接: http://codeforces.com/problemset/problem/149/D D. Coloring Brackets time limit per test2 secon ...