装饰器

装饰器本质上是可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。有了装饰器,我们就可以抽离出大量与函数功能本身无关的雷同代码并继续重用.

[TOC]

装饰器的写法

比如说,我们有两个函数,它们分别实现了一个功能,但是其中一个模块的函数出了问题,需要为每个函数添加debug信息,方便找到问题,下方是两个函数:

def say_hello():
print("hello")

def say_world():
print("world")

以及想要实现的效果:

[DEBUG]: enter say_hello()
hello
[DEBUG]: enter say_world()
world

那么,你可能会想,不就是在每个函数加入个print的事嘛,但是我的老哥们,这仅仅是两个函数,这要是几百个函数,那岂不是得累死,所以我们不能这么干.

这时候我们的装饰器就闪亮登场了.先来看下代码.

def debug(func):
def wrapper(*args, **kwargs):
print("[DEBUG]: enter {}()".format(func.__name__))
return func()
return wrapper

我们来看下这个装饰器函数,他的执行过程是什么样的呢

  • 首先,它接受的参数是个函数
  • 然后将接受的函数进行包装,即,为其添加了一些新的功能特性
  • 最后返回了一个新的包装好的函数
  • 最后执行这个函数

好的,装饰器的执行过程我们知道了,那么我们如何调用这个装饰器为我们服务呢?
很简单,直接使用python的@语法糖就可以调用了,我们看下面的例子.

@debug
def say_hello():
print("hello")

看看这样的实现过程,是不是很简单,很实用,而且很优雅.

带参数装饰器

有小伙伴就可能要问了,我们debug完成了,难道我们还让他是输出debug字样的log嘛?是不是很不好(low)啊,这时候有的同学就要说了,很简单啊,在重新写个装饰器不就好了,可以是可以,但是改来改去的,你不会觉得很麻烦嘛?
这是我们想到了给装饰器加个参数,如果正常运行的时候就让他显示[执行]xxx,调试的时候我们就让他显示[debug]xxx,这样是不是就舒服多了.

def logging(level):
def wrapper(func):
def inner_wrapper(*args, **kwargs):
print ("[{level}]: enter function {func}()".format(
level=level,
func=func.__name__))
return func(*args, **kwargs)
return inner_wrapper
return wrapper

这样我们就定义好了一个带参数的装饰器,其实也就是加了一层嵌套,传了一个参数进去.这样我们只要指定level的值就可以了,完全不用再写一个参数,也更方便后期维护.

@logging(level='执行')
def say_hello():
print("hello")

类装饰器

前面讲的都是基于函数去实现一个装饰器,下面讲讲如何通过类来实现装饰器。回到装饰器上的概念上来,装饰器要求接受一个callable对象,并返回一个callable对象。那么用类来实现也是也可以的。我们可以让类的构造函数__init__()接受一个函数,然后重载__call__()并返回一个函数,也可以达到装饰器函数的效果。

class logging1(object):
def __init__(self, level='默认'):
self.level = level

def __call__(self, func): # 接受函数
def wrapper(*args, **kwargs):
print("[{level}]: enter function {func}()".format(
level=self.level,
func=func.__name__))
func(*args, **kwargs)

return wrapper # 返回函数

调用还是使用@语法糖,这里就不多赘述了.

装饰器嵌套

有的时候。我们并不是在原有的基础上增加一个功能,而是几个功能。那可不可以使用几个装饰器呢?Python告诉你,它是可以的。

@A
@B
@C
def xxx(...):
...

这样就可以实现装饰器的嵌套。实际上他的执行过程是这样的,先执行接近函数的那一个:

f = A(B(C(f)))  # first C, seconds B, third A

内置装饰器

内置的装饰器和普通的装饰器原理是一样的,只不过返回的不是函数,而是类对象,在Python中有三个内置的装饰器,都是跟class相关的:staticmethod、classmethod 和property。

  • staticmethod 是类静态方法,其跟成员方法的区别是没有 self 参数,并且可以在类不进行实例化的情况下调用
  • classmethod 与成员方法的区别在于所接收的第一个参数不是 self (类实例的指针),而是cls(当前类的具体类型)
  • property 是属性的意思,表示可以通过通过类实例直接访问的信息

@property

Python内置的@property装饰器是负责把一个方法变成属性调用的,这里其实查了很多资料但没有很好的讲解,我就查看了函数的内部,发现了里面给讲解了这个装饰器的使用,而且很形象。

class C(object):
@property
def x(self):
return self._x

@x.setter
def x(self, value):
self._x = value

@x.deleter
def x(self):
del self._x

@property、x.setter x.deleter实现了属性的读取、赋值、和删除。
上实例,便于理解,这里以一个学生的成绩修改进行操作。

class Stu(object):

@property
def score(self):
return self._score

@score.setter
def score(self, value):
self._score = value

@score.deleter
def score(self):
del self._score

if __name__ == '__main__':
stu = Stu() #实例化
stu.score = 60 #赋值
print(stu.score)
del stu.score #删除
print(stu.score)

>>>
60
AttributeError: 'Stu' object has no attribute '_score'

@classmethod

类方法,按照我的理解就是,可以在类的内部定义函数,并且将这个函数的返回值作为参数,传给这个类。听起来好像有点拗口,下面我们来看个实例:

class Date_test(object):
day, month, year = 0, 0, 0

def __init__(self, year=0, month=0, day=0):
self.day = day
self.month = month
self.year = year

@classmethod
def get_date(cls, string_date):
# 这里第一个参数是cls, 表示调用当前的类名
year, month, day = map(int, string_date.split('-'))
date1 = cls(year, month, day)
# 返回的是一个初始化后的类
return date1

def out_date(self):
print("日期: %4d年 %2d月 %2d日" %(self.year, self.month, self.day))



if __name__ == '__main__':
date = Date_test(2012, 6, 6)
date.out_date()
date1 = Data_test.get_date('2013-12-1')
date1.out_date()

日期: 201266
日期: 2013121

上面代码中的date1,就是将时间戳转换成该类可接受的参数,再重新调用这个class.

@staticmethod

静态方法,该方法不强制要求传递参数,也不需要实例化,可以直接调用。

class Foo:
@staticmethod # 静态方法
def spam(x,y,z):
print(x,y,z)


if __name__ == '__main__':
Foo.spam(1,2,3)

当然也可以实例化调用,这里就不演示了。

应用场景:编写类时需要采用很多不同的方式来创建实例,而我们只有一个__init__函数,此时静态方法就派上用场了.

class Date:
def __init__(self, year, month, day):
self.year = year
self.month = month
self.day = day

@staticmethod
def now(): # 用Date.now()的形式去产生实例,该实例用的是当前时间
t = time.localtime() # 获取结构化的时间格式
return Date(t.tm_year, t.tm_mon, t.tm_mday) # 新建实例并且返回

@staticmethod
def tomorrow(): # 用Date.tomorrow()的形式去产生实例,该实例用的是明天的时间
t = time.localtime(time.time() + 86400)
return Date(t.tm_year, t.tm_mon, t.tm_mday)

if __name__ == '__main__':
a = Date('1987', 11, 27) # 自己定义时间
b = Date.now() # 采用当前时间
c = Date.tomorrow() # 采用明天的时间
print(a.year, a.month, a.day)
print(b.year, b.month, b.day)
print(c.year, c.month, c.day)
1987 11 27
2018 3 9
2018 3 10
-------------本文结束感谢您的阅读-------------