Python进阶(十一)装饰器
装饰器是Python
的语法糖,它可以将复杂的功能封装起来,在外部表现出特别简单的语法和语义。装饰器还可以实现AOP
面向切面的编程。
Python
装饰器的本质,就是闭包。一般谈Python
的闭包,都是指普通的入参,而谈装饰器的时候,入参一定有函数。闭包和装饰器,返回都是函数。函数是代码的最小封装单位,装饰器作用于函数,它不影响函数自身的执行,只是在函数的执行前后增加一些“装饰性”的动作。装饰器被称为Python
的语法糖(Syntax Sugar
),也被视为Python
支持AOP
编程(面向切面编程)的工具。
简单装饰器
在函数执行前,增加一行print
打印,代码如下:
def calling_trace(func):def wrapper():print('calling', func.__name__)func()return wrapper@calling_trace
def test1():print('test1 is runing...')test1()
test1
函数加上了calling_trace
装饰器,相同于test1 = calling_trace(test1)
。以上代码运行效果如下:
calling test1
test1 is runing...
这就是装饰器的概念:不修改已有函数的代码,也不修改已有函数的调用处代码,却达到了丰富函数功能的效果。
装饰带参数的函数
如果被装饰的函数有参数,需要在装饰器内部原样复制函数的参数定义。
def calling_trace(func):def wrapper(*args):print('calling', func.__name__)a = func(*args)print('reture value:',a)return wrapper@calling_trace
def test4(*args):print('test4 is runing...')return sum(args)test4(1,2,3,4,5,6,7,8)
test4(23,34,45,56)
*args
表示一个tuple
,在函数定义处出现,就是packing
打包调用时的参数,在调用时出现,就是unpacking
展开tuple
。跟**kw
(对应dict
)用法一样。以上程序运行结果如下:
calling test4
test4 is runing...
reture value: 36
calling test4
test4 is runing...
reture value: 158
带参数的装饰器
装饰器本身也可以带参数,通过在装饰器外再使用闭包,给装饰器封装其执行环境,可以使装饰器的功能更强大更灵活,也可以更好的控制函数的执行(比如在某些情况下不执行)。
而带参数的装饰器,在语法上,也就跟闭包区分开来。(应该就是这个原因,闭包和装饰器在Python中是分成了两个概念)。
def calling_trace(run):def deco(func):def wrapper(*args, **kw):print('calling', func.__name__)if run == 1:a = func(*args, **kw)print('reture value:',a)else:print('not allow to run')return wrapperreturn deco# test5 = calling_trace(run=1)(test5)
@calling_trace(run=1)
def test5(a,b,c=3):print('test5 is runing...')return a+b+c@calling_trace(run=0)
def test6(*args):print('test6 is runing...')return sum(args)test5(1,2)
test5(1,2,c=8)
test6(23,34,45,56)
代码的运行结果:
calling test5
test5 is runing...
reture value: 6
calling test5
test5 is runing...
reture value: 11
calling test6
not allow to run
多重装饰器
函数可以有多个装饰器!多个装饰器的效果,就相当于对函数进行了多层的封装包裹,而不同的装饰器对函数执行的功能影响,完全独立。比如有个装饰器用来控制函数是否能够被执行,另一个装饰器控制函数的所有raise
出来的异常。
@a
@b
@c
def tt(): pass
# tt = a(b(c(tt)))
基于类的python装饰器
Python
装饰器函数其实是这样一个接口约束,它必须接受一个callable
对象作为参数,然后返回一个callable
对象。在Python
中一般callable
对象都是函数,但也有例外。只要某个对象重载了__call__()
方法,那么这个对象就是callable
的。
class call_class():def __call__(self):print('I am in call_class')tt = call_class()
tt()
执行结果如下:
I am in call_class
我们判断一个对象(变量和函数都是对象)是否可调用,其实就是判断这个对象是否含有__call__
函数。
像__call__
这样前后都带下划线的方法在Python
中被称为内置方法,有时候也被称为魔法方法。重载这些魔法方法一般会改变对象的内部行为。上面这个例子就让一个类对象拥有了被调用的行为。
回到装饰器上的概念上来,装饰器要求接受一个callable
对象,并返回一个callable
对象,那么用类来实现也是也可以的。我们可以让类的构造函数__init__()
接受一个函数,然后重载__call__()
并返回一个函数,也可以达到装饰器函数的效果。
class calling_trace():def __init__(self, run):self.run = rundef __call__(self, func):def wrapper(*args, **kw):print('calling', func.__name__)if self.run == 1:a = func(*args, **kw)print('return value:', a)else:print('not allow to run')return wrapper#def calling_trace(run):
# def deco(func):
# def wrapper(*args, **kw):
# print('calling', func.__name__)
# if run == 1:
# a = func(*args, **kw)
# print('reture value:',a)
# else:
# print('not allow to run')
# return wrapper
# return deco# test5 = calling_trace(run=1)(test5)
@calling_trace(run=1)
def test5(a,b,c=3):print('test5 is runing...')return a+b+c@calling_trace(run=0)
def test6(*args):print('test6 is runing...')return sum(args)test5(1,2)
test5(1,2,c=8)
test6(23,34,45,56)
class calling_trace
与注释掉的那个函数,效果是完全一样的。运行结果为:
calling test5
test5 is runing...
return value: 6
calling test5
test5 is runing...
return value: 11
calling test6
not allow to run
@staticmethod的使用
一般类中的函数,都是与对象绑定,其第1个参数习惯上都使用self这个名称(也可以使用别的名称),表示当此函数被调用的时候,对象自己会作为第1个参数(隐式地)传入。不过有趣的是,这些函数在被调用的时候,此对象并没有显式的出现在参数列表中。将对象自身作为第1个参数传入,是python解释器自动完成的动作。
>>> '-'.join('12345')
'1-2-3-4-5'
以上代码,调用了str对象的join函数,join函数的定义如下:
join(self, iterable, /)Concatenate any number of strings.The string whose method is called is inserted in between each given string.The result is returned as a new string.Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'
join函数的第1个参数是self,表示对象本身,按上文的示例代码,就是 ‘-’ 这个str对象。不过写出来的代码,参数只有’12345’,没有写出self参数。这里请仔细体会!
被@staticmethod
装饰过的函数,就不再是一般的成员函数了,而是“静态函数”。这类函数在调用的时候:
(1)Python
解释器不会将对象作为第1
个参数传入;
(2)不要通过对象来调用此类函数,要通过类来调用;
(3)因为没有对象传入,这类函数就无法获取与对象绑定的成员信息,也就是不能访问任何一个对象绑定的属性信息,只能通过类名来访问类变量(class variable
),这也是第2
点的原因,要用类来调用静态函数;
(4)此类函数,被封装在类中,只能在此类以及继承此类的范围内访问,外界不可访问,也是一种封装技术。
它是静止的,不会随不同的类和对象而发生变动,只会出现在类以及继承于此类的空间中。
class group():num = 0def __init__(self, sn):self.sn = sngroup.num += 1@staticmethoddef getTotal():return group.numg1 = group('123')
g2 = group('124')
g3 = group('125')
print(group.getTotal()) # >>> 3
@staticmethod
定义的函数,只存在此类或继承此类的空间(namespace
)中,它不接受对象参数,因此它的最佳使用场景,就是为此类以及继承此类的类提供某种计算服务(比如某些属性值的检查),这类计算不需要具体对象的参与,也最好不要去访问类变量(在继承的情况下,类的名称会变),它的计算参数很可能均来自外部输入,但是计算结果为类和对象服务。或者,@staticmethod
就是用一个类来封装一个功能块接口,如果没有继承,它们之间可以通过类名来相互调用。
@classmethod的使用
@classmethod
一样,也是应用于类中的函数,将这些函数转换为类函数。装饰器也是一个函数!内置的装饰器,当然也是内置函数。我们定义的Python
类,内部可以有三种成员函数:
- 一般的对象函数,没有任何装饰器修饰的都是这类。
@staticmethod
装饰的静态函数,静态类的成员函数,只能通过明确的类名来访问类中的变量,使用方面受到限制。@classmethod
装饰的类函数,使用比静态函数要广一些,因为类函数的第1
个参数默认为当前类名。由于类函数可以访问类变量(class variable
),使得类函数常常用来提供创建对象不同的接口。
class fruit():num = 0def __init__(self, price):self.price = priceself.count()@classmethoddef count(cls):cls.num += 1@classmethoddef float_price(cls, price):if not isinstance(price, float):try:price = float(price)except: raisereturn cls(price)f1 = fruit(1.234)
print(fruit.num)
f2 = fruit.float_price(1.2345)
print(fruit.num)
f3 = fruit.float_price('1.234')
print(fruit.num)
f4 = fruit.float_price('abcde')
一般情况下,我们实用f1 = fruit(1.234)
的方式创建对象。如果我们需要对入参进行一些限制和判断,甚至转换,就可以考虑实用float_price
这个类函数。请看f2和f3的创建,确定参数没有问题之后,再调用cls(price)创建对象。
cls
是classmethod
类函数的第1
个默认参数,cls
这个名字,与self
一样,只是一个约定俗称的写法。cls(price)
,就跟fruit(price)
一样,内部调用__init__
创建对象。
count
函数也是类函数,用于累加类变量num
,用来计数fruit
对象的个数。上面代码执行效果如下:
1
2
3
Traceback (most recent call last):File "classmethod.py", line 30, in <module>f4 = fruit.float_price('abcde')File "classmethod.py", line 19, in float_priceprice = float(price)
ValueError: could not convert string to float: 'abcde'
为什么这个简单的记录对象个数的函数,要用@classmethod
来修饰呢?因为,在有继承关系的时候,可以让你少些很多代码,整体结构也更优雅。
class fruit():num = 0def __init__(self, price):self.price = priceself.count()@classmethoddef count(cls):cls.num += 1@classmethoddef float_price(cls, price):if not isinstance(price, float):try:price = float(price)except: raisereturn cls(price)class apple(fruit):@classmethoddef int_price(cls, price):if not isinstance(price, int):try:price = int(price)except: raisereturn cls(price)f1 = apple(12)
print(apple.num)
f2 = apple.float_price(1.2345)
print(apple.num)
f3 = apple.int_price('12')
print(apple.num)
f4 = apple.int_price('abcde')
现在我们来创建apple
对象,apple
类继承fruit
,只是增加了一个int_price
类函数。同样,我们尝试创建4
个apple
对象,预期输入'abcde'
的时候,创建会失败,以上代码执行结果如下:
1
2
3
Traceback (most recent call last):File "classmethod.py", line 41, in <module>f4 = apple.int_price('abcde')File "classmethod.py", line 30, in int_priceprice = int(price)
ValueError: invalid literal for int() with base 10: 'abcde'
结果符合我们的预期,apple
对象的计数也在正常进行,这就要归功于fruit
类中定义的那个count class method
。因为classmethod
函数可以使用第1
个参数,cls
,来访问类变量,因此继承之后,cls
自动就指向了继承后的类。注意,这是@classmethod与@staticmethod不一样的地方。
这类函数是服务于类的,它可以访问类变量,因此在继承之后,依然可以保持对继承后的类的支持。
@property装饰器
@property
装饰器,它的作用,就是能够把一个类的成员函数,当成一个属性来访问,访问这个由函数装扮成的属性,表面上看是对属性的直接访问,实质上是在调用函数。
class fruit():def __init__(self, num, price):self.num = numself.price = price@propertydef totalcost(self):return round(self.num*self.price,2)ft = fruit(100, 12.34)
print(ft.totalcost) #>>> 1234.0
fruit
类中有一个函数叫totalcost
,被@property
装饰了一下。与你你会看到,代码在访问(调用)totalcost
的时候,并没有带括号,就像是一个普通的属性一下直接访问,但是实际上是在做函数调用。
Python
解释器在访问对象属性的时候,要先判断属性本身属于什么类型,内部会做一些转换,因此加上@property
装饰器后,用访问普通属性的方式调用函数成为了可能。
- @property装饰器访问私有成员
Python
对象本质上是没有私有成员的,用双下划线的方式定义的成员,也可以通过附加类名的方式来访问。不过这种访问方式应该要被禁止。
Python
提供了一个@property
装饰器,可以更好的控制对对象成员的访问,也可以更好的控制对成员的修改。
class test():def __init__(self):self.__foo = 'foo'self.__bar = 'bar'@propertydef foo(self):# print('running in foo function')return self.__foot = test()
print(t.foo)
用@property
装饰的foo
函数,就变成了foo
成员变量,访问这个成员变量的时候,触发内部的一个函数执行。此时,foo
这个成员变量只能读,不能写,如果尝试使用t.foo=123
来赋值,会出现错误:AttributeError: can't set attribute
。若想要实现修改内部__foo
成员,可采用以下代码:
class test():def __init__(self):self.__foo = 'foo'self.__bar = 'bar'@propertydef foo(self):# print('running in foo function')return self.__foo@foo.setterdef foo(self, foo_value):if foo_value < 100: passelse:self.__foo = foo_valuet = test()
print(t.foo)
t.foo = 123
print(t.foo)
注意@foo.setter
这个装饰器的由来。在设置的时候,外部代码还是简简单单地使用等号实现,而触发的是内部的一段代码,这段代码可以实现设置前的检查!
下面的代码,实现对某个成员的删除控制:
class test():def __init__(self):self.__foo = 'foo'self.__bar = 'bar'@propertydef foo(self):# print('running in foo function')return self.__foo@foo.setterdef foo(self, foo_value):if foo_value < 100: passelse:self.__foo = foo_value@foo.deleterdef foo(self):if self.__foo > 200:self.__foo = 200t = test()
print(t.foo)
t.foo = 123
print(t.foo)
t.foo = 234
print(t.foo)
del t.foo
print(t.foo)
注意@foo.deleter
这个装饰器。执行del t.foo
语句的时候,同样触发一段自定义代码,上面的代码示例,并没有真正删除foo
这个变量,只是限制它的值。
通过@property
装饰器,以及其衍生出来的一些其它装饰器,我们可以通过简单的语义实现复杂的功能,可以增加代码的可读性。
转载文章链接
- https://www.pynote.net/archives/tag/decorator
Python进阶(十一)装饰器相关推荐
- python进阶20装饰器
原创博客地址:python进阶20装饰器 Nested functions Python允许创建嵌套函数,这意味着我们可以在函数内声明函数并且所有的作用域和声明周期规则也同样适用. 1 2 3 4 5 ...
- Python进阶: Decorator 装饰器你太美
函数 -> 装饰器 函数的4个核心概念 1.函数可以赋与变量 def func(message):print('Got a message: {}'.format(message))send_m ...
- 二十一、深入Python强大的装饰器
@Author: Runsen 文章目录 闭包 装饰器 嵌套函数的装饰器 带参数嵌套函数的装饰器 类装饰器 嵌套装饰器 @Date:2019年07月11日 最近有同学在问关于Python中装饰器的问题 ...
- python 进阶:修饰器的介绍
参考链接:Python 函数装饰器 我认为python中的装饰器是一个很厉害的功能,他能瞬间提升代码的逼格,但对于我这样的小白来说,别说为所欲为的使用了,就连简单的尝试一下,却也是难于登天.经过长达半 ...
- python装饰器原理-python 中的装饰器及其原理
装饰器模式 此前的文章中我们介绍过装饰器模式: 装饰器模式中具体的 Decorator 实现类通过将对组建的请求转发给被装饰的对象,并在转发前后执行一些额外的动作来修改原有的部分行为,实现增强 Com ...
- python装饰器类-PYTHON里的装饰器能装饰类吗
扩展回答 如何理解python里的装饰器 通常可以理解它是一个hook 的回调函数. 或者是理解成python 留给二次开发的一个内置API. 一般是用回调和hook 方式实现的. 如何理解Pytho ...
- python类装饰器详解-python 中的装饰器详解
装饰器 闭包 闭包简单的来说就是一个函数,在该函数内部再定义一个函数,并且这个内部函数用到了外部变量(即是外部函数的参数),最终这个函数返回内部函数的引用,这就是闭包. def decorator(p ...
- python中的装饰器decorator
python中的装饰器 装饰器是为了解决以下描述的问题而产生的方法 我们在已有的函数代码的基础上,想要动态的为这个函数增加功能而又不改变原函数的代码 例如有三个函数: def f1(x):return ...
- python生成器和装饰器_python之yield与装饰器
防伪码:忘情公子著 python中的yield: 在之前发布的<python之列表解析与生成器>中我们有提到过,生成器所实现的是跟列表解析近似的效果,但是我们不能对生成器做一些属于列表解析 ...
- Python闭包与装饰器
Python闭包与装饰器 一.闭包 函数是一个对象,所以可以对象的形式作为某个函数的结果返回.函数执行完后内部变量将会被回收.在闭包中,由于内部函数存在对外部函数的变量的引用,所以即使外部 ...
最新文章
- java截取图片-设置方位+设置大小
- Linux进程间通讯
- python五种调试或排错的方法
- excel 某个单元格不是等于空值_这些稀奇古怪的符号,却是Excel高手们常玩的!...
- 【转】深入分析 Parquet 列式存储格式
- android shape 按钮背景_Android UI:XML文件配置按钮等背景方案
- 跑步进入全站 HTTPS ,这些经验值得你看看
- c# gerber文件读取_Gerber文件查看器
- Proe/Creo经典曲面造型实战案例大合集
- HTML实现简单水平导航栏
- Virtualbox虚拟机Ubuntu联网
- 供应链金融业务基础模式、实施路径、服务对象深度解析
- Panoramic Photography
- 路由器接口配置与管理——6
- 暗影精灵4风扇转速调节_答疑解惑,暗影精灵4用了半年的真实感受
- 【相机标定系列】相机sensor传感器尺寸,CMOS靶面尺寸,分辨​率​和​镜头​焦距,畸变处理效果,相机主点
- PicPick 5.1.3 中文版,一个全功能的屏幕截图工具,图像编辑器,颜色选择器
- Android App加载图片内存空间计算
- 美国H1B基本情况及相关数据
- linux命令学习之---- chgrp