Python之装饰器

 什么是装饰器?

在说装饰器之前啊. 我们先说一个软件设计的原则: 开闭原则, 又被成为开放封闭原则,你的代码对功能的扩展是开放的你的程序对修改源代码是封闭的. 这样的软件设计思路可以更好的维护和开发。
  开放:对功能扩展开放
  封闭:对修改代码封闭

谈装饰器前,还要先要明白一件事,Python 中的函数可以像普通变量一样当做参数传递给另外一个函数,例如:

def foo():
    print("foo")

def bar(func):
    func()

bar(foo)

装饰器的目的: 在不改变原来代码的基础上. 给代码添加新功能。

 装饰器形成过程

简单装饰器

def func1():
    print("欢迎访问主页")

# 添加一个登录验证的功能
def wrapper(fn):
    def inner():
        print("请先登录....")
        fn()  
    return inner

func1 = wrapper(func1)
func1()

# 装饰器的基本雏形
# def wrapper(fn): # fn:目标函数.
#     def inner():
#         '''执行函数之前'''
#         fn() # 执行被装饰的函数
#         '''执行函数之后'''
#     return inner

来看一下,执行流程:

语法糖

   但是如果有多个函数,都需要添加登录验证的功能,每次都得func1 = timer(func1)?这样还是有点麻烦,因为这些函数的函数名可能是不相同,有func1,func2,graph,等等,所以更简单的方法,python给你提供了,那就是语法糖。 @装饰器

def wrapper(fn):
    def inner():
        print("请先登录....")
        fn()
    return inner

@wrapper  # 相当于 => func1 = wrapper(func1)
def func1():
    print("欢迎访问主页")

func1()

有个小问题,就是查看func1函数名和函数注释都变成inner了。

print(func1.__doc__)     # 查看函数注释信息
print(func1.__name__)   # 查看函数名
# 我是内部函数
# inner

如何解决???
from functools import wraps
def wrapper(fn):
    @wraps(fn)   # 改变inner的名字
    def inner():
        """我是内部函数"""
        print("请先登录....")
        fn()
    return inner

@wrapper  # 相当于 => func1 = wrapper(func1)
def func1():
    """ 主页函数 """
    print("欢迎访问主页")

func1()

print(func1.__doc__)
print(func1.__name__)
#  主页函数 
# func1

被装饰函数带参数和返回值

from functools import wraps
def wrapper(fn):  # fn: 目标函数, 被装饰的函数
    @wraps(fn)   # 改变inner的名字
    def inner(*args,**kwargs):  # 万能参数  *args, **kwargs: 接收参数
        """我是内部函数"""
        print("请先登录....")
        ret = fn(*args,**kwargs)      # 调用目标函数.
        """被装饰函数执行之后"""
        return ret                    # 返回结果
    return inner

@wrapper  # 相当于 => func1 = wrapper(func1)
def func1(usernamee):
    """ 主页函数 """
    print("欢迎 %s 访问主页" % usernamee)
    return True

@wrapper  # 相当于 => func2 = wrapper(func2)
def func2(name,age):
    print("名字:%s 年龄:%s" %(name,age))
    return True

ret = func1("lishichao")   # 实际执行的是inner 参数传给了inner函数
print(ret)

func2("lishichao","19")

带有参数的装饰器

给装饰器传参,可以控制装饰器是否使用。

def wrapper(flag):  # 装饰器参数
    def inner1(fn):  # fn: 目标函数, 被装饰的函数
        def inner2(*args,**kwargs):  # 万能参数  *args, **kwargs: 接收参数
            """我是内部函数"""
            if flag:  # 判断传入的参数  True就执行装饰器 False不执行装饰器
                print("请先登录....")
            ret = fn(*args,**kwargs)      # 调用目标函数.
            """"""
            return ret                    # 返回结果
        return inner2
    return inner1

@wrapper(False) # 执行流程: 先执行wrapper(False)  返回装饰器 再和 @ 拼接起来 @inner1
def func1(usernamee):
    """ 主页函数 """
    print("欢迎 %s 访问主页" % usernamee)
    return True

ret = func1("lishichao")   # 实际执行的是inner 参数传给了inner函数
print(ret)

执行流程:

多个装饰器装饰一个函数

def wrapper1(fn):
    def inner(*args, **kwargs):
        print("第一个装饰器开始")
        ret = fn(*args, **kwargs)
        print("第一个装饰器结束")
        return ret
    return inner

def wrapper2(fn):
    def inner(*args, **kwargs):
        print("第二个装饰器开始")
        ret = fn(*args, **kwargs)
        print("第二个装饰器结束")
        return ret
    return inner

def wrapper3(fn):
    def inner(*args, **kwargs):
        print("第三个装饰器开始")
        ret = fn(*args, **kwargs)
        print("第三个装饰器结束")
        return ret
    return inner

@wrapper1
@wrapper2
@wrapper3
def func():
    print("瞬间来三")

func()
# 执行结果
# 第一个装饰器开始
# 第二个装饰器开始
# 第三个装饰器开始
# 瞬间来三
# 第三个装饰器结束
# 第二个装饰器结束
# 第一个装饰器结束

执行流程:

wrapper1   
wrapper2   
wrapper3   # 调用函数执行之前
func
wrapper3   # 调用函数执行之后
wrapper2
wrapper1

练习题

1、装饰器的固定格式

from functools import wraps
def wrapper(flag):  # 接收装饰器的参数
    def inner1(fn): # 接收的是被装饰函数的函数名
        @wraps(fn)  # 改变inner2函数的名字为被装饰函数的名字
        def inner2(*args,**kwargs):   # 无敌传参,接收参数
            if flag:  # 判断是否执行装饰器
                print("被装饰函数执行之前")
            ret = fn(*args,**kwargs)  #被装饰函数, 调用参数
            print("被装饰函数执行之后")
            return ret   # 返回值
        return inner2
    return inner1

@wrapper(True)
def func1():
    print("我是被装饰函数")
    return True

func1()
print(func1.__name__)
装饰器的固定格式

2、编写装饰器,在每次执行被装饰函数之前打印一句’每次执行被装饰函数之前都得先经过这里,这里根据需求添加代码’

def wrapper(fn):
    def inner(*args,**kwargs):
        print("每次执行被装饰函数之前都得先经过这里,这里根据需求添加代码")
        ret = fn(*args,**kwargs)  #调用被装饰函数,ret接收被装饰函数的返回值
        return ret
    return inner

@wrapper
def func():
    print("我是被装饰函数")
    return "被装饰函数的返回值"

ret = func()
print(ret)
View Code

3、编写装饰器,在每次执行被装饰函数之后打印一句’每次执行完被装饰函数之后都得先经过这里,这里根据需求添加代码’

def wrapper(fn):
    def inner(*args,**kwargs):
        ret = fn(*args,**kwargs)
        print("每次执行完被装饰函数之后都得先经过这里,这里根据需求添加代码")
        return ret
    return inner

@wrapper
def func():
    print("我是被装饰函数")

func()
View Code

4、编写装饰器,在每次执行被装饰函数之前让用户输入用户名,密码,给用户三次机会,登录成功之后,才能访问该函数.

flag = False
def login():
    num = 1
    while num <= 3:
        username = input("请输入用户名:").strip().lower()
        password = input("请输入密码:").strip().lower()
        if username == "lishichao" and password == "123":
            global flag
            flag = True
            break
        else:
            num += 1
            print("输入错误,请重新输入")
            continue
    else:
        print("输入超过三次,已锁定")


def login_wrapper(fn):
    def inner():
        login()
        if flag:
            fn()
        else:
            print("登录失败")
    return inner

@login_wrapper
def func1():
    print("欢迎访问文章页面")
View Code

5、编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件,只支持单用户的账号密码,给用户三次机会)要求登录成功一次,后续的函数都无需再输入用户名和密码

import sys
flag = False

def sign_up():
    with open("user_info",mode="w",encoding="utf-8") as f:
        username = input("请输入注册账号:").strip()
        password = input("请输入注册密码:").strip()
        f.write("%s|%s
" %(username,password))
        print("注册成功,请登录")

def login():
    num = 1
    while num <= 3:
        username = input("请输入账号:").strip()
        if username == "":
            print("账号不能为空")
            continue
        password = input("请输入密码:").strip("|")
        if password == "":
            print("密码不能为空,请重新输入")
            continue
        num += 1
        with open("user_info", mode="r", encoding="utf-8") as f:
            for i in f:
                user_list = i.strip().split("|")
                if username == user_list[0] and password == user_list[1]:
                    print("登录成功")
                    global flag
                    flag = True
                    return
            else:
                print("登录失败,账号或密码不正确.")
    else:
        print("===========================================")
        print("登录超过三次,账号已锁定")
        print("===========================================")
        sys.exit()


def wrapper(fn):
    def inner(*args,**kwargs):
        if flag:
            ret = fn(*args,**kwargs)
            return ret
        else:
            login()
    return inner

@wrapper
def func1():
    print("欢迎访问文章页面")

@wrapper
def func2():
    print("欢迎访问管理页面")

@wrapper
def func3():
    print("欢迎访问评论页面")

dic = {
    "1":sign_up,
    "2":login,
    "3":func1,
    "4":func2,
    "5":func3
}

def main():
    while 1:
        print("""
           请选择:
                1.注册
                2.登录
                3.访问文章页面
                4.访问管理页面
                5.访问评论页面
                [q] 退出
        """)
        n = input(">>>").strip().lower()
        if n == "q":
            break
        else:
            dic[n]()

main()
View Code

6、编写装饰器,为多个函数加上认证的功能(用户的账号密码来源于文件,可支持多账号密码)要求登录成功一次(给三次机会),后续的函数都无需再输入用户名和密码。

import sys
flag = False

def sign_up():
    with open("user_info",mode="a",encoding="utf-8") as f:
        username = input("请输入注册账号:").strip()
        password = input("请输入注册密码:").strip()
        f.write("%s|%s
" %(username,password))
        print("注册成功,请登录")

def login():
    num = 1
    while num <= 3:
        username = input("请输入账号:").strip()
        if username == "":
            print("账号不能为空")
            continue
        password = input("请输入密码:").strip("|")
        if password == "":
            print("密码不能为空,请重新输入")
            continue
        num += 1
        with open("user_info", mode="r", encoding="utf-8") as f:
            for i in f:
                user_list = i.strip().split("|")
                if username == user_list[0] and password == user_list[1]:
                    print("登录成功")
                    global flag
                    flag = True
                    return
            else:
                print("登录失败,账号或密码不正确.")
    else:
        print("===========================================")
        print("登录超过三次,账号已锁定")
        print("===========================================")
        sys.exit()


def wrapper(fn):
    def inner(*args,**kwargs):
        if flag:
            ret = fn(*args,**kwargs)
            return ret
        else:
            login()
    return inner

@wrapper
def func1():
    print("欢迎访问文章页面")

@wrapper
def func2():
    print("欢迎访问管理页面")

@wrapper
def func3():
    print("欢迎访问评论页面")

dic = {
    "1":sign_up,
    "2":login,
    "3":func1,
    "4":func2,
    "5":func3
}

def main():
    while 1:
        print("""
           请选择:
                1.注册
                2.登录
                3.访问文章页面
                4.访问管理页面
                5.访问评论页面
                [q] 退出
        """)
        n = input(">>>").strip().lower()
        if n == "q":
            break
        else:
            dic[n]()

main()
View Code

7、给每个函数写一个记录日志的功能
功能要求:每一次调用函数之前,要将函数名称,时间节点记录到log的日志中。
所需模块:
import time
struct_time = time.localtime()
print(time.strftime("%Y‐%m‐%d %H:%M:%S",struct_time))

def wrapper_log(flag):
    def wrapper(fn):
        def innrt(*args,**kwargs):
            if flag:
                import time
                struct_time = time.localtime()
                Time = time.strftime("%Y-%m-%d %H:%M:%S",struct_time)
                func_name = fn.__name__
                with open("func_accecc.log",mode="a",encoding="utf-8") as f:
                    f.write("在 %s    执行函数: %s
" %(Time,func_name))
            ret = fn(*args,**kwargs)
            return ret
        return innrt
    return wrapper

@wrapper_log(True)
def func1():
    print("欢迎访问文章页面")

@wrapper_log(True)
def func2():
    print("欢迎访问评论页面")

@wrapper_log(True)
def func3():
    print("欢迎访问管理页面")

func1()
func2()
func3()
View Code
原文地址:https://www.cnblogs.com/root0/p/10307600.html