Python核心技术与实战——十四|Python中装饰器的使用
我在以前的帖子里讲了装饰器的用法,这里我们来具体讲一讲Python中的装饰器,这里,我们从前面讲的函数,闭包为切入点,引出装饰器的概念、表达和基本使用方法。其次,我们结合一些实际工程中的例子,以便能再次理解。
一.函数与装饰器
函数的核心
第一点,在Python中,函数是“一等公民”(first-class citizen)(在有些资料例如流畅的Python中被叫做一等对象),函数也是对象,我们可以把函数赋予变量,比如下面的代码
def fun(message):
print('Get a message:{}'.format(message)) send_message = fun
send_message('hello world!') #输出
Get a message:hello world!
在这个例子中,我们把函数fun()赋给了变量send_message,这样之后调用send_message,就相当于调用了fun()
第二,我们还可以把函数当参数,传入另一个函数中,比如下面这段代码
def get_message(message):
return 'get a message:'+message
def root_call(func,message):
print(func(message)) root_call(get_message,'hello world!')
这个例子中,我们就把函数get_message()以参数的形式传递给root_call()中然后调用他。
第三点,我们可以在函数中定义函数,也就是函数的嵌套。看看下面的例子:
def fun(message):
def get_message(message):
print('get a message:{}'.format(message))
return get_message(message) fun('hello world')
在这段代码中,我们在函数fun()中定义了新的函数get_message(),调用后fun的值返回。
第四点,函数的返回值也可以是函数对象(闭包),比如下面的例子
def fun_closure():
def get_message(message):
print('got a message:{}'.format(message))
return get_message
send_message = fun_closure()
send_message('hello world')
在上面的例子里,函数func_closure()的返回值是get_message(),之后我们将其赋值给变量send_message,在调用该函数,最后输出字符串。
简单的装饰器
简单的复习了上面的知识点后,我们接下来看一看下面简单的装饰器
def my_decortor(fun):
def wrapper():
print('wrapper of decorator')
fun()
return wrapper def greet():
print('hello world') greet = my_decortor(greet)
greet()
在这段代码中,变量greet指向了内部函数wrapper(),而内部函数wrapper()有会调用原函数greet(),所以在调用greet()时,打印的字符串是会变化的。
这里的函数my_decortor()就是一个装饰器,他把需要执行的函数greet()包裹在其中,并且改变了他的行为,但是原函数不变。而上面的代码在Python中是可以有更加优雅的语法表示的
def my_decortor(fun):
def wrapper():
print('wrapper of decorator')
fun()
return wrapper
@my_decortor
def greet():
print('hello world') greet()
这里的@,我们称之为语法糖,@my_decortor和前面的greet = my_decortor(greet)是等价的,只不过显得更加简洁。因此,程序中需要有其他的函数需要做类似的装饰,只需在他们上方添加@my_decorator就可以了。
带有参数的装饰器
还是上面的函数,可是如果我们的原函数greet()有参数需要传递给装饰器又该怎么办呢?
一个简单的方法就是在对应的装饰器上加上相应的参数
def my_decortor(func):
def wrapper(message): #这里就把参数传递给了func
print('wrapper of decorator')
func(message) #func接收到参数
return wrapper
@my_decortor
def greet(message):
print(message) greet('hello world')
可是新的问题又出来了!如果这里有另外一个函数,也需要my_decortor()这个装饰器,但是这个新的函数带了两个参数,又该怎么办呢?
@my_decortor
def celebrate(name,message):
pass
事实上,通常情况我们会把*args和**kwargs作为装饰器内部wrapper()函数的参数,因为这两个参数表示接受任意数量和类型的参数,所以装饰器内就可以写成这样的形式
def my_decortor(func):
def wrapper(*args,**kwargs):
print('wrapper of decorator')
func(*args,**kwargs)
return wrapper
带有自定义参数的装饰器
其实,装饰器还有更大程度的灵活性,上面的例子说明装饰器可以接受原函数任意类型和数量的参数。此外,他还可以自己接受自己定义的参数
def repeat(num): #装饰器自定义参数
def my_decorator(func):
def wrapper(*args,**kwargs):
for i in range(num):
print('wrapper of decorator')
func(*args,**kwargs)
return wrapper
return my_decorator @repeat(4) #调用装饰器时传递参数
def greet(message):
print(message) greet('hello world')
原函数韩式原函数么?
现在我们看下这个现象,还是之前的例子,我们打印一下greet()函数的一些元信息
print(greet.__name__)
看下结果,会发现greet被装饰器装饰以后,他的元信息发生变化,元信息告诉我们被装饰后的greet()已经被新的wrapper()取代了。
为了解决这个问题(有些时候我们需要保留原函数的元信息),我们通常使用内置装饰器@functools.wrap,他会帮助保留原函数的元信息(其实是把原函数的元信息拷贝到对应的装饰器函数里)。
import functools
def my_decortor(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print('wrapper of decorator')
func(*args,**kwargs)
return wrapper def greet(message):
print(message) print(greet.__name__)
print(help(greet))
可以看到打印输出的函数名是greet()
类装饰器
前面讲的都是函数作为装饰器的用法,实际上类也可以作为装饰器。类装饰器主要依赖于函数__call__(),每当调用一个类的实例时,函数__call__()都会被执行一次
class Count():
def __init__(self,func):
self.func = func
self.num_calls = 0 def __call__(self,*args,**kwargs):
self.num_calls += 1
print('num of call is:{}'.format(self.num_calls))
return self.func(*args,**kwargs)
@Count
def example():
print('hello world') example()
example()
这里,我们定义了一个类Count,初始化时候传入了原函数func(),而__call__()表示让变量num_calls自增1,然后打印。因此我们第一次调用example()时,打印的值为1,第二次就成2了。(其实这种用法我还不太清楚)。
装饰器的嵌套
前面的例子中,基本上都是一个装饰器的情况,但实际上,Python也支持多个装饰器,比方写成下面的形式
@deccorator1
@deccorator2
@deccorator2
def fun():
pass
他的执行顺序是从里到外,所以上面的语句也等价于下面的代码
deccorator1(deccorator2(deccorator3(func)))
放个例子吧,虽然这种用法可能我暂时用不到
import functools def my_decorator1(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print('excute decorator1')
func(*args,**kwargs)
return wrapper def my_decorator2(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print('excute decorator2')
func(*args,**kwargs)
return wrapper def my_decorator3(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
print('excute decorator3')
func(*args,**kwargs)
return wrapper @my_decorator1
@my_decorator2
@my_decorator3
def greet(message):
print(message)
装饰器用法实例
前面基本讲了装饰器所有的用法和概念,接下了我们就结合几个例子来加深对他的理解
身份认证
首先,最常见的使用环境是身份认证的应用,这个还是比较容易理解,比方我们登录微信,需要输入账户密码,然后点击确认,这样,服务器变回查询用户名是否存在,密码是否匹配,如果认证通过就可以顺利登录,如果不通过就抛出异常并提示登录失败。
而一些其他的网站,比如博客园,不登陆时也可以浏览内容,但如果想要发布文章或留言,在请求相关服务时,服务器端会查询是否登录,如果没有登录就不允许这项操作。
我们看看一个大概的代码示例
import functools
def authenticate(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
request = args[0]
if check_user_logger_in(request):
return func(*args,**kwargs)
else:
raise Exception('Authentication failed')
return wrapper @authenticate
def post_comment(request,...):
pass
在上面的代码里,我们定义了装饰器authenticate,而函数post_commen()则表示发表用户对某篇文章的评论,每次调用这个函数前,都会先检查用户的登录状态,如果登录则允许操作,否则抛出登录异常的状态。
日志记录
日志记录也是个常见的案例,在实际工作中,如果怀疑某些函数耗时过长,导致整个系统的延迟(latency)增加,所以想在线测试某些函数的执行时间,那么装饰器就是很常用的手段
import time
import functools
def log_excution_time(func):
@functools.wraps(func)
def wrapper(*args,**kwargs):
start = time.perf_counter()
res = func(*args,**kwargs)
end = time.perf_counter()
print('{} took {} ms'.format(func.__name__,(end-start)*1000))
return res
return wrapper @log_excution_time
def calulate_similarity():
print('func start')
time.sleep(1)
print('func stop') calulate_similarity()
这里,装饰器log_execution_time记录了某个函数的运行时间,并返回其结果。如果想计算任何函数的执行时间,在这个函数上加上@log_excution_time即可。
输入合理性检查
第三个应用,输入性检查。
在大型公司的机器学习框架中,我们调用机器集群进行模型训练前,往往会用装饰器对齐输入(往往是很长的json文件)进行合理性检查。这样就可以大大避免输入不正确对机器的巨大开销。他往往写成下面的格式:
import functools def validation_check(input):
@functools.wraps(func)
def wrapper(*args,**kwargs):
##检查输入是否合法 @validation_check
def neural_network_training(parm1,parm2,...):
pass
其实在工作中,很多情况下都会出现输入不合理的现象,因为我们调用的训练模型往往很复杂,输入的文件有成千上万行,很多时候确实难发现错误的地方。
试想一下,如果没有输入的合理性检查,很容易出现模型训练了好几个小时后系统报错说有个输入的参数不对,成果付之一炬。通过这样的方法大大减缓了开发效率,也避免对机器造成了巨大的浪费。
缓存
最后,装饰器还在缓存方面的使用也是十分常见的,Python内置的LRU cache就是最好的例子(LRU cache的连接可以自己点开查查)
LRU cache,在Python中的表示形式是@lru_cache。它会缓存进程中的函数和参数和结果,但缓存满了以后,会删除least recenly used的数据。
正确谁用缓存装饰器,往往能极大提高程序的运行效率。比方下面的例子:
大型公司的服务器端的代码往往存在很多关于设备的检查,比如使用的设备是安卓还是IOS,版本号是多少。这其中的一个原因是一些新的feature往往只能在某些特定的手机系统或版本上才有。
这样一来,我们通常使用缓存装饰器来包裹这些检查函数,避免其被反复调用,进而提高程序运行效率,比如写成这样
@lru_cache
def check(param1,param2...):
pass
总结
总之,装饰器的概念和用法,就是所谓装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改
而实际工作中,装饰器通常运用在身份认证,日志记录,输入合理性检查及缓存等多个领域中。合适的使用装饰器,往往能极大的提高程序的可读性及运行效率。
Python核心技术与实战——十四|Python中装饰器的使用的更多相关文章
- Python核心技术与实战——十六|Python协程
我们在上一章将生成器的时候最后写了,在Python2中生成器还扮演了一个重要的角色——实现Python的协程.那什么是协程呢? 协程 协程是实现并发编程的一种方式.提到并发,肯很多人都会想到多线程/多 ...
- Python核心技术与实战——十八|Python并发编程之Asyncio
我们在上一章学习了Python并发编程的一种实现方法——多线程.今天,我们趁热打铁,看看Python并发编程的另一种实现方式——Asyncio.和前面协程的那章不太一样,这节课我们更加注重原理的理解. ...
- Python核心技术与实战——十二|Python的比较与拷贝
我们在前面已经接触到了很多Python对象比较的例子,例如这样的 a = b = a == b 或者是将一个对象进行拷贝 l1 = [,,,,] l2 = l1 l3 = list(l1) 那么现在试 ...
- Python开发【第十四篇】装饰器
装饰器 什么是装饰器? 装饰器是一个函数,主要作用是用来给包装另一个函数或者类 包装的目的是不改变原函数名(或类名)的情况下改变或添加被包装对象的功能 函数装饰器 是指装饰器是一个函数,传入的是一 ...
- Python核心技术与实战——十九|一起看看Python全局解释器锁GIL
我们在前面的几节课里讲了Python的并发编程的特性,也了解了多线程编程.事实上,Python的多线程有一个非常重要的话题——GIL(Global Interpreter Lock).我们今天就来讲一 ...
- Python小白学习之路(二十四)—【装饰器】
装饰器 一.装饰器的本质 装饰器的本质就是函数,功能就是为其他函数添加附加功能. 利用装饰器给其他函数添加附加功能时的原则: 1.不能修改被修饰函数的源代码 2.不能修改被修饰函数的调用 ...
- Python核心技术与实战——十五|深入了解迭代器和生成器
我们在前面应该写过类似的代码 for i in [1,2,3,4,5]: print(i) for in 语句看起来很直观,很便于理解,比起C++或Java早起的 ; i<n;i++) prin ...
- Python核心技术与实战——十|面向对象的案例分析
今天通过面向对象来对照一个案例分析一下,主要模拟敏捷开发过程中的迭代开发流程,巩固面向对象的程序设计思想. 我们从一个最简单的搜索做起,一步步的对其进行优化,首先我们要知道一个搜索引擎的构造:搜索器. ...
- Python学习笔记(十四)
Python学习笔记(十四): Json and Pickle模块 shelve模块 1. Json and Pickle模块 之前我们学习过用eval内置方法可以将一个字符串转成python对象,不 ...
随机推荐
- Cross-Multimedia dataset
Wikipedia: http://www.svcl.ucsd.edu/projects/crossmodal/ PKU Xmedia: https://github.com/yeqinglee/mv ...
- flutter column row布局的列表自适应宽高
mainAxisSize: MainAxisSize.min
- Octavia Rocky UDP 负载均衡功能试验
目录 文章目录 目录 前言 UDP 简述 功能验证 网络拓扑 资源对象清单 验证 TS Amphorae UDP 包被非法篡改 存疑 前言 以往,Octavia 通过 HAProxy + Keepal ...
- java:LeakFilling (Mybatis)
1.实体类属性与数据库中字段名字不一样时,或者起别名时: TbOrderMapper.xml配置文件中,配置resultMap标签: 其它相同的标签也需要配,否则查询不出来对应数据. 2.一对一关联: ...
- Could not find aapt Please set the ANDROID_HOME environment variable with the Android SDK root directory path
写case写好好哒,突然debug的时候就冒出这个错误: selenium.common.exceptions.WebDriverException: Message: An unknown serv ...
- 如何理解springcloud微服务项目中,eureka,provider,consumer它们之间的关系?
eureka负责注册provider和consumer的服务信息 provider负责与数据库进行交互,实现数据持久化,并给consumer提供服务 consumer与前端交互,通过与Eureka同源 ...
- Windos下navcat连接虚拟机中的mysql
进入mysql命令行, 使用root用户,密码:youpassword(你的root密码)连接到mysql服务器: # mysql -u root -proot mysql>GRANT ALL ...
- 【神经网络与深度学习】caffe+VS2013+Windows无GPU快速配置教程
首先来一波地址: happynear大神的第三方caffe:http://blog.csdn.net/happynear/article/details/45372231 Neil Z大神的第三方ca ...
- 【DSP开发】【VS开发】MUX和DEMUX的含义
MUX和DEMUX Mux 是 Multiplex 的缩写,意为"多路传输",其实就是"混流"."封装"的意思,与"合成" ...
- python 并发编程 多线程 GIL与Lock
GIL与Lock Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要互斥锁lock? 锁的目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据 GIT保证了 ...