15 Python学习之装饰器

装饰器

装饰器:在不改变源函数的代码及调用方式的前提下,为其增加新的功能,装饰器就是一个函数,他的本质是闭包

装饰器开放封闭的原则:

开放:对代码的扩展开放

封闭:对源码的修改是封闭的

被装饰函数无参数,无返回值

例1:

import time

def timer(func):
    def inner():
        start_time = time.time()
        func()
        end_time = time.time()
        print(f'装饰器打印执行时间为:{round(end_time - start_time, 5)}')
    return inner            # 闭包,返回内层函数的引用

# show_info = timer(show_info)第一个show_info是一个新的变量,第二个show_info的函数名,@timer是show_info = timer(show_info)的简写,官方叫做语法糖
@timer
def show_info():
    print(f"我的姓名是:张三")
    time.sleep(2)


show_info()

运行结果:

我的姓名是:张三
装饰器打印执行时间为:2.00051

被装饰函数有返回值

被装饰函数有返回值,那么在设计装饰器的时候,就应该有变量进行接收,然后再将该返回值返回给调用者

例1:

import time

def timer(func):
    def inner():
        start_time = time.time()
        inner_ret = func()              # 调用原函数并接受返回值
        end_time = time.time()
        print(f'装饰器打印执行时间为:{round(end_time - start_time, 5)}')
        return inner_ret                # 将调用原函数的返回值返回给调用者
    return inner            # 闭包,返回内层函数的引用


@timer          # show_info = timer(show_info)第一个show_info是一个新的变量,第二个show_info的函数名,@timer是show_info = timer(show_info)的简写,官方叫做语法糖
def show_info():
    print(f"我的姓名是:张三")
    time.sleep(2)
    return 'show_info函数的返回值'


ret = show_info()           # ret接受的是inner的返回值inner_ret
print(ret)

我的姓名是:张三
装饰器打印执行时间为:2.00168
show_info函数的返回值

被装饰函数有参数

被装饰函数有参数,由于实际调用的时候,掉的是闭包中的内部函数,所以内部函数相应的也要设计成跟原函数一样的带参数

例1:

import time

def timer(func):
    def inner(name):               # 设计时需要添加参数
        start_time = time.time()
        inner_ret = func(name)              # 调用原函数indxe,传入参数并接受返回值
        end_time = time.time()
        print(f'装饰器打印执行时间为:{round(end_time - start_time, 5)}')
        return inner_ret                # 将调用原函数的返回值返回给调用者
    return inner            # 闭包,返回内层函数的引用


@timer          # show_info = timer(show_info)第一个show_info是一个新的变量,第二个show_info的函数名,@timer是show_info= timer(show_info)的简写,官方叫做语法糖
def show_info(name):
    print(f"我的姓名是:{name}")
    time.sleep(2)
    return 'show_info函数的返回值'


ret = show_info('张三')       # 相当于ret = inner('张三')
print(ret)

我的姓名是:张三
装饰器打印执行时间为:2.00031
show_info函数的返回值

标准装饰器

由于被装饰函数的参数个数不确定,所以我们在设计装饰器时,内部函数的参数个数就无法确定,因此要设计成不定长的形式

例1:

import time

def timer(func):
    def inner(*args, **kwargs):               # 设计时需要添加参数
        start_time = time.time()
        inner_ret = func(*args, **kwargs)     # 调用原函数indxe,传入参数并接受返回值
        end_time = time.time()
        print(f'装饰器打印执行时间为:{round(end_time - start_time, 5)}')
        return inner_ret                # 将调用原函数的返回值返回给调用者
    return inner            # 闭包,返回内层函数的引用


@timer          # show_info = timer(show_info)第一个show_info是一个新的变量,第二个show_info的函数名,@timer是show_info= timer(show_info)的简写,官方叫做语法糖
def show_info(name, age):
    print(f"我的姓名是:{name}, 今年{age}岁")
    time.sleep(2)
    return 'show_info函数的返回值'


ret = show_info('张三', 25)
print(ret)

我的姓名是:张三, 今年25岁
装饰器打印执行时间为:2.0017
show_info函数的返回值

特别注意

在标准装饰器中,def inner(*args, **kwargs): 中的 * 在函数 定义 的时候是将传入函数的参数聚合成一个元素,在 调用函数 inner_ret = func(*args, **kwargs) 的时候 * 的作用是将一个可迭代的对象进行打散,即将变量 args或kwargs 打散,拆分成一个个要传入的实参

标准装饰器模板:

def decorator(real_func_name):
    def inner(*args, **kwwargs):
        '''调用装饰器访问函数前要执行的操作'''
        ret = real_func_name(*args, **kwwargs)
         '''调用装饰器访问函数后要执行的操作'''
        return ret
   	return inner

装饰器的应用

装饰器一般用于登录验证和日志

例1:

def login():
    name = input('请输入用户名:')
    pwd = input('请输入密码:')
    login_status['name'] = name
    if pwd == '123456':
        login_status['status'] = True
        return 1
    else:
        return 0


login_status = {
    'name': None,
    'status': False
}


def decorator(func_name):
    def inner(*args, **kwargs):
        if login_status['status']:
            ret = func_name(*args, **kwargs)
            return ret
        else:
            ret = login()
            if ret:
                ret = func_name()
                return ret
            else:
                print('登录失败')

    return inner


@decorator
def index():
    print("index页面")

@decorator
def logger():
    print("logger页面")

# 只有登录成功后才会执行函数,并打印信息
index()
logger()

请输入用户名:张三
请输入密码:123456
index页面
logger页面

带参数的装饰器

如果给不同的函数在调用装饰器的时候,给出不同的日志记录文件,那么就需要在装饰函数的时候,传入一个装饰器路径参数

例1:

import datetime
import os

# 带参数的装饰器
def logger(file):
    def outer(func):
        def inner(*args, **kwargs):
            # 如果目录不存在创建目录,并修改目录权限
            dir_path = '/projects/logger/'
            if not os.path.exists(dir_path):
                os.makedirs(dir_path)
                os.chmod(dir_path, mode=0o777)
                print('文件夹不存在已自动创建')
                
            # 执行被装饰的函数
            ret = func(*args, **kwargs)
            
            # 将创建文件信息写入文件
            with open(os.path.join(dir_path, file), mode='a+',encoding='utf-8') as f:
                dt_ms = datetime.datetime.now()
                f.write(f'于 {dt_ms} 调用了 {func.__name__}\n')
            
            return ret

        return inner

    return outer

# 带参数的装饰器
@logger('goods.log')
def goods():
    print('购买商品')

# 带参数的装饰器
@logger('login.log')
def login():
    print('登录')

# 带参数的装饰器
@logger('order.log')
def order():
    print('下单')


if __name__ == '__main__':
    for i in range(5):
        goods()
        login()
        order()

运行结果:

[root@localhost logger]# cat login.log
于 2020-08-18 11:56:44.415420 调用了 login
于 2020-08-18 11:56:44.415650 调用了 login
于 2020-08-18 11:56:44.415873 调用了 login
于 2020-08-18 11:56:44.416067 调用了 login
于 2020-08-18 11:56:44.416260 调用了 login

[root@localhost logger]# cat order.log
于 2020-08-18 11:56:44.415511 调用了 order
于 2020-08-18 11:56:44.415745 调用了 order
于 2020-08-18 11:56:44.415944 调用了 order
于 2020-08-18 11:56:44.416126 调用了 order
于 2020-08-18 11:56:44.416364 调用了 order

[root@localhost logger]# cat goods.log
于 2020-08-18 11:56:44.415233 调用了 goods
于 2020-08-18 11:56:44.415586 调用了 goods
于 2020-08-18 11:56:44.415813 调用了 goods
于 2020-08-18 11:56:44.416007 调用了 goods
于 2020-08-18 11:56:44.416199 调用了 goods

posted @ 2020-08-06 17:11  爬坡的蜗牛  阅读(128)  评论(0编辑  收藏  举报