Python(三)对装饰器的理解
- 装饰器是 Python 的一个重要部分,也是比较难理解和使用好的部分。下面对装饰器做一下简单整理
1. 前言
- 装饰器实际上是应用了设计模式里,装饰器模式的思想:
- 在不概念原有结构的情况下,添加新的功能
- 类似于我们穿不同的衣服,可以先穿一件衬衫,再穿一件毛衣,再穿一件羽绒服
- 但是毛衣不会影响羽绒服,羽绒服也不会影响衬衫
- 随时更换,同一个人可以有不同的穿衣打扮
- 对比之下,每一个装饰器就代表上述的一件衣服,我们可以根据功能需求,给一个函数本身加上不同的外套,也可以调整外套之间的顺序
- 装饰器本质上就是一个个的 Python 函数对象,不改动代码的前提下增加功能,返回值也为是一个函数对象
- 主要用于有切面需求的场景:记录日志、打点监控、权限校验、事务处理等场景
2. 函数
- 理解装饰器首先要理解 Python 的函数
2.1 函数对象
- Python 认为一切皆为对象:
def hi(name="yasoob"):return "hi " + nameprint(hi()) # output: 'hi yasoob'
- 可以将一个函数赋值给一个变量:
greet = hi # 我们这里没有在使用小括号,因为我们并不是在调用hi函数 # 而是在将它放在greet变量里头。我们尝试运行下这个print(greet()) # output: 'hi yasoob'
- 可以删除一个函数:
del hi print(hi()) #outputs: NameErrorprint(greet()) #outputs: 'hi yasoob'
2.2 返回值类型为函数
- 函数的返回值可以为函数对象:
def hi(name="yasoob"):def greet():return "now you are in the greet() function"def welcome():return "now you are in the welcome() function"if name == "yasoob":return greetelse:return welcomea = hi() print(a) #outputs: <function greet at 0x7f2143c01500>print(a()) #outputs: now you are in the greet() function
- a 为 hi 函数执行的返回值,此时 a 为一个函数对象,执行 a 得到返回值执行的结果
2.3 参数类型为函数
- 既然函数对象可以作为返回值,那么不难理解也可以作为参数:
def hi():return "hi yasoob!"def doSomethingBeforeHi(func):print("I am doing some boring work before executing hi()")print(func())doSomethingBeforeHi(hi) #outputs:I am doing some boring work before executing hi() # hi yasoob!
3. 装饰器
- 上文提到,装饰器本质也是一个返回值为函数类型的函数对象。通过装饰器可以抽离大量与函数本身无关的雷同代码进行复用
- 一个简单例子,需要在代码中添加日志代码:
def foo1():print('I am foo1')logging.info('[foo1] Running')
- 如果其他函数同样需要执行代码,为避免大量雷同代码,需要重新定义一个函数专门处理日志 ,日志处理完之后再执行真正的业务代码:
def use_loggine(func)logging.info('[%s] Running' % func.__name__)func()def foo2():print('I am foo2')use_logging(foo2)
- 使用上述方法实现了复用,但是也破坏了带结构,每次直接调用相应函数可以实现的功能,现在需要调用 use_logging 并传入相应函数做为参数,可读性也比较差。因此需要引入装饰器
3.1 简单装饰器
def use_logging(func):def wrapper(*args, **kwargs):logging.warn('[%s] Running' % func.__name__)return func(*args, **kwargs)return wrapperdef foo3():print('I am foo3')foo3 = use_logging(foo3) foo3()
- 函数 use_logging 就是装饰器,执行真正业务的方法 func 包裹在返回函数里,并重新对参数 foo3 赋值。相当于丰富了 foo3 的功能
- 在这个例子中,函数进入和退出时 ,被称为一个横切面(Aspect),这种编程方式被称为面向切面的编程(Aspect-Oriented Programming)
- @ 符号是装饰器的语法糖,在定义函数的时候使用,避免再一次赋值操作。其他函数也可以使用此装饰器包装,增加了代码可读性:
def use_logging(func):def wrapper(*args, **kwargs):logging.warn('[%s] Running' % func.__name__)return func(*args, **kwargs)return wrapper@use_logging def foo4():print('I am foo4')foo4()
3.2 带参数的装饰器
- 还可以使用带参数的装饰器
- 在上面的装饰器调用中,比如 @use_logging,该装饰器唯一的参数就是执行业务的函数
- 装饰器的语法允许我们在调用时,提供其它参数,比如 @decorator(a):
def use_logging(level):def decorator(func):def wrapper(*args,**kwargs):if level == "warn"logging.warn("%s is running"% func.__name__)return func(*args)return wrapperreturn decorator@use_logging(level="warn") def foo5(name='foo'):print('I am foo5')foo5()
- 上述的 use_logging 是允许带参数的装饰器。它实际上是对原有装饰器的封装,并返回一个装饰器
- 我们可以将它理解为一个含有参数的闭包。当我们调用 @use_logging(level=“warn”) 的时候,Python 能够发现这一层的封装,并把参数传递到装饰器的环境中
3.3 类装饰器
- 相比函数装饰器,类装饰器具有灵活度大、高内聚和封装性等优点
- 使用类装饰器还可以依靠类内部的__call__方法,当使用 @ 形式将装饰器附加到函数上时,就会调用此方法:
class FooClass(object):def __init__(self, func):self._func = funcdef __call__(self):print ('class decorator runing')self._func()print ('class decorator ending')@FooClass def foo6():print ('I am foo6')foo6()
3.4 带 functools.wraps 的装饰器
- 使用装饰器复用了代码,但是有一个缺点就是原函数元信息不见了,如 docstring、__name、参数列表:
def logged(func):def with_logging(*args, **kwargs):print(func.__name__ + " was called")return func(*args, **kwargs)return with_logging@logged def foo7(x):"""do some math"""return x + x * xfoo7(8) # prints 'foo7 was called\n72'
- 上述代码等价于如下:
foo7 = logged(foo7)print foo7.__name__ # prints 'with_logging' print foo7.__doc__ # prints None
- 可以看出,foo7 的信息会被 with_logging 替代,元信息会被 logged 的元信息替代
- 使用 functools.wraps 可以把原函数的元信息拷贝到装饰器函数中,这使得装饰器函数元信息与原函数一致:
from functools import wraps def logged(func):@wraps(func)def with_logging(*args, **kargs):print(func.__name__ + " was called")return func(*args, **kargs)return with_logging@logged def foo8(x):"""do some math"""return x + x * xprint foo8.__name__ # prints 'f' print foo8.__doc__ # prints 'do some math'
3.5 内置装饰器
- 主要包括:@property、@staticmathod、@classmethod
3.5.1 @property
- 对于 Class 的某个方法,可以通过 @property 装饰器将其伪装成一个属性,以 实例.方法 方式调用:
from math import pi class Circle:def __init__(self,r):self.r = r@propertydef perimeter(self):return 2*pi*self.r@propertydef area(self):return self.r**2*pic1 = Circle(5) print(c1.area) # prints '78.5398163397' print(c1.perimeter) # prints '31.4159265359'
- 类中的方法名本来是不能用相同名字的,但只要使用 @property 加在 相应方法之前,并且在再次定义同名方法此前加上 @someone.setter、@someone.deleter:
- 这样在类实例化化后,调用 实例.方法 就相当于调用 @property 包装的方法
- 调用 实例.方法=val 就相当于调用 @someone.setter 下的方法,并且传进去一个参数 val
- 调用 del 实例.方法 相当于调用 @someone.deleter 下的方法
class Person:def __init__(self,name):self.__name = name@propertydef name(self):return self.__name@name.deleterdef name(self):del self.__name@name.setterdef name(self,new_name):self.__name = new_name
- property 函数可以起到和 @propery 装饰器相同的效果:
class C(object):def __init__(self):self._x = Nonedef getx(self):return self._xdef setx(self, value):self._x = valuedef delx(self):del self._xx = property(getx, setx, delx, "I'm the 'x' property.") # 分别表示获取、设置和删除属性的函数以及 doc 信息 c = C() c.x = 1 print(c.x) # prints '1'
3.5.2 @classmethd
- 把一个方法 变成一个类中的方法,这个方法就直接可以被类调用,不需要依托任何对象:
class Someone:@classmethoddef set(cls, new_val):cls.__val = new_val@classmethoddef get(cls):return cls.__val__val = "abc"print(Someone.get()) # prints 'abc' Someone.set('abcd') print(Someone.get()) # prints 'abcd' s1 = Someone() s2 = Someone() print(s1.get()) # prints 'abcd' print(s2.get()) # prints 'abcd'
- 当这个方法的操作只涉及静态属性的时候就应该使用 @classmethod 来装饰这个方法
- 类似于实例方法要传个 self 代表实例对象,此类方法在传参时,要传一个形参代表当前类,默认 cls
3.5.3 @staticmethod
如果一个函数既和对象没有关系,也和类没有关系 那么就用 @staticmethod 将这个函数变成一个静态方法:
class Someone:@staticmethoddef set(new_val):Someone.__val = new_val@classmethoddef get(cls):print("global __val: %s" % Someone.__val)return cls.__val__val = "abc"
- 静态方法就是一个写在类里的普通函数
- 对象可以调用类方法和静态方法,一般情况下推荐用类名调用
- 类方法 有一个默认参数 cls 代表这个类 cls
- 静态方法没有默认的参数,就象函数一样
- 通过 类.静态方法 调用时不会实例化
3.5.4 @classmethod 和 @staticmethod 区别
- @staticmethod 不需要表示自身对象的 self 和自身类的 cls 参数,就和使用普通的函数一样
- @classmethod 不需要 self 参数,但是第一个参数需要表示自身类的 cls 参数
- 如果在 @staticmethod 中要调用到这个类的一些属性方法,只能 类名.属性名 或 类名.方法名
- 而 @classmethod 因为持有 cls 参数,可以来调用类的属性、类的方法、实例化对象等
3.6.装饰器执行顺序
@a @b @c def foo9():pass# 等效于 foo9 = a(b(c(foo9)))
4. 参考文献
- 如何理解Python装饰器?
- Python 函数装饰器
- 三个面向对象相关的装饰器@property@staticmathod@classmethod
转载于:https://www.cnblogs.com/wangao1236/p/10899711.html
Python(三)对装饰器的理解相关推荐
- python中自带的三个装饰器_python三个自带装饰器的功能与使用(@property、@staticmethod、@classmethod)...
本篇随笔只是记录我对这三个装饰器的理解,可能会有不准确的地方,敬请指出. property装饰器 功能:通过property装饰器控制类的属性的绑定与获取,一般就是给某个属性增加一个验证类型等功能. ...
- python三层装饰器-python中自带的三个装饰器的实现
说到装饰器,就不得不说python自带的三个装饰器: 1.@property 将某函数,做为属性使用 @property 修饰,就是将方法,变成一个属性来使用. class A(): @propert ...
- python中自带的三个装饰器
说到装饰器,就不得不说python自带的三个装饰器: 1.@property 将某函数,做为属性使用 @property 修饰,就是将方法,变成一个属性来使用. class A():@property ...
- python装饰器哪个好_[Python] 对 Python 装饰器的理解心得
最近写一个py脚本来整理电脑中的文档,其中需要检校输入的字符,为了不使代码冗长,想到使用装饰器. 上网搜索有关python的装饰器学习文档,主要看的是AstralWind的一篇博文,以及Limodou ...
- python 三个内置装饰器,python中自带的三个装饰器
说到装饰器,就不得不说python自带的三个装饰器: 1.@property 将某函数,做为属性使用 @property 修饰,就是将方法,变成一个属性来使用. class A(): @propert ...
- [转载]理解PYTHON中的装饰器
[翻译]理解PYTHON中的装饰器 来源stackoverflow上的问题 链接 python的函数是对象 要理解装饰器,首先,你必须明白,在python中,函数是对象. 这很重要. 简单例子来理解为 ...
- 设计模式学习(三)——装饰器模式
前言 距离上一次正儿八经地写随笔已经有一段时间了,虽然2月10号有一篇关于泛型的小记,但是其实只是简单地将自己的学习代码贴上来,为了方便后续使用时查阅,并没有多少文字和理解感悟.之所以在今天觉得有必要 ...
- python高级语法装饰器_Python高级编程——装饰器Decorator超详细讲解上
Python高级编程--装饰器Decorator超详细讲解(上篇) 送你小心心记得关注我哦!! 进入正文 全文摘要 装饰器decorator,是python语言的重要特性,我们平时都会遇到,无论是面向 ...
- 初学者python笔记(装饰器、高阶函数、闭包)
一个函数被定义完成后,甚至程序发布后,后期可能需要添加某些功能,但是我们不可能每次都去修改原函数的代码,这时候装饰器就可以上场了,本篇文章将会用一个个可实现的代码,由浅入深.循序渐进得阐述装饰器的强大 ...
- [转载] python 闭包和装饰器详解
参考链接: Python中的装饰器 python 闭包,装饰器 一 闭包 如果在一个函数的内部定义了另一个函数,外部的函数叫它外函数,内部的函数叫它内函数. 1 闭包条件 1 在一个外函数中定义了一个 ...
最新文章
- java循环使用范围_Java循环流程控制语句
- 浅析移动端网站是如何做好前期策划工作的?
- 用户控件的后台代码关联使用CodeBehind还是CodeFile
- LiveVideoStack:祝大家 2019 新年快乐!
- 【0718作业】收集和整理面向对象的六大设计原则
- 使用easyexcel导出时行高不自动调整的解决
- CRS磁盘force dismount引起的RAC节点宕机故障
- gstreamer插件开发_测评丨高性能多媒体处理器—飞凌OKMX8MM-C开发板
- 完美手柄震动效果-xbox360手柄模拟器下载
- windows无法打开添加打印机_打印机常见故障机及处理方法
- 渗透测试学习笔记(提权)
- 解决 error: Raw kernel process exited code: 3221226505
- 串操作指令---movs,stos,rep
- 智能家居(3)智能交互的竞品分析
- 如何使用OLED显示图像
- 谁是鱼谁是饵?红队视角下蜜罐识别方式汇总
- 大屏联屏发布系统解决方案
- 数学建模竞赛2022美赛
- Axure第7讲:设置元件文字行距、边距
- 数据库复习——关系数据理论中的几个重要概念(闭包,逻辑蕴含,覆盖...)
热门文章
- 常见OJ评判结果对照表
- 3项目里面全局用less变量 cli vue_VUE CLI3 less 全局变量引用
- Android 请求PHP接口, 返回json, 开头有问号, 解决方案
- 数字经济时代下老年群体手机APP软件网络推广适老化需求日益明显
- 网络营销外包期间站长如何挖掘用户真实需求探索网络营销外包真谛
- java book打印机_Java调用打印机进行打印
- 放个手机在单位自动打卡_钉钉自动打卡(家校打卡,寒假特辑)
- vrp车辆路径问题 php,蚁群算法在车辆路径问题(VRP)中的应用.ppt
- Java函数式折叠,循环,记忆化效率初识
- go语言学习(6)select的使用