您的位置:首页 > 编程语言 > Python开发

Python:关于装饰器

2017-12-23 09:31 337 查看

目的

AOP编程,解耦。

一是为了少写代码,二是为了解耦。

为了把重点放在装饰器上,本文的代码没有引入很多包。大概要引入datetime,math等。

例子

问题提出

比如,需要添加一些通用的动作,又不想在原有的函数里面修改。

e.g. 要在函数执行前打印出执行的时间(相当于写日志)。

def say():
print('hi')


最土的解决办法

def say():
time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('Begin @ %s' % time1)
print('hi')


缺点至少有2个。

1. 直接改变了say函数的业务。

2. 若有N个say函数,会有N段重复的代码。

少写代码但流程诡异的解决办法

想个办法,把say函数指针(C里面的说法)当做变量传给一个写日志的函数。像这样。

def logger(func):

time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('Begin @ %s'%time1)

func()
而say函数保持不动。

def say():
print('hi')
在main流程中调用时这么写。

logger(say)
即可。

又有个问题。

在main流程中,明明是想执行say。

希望看见的是say(),这里却写的是logger(say)。

非常诡异。其实不想看见调用logger,而是想看见调用say。

自制装饰器(原生态)

有以下办法可以让main里面看见是say在调用,而不是logger在调用。

def logger(func):

time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('Begin @ %s'%time1)

return func

def say(): print('hi')

say2 = logger(say)
say2()


在调用logger(say)时,返回了say函数本身的地址。然而在调用它之前,执行了输出当前时刻的函数。

say2整个函数相当于先输出时间,再执行say函数。这和预期一致。

当然,把变量say2的名字改成say也行,它只是一个名字而已,但其实这样做变量替换,看起来不太好。

就像数学 f(t) 做变量替换时,会写令 t' = t+1,不会写 t = t+1。t只是个哑指标。写成 t = t+1,你都不知道哪个t是哪个t。

Python提供的装饰器

自制一个装饰器是,难免会看见 say2 = logger(say) 这种我们对say函数改造的话。

但其实在main流程中,我们并不想看见。

通过装饰器注解(注解也许是Java里面的话),可以避免这样的情况。

def logger(func):

def wrapper():
time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('Begin @ %s'%time1)
func()

return wrapper

@logger
def say(): print('hi')

say()
解释如下。

@logger

def say()

的作用,相当于say = logger(say),左边的say已经不仅是print('hi')的say。正如上面所说的,写成say2 = logger(say)更好。

调用logger(say)的时候,把say函数的指针传给了logger作为输入参数,在logger的定义的wrapper函数作为返回参数。

所以 say2 = logger(say) 左边的say2其实是个函数,之后再调用say2,写为say2()。

现在的say2变成了wrapper函数。第一步输出时间,第二部执行say函数。

在wrapper里面,func()这句话就是在调用(call)函数,而被调用的函数正是作为logger输入参数的say函数。

所以wrapper里面调用的func()其实是只会pritn('hi')的say函数。

至于为什么要写成@logger的样子,以及为什么就相当于say = logger(say)了,这是语法规定了。这门语言就是这么设计的。

被装饰的函数有输入参数

def logger(func):

def wrapper(name):
time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('Begin @ %s' % time1)
func(name)

return wrapper

@logger
def say(name):
print('hi, %s!' % name)

say('qcy')


@logger def say(name),相当于say2 = logger(say),那其实say2就是wrapper函数。
wrapper需要一个输入参数name,和原来的say所需要的是一样的。
而在wrapper里面执行的func(name),也就把name穿进去了,相当于在调用原生态(没加注解)的say函数,say(name)。

更通用的可变输入参数的写法

对于可变参数,更喜欢写成 f(*args, **kwargs)。所以装饰器里面的wrapper更习惯写成wrapper(*args, **kwargs)。

def logger(func):

def wrapper(*args, **kwargs):
time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('Begin @ %s' % time1)
func(*args, **kwargs)

return wrapper

@logger
def say(name):
print('hi, %s!' % name)

say('qcy')


被装饰的函数有返回参数

那就在wrapper里面再返回一个返回值就好。

一个例子,计算算术平方根。要引入math。

def logger(func):

def wrapper(*args, **kwargs):
time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('Begin @ %s' % time1)
res = func(*args, **kwargs)
return res

return wrapper

@logger
def calc_sqrt(x):
print('This is a function to calculate sqrt.')
res = math.sqrt(x)
return res

result = calc_sqrt(81)
print(result)


整个例子的输出结果是:

Begin @ 2017-12-23 10:10:00

This is a function to calculate sqrt.

9.0

多看看也就这么回事。

装饰器本身需要输入参数

装饰器本身就要参数,如有很多logger,但此时需要知道是哪一个logger在输出。
import math
import time

def logger(logger_name):

def decorator(func):

def wrapper(*args, **kwargs):
time1 = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')
print('[%s] Begin @ %s' % (logger_name, time1))
return func(*args, **kwargs)
return wrapper

return decorator

@logger('calculator_log')
def calc_sqrt(x):
print('This is a function to calculate sqrt.')
res = math.sqrt(x)
return res

result = calc_sqrt(81)
print(result)


被装饰以后,大概与以下几条命令相似。

calc_sqrt2 = logger('calculator_log') --> 函数calc_sqrt2变成了decorator

calc_sqrt3 = calc_sqrt2(calc_sqrt) --> calc_sqrt3 = decorator(calc_sqrt), 函数calc_sqrt3变成了wrapper

最后,计算calc_sqrt3(81) --> wrapper(81)

总结

引入functools,是为了把原生态的say函数的属性赋给wrapper。

这是一个完整的装饰器。
import functools

def log(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print 'call %s():' % func.__name__
return func(*args, **kw)
return wrapper


这是一个带有输入参数的装饰器
import functools

def log(text):
def decorator(func):
@functools.wraps(func)
def wrapper(*args, **kw):
print '%s %s():' % (text, func.__name__)
return func(*args, **kw)
return wrapper
return decorator


装饰器的应用

装饰器通常用在一些通用的功能上,简化代码。
如,写日志、安全验证、异常处理、传入参数等。

异常捕获及处理的例子

# -*- coding: utf-8 -*-

import functools

def send_email(email_info):

print('send_email: 这里开始发邮件')
print('内容: %s' % email_info['content'])
print('...')

def exception_handler(func):

@functools.wraps(func)
def wrapper(*args, **kwargs):

print('函数执行前的一些操作')
try:
return func(*args, **kwargs)
except Exception as e:
print(e)
print('函数执行失败,发送邮件提醒等...')
send_email({'subject':'异常', 'content': e})
print('函数执行后的一些操作')

return wrapper

@exception_handler
def say():
print('你好!')
print('===现在要故意造一个错误')
print1

say()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  C++ python 函数 aop