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

智能推荐

Sentinel详解_sentinel文档-程序员宅基地

文章浏览阅读966次。Sentinel是阿里开源的项目,提供了流量控制、熔断降级、系统负载保护等多个维度来保障服务之间的稳定性。中文官网地址:https://sentinelguard.io/zh-cn/docs/introduction.html当调用链路中某个资源出现不稳定,则对这个资源的调用进行限制,并让请求快速失败,避免影响到其它的资源。sentinel处理这个问题采取了两中方式:2、下载Sentinelhttps://github.com/alibaba/Sentinel/releases下载完成后找到下载的目录_sentinel文档

360 支持linux版本下载地址,360安全浏览器国产稳定版本发布,提供deb软件包下载,附介绍...-程序员宅基地

文章浏览阅读2.2k次。360安全浏览器已经正式上线国产版,提供有deb软件包下载,为稳定版本(browser360-cn-stable_10.0.2001.0-1_amd64.deb),当前版本号为10.0.2001.0,内核是Chromium 63。有用户反馈,使用-2的有依赖问题,-1没有依赖问题,能正常使用,在下载链接中提供该deb包下载,可参考安装360浏览器稳定版依赖关系不满足的解决思路一文。360安全浏览器..._360deb

cobbler源码安装-程序员宅基地

文章浏览阅读608次。cobbler源码安装1. cobbler简介Cobbler是一个Linux服务器安装的服务,可以通过网络启动(PXE)的方式来快速安装、重装物理服务器和虚拟机,同时还可以管理DHCP,DNS等。Cobbler可以使用命令行方式管理,也提供了基于Web的界面管理工具(cobbler-web),还提供了API接口,可以方便二次开发使用。Cobbler是较早前的kickstart的升级版,优点是比较容易配置,还自带web界面比较易于管理。Cobbler内置了一个轻量级配置管理系统,但它也支持和其它配置_cobbler源码安装

踩雷:Win10安装anaconda3-4.4.0+tensorflow-gpu1.12.0+keras2.2.4_anaconda3 4.4.0-程序员宅基地

文章浏览阅读477次。**Win10安装anaconda3-4.4.0+tensorflow-gpu1.12.0+keras2.2.4**本人的电脑配置:NVIDIA GTX1050ti这次安装也是破费周转,特此做下记录。不会再详细地写出具体的操作,大多数都用链接代替了。1.安装anacondaWindows安装anaconda2.安装cuda下载cudnntensorflow各个版本的CUDA以及Cudnn版本对应关系首先要清楚自己需要装什么版本的tensorflow。根据自己项目的实际需求确定tensorf_anaconda3 4.4.0

阿里云ACP认证考试过关心得经验及考试费用详解 ...-程序员宅基地

文章浏览阅读5.5k次,点赞2次,收藏6次。阿里云ACP专业认证考试值得考吗?当然值得!云吞铺子来说说ACP认证考试过关心得、经验分享、考试费用、报名考场等相关问题:ACP认证种类范围ACP认证可以分为五个领域,即云计算、大数据、云安全、人工智能和中间件。目前阿里云ACP认证考试分为:云计算工程师、大数据分析师和大数据工程师、云安全工程师和云安全行业工程师、人工智能工程师和企业级互联网架构工程..._阿里云acp考试没过要重新交费吗

centos7安装codeblocks教程-程序员宅基地

文章浏览阅读4次。author:lidabo装了好多次系统,每次装的时候都有要在网上各种查,太麻烦了。所以决定记录一下,以后用到的时候会方便一些。当然,本文来源于网络,取百家之长,最重要的是本人已验证过,说明对本系统是可行的。在CentOS7上安装Codelocks的过程。1.安装gcc,需要c和c++两部分,默认安装下,CentOS不安装编译器的,在终端输入以下命令即可yum install gccyum...

随便推点

Unknown URL-程序员宅基地

文章浏览阅读1.8k次。菜鸟错误大全(六)我们都是从新手一步一个坑踩过来的,下面我们来讲讲会遇到的常见错误和解决办法: Unknown URL content://com.exaple.databasetest.provider/book:这是写错包名了,应该为“content://com.example.databasetest.provider/book”_unknown url

【微信小程序】支付及退款流程详解_微信小程序退款前端开发-程序员宅基地

文章浏览阅读2.5k次,点赞2次,收藏15次。一. 支付支付主要分为几个步骤:前端携带支付需要的数据(商品id,购买数量等)发起支付请求后端在接收到支付请求后,处理支付数据,然后携带处理后的数据请求 微信服务器 的 支付统一下单接口后端接收到上一步请求微信服务器的返回数据,再次处理,然后返回前端让前端可以开始支付。前端进行支付动作前端支付完成后,微信服务器会向后端发送支付通知(也就是微信要告诉你客户已经付过钱了),后端根据这个通..._微信小程序退款前端开发

Sublime Text 3正式版发布_sublime3 toolchain-程序员宅基地

文章浏览阅读2.2w次。Sublime Text 3正式版发布作者:chszs,转载需注明。博客主页:http://blog.csdn.net/chszs一、下载地址:1. Windows 32位版http://c758482.r82.cf2.rackcdn.com/Sublime%20Text%20Build%203010%20Setup.exe2. Windows 64位版http://c758482.r82.cf2_sublime3 toolchain

flutter 参数函数_Flutter完整开发实战详解(一、基础)-程序员宅基地

文章浏览阅读776次。前言在如今的 Flutter 大潮下,本系列是让你看完会安心的文章。本系列将完整讲述:如何入门 Flutter 开发,如何快速从 0 开发一个完整的 Flutter APP,配套高完成度 Flutter 开源项目 GSYGithubAppFlutter,提供 Flutter 的开发技巧和问题处理,之后深入源码和实战为你全面解析 Flutter。 笔者相继开发过 Flutter、React Nat..._flutter 函数详解

Java实现第八届蓝桥杯杨辉三角-程序员宅基地

文章浏览阅读3.5k次。杨辉三角杨辉三角也叫帕斯卡三角,在很多数量关系中可以看到,十分重要。第0行: 1第1行: ..._杨辉三角java第八届蓝桥杯

硕士阶段人工智能有哪些比较好的发论文的方向?_硕士强化学习做什么方向好毕业-程序员宅基地

文章浏览阅读3.7k次。人们对于一些新时代诞生得词语总是会提出疑问,比如说什么是人工智能?在帮助盟军通过破解纳粹加密机Enigma赢得第二次世界大战后不到十年,数学家艾伦·图灵(Alan Turing)第二次改变了历史,提出了一个简单的问题:“机器能思考吗?”图灵1950年的论文“计算机与智能”及其随后的图灵测试确立了人工智能的基本目标和愿景。从本质上讲,人工智能是计算机科学的一个分支,旨在肯定地回答图灵的问题。它是在机器中复制或模拟人类智能的努力。人工智能的广泛目标引发了许多问题和辩论如此之多,以至于没有一个单一的领域定义被_硕士强化学习做什么方向好毕业