装饰器(Decorators)多是Python中最难掌握的概念之一了,也是最具Pythonic特点的技巧,深刻理解并应用装饰器,你会更加感慨——人生苦短,我用Python。mysql
在解释什么是装饰器以前,咱们有必要回顾一下Python中的一些思想和概念。咱们都知道,Python是一门面向对象的语言,Python基本思想就是一些皆对象,数据类型是对象、类是对象、类实例也是对象……对于接下来咱们要说的装饰器而言,最重要的是,函数也是对象!sql
你没看错,函数也和数据类型等概念同样,都是对象,那么既然数据类型能够进行赋值操做,那么函数是否是也能够赋值呢?固然能够!编程
def do_something(): print('完成一些功能') if __name__ == '__main__': do = do_something do()
输出:函数
完成一些功能工具
看,本来咱们定义的函数名是do_something,但咱们把函数赋值给do后,也能够经过do()调用函数。不只如此,函数当作参数传递给其余函数:spa
def do_something(): print('正在完成功能') def func(f): f() if __name__ == '__main__': func(do_something)
输出:code
完成一些功能orm
正是由于Python中函数既能够赋值给其余变量名,也能够当作参数参数进其余函数,因此,上面的代码没有任何问题。对象
固然,毕竟被称呼为函数,有别有变量之类的概念,因此它也有本身的特性,例如在函数内部,还能够定义函数,甚至做为返回值返回:blog
def func(): def inner_func(): print('我是內部函数') return inner_func if __name__ == '__main__': fun1 = func() fun1()
输出结果:
我是內部函数
咱们来总结一下函数的这几个特性:
能够赋值给其余变量;
能够做为参数传递给其余函数;
能够在内部定义一个函数;
能够当作返回值返回。
不要疑惑我为何要着重说明Python中函数的这几个特性,由于装饰器中正是这几个特性为基石。为何这么说呢?从本质上来讲,装饰器就是一个函数,它也具备咱们上面说到的4个特性,并且充分利用了这4个特性。装饰器接受一个普通函数做为参数,并在内部定义了一个函数,在这个内部函数中实现了一些功能,并调用了传递进来的函数,最后将内部函数做为返回值返回。若是一个函数把这个步骤全走了一遍,咱们就能够认为,这个函数是一个装饰器函数,或者说装饰器。
咱们来动手写一个装饰器:
def func(f): def inner_func(): print('{}函数开始运行……'.format(f.__name__)) f() print('{}函数结束运行……'.format(f.__name__)) return inner_func def do_something(): print('正在完成功能') if __name__ == '__main__': do_something = func(do_something) do_something()
输出结果:
do_something函数开始运行……
正在完成功能
do_something函数结束运行……
在上面代码中,咱们将do_something方法做为参数传递给func方法,在func方法内部咱们定义了一个inner_func方法,并在这个方法中添加了函数开始执行和结束执行的提示,在func方法最后,咱们将inner_func做为参数返回,更加值得一说的是,咱们从新将func函数的返回值赋给了do_something,因此,最后一行咱们再次调用的do_something方法已经再也不是最初的do_something函数,而是func方法内定义的inner_func函数,因此最后执行do_something函数时,会有函数开始执行和结束执行的提示功能,然后面再调用do_something函数时,也都直接使用do_something()。
正如你所预料,func函数就是一个装饰器,捋一捋你会发现,func函数把咱们上面说过的全部特性、步骤全实现了。
若是你在别处见过Python装饰器的使用,你可能会疑惑,咱们实现的func装饰器跟你见过的装饰器不都同样,由于在实际应用中,装饰器大可能是与“@”符号结合起来使用。其实“@”符号所实现的功能就是 do_something = func(do_something)这行代码的功能。来,咱们尝试一下使用“@”:
def func(f): def inner_func(): print('{}函数开始运行……'.format(f.__name__)) f() print('{}函数结束运行……'.format(f.__name__)) return inner_func @func def do_something(): print('正在完成功能') if __name__ == '__main__': do_something()
输出结果:
do_something函数开始运行……
正在完成功能
do_something函数结束运行……
以前咱们知道,func函数就是一个装饰器,因此使用“@”符号时,咱们只须要在被装饰的函数前面加上“@func”就表示该函数被func装饰器装饰,在须要处直接调用do_something函数便可。
在上面代码中,咱们写了一个装饰器func,在这个装饰器中,使用装饰器的好处就已经初见端倪了。
(1)能够在不对被装饰函数作任何修改的前提下,给被装饰函数附加上一些功能。使用@func对do_something函数进行装饰时,咱们没有对do_something函数的代码作什么的改变,可是被装饰后的do_something函数却多了开始运行和结束运行的功能。
(2)不改变被装饰函数的调用方式。在被装饰前,咱们经过do_something()调用这个函数,被装饰后,仍是经过do_something()调用这个函数。
(3)代码更加精简。在上面代码中,咱们只是用@func装饰了do_something一个函数,可是若是有多个函数须要添加开始运行和结束运行的提示功能,若是不用装饰器,那么就须要对每个函数进行修改,则工做量和须要修改的代码量……用了装饰器以后,只须要在须要添加这一功能的函数前面添加@func就能够了。
一言以盖之,装饰器能够在不改变原函数调用方式和代码状况下,为函数添加一些功能,使代码更加精简。
咱们在写一个装饰器来加深一下理解。相比你们都写过代码来统计一个函数的运行时间的功能,咱们使用装饰器来实现一下这个功能:
import time def timmer(f): def inner_func(): start_time = time.time() f() end_time = time.time() print('{}函数运行消耗时间为:{}'.format(f.__name__, end_time-start_time)) return inner_func @timmer def do_something(): print('do_something函数运行……') time.sleep(1) if __name__ == '__main__': do_something()
输出结果:
do_something函数运行……
do_something函数运行消耗时间为:1.000662088394165
在上面例子中,咱们首先定义了一个计时装饰器timmer,当须要统计某个函数运行时间时,只须要在函数定义时,在前面添加一行写上@timmer便可,例如上面对do_something函数运行时间进行统计,对do_something原来要实现什么功能就继续实现这一功能,原来代码该怎样还怎样,该怎么调用还怎么调用。因此说,使用装饰器能够在不改变原函数代码和调用方式的状况下附加上其余功能。
若是你阅读到了这里,我想你对装饰器已经有了初步的理解。接下来,咱们继续聊一聊更加复杂的装饰器。
咱们上面写的两个装饰器所装饰的do_something函数是没有返回值的,但大多数函数可都是有返回值的。针对有返回值的函数,装饰器该怎么写呢?
def func(f): def inner_func(): print('{}函数开始运行……'.format(f.__name__)) ret = f() print('{}函数结束运行……'.format(f.__name__)) return ret # 这里返回值 return inner_func @func def do_something(): print('正在完成功能') return '我是返回值' if __name__ == '__main__': ret = do_something() print(ret)
输出结果:
do_something函数开始运行……
正在完成功能
do_something函数结束运行……
我是返回值
咱们知道,被装饰后的do_something函数其实再也不是最初的do_something函数,而是装饰器内部定义的inner_func函数,因此,被装饰的函数的返回值只须要经过装饰器内部定义的inner_func函数返回返回便可便可。有点绕,不过对着上面的代码应该好理解。
对于装饰器,咱们要深入理解一件事:以上面的装饰器func和被装饰函数do_something为例,被装饰后的do_something函数已经再也不是原来的do_something函数,而是装饰器内部的inner_func函数。这句话我已经在上文中我已经不止提过一次,由于真的很重要。若是被装饰的函数有参数(加入参数为name),咱们仍是经过do_something(name)的方式传递传输,不过,既然咱们最终调用的时候,经过do_something实质调用的inner_func函数,那么在定义装饰器是,定义的inner_func函数时也须要接受参数。
def func(f): def inner_func(name): print('{}函数开始运行……'.format(f.__name__)) ret = f(name) print('{}函数结束运行……'.format(f.__name__)) return ret return inner_func @func def do_something(name): print('你好,{}!'.format(name)) return '我是返回值' if __name__ == '__main__': ret = do_something('姚明') print(ret)
输出结果:
do_something函数开始运行……
你好,姚明!
do_something函数结束运行……
我是返回值
一个装饰器可用于装饰千千万万个函数,则千千万万个函数参数状况可能各不相同,有的没有参数,有的可能多个参数,甚至还有关键字参数,对于这参数状况不一样的函数,咱们不可能为每一个函数都写一个func装饰器,那怎么办呢?
Python中提供了*args, **kwargs这种机制来接受任意位置的位置参数和关键字参数,参数前面带*表示接受任意个数位置参数,接收到的全部位置参数存储在变量名为args的元组中,带**表示接受任意个数的关键字参数,接收到的全部关键字参数以字典的形式参数在变量名为kwargs的字典中。
当咱们知道只有位置参数,但不知道有多少个位置参数是func装饰器能够这么写:
def func(f): def inner_func(*name): print('{}函数开始运行……'.format(f.__name__)) ret = f(*name) print('{}函数结束运行……'.format(f.__name__)) return ret return inner_func @func def do_something(name): print('你好,{}!'.format(name)) @func def do_something_2(name_1, name_2): print('你好,{}!'.format(name_1)) print('你好,{}!'.format(name_2)) @func def do_something_3(*name): for n in name: print('你好,{}!'.format(n)) if __name__ == '__main__': do_something('姚明') print('-------------------------------') do_something_2('姚大明', '姚小明') print('-------------------------------') do_something_3('姚一明', '姚二明', '姚三明', '姚四明')
输出结果:
do_something函数开始运行……
你好,姚明!
do_something函数结束运行……
-------------------------------
do_something_2函数开始运行……
你好,姚大明!
你好,姚小明!
do_something_2函数结束运行……
-------------------------------
do_something_3函数开始运行……
你好,姚一明!
你好,姚二明!
你好,姚三明!
你好,姚四明!
do_something_3函数结束运行……
上面例子定义func装饰器时,咱们用*name来接受任意个数的位置参数,可别觉得只能用*args,args只是一个变量名,只不过约定俗成,用的多一些,实际开发时你爱取啥名就用啥名,对于这个知识点再也不多说,毕竟本篇主角是装饰器。咱们继续装饰器内容!
当咱们知道只有关键字参数,殊不知道参数个数时,能够func装饰器这么写:
def func(f): def inner_func(**name): print('{}函数开始运行……'.format(f.__name__)) ret = f(**name) print('{}函数结束运行……'.format(f.__name__)) return ret return inner_func @func def do_something(name='无名氏'): print('你好,{}!'.format(name)) @func def do_something_2(name_1='无名氏', name_2='无名氏'): print('你好,{}!'.format(name_1)) print('你好,{}!'.format(name_2)) @func def do_something_3(**name): for n in name.keys(): print('你好,{}!'.format(name[n])) if __name__ == '__main__': do_something(name='姚明') print('-------------------------------') do_something_2(name_1='姚大明', name_2='姚小明') print('-------------------------------') do_something_3(name_1='姚一明', name_2='姚二明', name_3='姚三明', name_4='姚四明')
输出结果:
do_something函数开始运行……
你好,姚明!
do_something函数结束运行……
-------------------------------
do_something_2函数开始运行……
你好,姚大明!
你好,姚小明!
do_something_2函数结束运行……
-------------------------------
do_something_3函数开始运行……
你好,姚一明!
你好,姚二明!
你好,姚三明!
你好,姚四明!
do_something_3函数结束运行……
事实上,大多数状况下,咱们对被装饰函数是一无所知的——咱们不知道有多少个位置参数、多少个关键字参数,甚至对有没有位置参数、关键字参数都不知道,这时候,咱们就只能*args和**kwargs齐上阵了:
def func(f): def inner_func(*name1, **name2): print('{}函数开始运行……'.format(f.__name__)) ret = f(*name1, **name2) print('{}函数结束运行……'.format(f.__name__)) return ret return inner_func @func def do_something(name): print('你好,{}!'.format(name)) @func def do_something_2(name_1, name_2='无名氏'): print('你好,{}!'.format(name_1)) print('你好,{}!'.format(name_2)) @func def do_something_3(*name1, **name2): for n in name1: print('你好,{}!'.format(n)) for n in name2.keys(): print('你好,{}!'.format(name2[n])) if __name__ == '__main__': do_something(name='姚明') print('-------------------------------') do_something_2(name_1='姚大明', name_2='姚小明') print('-------------------------------') do_something_3('姚一明', '姚二明', '姚三明', name_4='姚四明')
输出结果:
do_something函数开始运行……
你好,姚明!
do_something函数结束运行……
-------------------------------
do_something_2函数开始运行……
你好,姚大明!
你好,姚小明!
do_something_2函数结束运行……
-------------------------------
do_something_3函数开始运行……
你好,姚一明!
你好,姚二明!
你好,姚三明!
你好,姚四明!
do_something_3函数结束运行……
咱们上面写的装饰器都没有参数,或者说只有一个自带参数,也就是被装饰函数f。其实,装饰器也是能够有其余参数的,这样的装饰器更加灵活。咱们经过实例来讲明:如今咱们要对上面的func装饰器进行改进,须要作到灵活控制装饰器是用中文输出仍是用英文输出,代码以下。
def language(lang='中文'): # 这里带参数 def func(f): # 往里嵌套了一层 def inner_func(*name1, **name2): if lang=='中文': print('{}函数开始运行……'.format(f.__name__)) else: print('The function of {} starts runging…'.format(f.__name__)) ret = f(*name1, **name2) if lang=='中文': print('{}函数结束运行……'.format(f.__name__)) else: print('The function of {} ends runging…'.format(f.__name__)) return ret return inner_func return func @language('中文') def do_something(name): print('你好,{}!'.format(name)) @language('English') def do_something_2(name): print('你好,{}!'.format(name)) if __name__ == '__main__': do_something(name='姚明') print('-------------------------') do_something_2(name='姚明')
输出以下:
do_something函数开始运行……
你好,姚明!
do_something函数结束运行……
-------------------------
The function of do_something_2 starts runging…
你好,姚明!
The function of do_something_2 ends runging…
能够看到,经过装饰器带参数的方式,咱们只须要在定义被装饰函数时,指定装饰器参数,就能够灵活控制每一个被装饰函数提示的语言。
固然,必须认可,装饰器带参数后,看起来更加复杂,须要多嵌套一层函数,由最外层的函数接受参数,里层函数才是真正的装饰器。使用装饰器时,会首先运行带参数的最外层函数,返回装饰器,这一步Python会自动帮咱们完成。因此,带参数的装饰器甚至能够这么使用:
h = language('中文') @h def do_something(name): print('你好,{}!'.format(name))
装饰器也是能够多层嵌套使用的,也就是说,一个函数能够经过是被多个装饰器所装饰,执行顺序是从下到上的优先顺序加载装饰:
# -*- coding: utf-8 -*- import time print(1) def func(f): print(2) def inner_func(*name1, **name2): print('{}函数开始运行……'.format(f.__name__)) f(*name1, **name2) print('{}函数结束运行……'.format(f.__name__)) print(3) return inner_func print(4) def timmer(f): print(5) def inner_timmer(*args, **kwargs): print('开始计时……') start_time = time.time() f(*args, **kwargs) end_time = time.time() print('开始结束……') time_cost = end_time - start_time print('{}函数运行时长为:{}秒'.format(f.__name__, time_cost)) print(6) return inner_timmer print(7) @func @timmer def do_something(name): time.sleep(1) print('你好,{}!'.format(name)) print(8) def do_something_2(name): time.sleep(1) print('你好,{}!'.format(name)) if __name__ == '__main__': print(9) do_something(name='姚明') print('-------------------------') func(timmer(do_something_2))(name='姚明') # 执行效果与上面使用了@符号的do_something同样
输出结果:
1
4
7
5
6
2
3
8
9
inner_timmer函数开始运行……
开始计时……
你好,姚明!
开始结束……
do_something函数运行时长为:1.0004358291625977秒
inner_timmer函数结束运行……
-------------------------
5
6
2
3
inner_timmer函数开始运行……
开始计时……
你好,姚明!
开始结束……
do_something_2函数运行时长为:1.000028133392334秒
inner_timmer函数结束运行……
在上面代码中,咱们同时用func和timmer两个装饰器来装饰do_something,从运行结果中能够看出,两个装饰器都发挥了做用。同时,为了方便你们理解,咱们使用不带@符号的来使用两个装饰器装饰,二者运行结果是同样的,结合代码中的输出标记,咱们来分析一下装饰器的执行过程:开始运行后,1->4->7这几个过程我相信你们都是能够理解的,到了位置7后,遇到了@符号标识的装饰器,并且是多层的,两个@装饰器至关于func(timmer(do_something_2)),因此是先执行timmer函数获取返回值做为参数传递给func,因此有了7以后是5->6,timmer函数返回值是inner_timmer函数,这时候就至关于func(inner_timmer),因此程序退出timmer函数后进入func函数,就有了2->3,从func函数返回后,继续向下执行遇到位置8,而后就进入了主函数运行,因此是8->9,此时的函数是被装饰过的,本质已是func函数返回的inner_func函数了,因此最终在主函数中执行do_something时执行的是inner_func方法,因此先输出了func装饰器的函数开始提示,而后才是timmer装饰器的计时开始提示。
嗯,有点复杂!
经过上面的介绍,我想你已经知道,@符号是装饰器的一个表示,或者说一个装饰器语法糖,当使用@时,例如@A,这种语法糖会自动将被装饰函数f做为参数传递给A函数,而后将A函数的返回值从新f给f这个变量名,这就是@语法糖帮咱们作的事情,归纳来讲就是f=A(f)()。
咱们如今发散一下思惟,假设A若是是一个类会怎么样呢?咱们知道当A是一个类时,A()表示调用A类的构造函数__init__实例化一个A类对象,那么A(f)就表示将函数f做为参数传递给A类的构造方法__init__来构造一个A类实例对象,若是使用了@符号,那么这种语法机制就还会在A(f)后面加一个括号变成A(f)(),这是什么鬼?执行一个类实例对象?不过话要说回来,若是A(f)()这种结构要是没有问题,可以成功执行,是否是就意味着Python中类也能够成为装饰器了呢?确实如此。咱们先看看经过A()()执行一个类实例对象会怎么样:
class A(object): def __init__(self): print('实例化一个A类对象') def __call__(self, *args, **kwargs): print('__call__方法被调用……') if __name__ == '__main__': A()()
输出结果:
实例化一个A类对象
__call__方法被调用……
看到没,经过A()()执行一个类实例对象时,执行的是A类内部的__call__方法。那么若是用A用做装饰器时,@A返回的就是A类内部定义的__call__方法,至关于函数装饰器func内的inner_func。来,咱们感觉一下类装饰器:
class A(object): def __init__(self, f): print('实例化一个A类对象……') self.f = f def __call__(self, *args, **kwargs): print('{}函数开始运行……'.format(self.f.__name__)) self.f(*args, **kwargs) print('{}函数结束运行……'.format(self.f.__name__)) @A def do_something(name): print('你好,{}!'.format(name)) if __name__ == '__main__': do_something('姚明')
输出结果:
实例化一个A类对象……
do_something函数开始运行……
你好,姚明!
do_something函数结束运行……
若是类装饰器带参数呢?这时候,类装饰器的参数也可定是经过@A(t)的形式传递,这时候,由于@语法糖会自动加括号的缘由,结构就编程这样A(t)(),A(t)是类实例对象,A(t)()就是__call__方法,因此,@语法糖会把被装饰函数f做为参数传递给__call__方法,被装饰函数的参数须要在__call__内部定义一个函数来接受。也就是话说,定义类装饰器时,装饰器的参数经过__init__构造方法接收,被装饰函数的做为参数被__call__方法接收,。(这段子很绕,有点烧脑,没点儿Python基础还真很差理解,语言表达能力优先感受要枯竭了)
import time class A(object): def __init__(self, t): print('实例化一个A类对象……') self.t = t def __call__(self, f): def inner_A(*args, **kwargs): print('延迟{}秒后开始执行……'.format(self.t)) time.sleep(self.t) print('{}函数开始运行……'.format(f.__name__)) f(*args, **kwargs) print('{}函数结束运行……'.format(f.__name__)) return inner_A @A(1) def do_something(name): print('你好,{}!'.format(name)) if __name__ == '__main__': do_something('姚明')
输出结果:
实例化一个A类对象……
延迟1秒后开始执行……
do_something函数开始运行……
你好,姚明!
do_something函数结束运行……
不管是函数装饰器仍是类装饰器,原理上是同样的,区别在于若是A是函数,A()是直接调用函数,而A是类时,A()是实例化,经过A()()是调用A类的__call__方法。
@property,@setter,@deleter这三个装饰器提供了更加友好的方式来获取、设置或删除类中的属性。@property装饰器所装饰的函数能够像访问属性同样调用函数,注意,@property装饰器必须先于@setter,@deleter使用,且三者说装饰的函数必须同名。
class A(object): def __init__(self, v): print('实例化一个A类对象……') self.__value = v @property def value(self): print('取值时被调用') return self.__value @value.setter def value(self, value): print('赋值时被调用') self.__value = value @value.deleter def value(self): print('删除值时被调用……') del self.__value if __name__ == '__main__': a = A(123) print('-------------') print('__value的值为:{}'.format(a.value)) print('-------------') a.value = 234 print('__value的值为:{}'.format(a.value)) print('--------------') del a.value print('__value的值为:{}'.format(a.value))
输出为:
Traceback (most recent call last):
实例化一个A类对象……
-------------
取值时被调用
File "E:/WorkProjectCode/study_pymysql/study_secorators/test2.py", line 33, in <module>
__value的值为:123
-------------
print('__value的值为:{}'.format(a.value))
赋值时被调用
取值时被调用
__value的值为:234
File "E:/WorkProjectCode/study_pymysql/study_secorators/test2.py", line 11, in value
--------------
return self.__value
删除值时被调用……
取值时被调用
AttributeError: 'A' object has no attribute '_A__value'
运行产生异常,由于最后访问了已经删除了的元素。
在一个类中,若是一个方法被@classmethod所装饰,就表明该方法与类绑定,而不是与实例对象绑定,第一个参数cls由Python机制自动传递,表示类自己。
class A(object): @classmethod def f(cls): print('当前类名为:{}'.format(cls.__name__)) if __name__ == '__main__': A.f()
输出结果:
当前类名为:A
被@staticmethod所装饰的方法为静态方法,静态方法通常使用场景就是和类相关的操做,可是又不会依赖和改变类、实例的状态,好比一些工具方法。
import time class A(object): @staticmethod def f(): time_now = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) return time_now if __name__ == '__main__': print(A.f()) print(A().f())
输出:
2019-08-16 19:29:32
2019-08-16 19:29:32