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

智能推荐

class和struct的区别-程序员宅基地

文章浏览阅读101次。4.class可以有⽆参的构造函数,struct不可以,必须是有参的构造函数,⽽且在有参的构造函数必须初始。2.Struct适⽤于作为经常使⽤的⼀些数据组合成的新类型,表示诸如点、矩形等主要⽤来存储数据的轻量。1.Class⽐较适合⼤的和复杂的数据,表现抽象和多级别的对象层次时。2.class允许继承、被继承,struct不允许,只能继承接⼝。3.Struct有性能优势,Class有⾯向对象的扩展优势。3.class可以初始化变量,struct不可以。1.class是引⽤类型,struct是值类型。

android使用json后闪退,应用闪退问题:从json信息的解析开始就会闪退-程序员宅基地

文章浏览阅读586次。想实现的功能是点击顶部按钮之后按关键字进行搜索,已经可以从服务器收到反馈的json信息,但从json信息的解析开始就会闪退,加载listview也不知道行不行public abstract class loadlistview{public ListView plv;public String js;public int listlength;public int listvisit;public..._rton转json为什么会闪退

如何使用wordnet词典,得到英文句子的同义句_get_synonyms wordnet-程序员宅基地

文章浏览阅读219次。如何使用wordnet词典,得到英文句子的同义句_get_synonyms wordnet

系统项目报表导出功能开发_积木报表 多线程-程序员宅基地

文章浏览阅读521次。系统项目报表导出 导出任务队列表 + 定时扫描 + 多线程_积木报表 多线程

ajax 如何从服务器上获取数据?_ajax 获取http数据-程序员宅基地

文章浏览阅读1.1k次,点赞9次,收藏9次。使用AJAX技术的好处之一是它能够提供更好的用户体验,因为它允许在不重新加载整个页面的情况下更新网页的某一部分。另外,AJAX还使得开发人员能够创建更复杂、更动态的Web应用程序,因为它们可以在后台与服务器进行通信,而不需要打断用户的浏览体验。在Web开发中,AJAX(Asynchronous JavaScript and XML)是一种常用的技术,用于在不重新加载整个页面的情况下,从服务器获取数据并更新网页的某一部分。使用AJAX,你可以创建异步请求,从而提供更快的响应和更好的用户体验。_ajax 获取http数据

Linux图形终端与字符终端-程序员宅基地

文章浏览阅读2.8k次。登录退出、修改密码、关机重启_字符终端

随便推点

Python与Arduino绘制超声波雷达扫描_超声波扫描建模 python库-程序员宅基地

文章浏览阅读3.8k次,点赞3次,收藏51次。前段时间看到一位发烧友制作的超声波雷达扫描神器,用到了Arduino和Processing,可惜啊,我不会Processing更看不懂人家的程序,咋办呢?嘿嘿,所以我就换了个思路解决,因为我会一点Python啊,那就动手吧!在做这个案例之前先要搞明白一个问题:怎么将Arduino通过超声波检测到的距离反馈到Python端?这个嘛,我首先想到了串行通信接口。没错!就是串口。只要Arduino将数据发送给COM口,然后Python能从COM口读取到这个数据就可以啦!我先写了一个测试程序试了一下,OK!搞定_超声波扫描建模 python库

凯撒加密方法介绍及实例说明-程序员宅基地

文章浏览阅读4.2k次。端—端加密指信息由发送端自动加密,并且由TCP/IP进行数据包封装,然后作为不可阅读和不可识别的数据穿过互联网,当这些信息到达目的地,将被自动重组、解密,而成为可读的数据。不可逆加密算法的特征是加密过程中不需要使用密钥,输入明文后由系统直接经过加密算法处理成密文,这种加密后的数据是无法被解密的,只有重新输入明文,并再次经过同样不可逆的加密算法处理,得到相同的加密密文并被系统重新识别后,才能真正解密。2.使用时,加密者查找明文字母表中需要加密的消息中的每一个字母所在位置,并且写下密文字母表中对应的字母。_凯撒加密

工控协议--cip--协议解析基本记录_cip协议embedded_service_error-程序员宅基地

文章浏览阅读5.7k次。CIP报文解析常用到的几个字段:普通类型服务类型:[0x00], CIP对象:[0x02 Message Router], ioi segments:[XX]PCCC(带cmd和func)服务类型:[0x00], CIP对象:[0x02 Message Router], cmd:[0x101], fnc:[0x101]..._cip协议embedded_service_error

如何在vs2019及以后版本(如vs2022)上添加 添加ActiveX控件中的MFC类_vs添加mfc库-程序员宅基地

文章浏览阅读2.4k次,点赞9次,收藏13次。有时候我们在MFC项目开发过程中,需要用到一些微软已经提供的功能,如VC++使用EXCEL功能,这时候我们就能直接通过VS2019到如EXCEL.EXE方式,生成对应的OLE头文件,然后直接使用功能,那么,我们上篇文章中介绍了vs2017及以前的版本如何来添加。但由于微软某些方面考虑,这种方式已被放弃。从上图中可以看出,这一功能,在从vs2017版本15.9开始,后续版本已经删除了此功能。那么我们如果仍需要此功能,我们如何在新版本中添加呢。_vs添加mfc库

frame_size (1536) was not respected for a non-last frame_frame_size (1024) was not respected for a non-last-程序员宅基地

文章浏览阅读785次。用ac3编码,执行编码函数时报错入如下:[ac3 @ 0x7fed7800f200] frame_size (1536) was not respected for anon-last frame (avcodec_encode_audio2)用ac3编码时每次送入编码器的音频采样数应该是1536个采样,不然就会报上述错误。这个数字并非刻意固定,而是跟ac3内部的编码算法原理相关。全网找不到,国内音视频之路还有很长的路,音视频人一起加油吧~......_frame_size (1024) was not respected for a non-last frame

Android移动应用开发入门_在安卓移动应用开发中要在活动类文件中声迷你一个复选框变量-程序员宅基地

文章浏览阅读230次,点赞2次,收藏2次。创建Android应用程序一个项目里面可以有很多模块,而每一个模块就对应了一个应用程序。项目结构介绍_在安卓移动应用开发中要在活动类文件中声迷你一个复选框变量

推荐文章

热门文章

相关标签