Python——装饰器(Decorator)
1.什么是装饰器?
装饰器放在一个函数开始定义的地方,它就像一顶帽子一样戴在这个函数的头上。和这个函数绑定在一起。在我们调用这个函数的时候,第一件事并不是执行这个函数,而是将这个函数做为参数传入它头顶上这顶帽子,这顶帽子我们称之为装饰器 。
2.装饰器的使用方法
@ 符号是装饰器的语法糖,一般情况下我们使用@函数名或者@类名来使用装饰器。
3.函数装饰器
函数装饰器 = 高阶函数 + 嵌套函数 + 闭包
• 高阶函数:外层函数可以接收内层函数作为参数
• 函数嵌套 :内函数作为外函数的参数使用
• 闭包 :外部函数返回内部函数的函数名,内部函数能够使用外部函数的自由变量
(1)不带参数的函数装饰器(日志打印器)
实现的功能是:在函数执行前,先打印一行日志“Before”,在函数执行完,再打印一行日志“After”。
代码如下:
- #coding=utf-8
- # -*- coding=utf-8 -*-
- #不带参数装饰器
- def dec1_outer(func):
- def dec1_inner():
- print("Before")
- #函数真正执行的地方
- func()
- print("After")
- return dec1_inner
- @dec1_outer
- def func():
- print('func')
- func()
运行结果:
以上代码定义了一个装饰器函数dec1_outer,当我们在func函数前加上@dec1_outer时,就等于给func函数使用了dec1_outer这个装饰器。所以func()在运行前会先将函数名func作为参数传给装饰器函数,这个语句等价于func = dec1_outer(func)。装饰器函数在接收到参数后执行,先返回内函数的函数名dec1_inner,此时18行的func()相当于调用了dec1_inner(),即进行了dec1_inner函数的操作。func函数真正执行的地方则是第9行的那段代码。
以上对装饰器的使用相当于:
- func = dec1_outer(func)
- func()
(2)带参数的函数装饰器
带参数的函数装饰器常用于定时发送邮件等场景,但是代码过于复杂,不利于讲解。以下代码实现的是在装饰器里传入一个参数,指明国籍,并在函数执行前,用自己国家的母语打一个招呼。
代码如下:
- #coding=utf-8
- # -*- coding=utf-8 -*-
- #带参数的装饰器
- def dec2_para(country):
- def dec2_outer(func):
- def dec2_inner(*args, **kwargs):
- if country == "中国":
- print("你好!")
- elif country == 'America':
- print("Hello!")
- else:
- print("Where are you from?")
- #函数真正执行的地方
- func(*args, **kwargs)
- return dec2_inner
- return dec2_outer
- @dec2_para('中国')
- def Chinese():
- print("中国")
- @dec2_para('America')
- def American():
- print("America")
- Chinese()
- print('----------------------')
- American()
运行结果:
以上代码的装饰器dec2_para采用了两层嵌套,所以Chinese()在运行前会先将‘中国’作为参数传值给dec2_para,装饰器函数在接收到参数后返回dec2_outer函数名。接下来Chinese函数的函数名Chinese会作为参数传给装饰器函数,dec2_outer接收到参数后返回dec2_inner函数名。26行的Chinese()此时相当于调用了dec2_inner(),即进行了dec2_inner函数的操作,dec2_inner会先判断传入的country参数值输出相应的消息。Chinese函数真正执行的地方则是第14行的那段代码。
以上对装饰器的使用相当于:
- Chinese = dec2_para('中国')(Chinese)
- Chinese()
4.类装饰器
在我们的代码中如果有出现不同装饰器需要部分功能重叠时,使用类装饰器能使代码更加简洁。比方说有时你只想打印日志到一个文件。而有时你想把引起你注意的问题发送到一个email,同时也保留日志,留个记录。这是一个使用继承的场景,我们可以用类来构建装饰器。
类作为装饰器,需要重写__call__方法。
(1)不带参数的类装饰器:
代码如下:
- #coding=utf-8
- from functools import wraps
- class logit(object):
- def __init__(self, logfile='out.log'):
- self.logfile = logfile
- def __call__(self, func):
- @wraps(func)
- def wrapped_function(*args, **kwargs):
- log_string = func.__name__ + " was called"
- print(log_string)
- # 打开logfile并写入
- try:
- with open(self.logfile, 'a') as opened_file:
- # 现在将日志打到指定的文件
- opened_file.write(log_string + '\n')
- except IOError as e:
- print(e)
- # 现在,发送一个通知
- self.notify()
- return func(*args, **kwargs)
- return wrapped_function
- def notify(self):
- # logit只打日志,不做别的
- pass
- class email_logit(logit):
- '''
- 一个logit的实现版本,可以在函数调用时发送email给管理员
- '''
- def __init__(self, email='admin@myproject.com', *args, **kwargs):
- self.email = email
- super(email_logit, self).__init__(*args, **kwargs)
- def notify(self):
- # 发送一封email到self.email
- # 这里就不做实现了
- print('send')
- @email_logit()
- def myfunc1():
- print("func1")
- @logit()
- def myfunc2():
- print("func2")
- myfunc1()
- print("-----------------------")
- myfunc2()
运行结果:
文本中的记录:
以上代码,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()。
以上对类装饰器的使用相当于:
- myfun1 = email_logit(myfun1)
- myfun1()
(2)带参数的类装饰器
代码如下:
- #coding=utf-8
- # -*- coding=utf-8 -*-
- #带参数的类装饰器
- class dec4_monitor(object):
- def __init__(self, level = 'INFO'):
- print(level)
- self.level = level
- def __call__(self, func):#接收函数
- def call_inner(*args, **kwargs):
- print("[%s]:%s is running"%(self.level, func.__name__))
- func(*args, **kwargs)
- return call_inner #返回函数
- @dec4_monitor(level = 'WARNING')
- def func_warm(warn):
- print(warn)
- func_warm("WARNING Message!")
运行结果:
类装饰器和函数装饰器一样也可以实现参数传递,上面的代码给装饰器传递了一个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()。
以上对类装饰器的使用相当于:
- func_warm = dec4_monitor(level = "WARNING")(func_warm)
- func_warm("WARMING Message")
5.@wraps
Python装饰器(decorator)在实现的时候,被装饰后的函数的函数名等函数属性会发生改变,为了不影响原函数,Python的functools包中提供了一个叫wraps的decorator来消除这样的副作用。写一个decorator的时候,最好在实现之前加上functools的wraps,它能保留原有函数的名称和docstring等属性。
代码如下:
- #coding=utf-8
- # -*- coding=utf-8 -*-
- #@wraps
- from functools import wraps
- def decw_outer(func):
- @wraps(func)
- def decw_inner():
- pass
- return decw_inner
- def dect_outer(func):
- def dect_inner():
- pass
- return dect_inner
- @decw_outer
- def decw_func():
- pass
- @dect_outer
- def dect_func():
- pass
- print(decw_func.__name__)
- print('---------------------')
- print(dect_func.__name__)
运行结果:
通过以上的运行结果我们可以看到,当装饰器函数没有使用@wraps时,被装饰的函数的函数名会发生改变,而使用了@wraps后,被装饰的函数的函数名能变回原来的函数名。
6.Python类中常用的内置装饰器
Python常用的内置装饰器有:@property、@staticmethod、@classmethod和@abstractmethod。
(1)@staticmethod、@classmethod
@staticmethod和@classmethod,它们的作用是可以不需要实例化类,直接用类名.方法名()来调用类里面的方法。但它们也存在一些区别:
- @staticmethod不需要表示自身对象的self和自身类的cls参数,就跟使用函数一样。@classmethod也不需要self参数,但第一个参数需要是表示自身类的cls参数。
- 类方法有类变量cls传入,从而可以用cls做一些相关的处理。并且有子类继承时,调用该类方法时,传入的类变量cls是子类,而非父类。
- class A(object):
- bar = 1
- def foo(self):
- print 'foo'
- @staticmethod
- def static_foo():
- print 'static_foo'
- print A.bar
- @classmethod
- def class_foo(cls):
- print 'class_foo'
- print cls.bar
- cls().foo()
- A.static_foo()
- print('----------------------')
- A.class_foo()
运行结果:
上面的类函数static_foo()和class_foo(cls)因为使用了@staticmethod和@classmethod装饰器而可以直接用类名.方法名()来调用,15行的cls().foo()相当于A().foo(),A()是类A的实例化。
(2)@abstractmethod
Python的abc提供了@abstractmethod装饰器实现抽象方法,使用@abstractmethod装饰器类将不能被实例化。
代码如下:
- #@abstractmethod
- from abc import abstractmethod,ABCMeta
- class Animal():
- __metaclass__ = ABCMeta
- @abstractmethod
- def eat(self):
- pass
- class Person(Animal):
- def eat(self):
- print('eat thing')
- class Cat(Animal):
- pass
- #a = Animal()
- b = Person()
- b.eat()
- #c = Cat()
运行结果:
基类Animal的eat方法被@abstractmethod装饰了,所以Animal不能被实例化;子类Dog没有实现基类的eat方法也不能被实例化;子类Person实现了基类的抽象方法eat所以能实例化。当Animal和Cat类被实例化是会报如下错误:
(3)@property
既要保护类的封装特性,又要让开发者可以使用“对象.属性”的方式操作类属性,Python 提供了 @property 装饰器。通过 @property 装饰器,可以直接通过方法名来访问方法,不需要在方法名后添加一对“()”小括号。
比如以下代码:
- #coding=utf-8
- class Student(object):
- def __init__(self, name, age=None):
- self.name = name
- self.age = age
- class Studentlimit(object):
- def __init__(self, name):
- self.name = name
- def set_age(self, age):
- if not isinstance(age, int):
- raise ValueError('输入不合法:年龄必须为数值!')
- if not 0 < age < 100:
- raise ValueError('输入不合法:年龄范围必须0-100')
- self._age=age
- def get_age(self):
- return self._age
- def del_age(self):
- self._age = None
- # 实例化
- xm = Student("小明")
- xh = Studentlimit("小华")
- # 添加属性
- xm.age = 25
- #xm.age = -5
- xh.set_age(26)
- #xh.set_age(-5)
- # 查询属性
- print(xm.age)
- print(xh.get_age())
- # 删除属性
- del xm.age
- xh.del_age()
在代码中我设置了两个类这两个类的作用都是在类实例化以后增加age属性。
Student类可以使用对象.属性的方法赋值,但是对于赋的值没有办法判定合法性。Studentlimit类可以判定赋值的合法性但是不能使用对象.属性的方法赋值。
所以为了解决这个难题,Python 提供了 @property 装饰器。
在使用@property以后的代码如下:
- #coding=utf-8
- # -*- coding=utf-8 -*-
- #@property
- class Student(object):
- def __init__(self, name):
- self.name = name
- self.name = None
- @property
- def age(self):
- return self._age
- @age.setter
- def age(self, value):
- if not isinstance(value, int):
- raise ValueError('输入不合法:年龄必须为数值!')
- if not 0 < value < 100:
- raise ValueError('输入不合法:年龄范围必须0-100')
- self._age=value
- @age.deleter
- def age(self):
- del self._age
- xiaoming = Student("小明")
- # 设置属性
- xiaoming.age = 25
- # 查询属性
- print(xiaoming.age)
- print(xiaoming.name)
- #更改属性
- xiaoming.age = 22
- # 查询属性
- print(xiaoming.age)
- # 删除属性
- del xiaoming.age
- print(xiaoming.age)
此时,我们既可以使用使用对象.属性的方法赋值,而且对于赋的值也可以判定其判定合法性。
以上部分内容参考自:https://blog.csdn.net/ajian6/article/details/100940857
Python——装饰器(Decorator)的更多相关文章
- python 装饰器(decorator)
装饰器(decorator) 作者:Vamei 出处:http://www.cnblogs.com/vamei 欢迎转载,也请保留这段声明.谢谢! 装饰器(decorator)是一种高级Python语 ...
- Python装饰器--decorator
装饰器 装饰器实质是一个函数,其作用就是在不改动其它函数代码的情况下,增加一些功能.如果我们需要打印函数调用前后日志,可以这么做 def log(func): print('%s is running ...
- Python 装饰器Decorator(一)
(一) 装饰器基础知识 什么是Python装饰器?Python里装饰器是一个可调用的对象(函数),其参数是另一个函数(被装饰的函数) 假如有一个名字为somedecorator的装饰器,target是 ...
- Python装饰器(Decorator)简介
Python有许多出色的语言特性,装饰器(Decorator)便是其中一朵奇葩.先来看看一段代码: def deco1(f): print 'decorate 1' return f def deco ...
- Python 装饰器Decorator(二)
对于上一篇“”Python闭包“”随笔中提到的make_averager()函数的如下实现,我们把历史值保存在列表里,每次计算平均值都需要重新求和,当历史值较多时,需要占用比较多的空间并且效率也不高. ...
- python 装饰器学习(decorator)
最近看到有个装饰器的例子,没看懂, #!/usr/bin/python class decorator(object): def __init__(self,f): print "initi ...
- python语法32[装饰器decorator](转)
一 装饰器decorator decorator设计模式允许动态地对现有的对象或函数包装以至于修改现有的职责和行为,简单地讲用来动态地扩展现有的功能.其实也就是其他语言中的AOP的概念,将对象或函数的 ...
- python 语法之 装饰器decorator
装饰器 decorator 或者称为包装器,是对函数的一种包装. 它能使函数的功能得到扩充,而同时不用修改函数本身的代码. 它能够增加函数执行前.执行后的行为,而不需对调用函数的代码做任何改变. 下面 ...
- python函数编程-装饰器decorator
函数是个对象,并且可以赋值给一个变量,通过变量也能调用该函数: >>> def now(): ... print('2017-12-28') ... >>> l = ...
随机推荐
- 关于element ui滚动条使用
element ui 自带的滚动条使用 在容器的直接外层添加 (需要展现滚动条的那一级) <el-scrollbar style="height:100%"></ ...
- js 实现复制功能的四种方式的优劣对比
今日网上浏览别人项目,看到有人用了document.execCommand这个属性,于是想起之前我选用Clipboard.js 来实现.对于这种不常用的属性还是不太放心,于是随手查了下关于复制的资料, ...
- 一组简单好看的css3渐变按钮
主要代码如下: body { background:#fff } /* Mixins */ /* bg shortcodes */ .bg-gradient1 span,.bg-gradient1:b ...
- gojs Diagram Events(图表事件)
GoJS涵盖了三种基本事件:DiagramEvents(图表事件).InputEvents(输入事件)以及ChangedEvents(变更事件).这一页我们讨论前两种事件:至于最后一种事件请见 Cha ...
- PAT Basic 1050 螺旋矩阵 (25 分)
本题要求将给定的 N 个正整数按非递增的顺序,填入“螺旋矩阵”.所谓“螺旋矩阵”,是指从左上角第 1 个格子开始,按顺时针螺旋方向填充.要求矩阵的规模为 m 行 n 列,满足条件:m×n 等于 N:m ...
- 搭建简单的mongod服务器
1.首先,创建一个叫做mongodb_simple的目录,进入到目录中. 2.创建文件夹:data,用来存储数据库的数据文件. 3.创建文件夹:log,用来存储数据库的日志文件. 4.创建文件夹:bi ...
- Ubuntu系统---C++之Eclipse 开始工程项目
Ubuntu系统---C++之Eclipse 开始工程项目 安装完Eclipse,那就像其它项目一样,先要测试一下是否能用. 一.测一个hello world例子二.利用OpenCV测试一个显示图片的 ...
- Python 常用的ORM框架简介
ORM概念ORM(Object Ralational Mapping,对象关系映射)用来把对象模型表示的对象映射到基于S Q L 的关系模型数据库结构中去.这样,我们在具体的操作实体对象的时候,就不需 ...
- win10 专业版永久密钥
激活码/密匙: 1.专业版: W269N-WFGWX-YVC9B-4J6C9-T83GXMH37W-N47XK-V7XM9-C7227-GCQG92X7P3-NGJTH-Q9TJF-8XDP9-T83 ...
- POJ-3186-Treats for the Cows(记忆化搜索)
链接: https://vjudge.net/problem/POJ-3186 题意: FJ has purchased N (1 <= N <= 2000) yummy treats f ...