python装饰器作用和功能_Python装饰器是个什么鬼?-程序员宅基地

技术标签: python装饰器作用和功能  

这一篇我们主要介绍一下Python中装饰器的常见用法。

所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。

函数也是对象,可以赋值给变量,可以做为参数,也可以嵌套在另一个函数内。

对于第三种情况,如果在一个函数的内部定义了另一个函数,外部的我们叫他外函数,内部的我们叫他内函数。在一个外函数中定义了一个内函数,内函数里运用了外函数的临时变量,并且外函数的返回值是内函数的引用。这样就构成了一个闭包。

一般情况下,在我们认知当中,如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。

装饰器从0到1Decorators is to modify the behavior of the function through a wrapper so we don’t have to actually modify the function.

所谓的装饰器,其实就是通过装饰器函数,来修改原函数的一些功能,使得原函数不需要修改。实际工作中,装饰器通常运用在身份认证(登录认证)、日志记录、性能测试、输入合理性检查及缓存等多个领域中。合理使用装饰器,可极大提高程序的可读性及运行效率。

在面向对象(OOP)的设计模式中,decorator被称为装饰模式。OOP的装饰模式需要通过继承和组合来实现,而Python除了能支持OOP的decorator外,直接从语法层次支持decorator。Python的decorator可以用函数实现,也可以用类实现。def my_decorator(func): def inner_wrapper(): print('inner_wrapper of decorator') func() return inner_wrapper

@my_decorator def hello(): print('hello world')

hello()

"""inner_wrapper of decoratorhello world"""my_decorator.__name__ # 'my_decorator'hello.__name__ # 'inner_wrapper'

这里的@,我们称之为语法糖。@my_decorator 相当于 greet=my_decorator(greet)。

对于需要传参数的函数,可以在在对应的装饰器函数inner_wrapper()上,加上相应的参数:def my_decorator(func): def inner_wrapper(arg1): print('inner_wrapper of decorator') func(arg1) return inner_wrapper

@my_decoratordef hello(arg1): print('hello world') print(arg1)

hello("I'm arg1")

"""inner_wrapper of decoratorhello worldI'm arg1"""my_decorator.__name__ # 'my_decorator'hello.__name__ # 'inner_wrapper'

但是,假设我们有一个新函数需要两个参数,前面定义的@my_decorator就会不适用。如:@my_decoratordef hello(arg1,arg2): print('hello world') print(arg1) print(arg2)

我们可以把*args和**kwargs,作为装饰器内部函数inner_wrapper()的参数 ,表示接受任意数量和类型的参数,因此装饰器就可以写成下面的形式:def my_decorator(func): def inner_wrapper(*args, **kwargs): print('inner_wrapper of decorator') func(*args, **kwargs) return inner_wrapper

还可以给decorator函数加参数:def loginfo(info, n): def my_decorator(func): def inner_wrapper(*args, **kwargs): for i in range(n): print(f'<{i}> loginfo: {info}') func(*args, **kwargs) return inner_wrapper return my_decorator

@loginfo("NOBUG", 3)def hello(arg1): print('hello world') print(arg1)

hello("I'm arg1")

"""<0> loginfo: NOBUGhello worldI'm arg1<1> loginfo: NOBUGhello worldI'm arg1<2> loginfo: NOBUGhello worldI'm arg1"""my_decorator.__name__ # 'my_decorator'hello.__name__ # 'inner_wrapper'

但是经过装饰器装饰之后,hello()函数的元信息被改变,它不再是以前的那个 hello()函数,而是被inner_wrapper()取代了:hello.__name__ # 'inner_wrapper'

help(hello)"""Help on function inner_wrapper in module __main__:

inner_wrapper(*args, **kwargs)"""

这个问题很好解决:

内置的装饰器@functools.wrap,它会帮助保留原函数的元信息(也就是将原函数的元信息,拷贝到对应的装饰器函数里)。import functools

def my_decorator(func): @functools.wraps(func) def inner_wrapper(*args, **kwargs): print('inner_wrapper of my_decorator.') func(*args, **kwargs) return inner_wrapper

@my_decoratordef hello(): print("hello world")

hello.__name__# 'hello'

上面的例子可以写成:import functools

def loginfo(info,n): def my_decorator(func): @functools.wraps(func) def inner_wrapper(*args, **kwargs): for i in range(n): print(f'<{i}> loginfo: {info}') func(*args, **kwargs) return inner_wrapper return my_decorator

@loginfo("NOBUG",3)def hello(arg1): print('hello world') print(arg1)

hello("I'm arg1")

"""<0> loginfo: NOBUGhello worldI'm arg1<1> loginfo: NOBUGhello worldI'm arg1<2> loginfo: NOBUGhello worldI'm arg1"""my_decorator.__name__ # 'my_decorator'hello.__name__ # 'hello'

用类作为装饰器

绝大多数装饰器都是基于函数和闭包实现的,但这并非构造装饰器的唯一方式。事实上,Python 对某个对象是否能通过装饰器( @decorator)形式使用只有一个要求:decorator 必须是一个“可被调用(callable)的对象。

函数自然是“可被调用”的对象。但除了函数外,我们也可以让任何一个类(class)变得“可被调用”(callable),只要自定义类的 __call__ 方法即可。

因此不仅仅是函数,类也可以做为装饰器来用。但作为装饰器的类需要包含__call__()方法。import functools

class Count: def __init__(self, func): self.func = func self.num_calls = 0 functools.update_wrapper(self, func) # 类似于函数方法中的:@functools.wraps(func)

def __call__(self, *args, **kwargs): self.num_calls += 1 print('num of calls is: {}'.format(self.num_calls)) return self.func(*args, **kwargs)

@Countdef hello(): print("hello world")

hello()

# # 输出# num of calls is: 1# hello world

hello()

# # 输出# num of calls is: 2# hello world

hello()

# # 输出# num of calls is: 3# hello world

hello.__name__# 'hello'

通过名为__call__的特殊方法,可以使得类的实例能像python普通函数一样被调用:class Count: def __init__(self, num_calls=5): self.num_calls = num_calls

def __call__(self): print('num of calls is: {}'.format(self.num_calls))

a = Count(666)a()

"""num of calls is: 666"""

装饰器的嵌套使用import functools

def my_decorator1(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('execute decorator1') func(*args, **kwargs) return wrapper

def my_decorator2(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('execute decorator2') func(*args, **kwargs) return wrapper

def my_decorator3(func): @functools.wraps(func) def wrapper(*args, **kwargs): print('execute decorator3') func(*args, **kwargs) return wrapper

@my_decorator1@my_decorator2@my_decorator3def hello(message): print(message)# 类似于调用:decorator1(decorator2(decorator3(func)))

hello('hello world')hello.__name__

# 输出# execute decorator1# execute decorator2# execute decorator3# hello world# 'hello'

装饰器的一些常见用途

1. 记录函数运行时间(日志)import timeimport functools

def log_execution_time(func): @functools.wraps(func) def wrapper(*args, **kwargs): start = time.perf_counter() res = func(*args, **kwargs) end = time.perf_counter() print(f'{func.__name__} took {(end - start) * 1000} ms') return res return wrapper

@log_execution_timedef calculator(): for i in range(1000000): i = i**2**(1/3)**(1/6) return i

calculator()"""calculator took 109.1254340026353 ms48525172657.38456"""import functools

def log(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f'call {func.__name__}():') return func(*args, **kwargs) return wrapper

@logdef now(): print('2019-3-25')

def logger(text): def decorator(func): @functools.wraps(func) def wrapper(*args, **kwargs): print(f'{text} {func.__name__}():') return func(*args, **kwargs) return wrapper return decorator

@logger('DEBUG')def today(): print('2019-3-25')

now()# call now():# 2019-3-25today()# DEBUG today():# 2019-3-25today.__name__# today

2. 登录验证

有些网页的权限是需要登录后才有的。可以写一个装饰器函数验证用户是否登录,而不需要重复写登录验证的逻辑。

3. 输入合理性检查

对于一些需要做合理性检验的地方,可以抽象出合理性检验的逻辑,封装为装饰器函数,实现复用。例如:def validate_summary(func): @functools.wraps(func) def wrapper(*args, **kwargs): data = func(*args, **kwargs) if len(data["summary"]) > 80: raise ValueError("Summary too long") return data return wrapper

@validate_summary def fetch_customer_data(): # ...

@validate_summary def query_orders(criteria): # ...

@validate_summary def create_invoice(params): # ...

4. 缓存

LRU cache,在 Python 中的表示形式是@lru_cache,它会缓存进程中的函数参数和结果,当缓存满了以后,会删除 least recenly used 的数据。from functools import lru_cache

@lru_cache(maxsize=16) # default :128def sum2(a, b): print(f"Invoke func: sum2()") print(f"Calculating {a} + {b}") return a + b

print(sum2(1, 2))print("====================")print(sum2(1, 2))print("====================")print(sum2.cache_info())print(sum2.cache_clear())print(sum2.cache_info())

"""Invoke func: sum2()Calculating 1 + 23====================3CacheInfo(hits=1, misses=1, maxsize=16, currsize=1)NoneCacheInfo(hits=0, misses=0, maxsize=16, currsize=0)"""

5. 类中常用的@staticmethod和@classmethod

•@classmethod 装饰的类方法•@staticmethod装饰的静态方法•不带装饰器的实例方法

用@classmethod修饰的方法,第一个参数不是表示实例本身的self,而是表示当前对象的类本身的clf。@staticmethod是把函数嵌入到类中的一种方式,函数就属于类,同时表明函数不需要访问这个类。通过子类的继承覆盖,能更好的组织代码。class A(object): def foo(self, x): print("executing foo(%s,%s)" % (self, x)) print('self:', self) @classmethod def class_foo(cls, x): print("executing class_foo(%s,%s)" % (cls, x)) print('cls:', cls) @staticmethod def static_foo(x): print("executing static_foo(%s)" % x)

if __name__ == '__main__': a = A() # foo方法绑定对象A的实例,class_foo方法绑定对象A,static_foo没有参数绑定。 print(a.foo) # > print(a.class_foo) # > print(a.static_foo) #

普通的类方法foo()需要通过self参数隐式的传递当前类对象的实例。 @classmethod修饰的方法class_foo()需要通过cls参数传递当前类对象。@staticmethod修饰的方法定义与普通函数是一样的。

self和cls的区别不是强制的,只是PEP8中一种编程风格。self通常用作实例方法的第一参数,cls通常用作类方法的第一参数。即通常用self来传递当前类对象的实例,cls传递当前类对象。# foo可通过实例a调用,类对像A直接调用会参数错误。a.foo(1)"""executing foo(<__main__.A object at 0x0278B170>,1)self: <__main__.A object at 0x0278B170>"""A.foo(1)"""Traceback (most recent call last): File "", line 1, in TypeError: foo() missing 1 required positional argument: 'x'"""

# 但foo如下方式可以使用正常,显式的传递实例参数a。A.foo(a, 1)"""executing foo(<__main__.A object at 0x0278B170>,1)self: <__main__.A object at 0x0278B170>"""

# class_foo通过类对象或对象实例调用。A.class_foo(1)"""executing class_foo(,1)cls: """a.class_foo(1)"""executing class_foo(,1)cls: """a.class_foo(1) == A.class_foo(1)"""executing class_foo(,1)cls: executing class_foo(,1)cls: True"""

# static_foo通过类对象或对象实例调用。A.static_foo(1)"""executing static_foo(1)"""a.static_foo(1)"""executing static_foo(1)"""a.static_foo(1) == A.static_foo(1)"""executing static_foo(1)executing static_foo(1)True"""

继承与覆盖普通类函数是一样的。class B(A): passb = B()b.foo(1)b.class_foo(1)b.static_foo(1)"""executing foo(<__main__.B object at 0x007027D0>,1)self: <__main__.B object at 0x007027D0>executing class_foo(,1)cls: executing static_foo(1)"""

REFERENCE

[1] 5 reasons you need to learn to write Python decorators: https://www.oreilly.com/ideas/5-reasons-you-need-to-learn-to-write-python-decorators

[2] Meaning of @classmethod and @staticmethod for beginner?: https://stackoverflow.com/questions/12179271/meaning-of-classmethod-and-staticmethod-for-beginner

[3] staticmethod-and-classmethod: https://stackoverflow.com/questions/136097/what-is-the-difference-between-staticmethod-and-classmethod?rq=1

[4] Python 中的 classmethod 和 staticmethod 有什么具体用途?: https://www.zhihu.com/question/20021164/answer/537385841

[5] 正确理解Python中的 @staticmethod@classmethod方法: https://zhuanlan.zhihu.com/p/28010894

[6] Python 工匠:使用装饰器的技巧: https://github.com/piglei/one-python-craftsman/blob/master/zh_CN/8-tips-on-decorators.md

[7] Finally understanding decorators in Python: https://pouannes.github.io/blog/decorators/

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_39539733/article/details/110028530

智能推荐

JWT(Json Web Token)实现无状态登录_无状态token登录-程序员宅基地

文章浏览阅读685次。1.1.什么是有状态?有状态服务,即服务端需要记录每次会话的客户端信息,从而识别客户端身份,根据用户身份进行请求的处理,典型的设计如tomcat中的session。例如登录:用户登录后,我们把登录者的信息保存在服务端session中,并且给用户一个cookie值,记录对应的session。然后下次请求,用户携带cookie值来,我们就能识别到对应session,从而找到用户的信息。缺点是什么?服务端保存大量数据,增加服务端压力 服务端保存用户状态,无法进行水平扩展 客户端请求依赖服务.._无状态token登录

SDUT OJ逆置正整数-程序员宅基地

文章浏览阅读293次。SDUT OnlineJudge#include<iostream>using namespace std;int main(){int a,b,c,d;cin>>a;b=a%10;c=a/10%10;d=a/100%10;int key[3];key[0]=b;key[1]=c;key[2]=d;for(int i = 0;i<3;i++){ if(key[i]!=0) { cout<<key[i.

年终奖盲区_年终奖盲区表-程序员宅基地

文章浏览阅读2.2k次。年终奖采用的平均每月的收入来评定缴税级数的,速算扣除数也按照月份计算出来,但是最终减去的也是一个月的速算扣除数。为什么这么做呢,这样的收的税更多啊,年终也是一个月的收入,凭什么减去12*速算扣除数了?这个霸道(不要脸)的说法,我们只能合理避免的这些跨级的区域了,那具体是那些区域呢?可以参考下面的表格:年终奖一列标红的一对便是盲区的上下线,发放年终奖的数额一定一定要避免这个区域,不然公司多花了钱..._年终奖盲区表

matlab 提取struct结构体中某个字段所有变量的值_matlab读取struct类型数据中的值-程序员宅基地

文章浏览阅读7.5k次,点赞5次,收藏19次。matlab结构体struct字段变量值提取_matlab读取struct类型数据中的值

Android fragment的用法_android reader fragment-程序员宅基地

文章浏览阅读4.8k次。1,什么情况下使用fragment通常用来作为一个activity的用户界面的一部分例如, 一个新闻应用可以在屏幕左侧使用一个fragment来展示一个文章的列表,然后在屏幕右侧使用另一个fragment来展示一篇文章 – 2个fragment并排显示在相同的一个activity中,并且每一个fragment拥有它自己的一套生命周期回调方法,并且处理它们自己的用户输_android reader fragment

FFT of waveIn audio signals-程序员宅基地

文章浏览阅读2.8k次。FFT of waveIn audio signalsBy Aqiruse An article on using the Fast Fourier Transform on audio signals. IntroductionThe Fast Fourier Transform (FFT) allows users to view the spectrum content of _fft of wavein audio signals

随便推点

Awesome Mac:收集的非常全面好用的Mac应用程序、软件以及工具_awesomemac-程序员宅基地

文章浏览阅读5.9k次。https://jaywcjlove.github.io/awesome-mac/ 这个仓库主要是收集非常好用的Mac应用程序、软件以及工具,主要面向开发者和设计师。有这个想法是因为我最近发了一篇较为火爆的涨粉儿微信公众号文章《工具武装的前端开发工程师》,于是建了这么一个仓库,持续更新作为补充,搜集更多好用的软件工具。请Star、Pull Request或者使劲搓它 issu_awesomemac

java前端技术---jquery基础详解_简介java中jquery技术-程序员宅基地

文章浏览阅读616次。一.jquery简介 jQuery是一个快速的,简洁的javaScript库,使用户能更方便地处理HTML documents、events、实现动画效果,并且方便地为网站提供AJAX交互 jQuery 的功能概括1、html 的元素选取2、html的元素操作3、html dom遍历和修改4、js特效和动画效果5、css操作6、html事件操作7、ajax_简介java中jquery技术

Ant Design Table换滚动条的样式_ant design ::-webkit-scrollbar-corner-程序员宅基地

文章浏览阅读1.6w次,点赞5次,收藏19次。我修改的是表格的固定列滚动而产生的滚动条引用Table的组件的css文件中加入下面的样式:.ant-table-body{ &amp;amp;::-webkit-scrollbar { height: 5px; } &amp;amp;::-webkit-scrollbar-thumb { border-radius: 5px; -webkit-box..._ant design ::-webkit-scrollbar-corner

javaWeb毕设分享 健身俱乐部会员管理系统【源码+论文】-程序员宅基地

文章浏览阅读269次。基于JSP的健身俱乐部会员管理系统项目分享:见文末!

论文开题报告怎么写?_开题报告研究难点-程序员宅基地

文章浏览阅读1.8k次,点赞2次,收藏15次。同学们,是不是又到了一年一度写开题报告的时候呀?是不是还在为不知道论文的开题报告怎么写而苦恼?Take it easy!我带着倾尽我所有开题报告写作经验总结出来的最强保姆级开题报告解说来啦,一定让你脱胎换骨,顺利拿下开题报告这个高塔,你确定还不赶快点赞收藏学起来吗?_开题报告研究难点

原生JS 与 VUE获取父级、子级、兄弟节点的方法 及一些DOM对象的获取_获取子节点的路径 vue-程序员宅基地

文章浏览阅读6k次,点赞4次,收藏17次。原生先获取对象var a = document.getElementById("dom");vue先添加ref <div class="" ref="divBox">获取对象let a = this.$refs.divBox获取父、子、兄弟节点方法var b = a.childNodes; 获取a的全部子节点 var c = a.parentNode; 获取a的父节点var d = a.nextSbiling; 获取a的下一个兄弟节点 var e = a.previ_获取子节点的路径 vue