「Python」装饰器到底是个啥?
今天在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太优雅了。