# 装饰器

## 什么是装饰器

让其他函数在不需要做任何代码变动的前提下，增加额外的功能，装饰器的返回值也是一个函数对象。

装饰器的应用场景：比如插入日志，性能测试，事务处理，缓存等等场景。

## 装饰器的形成过程

如果我想测试某个函数的执行时间

```python
import time
def func1():
    print('in func1')
def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner
func1 = timer(func1)
func1()
```

但是如果有多个函数，我都想让你测试他们的执行时间，你每次是不是都得func1 = timer(func1)?这样还是有点麻烦，因为这些函数的函数名可能是不相同，有func1，func2,graph,等等，所以更简单的方法，python给你提供了，那就是语法糖。

```python
import time
def timer(func):
    def inner():
        start = time.time()
        func()
        print(time.time() - start)
    return inner
@timer
def func1():
    time.sleep(1)
    print('in func1')
func1()
```

装饰一个带参数的函数

```python
import time
def timer(func):
    def inner(a):
        start = time.time()
        func(a)
        print(time.time() - start)
    return inner
@timer
def func1(a):
    time.sleep(1)
    print(a)
func1('hello world')
```

装饰一个带各种参数的函数

```python
import time
def timer(func):
    def inner(*args,**kwargs):
        start = time.time()
        func(args,kwargs)
        print(time.time() - start)
    return inner
@timer
def func1(*args,**kwargs):
    print(args,kwargs)
func1('hello world','abc',123,432)
```

查看函数的相关信息，在加上装饰器后就失效了

```python
def index():
    '''这是一条注释信息'''
    print('from index')
print(index.__doc__)    # 查看函数注释
print(index.__name__)   # 查看函数名称
```

导入wraps装饰器

```python
from functools import wraps
def deco(func):
    @wraps(func)
    def wrapper(*args,**kwargs):
        return func(*args,**kwargs)
    return wrapper
@deco
def index():
    '''这是一条注释信息'''
    print('from index')
print(index.__doc__)    # 查看函数注释
print(index.__name__)   # 查看函数名称
```

## 开放封闭原则

一句话，软件实体应该是可扩展但是不可修改的。

* 对于扩展是开放的
* 对于修改是封闭的

装饰器完美的遵循了这个开放封闭原则

## 装饰器的主要功能和固定结构

```python
def timer(func):
    def inner(*args,**kwargs):
        '''执行函数之前要做的'''
        re = func(*args,**kwargs)
        '''执行函数之后要做的'''
        return re
    return inner
# 下面是加上wraps的固定结构
from functools import wraps
def timer(func):
    @wraps(func)
    def wrapper(*args,**kwargs)
        return func(*args,**kwargs)
    return wrapper
```

## 带参数的装饰器

加上一个outer函数，可以携带一个flag的值，然后控制装饰器是否生效

```python
def outer(flag):
    def timer(func):
        def inner(*args,**kwargs):
            if flag:
                print('函数开始执行')
            re = func(*args,**kwargs)
            if flag:
                print('函数执行完毕')
            return re
        return inner
    return timer
@outer(True)
def func():
    print('test')
func()
```

## 多个装饰器装饰一个函数

```python
def wrapper1(func):
    def inner():
        print('第一个装饰器，在程序运行之前')
        func()
        print('第一个装饰器，在程序运行之后')
    return inner
def wrapper2(func):
    def inner():
        print('第二个装饰器，在程序运行之前')
        func()
        print('第二个装饰器，在程序运行之后')
    return inner
@wrapper1
@wrapper2
def f():
    print('Hello')
f()
```

# 小练习

- 给前面的基于文件数据库的登录注册加上一个打游戏功能
- 使用装饰器验证用户是否登录

```python
def check_auth(func):
    def inner(name):
        if len(flag) == 1:
            func(name)
        else:
            print("没登录就别打游戏了！")
    return inner

@check_auth
def play_game(name):
    print(f"{name[0]}开始游戏")

flag = []
while True:
    # 获取文件中保存的用户名和密码信息
    dic = {}
    with open("db", "a+", encoding="utf-8") as f:
        f.seek(0)
        temp_data = f.readlines()
        for i in temp_data:
            data = i.strip()
            if len(data) == 0:
                continue
            temp_db = data.split("✨")
            dic[temp_db[0]] = temp_db[-1]

    # 登录注册的逻辑
    print("登录".center(30,"="))
    print("请选择：\n1.登录\n2.注册\n3.退出登录\n4.打游戏")
    choice = input(">>>")
    if choice == "1":
        username = input("输入用户名：")
        if username in dic.keys():
            password = input("输入密码：")
            if dic[username] == password:
                print("\033[42;30m 登录成功！ \033[0m")
                flag.append(username)
            else:
                print("\033[41;37m 密码错误 \033[0m")
        else:
            print("\033[41;37m用户名不存在！\033[0m")
    elif choice == "2":
        username = input("输入用户名")
        if username in dic.keys():
            print("\033[34;41m 用户名已存在！\033[0m")
            continue
        password = input("请输入密码")
        dic[username] = password
        print("\033[42;30m 注册成功！ \033[0m")
    elif choice == "3":
        flag.clear()
        print("退出成功！")
    elif choice == "4":
        play_game(flag)
    else:
        print("\033[41;37m输入错误！\033[0m")

    # 将数据写入文件
    with open("db", "w+", encoding="utf-8") as f:
        for k,v in dic.items():
            data = f"{k}✨{v}\n"
            f.write(data)

```

