今天在B站看了个视频,终于让我搞懂了什么是装饰器,解决了心里多年的一个困惑,记录下。

产生的需求

一个新闻门户网站,分为sports、news、user_center三个页面,想要实现用户必须登录才能看片的效果。

源代码:

user_info = {  # 用户信息
    'user_name': 'Yacan',
    'password': '123'
}

def sports():
    print("体育页面...")

def news():
    print("新闻页面...")

def user_center():
    print('用户中心...')

简单粗暴的实现方法

# 为用户增加一个is_login的属性,当用户登陆后,将其赋值为True
user_info = {
    'user_name': 'yacan',
    'password': '123',
    'is_login': False
}

# 增加一个check_login函数,用来检查用户的登录状态
def check_login():
    name = input('请输入用户名:')
    password = input('请输入密码:')
    if name == user_info['user_name'] and password == user_info['password']:
        user_info['is_login'] = True
        print('登录成功~')
    else:
        print('账号或者密码错误,登陆失败!')
        

# 在加载每一个板块内容前都先执行登录函数
def sports():
    check_login()
    print("体育页面...")


def news():
    check_login()
    print("新闻页面...")


def user_center():
    check_login()
    print('用户中心...')

问题:简单粗暴的方式违背了代码的“开放-封闭”原则,即当我们增加新功能时,要尽量地不在原代码上产生改动,尽量把所有的改动都写到函数外。

升级版

如果我们可以将需求函数sports()、news()、user_center()放入check_login()函数来执行的话,就可以不在源代码上进行修改,从而把全部的新增代码都放到了外面,通过Python函数的高级用法——把一个函数作为参数传入另一个函数内:

def check_login(fun):
    name = input('请输入用户名:')
    password = input('请输入密码:')
    if name == user_info['user_name'] and password == user_info['password']:
        user_info['is_login'] = True
        print('登录成功~')
        fun()  # 执行传入的函数
    else:
        print('账号或者密码错误,登陆失败!')
        
        
sports = check_login(sports)
sports()

news = check_login(news)
news()

user_center = check_login(user_center)
user_center()

问题:在执行sports = check_login(sports)的时候,由于check_login(fun)没有return(返回值),导致sports = None,后面执行sports()的时候,就是None(),直接报错。

小知识点

在Python中定义一个函数def fun()后,直接调用fun返回的是函数的内存地址,调用fun()返回的是函数的return值。

def fun():
    return 1


print(fun(), ' | ', fun)  # 1  |  <function fun at 0x000001FBF182EB90>

完善

想要解决上述问题,就是想办法让执行sports = check_login(sports)的时候返回的是函数的内存ID,就可以解决这个问题。这个时候就用到了一个伟大的概念——闭包。

def check_login(fun):
    def inner():  # 所谓闭包,就是在函数里面加上一个嵌套函数,然后将内部嵌套函数返回出去。
        name = input('请输入用户名:')
        password = input('请输入密码:')
        if name == user_info['user_name'] and password == user_info['password']:
            user_info['is_login'] = True
            print('登录成功~')
            fun()
        else:
            print('账号或者密码错误,登陆失败!')

    return inner


news = check_login(news)
sports = check_login(sports)
news()
sports()

所谓闭包,就是在函数里面加上一个嵌套函数,然后将内部嵌套函数返回出去。执行sports = check_login(sports)的时候返回的是函数的内存ID,而不是None了。

至此,装饰器起始已经产生了,但是为了书写方便,Python有一个专门的写法:

@check_login
def sports():
    print("体育页面...")

@check_login
def news():
    print("新闻页面...")

@check_login
def user_center():
    print('用户中心...')

就是在每一个函数前面的一行加上@标识符,在sports()函数前面一行加上@check_login的作用就是相当于执行了sports = check_login(sports)代码。

完美,Python太优雅了。