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

智能推荐

阿里面试:MySQL索引凭什么能让查询效率提高这么多?_什么公司要求mysql效率-程序员宅基地

文章浏览阅读1.7w次,点赞185次,收藏501次。面试官一定要让我从计算机底层开始说...._什么公司要求mysql效率

模拟实现memmove-程序员宅基地

文章浏览阅读268次,点赞12次,收藏3次。memmove函数的作用与memcpy的作用相似都是将内存复制到另一个区域但是他们的区别在于memmove函数可以进行同内存复制,但是memcpy函数不可以(在vs环境下也可以)

java web 题_javaweb试题带答案-程序员宅基地

文章浏览阅读1.4k次。《javaweb试题带答案》由会员分享,可在线阅读,更多相关《javaweb试题带答案(8页珍藏版)》请在金锄头文库上搜索。1、Javaweb试题一、选择题1二、问答题5一、 选择题没有注明则为单项。1.下面哪个不是Form的元素? ( D )A. Input B: textarea C: select D: table2. HTML页面中,下面哪个表示空格( B )A.& B. C. D..._javaweb程序阅读题

模块““react-router-dom“”没有导出的成员“useHistory”_模块“"react-router-dom"”没有导出的成员“usehistory”-程序员宅基地

文章浏览阅读1.9k次。useNavigate 是一个 React Router v6 中的新 Hook,用于在 React 组件中进行路由的导航。这个函数可以接受一个字符串参数,表示要导航到的路径,还可以接受一个可选的对象参数,表示导航的其他选项,例如替换当前路由、在历史记录中前进或后退等。注:useNavigate 只能在 React Router v6 中使用,如果你的项目中使用的是 React Router v5 或更早的版本,你需要继续使用 useHistory。_模块“"react-router-dom"”没有导出的成员“usehistory”

酒店宾馆在线订房小程序源码系统+商城点餐+会员卡等功能 附带完整的搭建教程_酒店订房源码-程序员宅基地

文章浏览阅读422次,点赞11次,收藏6次。无论是出差还是旅游,通过在线预订平台,用户可以方便快捷地预订到满意的酒店。因此,开发一款集合酒店预订、商城点餐和会员卡功能于一体的在线订房小程序源码系统,可以更好地服务用户,提高用户体验。在线订房小程序还集成了商城点餐功能,用户可以在小程序中直接点餐。同时,会员卡还可以根据用户的消费习惯和偏好推荐相应的产品和服务,提高用户的满意度和忠诚度。通过在线订房小程序,用户可以方便快捷地搜索到心仪的酒店,并在线预订房间。同时,用户还可以根据价格、地理位置、评分等多维度筛选酒店,提高预订效率。1.酒店宾馆在线订房。_酒店订房源码

java 修改源码_再谈给应用程序diy启动画面和java源代码补丁修改-程序员宅基地

文章浏览阅读96次。再谈给应用程序diy启动画面和java源代码补丁修改2006-8-21 16:186365再谈给应用程序diy启动画面和java源代码补丁修改2006-8-21 16:186365搞diy的朋友经常把自己设计的logo或者是打点广告窗口加进别人的应用程序作为启动画面.偶也搞了小例子给大家,并且编译了一个小java补丁小工具一下就搞定(只限本程序,程序异常概无负责任)现在偶把看雪论坛的一个logo加..._java如何更改补丁内容

随便推点

el-table-column循环生成时,在里面使用v-if问题_el-table-column v-if-程序员宅基地

文章浏览阅读2.2k次。el-table-column循环生成时,在里面使用v-if问题_el-table-column v-if

基于java的SCADA系统_基于普通PC的SCADA系统-程序员宅基地

文章浏览阅读1.1k次。1、普通pc运行scada系统和西门子plc通讯,对于wincc来说直接用内置的通讯驱动即可,对于第三方的scada软件(例如ifix)则可以通过西门子的simaticnet软件配置opcserver来实现通讯(当然ifix也可以走其它通讯驱动软件,例如s7A等等)2、博途的wincc和经典wincc目前是两种软件,功能方面有一定差异,但对于简易功能的系统,两者皆可。本回答有3位钻石用户推荐回答..._java scada

一次bash: docker: command not found bash: yum: command not found的解决_-bash: docker: command not found-程序员宅基地

文章浏览阅读9.2k次,点赞11次,收藏8次。解决bash: docker: command not found_-bash: docker: command not found

Duilib Label显示的文本实现自动换行_duilib富文本换行-程序员宅基地

文章浏览阅读4.2k次。Duilib-自动换行的处理Duilib中自动没有自动换行的属性,如果需要实现自动换行,就需要在代码中计算总字体的长度,根据需要添加‘\n’换行符进行换行。例子--Label显示的文本,自动换行的处理自定义个性化的弹窗提示框,提示文本在Label中显示。布局根据实际需要进行布局,这里说明一下计算文本长度,以及换行中需要注意的地方。具体实现计算文本长度,并换行显..._duilib富文本换行

python中 append() 和 extend() 的用法_python append用法-程序员宅基地

文章浏览阅读3w次,点赞45次,收藏132次。1. 概述python中内置了 append() 以及 extend() 方法,这两个方法功能类似,但是再处理多个利表示,完全不同;用法类似,均是list.appenf(obj) , list.extend(seq).list.appenf(obj)和list.extend(seq)的不同点:项list.appenf(obj)list.extend(seq)参数不相同..._python append用法

python如何下载库_python中如何下载库-程序员宅基地

文章浏览阅读9.2k次。python下载安装库的方法:1、在线安装在cmd窗口直接运行:pip install 包名,如 pip install requests注意:这种方式安装时会自动下载第三方库,安装完成后并不会删除,如需删除请到它的默认下载路径下手动删除;win7的默认路径://AppData可能是隐藏文件夹c:\用户\(你的用户名)\AppData\Local\pip\cache\linux的默认路径:~/.c..._python库下载