装饰器(Decorators)是 Python 的一个重要部分。简单地说:他们是修改其他函数的功能的函数。他们有助于让我们的代码更简短,也更Pythonic(Python范儿)。大多数初学者不知道在哪儿使用它们,所以我将要分享下,哪些区域里装饰器可以让你的代码更简洁。 首先,让我们讨论下如何写你自己的装饰器。
这可能是最难掌握的概念之一。我们会每次只讨论一个步骤,这样你能完全理解它。
1.闭包函数
Python是一种面向对象的编程语言,在Python中一切皆对象,这样使得变量所拥有的属性,函数也同样拥有。这样我们就可以理解在函数创建一个函数的行为是完全合法的。这种函数叫做内嵌函数,只可以在外包函数的作用域内被正常使用,在外部函数的作用域外调用会报错,例如:
而如果内部函数里引用了外部函数定义的对象(甚至是外层之外,但不是全局变量),那么此此时内部函数被称为闭包函数。闭包函数所引用的外部定义的变量被叫做自由变量。闭包从语法上看非常简单,但是却有强大的作用。闭包可以将其自己的代码和作用域以及外部的函数的作用结合在一起。下面给出一个简单的闭包的例子:
def count():
a = 1
b = 1
def sum():
c = 1
return a + c # a - 自由变量
return sum
总结:什么函数可以被称为闭包函数呢?主要是满足两点:函数内部定义的函数;引用了外部变量但非全局变量。
2.Python装饰器
Python装饰器本质上就是一个函数,它可以让其他函数在不需要做任何代码变动的前提下增加额外的功能,装饰器的返回值也是一个函数对象(函数的指针)。装饰器函数的外部函数传入我要装饰的函数名字,返回经过修饰后函数的名字;内层函数(闭包)负责修饰被装饰函数。从上面这段描述中我们需要记住装饰器的几点属性,以便能更好的理解:
(1)实质:是一个函数
(2)参数:是要装饰的函数名(并非函数调用)
(3)返回:是装饰完的函数名(并非函数调用)
(4)作用:为依据存在的对象添加额外的功能
(5)特点:不需要对对象做任何代码上的变动
Python装饰器的应用场景,比如:插入日志、性能测试、事务处理、权限校验等。装饰器是解决这类问题的绝佳设计。并且从引入中的例子中我们也可以归纳出:装饰器最大的作用是对于我们已经写好的程序,我们可以抽离出一些雷同的代码组件多个特定功能的装饰器,这样我们就可以针对不同的需求去使用特定的装饰器,这时因为源码去除了大量泛化的内容而使用源码具有更加清晰的逻辑。
2.1 函数的装饰器
类方法的函数装饰器和函数的函数装饰器一样
import time
def time_decrator(func):
def wrapper(*args,**kwargs):
start_time = time.time()
func(*args,**kwargs)
end_time = time.time()
print(end_time-start_time)
return wrapper
@time_decrator
def func():
time.sleep(0.8)
func() #函数调用
class Method(object):
@time_decrator
def func(self):
time.sleep(0.8)
p1 = Method()
p1.func() # 类中的函数调用
2.2类装饰器
前面我们提到的都是让 函数作为装饰器去装饰其他的函数或者方法,那么可不可以让 一个类发挥装饰器的作用呢?答案肯定是可以的,一切皆对象嚒,函数和类本质没有什么不一样。类的装饰器是什么样子的呢?
class Decorator(object):
def __init__(self, f):
self.f = f
def __call__(self):
print("decorator start")
self.f()
print("decorator end")
@Decorator
def func():
print("func")
func()
这里有注意的是:__call__()是一个特殊方法,它可将一个类实例变成一个可调用对象:
2.3装饰器链
一个python函数也可以被多个装饰器修饰,要是有多个装饰器时,这些装饰器的执行顺序是怎么样的呢?
2.4 Python装饰器库 - functools
def decorator(func):
def inner_function():
pass
return inner_function
@decorator
def func():
pass
print(func.__name__)
# 输出: inner_function
上述代码最后执行的结果不是 func,而是 inner_function!这表示被装饰函数自身的信息丢失了!怎么才能避免这种问题的发生呢?
可以借助functools.wraps()函数:
from functools import wraps
def decorator(func):
@wraps(func)
def inner_function():
pass
return inner_function
@decorator
def func():
pass
print(func.__name__)
#输出: func
2.5带参数的函数装饰器
上面讲到的所有装饰器都是不带参数的,如果我们想写一个带参数的装饰器呢
来想想这个问题,难道@wraps不也是个装饰器吗?但是,它接收一个参数,就像任何普通的函数能做的那样。那么,为什么我们不也那样做呢? 这是因为,当你使用@my_decorator语法时,你是在应用一个以单个函数作为参数的一个包裹函数。记住,Python里每个东西都是一个对象,而且这包括函数!记住了这些,我们可以编写一下能返回一个包裹函数的函数。
在函数中嵌入装饰器
from functools import wraps
def logit(logfile='out.log'):
def logging_decorator(func):
@wraps(func)
def wrapped_function(*args, **kwargs):
log_string = func.__name__ + " was called"
print(log_string)
# 打开logfile,并写入内容
with open(logfile, 'a') as opened_file:
# 现在将日志打到指定的logfile
opened_file.write(log_string + '\n')
return func(*args, **kwargs)
return wrapped_function
return logging_decorator
@logit()
def myfunc1():
pass
myfunc1()
# Output: myfunc1 was called
# 现在一个叫做 out.log 的文件出现了,里面的内容就是上面的字符串
@logit(logfile='func2.log')
def myfunc2():
pass
myfunc2()
# Output: myfunc2 was called
# 现在一个叫做 func2.log 的文件出现了,里面的内容就是上面的字符串
2.6带参数的类装饰器
我们现在使用装饰器类来实现上面的logit装饰器
from functools import wraps
class logit(object):
def __init__(self,logout='file.log'):
self.logout = logout
def __call__(self, func):
@wraps(func)
def logit_decrator(*args,**kwargs):
log_string = func.__name__ + " was called"
print(log_string)
with open(self.logout,"a") as opened_file:
opened_file.write(log_string)
self.notify()
return func(*args,**kwargs)
return logit_decrator
def notify(self):
print("打印日志信息")
@logit()
def func1():
print("test1")
@logit(logout="file2.log")
def func2():
print("test2")
func1()
func2()
这个实现有一个附加优势,在于比嵌套函数的方式更加整洁,而且包裹一个函数还是使用跟以前一样的语法.
现在,我们给 logit 创建子类,来添加 email 的功能(虽然 email 这个话题不会在这里展开)。
class email_logit(logit):
def __init__(self,email="[email protected]",*args,**kwargs):
self.email = email
super(email_logit,self).__init__(*args,**kwargs)
# 赋写方法,添加发送邮件的功能
def notify(self):
print("发送邮件")
@email_logit(logout="file3.log")
def func3():
print("test3")
func3()
从现在起,@email_logit 将会和 @logit 产生同样的效果,但是在打日志的基础上,还会多发送一封邮件给管理员。