Python——装饰器(Decorator)

1.什么是装饰器?

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

2.装饰器的使用方法

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

3.函数装饰器

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

• 高阶函数:外层函数可以接收内层函数作为参数
• 函数嵌套 :内函数作为外函数的参数使用
• 闭包 :外部函数返回内部函数的函数名,内部函数能够使用外部函数的自由变量

Python——装饰器(Decorator)

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

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

代码如下:

1 #coding=utf-8
 2 # -*- coding=utf-8 -*-
 3 #不带参数装饰器
 4 def dec1_outer(func):
 5
 6     def dec1_inner():
 7         print("Before")
 8         #函数真正执行的地方
 9         func()
10         print("After")
11
12     return dec1_inner
13
14 @dec1_outer
15 def func():
16     print('func')
17
18 func()

运行结果:

Python——装饰器(Decorator)

以上代码定义了一个装饰器函数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 def dec2_para(country):
 5     def dec2_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         return dec2_inner
16     return dec2_outer
17
18 @dec2_para('中国')
19 def Chinese():
20     print("中国")
21
22 @dec2_para('America')
23 def American():
24     print("America")
25
26 Chinese()
27 print('----------------------')
28 American()

运行结果:

Python——装饰器(Decorator)

以上代码的装饰器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 import wraps
 3
 4 class logit(object):
 5     def __init__(self, logfile='out.log'):
 6         self.logfile = logfile
 7
 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 + '\n')
18             except IOError as e:
19                 print(e)
20             # 现在,发送一个通知
21             self.notify()
22             return func(*args, **kwargs)
23         return wrapped_function
24
25     def notify(self):
26         # logit只打日志,不做别的
27         pass
28
29 class email_logit(logit):
30     '''
31     一个logit的实现版本,可以在函数调用时发送email给管理员
32     '''
33     def __init__(self, email='admin@myproject.com', *args, **kwargs):
34         self.email = email
35         super(email_logit, self).__init__(*args, **kwargs)
36
37     def notify(self):
38         # 发送一封email到self.email
39         # 这里就不做实现了
40         print('send')
41
42 @email_logit()
43 def myfunc1():
44     print("func1")
45
46 @logit()
47 def myfunc2():
48     print("func2")
49
50 myfunc1()
51 print("-----------------------")
52 myfunc2()

运行结果:

Python——装饰器(Decorator)

文本中的记录:

Python——装饰器(Decorator)

以上代码,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 class dec4_monitor(object):
 5     def __init__(self, level = 'INFO'):
 6         print(level)
 7         self.level = level
 8
 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 def func_warm(warn):
17     print(warn)
18
19 func_warm("WARNING Message!")

运行结果:

Python——装饰器(Decorator)

类装饰器和函数装饰器一样也可以实现参数传递,上面的代码给装饰器传递了一个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 import wraps
 5
 6 def decw_outer(func):
 7     @wraps(func)
 8     def decw_inner():
 9         pass
10     return decw_inner
11
12 def dect_outer(func):
13     def dect_inner():
14         pass
15     return dect_inner
16
17 @decw_outer
18 def decw_func():
19     pass
20
21 @dect_outer
22 def dect_func():
23     pass
24
25 print(decw_func.__name__)
26
27 print('---------------------')
28
29 print(dect_func.__name__)

运行结果:

Python——装饰器(Decorator)

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

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

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

(1)@staticmethod、@classmethod

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

  • @staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。@classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。
  • 类方法有类变量cls传入,从而可以用cls做一些相关的处理。并且有子类继承时,调用该类方法时,传入的类变量cls是子类,而非父类。
1 class A(object):
 2     bar = 1
 3     def foo(self):
 4         print 'foo'
 5
 6     @staticmethod
 7     def static_foo():
 8         print 'static_foo'
 9         print A.bar
10
11     @classmethod
12     def class_foo(cls):
13         print 'class_foo'
14         print cls.bar
15         cls().foo()
16
17 A.static_foo()
18 print('----------------------')
19 A.class_foo()

运行结果:

Python——装饰器(Decorator)

上面的类函数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 import abstractmethod,ABCMeta
 3
 4 class Animal():
 5
 6     __metaclass__ = ABCMeta
 7     @abstractmethod
 8     def eat(self):
 9         pass
10
11
12 class Person(Animal):
13
14     def eat(self):
15         print('eat thing')
16
17
18 class Cat(Animal):
19     pass
20
21
22 #a = Animal()
23
24 b = Person()
25 b.eat()
26
27 #c = Cat()

运行结果:

Python——装饰器(Decorator)

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

Python——装饰器(Decorator)

Python——装饰器(Decorator)

(3)@property

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

比如以下代码:

1 #coding=utf-8
 2 class Student(object):
 3     def __init__(self, name, age=None):
 4         self.name = name
 5         self.age = age
 6
 7 class Studentlimit(object):
 8     def __init__(self, name):
 9         self.name = name
10
11     def set_age(self, age):
12         if not isinstance(age, int):
13             raise ValueError('输入不合法:年龄必须为数值!')
14         if not 0 < age < 100:
15             raise ValueError('输入不合法:年龄范围必须0-100')
16         self._age=age
17
18     def get_age(self):
19         return self._age
20
21     def del_age(self):
22         self._age = None
23
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 del xm.age
38 xh.del_age()

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

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

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

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

1 #coding=utf-8
 2 # -*- coding=utf-8 -*-
 3 #@property
 4 class Student(object):
 5     def __init__(self, name):
 6         self.name = name
 7         self.name = None
 8
 9     @property
10     def age(self):
11         return self._age
12
13     @age.setter
14     def age(self, value):
15         if not isinstance(value, int):
16             raise ValueError('输入不合法:年龄必须为数值!')
17         if not 0 < value < 100:
18             raise ValueError('输入不合法:年龄范围必须0-100')
19         self._age=value
20
21     @age.deleter
22     def age(self):
23         del self._age
24
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 del xiaoming.age
41 print(xiaoming.age)

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

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

Original: https://www.cnblogs.com/vivianwenwen/p/11975853.html
Author: 温昀
Title: Python——装饰器(Decorator)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/585267/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • mycat2 读写分离配置(详解)

    mycat2相对mycat1来说升级还挺多的,但是全网资料太少了,这里尽可能详细的将读写分离说清楚,目前这套配置已经在我司生产环境应用,日UV6W左右,暂时没发现问题。 1.1下载…

    Linux 2023年6月6日
    0108
  • NoteOfMySQL-10-触发器与事件

    触发器是由事件来触发某个操作,这些事件包括insert语句、update语句、delete语句,当数据库系统执行这些事件时,就会激活触发器执行相应的操作。事件调度器(event s…

    Linux 2023年6月14日
    0100
  • Python之面向对象-反射

    一、什么是反射 反射的概念是由Smith在1982年首次提出的,主要是指程序可以访问,检测和修改它本省状态或行为的一种能力(自省)。这一概念的提出很快引发了计算机科学领域关于应用反…

    Linux 2023年6月14日
    0112
  • Dockerfile

    Docker可以通过Dockerfile构建镜像。Dockerfile是一个文本文档,它包含用户可以在命令行上调用的所有命令来组装镜像。使用 docker build用户可以创建一…

    Linux 2023年6月13日
    090
  • 一名研究生的自我修养

    一、如何学习 研究生阶段是学习效率最高的阶段。第一是因为动机纯粹,以前上学这么多年大部分的学习动机只是为了成绩,这个学习动机其实会很大限制同学的主动学习意愿,往往是被动学习,为了成…

    Linux 2023年6月14日
    085
  • 5.9 Linux Vim批量注释和自定义注释

    使用 Vim 编辑 Shell 脚本,在进行调试时,需要进行多行的注释,每次都要先切换到输入模式,在行首输入注释符 #再退回命令模式,非常麻烦。 连续行的注释其实可以用替换命令来完…

    Linux 2023年6月7日
    093
  • shell operator EOF shell 操作符 << <<<

    总结: 这些被叫做shell操作符 shell operator 主要分为 control operators和redirection operators < Origina…

    Linux 2023年5月28日
    0100
  • 高等代数: 2 行列式

    2 行列式 1、n个不同的自然数的一个全排列称为一个n元排列。 2、顺序、逆序、逆序数:τ(abcd…)(读音:tao)、奇排列、偶排列、对换(a,b) 3、定理1:对…

    Linux 2023年6月8日
    0152
  • Google Drive, Onedrive, Dropbox green check marks missing; 修复 google 硬盘,同步符号错误

    最近使用google 硬盘的时候,Windows平台总是出现安装后文件夹不能显示同步符号,而mac平台就无上述错误; 我查了一下资料,发现是因为系统安装的同步软件有点多,Windo…

    Linux 2023年6月13日
    0105
  • HTML笔记整理–上节

    一、认识WEB 「网页」主要是由 &#x6587;&#x5B57;、 &#x56FE;&#x50CF;和 &#x8D85;&#x94…

    Linux 2023年6月13日
    098
  • 操作系统实现-loader

    博客网址:www.shicoder.top微信:18223081347欢迎加群聊天 :452380935 大家好呀,终于我们到了操作系统的loader部分了,loader也是操作系…

    Linux 2023年6月13日
    075
  • Redis入门讲解(介绍、安装、常用命令)

    Redis入门讲解(介绍、安装、常用命令) Redis是非关系型数据库 关系型数据库 关系型数据库是采用了关系模型来组织数据的数据库,以行和列的形式存储数据,由二维表及其之间的关系…

    Linux 2023年6月6日
    0110
  • urandom和random区别

    linux中提供了 /dev/urandom 和 /dev/random 两个特殊设备来提供随机数。那么这两个文件有什么区别呢?要回答这个问题,先需要了解熵这个概念。 熵linux…

    Linux 2023年6月7日
    084
  • Linux下PAM模块学习总结

    Linux下PAM模块学习总结 转载自 https://www.cnblogs.com/kevingrace/p/8671964.html Original: https://ww…

    Linux 2023年6月7日
    0104
  • Java List和Map遍历的方法,forEach()的使用

    注意: 不要在foreach循环里进行元素的remove/add操作。remove元素请使用Iterator方式,如果并发操作,需要对Iterator对象加锁。 Java 8之前 …

    Linux 2023年6月7日
    0108
  • Linux指令_曾佳豪

    一、基础指令 1、ls指令 含义:ls (list) 用法1 :#ls 含义:列出当前工作目录下所有文件/文件夹的名称 [En] Meaning: list the names o…

    Linux 2023年5月27日
    0124
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球