Python的装饰器

 

概要

  1. 什么是装饰器
  2. 怎么使用装饰器
  3. 使用场景
  4. 如何给装饰器传递参数

1. 什么是装饰器

能够对其他函数的功能进行增强。也就是函数代码功能重用。它是一个设计模式。需要注意的是:

  1. 装饰器本身是一个函数
  2. 增强被装饰函数的功能,同时被增强的函数还不能感受到自己被增强了或者说被修了
  3. 装饰器需要接受一个函数对象作为参数以对其进行包装
  4. 不会改变被调用函数的调用方式

2. 怎么使用装饰器

要理解装饰器你需要理解如下内容:

  1. 函数即变量  def 定义一个函数就等于把函数体赋值给了一个变量(函数名)
  2. 高阶函数 满足两个条件 把一个函数名当做形参传入另外一个函数、返回值中包含函数名
  3. 嵌套函数 函数中嵌套函数

装饰器=高阶函数+函数嵌套

2.1 函数嵌套

#!/usr/bin/env python
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com

def fun3():
    print("in the fun3")
    def fun4():
        print("in the fun4")
    fun4()


if __name__ == '__main__':
    fun3()

执行结果

在fun3中定义一个fun4函数,执行fun3,同时也会执行fun4。这个比较好理解就是函数中嵌套一个函数,对于嵌套的函数(这里是fun4)你可以在你调用的函数(这里是fun3)中执行,也可以把这个fun4返回。

2.2 高阶函数

#!/usr/bin/env python
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com

def fun1():
    print("Hello fun1")


def fun2(fun):
    print("World fun2")
    # 这里是执行的传入的函数本身
    fun()
    # 返回回去
    return fun

# 执行fun2同时把fun1当做参数传递进去
fun2(fun1)

上面满足高阶函数的2个条件。但是如果我只是想给fun1增加功能,那么我这样执行显然成功了,可是我改变了调用方式,也就是说我必须执行fun2(fun1)才行,而我程序中很多地方写的都是fun1(),那如果我这样改起来自己麻烦同时如果别人调我的fun1(),那别人也要改。如何不改变调用方式呢?通过下面的方式调用,不过这不再是单纯的给fun1增加功能,因为最后相当于重新定义了fun1。

# 如何不改变调用方式也增加新功能呢?就这样。这里相当于把之前的fun1重新定义了
fun1 = fun2(fun1)

2.3 装饰器如何使用

#!/usr/bin/env python
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com

import time

def timer(func):
    def wapper(*args, **kwargs):
        start_time = time.time()
        func()
        stop_time = time.time()
        print("耗时:", stop_time - start_time)
    return wapper

@timer
def run():
    print("To do job.")
    time.sleep(2)
    print("Job is done.")

run()

我这里是给run函数增加一个功能计算该函数的运行时间。上面就是装饰器的用法。增加了功能、没有改变原来的调用方式,timer函数随处可以引用。如果上面的功能通过高阶函数如何实现呢?你看下面的代码几乎完全相同,只是run()函数上面去掉了@timer,以及后面的执行语句前面有加了一个语句,正像上面所说执行完 run = timer(run) 其实就等于重新定了run()。

#!/usr/bin/env python
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com

import time

def timer(func):
    def wapper(*args, **kwargs):
        start_time = time.time()
        func()
        stop_time = time.time()
        print("耗时:", stop_time - start_time)
    return wapper


def run():
    print("To do job.")
    time.sleep(2)
    print("Job is done.")

run = timer(run)
run()

加上 @timer 之后,运行 run()就等于运行 timer函数中的wapper。因为:如果不加 @timer 你可以这么用,其实这么用的效果和加 @timer 然后调用run()是一样的。

加上 @timer 之后,执行过程是什么样的呢?

  1. 从上到下执行,遇到 def timer(func) 函数解释器知道是定义了一个函数,因为此时没有调用所以解释器继续向下执行
  2. 遇到@timer,解释器执行 def timer(func) 并把 run() 函数传递给 def timer(func) 中的 func,然后发现定义了一个函数 def wapper,它此时也不执行,继续向下,遇到return wapper 则返回wapper函数
  3. 此时装饰完毕其实也就是对run()函数进行了重新赋值,继续向下,遇到run()这条语句,也就是我们直接调用,这时候它会去执行之前返回的wapper函数,此时run()就是wapper()函数  理解为 run = timer(run)
  4. 执行wapper函数的第一条语句 start_time = time.time()
  5. 执行wapper函数的第二条语句 func() 而这时候的func()就是之前传递进来的run()函数,此时执行run()函数的语句,直到执行完成返回
  6. 执行执行wapper函数第三条语句 stop_time = time.time()
  7. 执行执行wapper函数第四条语句  print("耗时:", stop_time - start_time)

2.4 装饰器如何传递参数

我们可能注意到 def wapper(*args, **kwargs) 其实这里就可以传递参数。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com


def printName(func):
    def wapper(*args, **kwargs):
        func(**kwargs)
    return wapper

@printName
def run(name):
    print("姓名为:", name)

def main():
    run(name="chen")

if __name__ == '__main__':
    main()

可能有些人对 **kwargs 有些迷惑,其实它就是一个字典形式,你传进来的参数必须是 KEY=VALUE形式,就行我在main()函数中的那样 run(name="chen"),如果还不明白我换一种写法如下图:

2.5 类中如何使用装饰器

#!/usr/bin/env python
# -*- coding: utf-8 -*-

"""

"""

import sys
import time


def timer(tagName):
    import time

    def wapper(func):
        def aa(self, *args, **kwargs):
            start = time.time()
            func(self)
            end = time.time()
            consume = end - start
            if consume > 60:
                min, sec = divmod(consume, 60)
                print(" - %s 执行耗时:%s 分 %s 秒。" % (tagName, str(min), str(sec)))
            else:
                print(" - %s 执行耗时:%s 秒。" % (tagName, str(consume)))
        return aa

    return wapper


class AA:
    def __init__(self):
        pass

    @timer(tagName="runTask方法")
    def runTask(self):
        time.sleep(2)


if __name__ == "__main__":
    try:
        aa = AA()
        aa.runTask()
    except Exception as err:
        print(err)
    finally:
        sys.exit()

3. 实例

我这里模拟一个场景就是网站登录,我们需要给所有页面增加一个验证功能。

#!/usr/bin/env python
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com

"""
模拟网站登录,也就是给某些页面加验证功能
"""

users = {"Tom": "123"}


# 验证功能
def auth(func):
    def wrapper(*args, **kwargs):
        username = input("Input your username: ")
        password = input("Input your password: ")
        if username.strip() in users and password.strip() == users[username]:
            func(username=username)
            # 如果你需要接收被装饰函数的返回值就要
            # res = func(username=username)
        else:
            exit("Invalid username or password.")
    return wrapper


# 下面是模拟两个页面

def indexPage():
    print("Welcome to Index.")


# 给home页面加验证
@auth
def homePage(**kwargs):
    print("Welcome: ", kwargs["username"], "to Home.")
    return "You are Home."


def main():
    indexPage()
    homePage()


if __name__ == '__main__':
    main()

执行结果

现在的需求变了,我又增加了一个页面,同时这个页面需要另外一种验证方式,怎么办?最笨的方法是再写一个装饰器用于其他验证方式,有没有更好的办法呢?就是设置装饰器参数。

#!/usr/bin/env python
# Author: rex.cheny
# E-mail: rex.cheny@outlook.com

"""
模拟网站登录,也就是给某些页面加验证功能
"""

users = {"Tom": "123"}

def authG_two(auth_type="local"):
    """
    这个装饰器带了参数,所以它就不能写fun,而必须是明确的参数。而且这里和之前比又多了一层,
    因为最外层不是传递的函数进来,而是参数。这个是如何执行的呢?

    @authG_two(auth_type="local")
    def fun1():
        pass

    fun1 = authG_two(auth_type="local") 然后返回 wrapper,这时候 fun1 = wrapper(fun1),然后在进入到  authG_two() 执行 wrapper()方法
    根据auth_type返回 对应的函数,如果是local 最终则是  fun1 = localAuth()并且 localAuth()里的func函数就是之前传递进去的func函数。
    """
    def wrapper(func):
        # 本地验证方式
        def localAuth(*args, **kwargs):
            username = input("Input your username: ")
            password = input("Input your password: ")
            if username.strip() in users and password.strip() == users[username]:
                print("Local authentication succeed.")
                func(username=username)
                # 如果你需要接收被装饰函数的返回值就要
                # res = func(username=username)
            else:
                exit("Invalid username or password.")

        # LDAP验证方式
        def ldapAuth(*args, **kwargs):
            username = input("Input your username: ")
            password = input("Input your password: ")
            func(username=username)
            print("LDAP authentication succeed.")

        if auth_type == "local":
            return localAuth
        else:
            return ldapAuth
    return wrapper


"""
下面三个模仿三个板块的主页
"""
def indexPage():
    print("Welcome to Index.")


@authG_two()
def homePage(**kwargs):
    """
    这里使用新的装饰器,它后面带括号,不设置参数表示使用默认参数设置。
    """
    print("Welcome: ", kwargs["username"], "to Home.")
    return "You are Home."


# 新增BBS页面
@authG_two(auth_type="ldap")
def bbsPage(**kwargs):
    """
    这里使用新的装饰器,它后面带括号,这就说明那个装饰器需要带参数,验证方式为LDAP
    """
    print("Welcome to BBS.")


def main():
    indexPage()
    homePage()
    bbsPage()


if __name__ == '__main__':
    main()

执行结果

 

posted @ 2018-08-18 12:30  昀溪  阅读(421)  评论(0编辑  收藏  举报