python装饰器

提前了解知识

赋值调用

函数也是对象,函数对象是可以被赋值给变量,所以变量也可以调用函数。

1
2
3
4
5
6
7
8
def hello():
print("hello world")

a = hello() #函数hello()的返回值给a
print(a) #由于函数hello()无返回值,所以a的结果为None
print("###########")
b = hello #此处将函数hello赋值给b,不是字符串的赋值
b() #b()相当于hello()

开发中的开放封闭原则

写代码要遵循开放封闭原则,该原则不仅适用于面向对象开发,也适用于函数式编程,简单点说,就是以实现的功能代码不允许被修改,但可以被扩展。

  • 封闭:以实现的功能代码
  • 开放:对扩展开放

    装饰器作用

    装饰器本质上是一个python函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外功能,装饰器的返回值也是一个函数对象。
    装饰器经常用于如下场景:插入日志、性能测试、事务处理、缓存、权限校验等。通过使用装饰器,可以抽离处大量与函数功能本身无关的雷同代码并继续重用。
    简单来说,装饰器就是在不改变函数本身的前提下,在函数前面或者后面添加一些额外功能。
    装饰器通过@调用。

    装饰器实例

    无参数的装饰器

    对于前面的函数

    1
    2
    def hello():
    print("hello world")

    现在需要打印日志,在前后加上时间点,在不改变原有函数hello()的情况下如何做?

    1
    2
    3
    4
    5
    6
    7
    8
    import  datetime

    def log(func):
    def wrapper():
    print("start_time: {0}".format(datetime.datetime.now()))
    func()
    print("end_time: {0}".format(datetime.datetime.now()))
    return wrapper

上面的log是一个装饰器(decorator),接受一个函数为参数(func),并返回一个函数(wrapper)。使用的时候使用前面说的@语法,把装饰器置于函数的定义处。

1
2
3
@log
def hello():
print("hello world")

调用hello()函数的时候,不仅会运行hello()函数本身,还会运行在函数前面和后面加上时间点。

1
2
3
4
>>> hello()
start_time: 2018-04-18 16:30:57.998073
hello world
end_time: 2018-04-18 16:30:58.001074

代码分析
@log放在hello()函数的定义处,相当于执行了语句hello = log(hello)
由于log()是一个装饰器,返回一个函数,所以原来的hello()函数仍然存在,但是同名的hello变量指向了新的函数,于是调用hello()将执行新函数,新函数是谁呢?新函数是log()函数的返回值,也就是wrapper()函数。

带参数的装饰器

当我们需要传递参数的时候时候怎么处理呢?对于上面的日志处理函数,要传递参数author该怎么处理呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import  datetime

def log(name):
def author(func):
def wrapper():
print("author: {0}".format(name))
print("start_time: {0}".format(datetime.datetime.now()))
func()
print("end_time: {0}".format(datetime.datetime.now()))
return wrapper
return author

@log("xiaohh")
def hello():
print("hello world")

hello()

输出结果

1
2
3
4
author: xiaohh
start_time: 2018-04-18 20:13:44.912610
hello world
end_time: 2018-04-18 20:13:44.912610

代码分析
@log("xiaohh")相当于执行了语句hello = log("xiaohh")(hello)
首先执行log("xiaohh"),返回的是author函数,再调用返回的函数,参数是hello函数,返回值最终是wrapper函数。

注意点

在上述的函数调用之后,最终返回的是wrapper函数,所以它的__name__属性发生了变化。

1
print(hello.__name__)    #输出结果wrapper

此处需要把原始函数的__name__等属性赋值到wrapper()函数中,否则有些依赖函数签名的代码执行会报错。要实现该功能使用functools.wraps就行。

  • 对于不带参数的装饰器
1
2
3
4
5
6
7
8
9
10
import  datetime
import functools

def log(func):
@functools.wraps(func)
def wrapper():
print("start_time: {0}".format(datetime.datetime.now()))
func()
print("end_time: {0}".format(datetime.datetime.now()))
return wrapper
  • 对于带参数的装饰器
1
2
3
4
5
6
7
8
9
10
11
12
13
import  datetime
import functools

def log(name):
def author(func):
@functools.wraps(func)
def wrapper():
print("author: {0}".format(name))
print("start_time: {0}".format(datetime.datetime.now()))
func()
print("end_time: {0}".format(datetime.datetime.now()))
return wrapper
return author

输出结果

1
print(hello.__name__)    #输出结果hello

也就是在定义wrapper函数之前加上@functools.wraps(func)即可。

基于类的装饰器

类的构造函数init()接受一个函数,然后重载call()并返回一个函数达到装饰器函数的效果。
具体的调用和程序执行过程其实和函数实现的装饰器是一致的。

无参数的类的装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import datetime

class log(object):
def __init__(self, func):
self.func = func

def __call__(self, *args, **kwargs):
print("start_time: {0}".format(datetime.datetime.now()))
self.func(*args, **kwargs)
print("end_time: {0}".format(datetime.datetime.now()))

@log
def hello():
print("hello world")

hello()

输出结果

1
2
3
start_time: 2018-04-18 21:05:56.422751
hello world
end_time: 2018-04-18 21:05:56.422751

这是对前面函数实现的不带参数的装饰器采用类方式实现。

带参数的类的装饰器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

import datetime

class log(object):
def __init__(self, name):
self.name = name

def __call__(self, func):
def wrapper(*args, **kwargs):
print("author: {0}".format(self.name))
print("start_time: {0}".format(datetime.datetime.now()))
func(*args, **kwargs)
print("end_time: {0}".format(datetime.datetime.now()))
return wrapper

@log("xiaohh")
def hello():
print("hello world")

hello()

输出结果

1
2
3
4
author: xiaohh
start_time: 2018-04-18 21:13:27.538287
hello world
end_time: 2018-04-18 21:13:27.538287

这是对前面函数实现的带参数的装饰器采用类方式实现。

python内置装饰器

内置装饰器有3个,分别是staticmethodclassmethodpropety

  • staticmethod
    把类中的方法定义为静态方法,使用staticmethod装饰的方法可以使用类或者类的实例对象来调用,不需要传入self。
  • classmethod
    把类中的方法定义为类方法,使用classmethod装饰的方法可以使用类或者类的实例对象来调用,并将该类对象隐式的作为第一个参数传入。
  • propety
    把方法变成属性。

    总结

    对于函数和类的装饰器实现,上述只是简单的理解装饰器的概念和用法。
    具体的需要在实际使用过程中体会,特别是关于类的实现部分和内置装饰器带来的便利,需要在面向对象编程中进一步理解。

Recommended Posts