装饰器

装饰器

  假设现在有一个网站,所有内容都是开放的,用户不需要登录认证,就可以访问所有的网站内容,突然有一天,网站增加了付费的内容,要求用户必须要登录之后,才能进行访问,这个时候需要对环境进行修改。当然,我们可以在每一个网站的地方,增加登录校验,也能实现这种功能,假如要修改的地方特别多,这个时候,一个一个修改就比较麻烦,要保证在原有函数/方法的调用方式不变的情况下,增加这些功能,这个时候就用到了装饰器。

  装饰器,就是在保证原有函数/方法的源代码不变,且调用方式不变的情况下,为函数/方法增加新的功能。

  装饰器的生成,需要提前了解三个概念:

  1. 函数即变量
  2. 高阶函数
  3. 嵌套函数

  下面对这三个概念,分别进行解释。

  所谓的函数即变量,意思就是说,在函数的定义过程中,会在内存中生成一个内存对象,有一个内存地址,而函数的名称,就是一个变量,指向该内存对象。这就是所谓的函数即变量。和定义普通的变量一样,比如x=1,就是在内存中有一个内存对象1,然后x是一个变量,指向1在内存中的地址,当调用x的时候,直接调用的就是1在内存中的地址。函数也是一样的,定义函数是有函数名的,变量名就相当于是函数名。当打印该函数名时,得到的就是一个内存地址,只有调用函数名的时候,才会执行该函数。

>>> def abc():

...     print(123)

...

>>> abc

<function abc at 0x000000C4E4023E18>

>>> abc()

123

  内存中对象的释放,有两个条件,一是当程序结束运行后,内存会自动将该程序相关的内存对象删除,释放内存空间;而是当没有变量指向该内存对象时,就会删除该内存对象,释放内存空间。第二个条件和硬盘删除数据的方式很像,在硬盘中删除一个数据,实际上删除的是一个文件和硬盘上数据的映射关系,硬盘上同一个数据,可能存在多个文件映射关系,多个文件可以指向硬盘上的同一份数据,当删除某一个文件时,实际上只是删除了一个映射关系,而当系统中没有任何文件与该硬盘数据有映射关系时,系统就会在硬盘上删除该数据(链接的关系)。实际上是一样的。

  在python 中,实际上是有一种函数,称为匿名函数,这种函数是没有函数名的,但是要调用的时候,必须为该匿名函数赋值一个变量,实际上还是定义了一个变量。这种解释也是可以说的通的,匿名函数一旦运行完,就会从内存中删除,而变量不会,除非是删除了该变量,或者是程序结束了运行。

所以函数即变量这种说法,还是比较好理解的。

 

下面来解释第二个条件:高阶函数

高阶函数有两种定义:

  1. 当调用函数A执行时,传入一个实参B,如果实参B是一个函数,则函数A就可以称为是一个高阶函数,函数A的定义时,形参是一个函数(内存对象地址)
  2. 当函数A定义返回值时,返回的是一个函数B(内存对象地址)时,函数A也可以成为是一个高阶函数。

基于以上两种说法,我们可以来解释一下下面的代码:

def func1(func):
    print("in func1")
    func()

def func2():
    print("in func2")

func1(func2)

    实际上,func1的参数,是func2的内存地址,直接将func2的内存地址传入到func1中,然后调用func2,这种情况下,func1就可以成为是一种高阶函数。

    还有一种高阶函数的表现形式,是返回值,返回一个函数的内存地址。

def func3():
    print("in func3")
    return func3

print(func3)
a = func3()
print(a)

    这个时候,打印的a和打印的func3的结果是一样的,都是返回的是一个内存地址,func3也可以成为是一种高阶函数。

 

    第三个概念是嵌套函数

    所谓的嵌套函数,就是在一个函数的函数体中,去定义另一个或多个函数,这样的外层函数,就成为是嵌套函数。比方下面的这种:

def func4():
    def func5():
        print("in func5")
    return func5

func5 = func4()
print(func5)
func5()

<function func4.<locals>.func5 at 0x000000A0AEF05730>

in func5

    这个时候的func4就是一个嵌套函数,嵌套了func5,而且fun4有一个返回值,返回的是fun5的内存地址,所以当在外层运行func5时,实际上就是执行了被嵌套的func5.

 

    以上就是说明了装饰器的三个概念,下面来说一下装饰器,实际上的装饰器就是高阶函数和嵌套函数组合而成,用到了函数即变量的概念。

我们先假设一下环境,当前环境中,有两个函数,分别为decorated和funny两个函数,现在我们要计算一下,每个函数的执行时间,在不改变两个函数的源代码和调用方式的情况下,为两个函数加上该功能。

import time
def timer(func):
    def deco(*args,**kwargs):
        start_time = time.time()
        func(*args,**kwargs)
        stop_time = time.time()
        print("the func run time is %s"%(stop_time - start_time))
    return deco
@timer#decoed = timer(decoed),
再次调用decoed时,实际上是在调用deco,因为timer的返回值是deco
def decoed():
    time.sleep(2)
    print("this is in decorated")
@timer
def funny(name):
    print("funny name is ",name)

decoed()
funny('bobo')

    在上述代码中,由于是装饰器,所以在被装饰的两个函数定义之前,需要增加装饰器代码,使用装饰器直接是@decorator即可。

    而定义decorator是定义了一个嵌套的高级函数timer,在timer中又定义了一个内层函数deco,而整个timer函数的返回值,是deco,也就是deco函数在内存中的地址。

    在执行被装饰函数decoed和funny的时候,首先是有将原有函数变量,重新赋值,将原有函数,作为参数,传递给装饰器函数中,也就是将decoed和funny,内存对象地址,传递给timer函数中,对应这timer的参数func;

    而timer只是一个定义函数,只是定义了一个deco函数,并且是返回了一个deco的内存地址。

    当执行外层函数decoed和funny时,直接是执行了deco,也就是调用了deco函数,在deco函数中会去执行func(),也就是会调用之前的装饰器参数decoed和funny,这个时候,就会执行deco中的代码。

    

  • 由于被装饰的函数可能有多个参数或参数组要输入,所以在装饰器函数中,也需要定义参数和参数组,实际上,被装饰的函数的调用,也就是装饰器函数的内层函数的调用,只要在内层函数中输入参数组和关键字参数组,*args和**kwargs,在装饰函数调用时,也传入该参数组,就可以实现被装饰函数的参数传入。
  • 当被装饰的函数,有返回值时,这个时候,如果使用了装饰器,也需要有一个返回值,在上面的函数例子中,由于是要打印函数运行时间,不能直接return func(),可以通过将func()的结果,赋值给一个变量,然后在内层函数的定义最后,返回该变量。如下所示。
  • import time
    def timer(func):
        def deco(*args,**kwargs):
            start_time = time.time()
            result = func(*args,**kwargs)
            stop_time = time.time()
            print("the func run time is %s"%(stop_time - start_time))
            return result
        return deco

    这种情况下,如果被装饰的函数有返回值,被装饰器装饰之后,依然还是会有返回值的。如果不是用return返回,则被装饰器装饰的函数,就没有了返回值,这会影响整个函数的调用的。

 

    还有一种情况,更复杂,当根据被装饰的的函数的不同,需要执行不同的装饰代码,这种情况就比较特殊了,这是要在利用装饰器装饰函数的时候,指定不同的参数,也就是在装饰器上指定参数。由于装饰器现在是一个两层的函数,外层函数的参数是被装饰的函数的内存地址,内层函数是被装饰函数的参数,如果要在装饰器上再加一系列的参数,则需要将装饰器再次修改,修改为一个三层函数,其中最外层的参数是装饰器的条件参数,第二层的参数是被装饰的函数,最内层的参数是被装饰的函数的参数。这样一个三层的嵌套函数,就可以实现该复杂功能。

    使用场景是这样的,比如在网络设备中,有line vty、line console和management三种登录方式,第一个需求是这三种方式,都要做认证,这个时候,我们可以写一个auth的装饰器,然后在这三个登录方式这里装饰,装饰这三个函数;第二个需求就比较复杂,我们希望通过line console和management登录的用户,认证方式为local本地数据库,而通过line vty方式登录设备的用户,希望使用radius认证方式,这个情况,就会用到我们所说的三层嵌套的装饰器。

    我们先写一个两层,实现认证的装饰器代码。

def auth(func):
    def deco_inner(*args,**kwargs):
        user,passwd = 'admin','admin123'
        username = input("username:").strip()
        password = input("password:").strip()
        if username == user and password == passwd:
            func(*args,**kwargs)
        else:
            print("Invalid username or password,please check again")
            # exit()
    return deco_inner

@auth
def console():
    print("welcome to login by console 0!")
@auth
def vty():
    print("welcome to login by vty 0!")
@auth
def management():
    print("welcome to login by management!")

console()
vty()
management()

    在这个装饰器函数auth中,定义了一个内层装饰函数deco_inner,在deco_inner中做了一个简单的用户认证,需要用户先输入用户名和密码,然后和已经存在的用户名和密码进行校验,当校验通过后,允许登录,执行相关的登录提示语;如果认证不通过,则直接跳出。执行结果如下图所示:

    现在我们来实现第二个需求,当用户通过management和console来登录时,使用本地数据库进行认证,如果用户通过vty来登录时,需要用radius来进行认证。现在还不会radius的认证部分,简单设计一下,沉睡2秒后返回成功。代码如下:

import time
def auth(type):
    def deco_outer(func):
        def deco_inner(*args,**kwargs):
            username = input("username:").strip()
            password = input("password:").strip()
            if type == 'local':
                user,passwd = 'admin','admin123'
                if username == user and password == passwd:
                    func(*args,**kwargs)
                else:
                    print("Invalid username or password,please check again")
            elif type == 'radius':
                print("Connecting to the radius server to authentication,Please wait...")
                time.sleep(2)
                print("authentication is OK")
                func(*args,**kwargs)
        return deco_inner
    return deco_outer

@auth(type='local')
def console():
    print("welcome to login by console 0!")
@auth(type='radius')
def vty():
    print("welcome to login by vty 0!")
@auth(type='local')
def management():
    print("welcome to login by management!")

console()
vty()
management()

    运行一下,结果如图:

    在这个三层的装饰器函数中,由于在装饰器装饰其他函数时,有输入参数值type,所以在定义这个三层装饰器时,最外层的输入参数,就是type。

    实际上这个代码中@auth(type='local'),是将console = auth(type='local')(console)

    这种情况下,实际上对被装饰的函数来说,也是一个重新定义的过程,只不过相比于普通装饰器来说,该装饰器还有外面的一层,需要对装饰器进行判断。

posted @ 2018-08-06 20:49  波波波波波  阅读(136)  评论(0编辑  收藏  举报