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对象,不 ...
随机推荐
- JS - neo4j-browser 初始化时运行命令的逻辑分析
背景 最近需要改点 neo4j-browser 的代码做个 demo,分析初始化时运行命令的代码时花了很多时间,记录一下. 目的 找出 dispatch SINGLE_COMMAND_QUEUED a ...
- 描述一下 Intent 和 IntentFilter?
Android 中通过 Intent 对象来表示一条消息,一个 Intent 对象不仅包含有这个消息的目的地,还可以包含消息的内容,这好比一封 Email,其中不仅应该包含收件地址,还可以包含具体的内 ...
- BurpSuite(二) proxy 模块
Proxy代理模块作为BurpSuite的核心功能,拦截HTTP/S的代理服务器,作为一个在浏览器和目标应用程序之间的中间人,允许你拦截,查看,修改在两个方向上的原始数据流. Burp 代理允许你 ...
- Prometheus存储模型分析
Prometheus是时下最为流行的开源监控解决方案,我们可以很轻松地以Prometheus为核心快速构建一套包含监控指标的抓取,存储,查询以及告警的完整监控系统.单个的Prometheus实例就能实 ...
- 24个MySQL面试题
一.为什么用自增列作为主键? 1.如果我们定义了主键(PRIMARY KEY),那么InnoDB会选择主键作为聚集索引. 如果没有显式定义主键,则InnoDB会选择第一个不包含有NULL值的唯一索引作 ...
- 【VS开发】【图像处理】V4L2 pixel format
目录(?)[-] v4l2_pix_format定义 2 具体Pixel Format定义 1. v4l2_pix_format定义 [cpp] view plain copy /* * V I D ...
- python logger理解
import logging#进行基本的日志配置 logging.basicConfig(filename = 'access.log',format = '%(asctime)s - %(name) ...
- 【Linux 网络编程】网络IP地址结构体
(1)IPv4套接口地址结构通常也称为"网际套接字地址结构",它以"sockaddr_in"命名, 定义在<netinet/in.h> ...
- 【Linux 驱动】简单字符设备驱动架构(LED驱动)
本文基于icool210开发板,内核版本:linux2.6.35: 驱动代码: (1)头文件:led.h #ifndef __LED_H__ #define __LED_H__ #define LED ...
- [转贴]linux lsof命令详解
linux lsof命令详解 https://www.cnblogs.com/sparkbj/p/7161669.html 简介 lsof(list open files)是一个列出当前系统打开文件的 ...