python装饰器-Python——装饰器(Decorator)

1.什么是装饰器?

装饰器放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为装饰器 。

2.装饰器的使用方法

@ 符号是装饰器的语法糖,一般情况下我们使用@函数名或者@类名来使用装饰器。

3.函数装饰器

函数装饰器 = 高阶函数 + 嵌套函数 + 闭包

• 高阶函数:外层函数可以接收内层函数作为参数

• 函数嵌套 :内函数作为外函数的参数使用

• 闭包 :外部函数返回内部函数的函数名,内部函数能够使用外部函数的自由变量

1235524-20191203210201703-454638727.jpg

(1)不带参数的函数装饰器(日志打印器)

实现的功能是:在函数执行前,先打印一行日志“Before”,在函数执行完,再打印一行日志“After”。

代码如下:

1 #coding=utf-8

2 #-*- coding=utf-8 -*-

3 #不带参数装饰器

4 defdec1_outer(func):5

6 defdec1_inner():7 print("Before")8 #函数真正执行的地方

9 func()10 print("After")11

12 returndec1_inner13

14 @dec1_outer15 deffunc():16 print('func')17

18 func()

运行结果:

1235524-20191203105953708-1299383503.png

以上代码定义了一个装饰器函数dec1_outer,当我们在func函数前加上@dec1_outer时,就等于给func函数使用了dec1_outer这个装饰器。所以func()在运行前会先将函数名func作为参数传给装饰器函数,这个语句等价于func = dec1_outer(func)。装饰器函数在接收到参数后执行,先返回内函数的函数名dec1_inner,此时18行的func()相当于调用了dec1_inner(),即进行了dec1_inner函数的操作。func函数真正执行的地方则是第9行的那段代码。

以上对装饰器的使用相当于:

1 func =dec1_outer(func)2 func()

(2)带参数的函数装饰器

带参数的函数装饰器常用于定时发送邮件等场景,但是代码过于复杂,不利于讲解。以下代码实现的是在装饰器里传入一个参数,指明国籍,并在函数执行前,用自己国家的母语打一个招呼。

代码如下:

1 #coding=utf-8

2 #-*- coding=utf-8 -*-

3 #带参数的装饰器

4 defdec2_para(country):5 defdec2_outer(func):6 def dec2_inner(*args, **kwargs):7 if country == "中国":8 print("你好!")9 elif country == 'America':10 print("Hello!")11 else:12 print("Where are you from?")13 #函数真正执行的地方

14 func(*args, **kwargs)15 returndec2_inner16 returndec2_outer17

18 @dec2_para('中国')19 defChinese():20 print("中国")21

22 @dec2_para('America')23 defAmerican():24 print("America")25

26 Chinese()27 print('----------------------')28 American()

运行结果:

1235524-20191203113015659-185675781.png

以上代码的装饰器dec2_para采用了两层嵌套,所以Chinese()在运行前会先将"中国’作为参数传值给dec2_para,装饰器函数在接收到参数后返回dec2_outer函数名。接下来Chinese函数的函数名Chinese会作为参数传给装饰器函数,dec2_outer接收到参数后返回dec2_inner函数名。26行的Chinese()此时相当于调用了dec2_inner(),即进行了dec2_inner函数的操作,dec2_inner会先判断传入的country参数值输出相应的消息。Chinese函数真正执行的地方则是第14行的那段代码。

以上对装饰器的使用相当于:

1 Chinese = dec2_para('中国')(Chinese)2 Chinese()

4.类装饰器

在我们的代码中如果有出现不同装饰器需要部分功能重叠时,使用类装饰器能使代码更加简洁。比方说有时你只想打印日志到一个文件。而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。这是一个使用继承的场景,我们可以用类来构建装饰器。

类作为装饰器,需要重写__call__方法。

(1)不带参数的类装饰器:

代码如下:

1 #coding=utf-8

2 from functools importwraps3

4 classlogit(object):5 def __init__(self, logfile='out.log'):6 self.logfile =logfile7

8 def __call__(self, func):9 @wraps(func)10 def wrapped_function(*args, **kwargs):11 log_string = func.__name__ + "was called"

12 print(log_string)13 #打开logfile并写入

14 try:15 with open(self.logfile, 'a') as opened_file:16 #现在将日志打到指定的文件

17 opened_file.write(log_string + ' ')18 exceptIOError as e:19 print(e)20 #现在,发送一个通知

21 self.notify()22 return func(*args, **kwargs)23 returnwrapped_function24

25 defnotify(self):26 #logit只打日志,不做别的

27 pass

28

29 classemail_logit(logit):30 '''

31 一个logit的实现版本,可以在函数调用时发送email给管理员32 '''

33 def __init__(self, email='admin@myproject.com', *args, **kwargs):34 self.email =email35 super(email_logit, self).__init__(*args, **kwargs)36

37 defnotify(self):38 #发送一封email到self.email

39 #这里就不做实现了

40 print('send')41

42 @email_logit()43 defmyfunc1():44 print("func1")45

46 @logit()47 defmyfunc2():48 print("func2")49

50 myfunc1()51 print("-----------------------")52 myfunc2()

运行结果:

1235524-20191203135529571-844241990.png

文本中的记录:

1235524-20191203134445720-119881815.png

以上代码,logit是一个类装饰器,它的功能是将函数运行情况记录在out.log文件中。email_logit同样是一个类装饰器,他继承了logit类,并增加了新的功能,即发送email的功能(这部分功能用print('send')代替)。@email_logit相当于 myfun1 = email_logit(myfun1)即,myfun1指向了 email_logit(myfun1)这个对象,func指向了函数myfunc1的函数名。

调用myfun1对象的时候相当于调用类email_logit的__call__方法,调用__call__方法的时候,先执行将函数运行日志写到out.log文件,然后再执行22行的func(*args, **kwargs) ,因为func函数指向的是myfunc1函数,所以func(*args, **kwargs)相当于执行myfun1()。

以上对类装饰器的使用相当于:

1 myfun1 =email_logit(myfun1)2 myfun1()

(2)带参数的类装饰器

代码如下:

1 #coding=utf-8

2 #-*- coding=utf-8 -*-

3 #带参数的类装饰器

4 classdec4_monitor(object):5 def __init__(self, level = 'INFO'):6 print(level)7 self.level =level8

9 def __call__(self, func):#接收函数

10 def call_inner(*args, **kwargs):11 print("[%s]:%s is running"%(self.level, func.__name__))12 func(*args, **kwargs)13 return call_inner #返回函数

14

15 @dec4_monitor(level = 'WARNING')16 deffunc_warm(warn):17 print(warn)18

19 func_warm("WARNING Message!")

运行结果:

1235524-20191203141517759-1788234222.png

类装饰器和函数装饰器一样也可以实现参数传递,上面的代码给装饰器传递了一个level值"WARNING"。@dec4_monitor(level = 'WARNING')相当于 func_warm = dec4_monitor(level = "WARNING")(func_warm)即,func_warm指向了 dec4_monitor(level = "WARNING")(func_warm)这个对象,func指向了函数func_warm的函数名。

调用myfun1对象的时候相当于调用类dec4_monitor的__call__方法,调用__call__方法的时候,输出相关信息[WARNING]:func_warm is running,然后再执行12行的func(*args, **kwargs) ,因为func函数指向的是func_warm函数,所以func(*args, **kwargs)相当于执行func_warm()。

以上对类装饰器的使用相当于:

1 func_warm = dec4_monitor(level = "WARNING")(func_warm)2 func_warm("WARMING Message")

5.@wraps

Python装饰器(decorator)在实现的时候,被装饰后的函数的函数名等函数属性会发生改变,为了不影响原函数,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wraps,它能保留原有函数的名称和docstring等属性。

代码如下:

1 #coding=utf-8

2 #-*- coding=utf-8 -*-

3 #@wraps

4 from functools importwraps5

6 defdecw_outer(func):7 @wraps(func)8 defdecw_inner():9 pass

10 returndecw_inner11

12 defdect_outer(func):13 defdect_inner():14 pass

15 returndect_inner16

17 @decw_outer18 defdecw_func():19 pass

20

21 @dect_outer22 defdect_func():23 pass

24

25 print(decw_func.__name__)26

27 print('---------------------')28

29 print(dect_func.__name__)

运行结果:

1235524-20191203143403162-573161189.png

通过以上的运行结果我们可以看到,当装饰器函数没有使用@wraps时,被装饰的函数的函数名会发生改变,而使用了@wraps后,被装饰的函数的函数名能变回原来的函数名。

6.Python类中常用的内置装饰器

Python常用的内置装饰器有:@property、@staticmethod、@classmethod和@abstractmethod。

(1)@staticmethod、@classmethod

@staticmethod和@classmethod,它们的作用是可以不需要实例化类,直接用类名.方法名()来调用类里面的方法。但它们也存在一些区别:

@staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。@classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。

类方法有类变量cls传入,从而可以用cls做一些相关的处理。并且有子类继承时,调用该类方法时,传入的类变量cls是子类,而非父类。

1 classA(object):2 bar = 1

3 deffoo(self):4 print 'foo'

5

6 @staticmethod7 defstatic_foo():8 print 'static_foo'

9 printA.bar10

11 @classmethod12 defclass_foo(cls):13 print 'class_foo'

14 printcls.bar15 cls().foo()16

17 A.static_foo()18 print('----------------------')19 A.class_foo()

运行结果:

1235524-20191203145525338-1955699199.png

上面的类函数static_foo()和class_foo(cls)因为使用了@staticmethod和@classmethod装饰器而可以直接用类名.方法名()来调用,15行的cls().foo()相当于A().foo(),A()是类A的实例化。

(2)@abstractmethod

Python的abc提供了@abstractmethod装饰器实现抽象方法,使用@abstractmethod装饰器类将不能被实例化。

代码如下:

1 #@abstractmethod

2 from abc importabstractmethod,ABCMeta3

4 classAnimal():5

6 __metaclass__ =ABCMeta7 @abstractmethod8 defeat(self):9 pass

10

11

12 classPerson(Animal):13

14 defeat(self):15 print('eat thing')16

17

18 classCat(Animal):19 pass

20

21

22 #a = Animal()

23

24 b =Person()25 b.eat()26

27 #c = Cat()

运行结果:

1235524-20191203151441964-788312860.png

基类Animal的eat方法被@abstractmethod装饰了,所以Animal不能被实例化;子类Dog没有实现基类的eat方法也不能被实例化;子类Person实现了基类的抽象方法eat所以能实例化。当Animal和Cat类被实例化是会报如下错误:

1235524-20191203151752900-721237775.png

1235524-20191203151647174-126819095.png

(3)@property

既要保护类的封装特性,又要让开发者可以使用“对象.属性”的方式操作类属性,Python 提供了 @property 装饰器。通过 @property 装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。

比如以下代码:

1 #coding=utf-8

2 classStudent(object):3 def __init__(self, name, age=None):4 self.name =name5 self.age =age6

7 classStudentlimit(object):8 def __init__(self, name):9 self.name =name10

11 defset_age(self, age):12 if notisinstance(age, int):13 raise ValueError('输入不合法:年龄必须为数值!')14 if not 0 < age < 100:15 raise ValueError('输入不合法:年龄范围必须0-100')16 self._age=age17

18 defget_age(self):19 returnself._age20

21 defdel_age(self):22 self._age =None23

24 #实例化

25 xm = Student("小明")26 xh = Studentlimit("小华")27

28 #添加属性

29 xm.age = 25

30 #xm.age = -5

31 xh.set_age(26)32 #xh.set_age(-5)

33 #查询属性

34 print(xm.age)35 print(xh.get_age())36 #删除属性

37 delxm.age38 xh.del_age()

在代码中我设置了两个类这两个类的作用都是在类实例化以后增加age属性。

Student类可以使用对象.属性的方法赋值,但是对于赋的值没有办法判定合法性。Studentlimit类可以判定赋值的合法性但是不能使用对象.属性的方法赋值。

所以为了解决这个难题,Python 提供了 @property 装饰器。

在使用@property以后的代码如下:

1 #coding=utf-8

2 #-*- coding=utf-8 -*-

3 #@property

4 classStudent(object):5 def __init__(self, name):6 self.name =name7 self.name =None8

9 @property10 defage(self):11 returnself._age12

13 @age.setter14 defage(self, value):15 if notisinstance(value, int):16 raise ValueError('输入不合法:年龄必须为数值!')17 if not 0 < value < 100:18 raise ValueError('输入不合法:年龄范围必须0-100')19 self._age=value20

21 @age.deleter22 defage(self):23 delself._age24

25 xiaoming = Student("小明")26 #设置属性

27 xiaoming.age = 25

28

29 #查询属性

30 print(xiaoming.age)31 print(xiaoming.name)32

33 #更改属性

34 xiaoming.age = 22

35

36 #查询属性

37 print(xiaoming.age)38

39 #删除属性

40 delxiaoming.age41 print(xiaoming.age)

此时,我们既可以使用使用对象.属性的方法赋值,而且对于赋的值也可以判定其判定合法性。

以上部分内容参考自:https://blog.csdn.net/ajian6/article/details/100940857

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值