python装饰器
假如你是一家视频网站的后端开发工程师,你们网站有以下几个版块:
def Home():
print("-"*10+"首页"+"-"*10)
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
def Beijing():
print("-"*10+"北京专区"+"-"*10)
在视频刚上线初期,为了吸引客户,你们采取了免费政策,所有视频免费观看,迅速吸引了一大批用户,免费一段时间后,发现每天巨大的带宽费用公司承受不起,所以准备对比较受欢迎的几个版块进行收费,其中包括“欧美”和“北京”专区,拿到这个需求后,想了想,想收费就需要对用户进行认证,认证通过后,再判断这个用户是否是VIP付费会员就可以了,是VIP就让他观看,不是VIP就不让他观看呗!你觉得这个需求很简单,因为要对多个版块进行认证,那应该把认证功能提取出来单独写个模块,然后每个版块里调用就可以了,于是你轻轻松松的实现了下面的功能:
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time : 2018/5/7 15:21
# @Author : XiaoYafei
# @File : 装饰器.py
user_status = False # 用户登陆了就把这个改成True
def login():
_username = "admin" # 假如这是数据库里存的用户信息
_password = '123' # 假如这是数据库里存的用户信息
global user_status # 声明全局变量
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password: # 如果输入的是相同的
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
print("用户已登陆,验证通过...")
def Home():
print("-"*10+"首页"+"-"*10)
def America():
login() # 执行前加上验证
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
def Beijing():
login() # 执行前加上验证
print("-"*10+"北京专区"+"-"*10)
America() # 调用欧美专区
Beijing() # 调用北京专区
代码执行结果:
请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
----------欧美专区----------
用户已登陆,验证通过...
----------北京专区----------
此时你信心满满的把这个代码提交给你的TEAM LEADER审核,没成想,没过5分钟,代码就被打回来了, TEAM LEADER给你反馈是,我现在有很多模块需要加认证模块,你的代码虽然实现了功能,但是需要更改需要加认证的各个模块的代码,这直接违反了软件开发中的一个原则“开放-封闭”原则,简单来说,它规定已经实现的功能代码不允许被修改,但可以被扩展,即:
- 封闭:已实现功能的代码块不应该被修改
- 开放:对现有功能的扩展开放
这个原则你还是第一次听说,再次感受到了野生程序员和正规军的差距,可是,老大要求的实现要如何实现呢?如何在不该原有功能的情况下加上认证功能呢?你一时想不出来思路,只好带着这个问题回家继续写,媳妇不在家,去隔壁老王家串门了,你正好落得清静,一不小心就想到了解决方案,不改源代码可以实现啊。
你肯定知道什么是高阶函数,就是把一个函数当作一个参数传递给另一个函数,那么,我只需要写一个认证方法,每次调用需要验证的功能时,直接把这个函数当作一个参数传给这个验证模块不就行了吗?哈哈,机智如我,于是你啪啪啪改写了之前的代码:
user_status = False # 用户登陆了就把这个改成True
def login(func):
_username = "admin" # 假如这是数据库里存的用户信息
_password = '123' # 假如这是数据库里存的用户信息
global user_status # 声明全局变量
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password: # 如果输入的是相同的
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
func() # 只要验证通过了,就调用相应功能
def Home():
print("-"*10+"首页"+"-"*10)
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
def Beijing():
print("-"*10+"北京专区"+"-"*10)
login(America) # 需要验证就调用loing,把需要验证的功能当作一个参数传递给login
login(Beijing)
你明白我想传达什么意思了么? 你:......好像不太明白。 老王:好吧,那我在给你点一下,你之前写的下面这段调用认证的代码
login(America)
login(Beijing)
你之所以修改了调用方式,是因为用户每次调用时需要执行login(America)类似的,其实只需要稍微改改就可以了:
America = login(America)
Beijing = login(Beijing)
这样你,其它人调用Beijing时,其实相当于调用了login(Beijing),通过login里的验证后,就会自动调用Beijing功能。 你:我擦,还真是唉。。。,老王,还是你nb。。。不过,等等,我这样写了好,那用户调用时,应该是下面这个样子:
America = login(America)
Beijing = login(Beijing)
America()
Beijing()
那么此时问题又来了,America = login(America)即先执行完右边的login(America)然后把结果赋值给了左边的America,即你还没有调用,程序自己就执行了,那么这个代码应该等我用户登陆的时候才执行对吧,不信我试给你看:
user_status = False # 用户登陆了就把这个改成True
def login(func):
_username = "admin" # 假如这是数据库里存的用户信息
_password = '123' # 假如这是数据库里存的用户信息
global user_status # 声明全局变量
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password: # 如果输入的是相同的
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
func() # 只要验证通过了,就调用相应功能
def Home():
print("-"*10+"首页"+"-"*10)
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
def Beijing():
print("-"*10+"北京专区"+"-"*10)
America = login(America) # 需要验证就调用loing,把需要验证的功能当作一个参数传递给login
Beijing = login(Beijing)
运行结果如下:
请输入你的姓名:
果然老王说的是对的,根本就没有去调用,这个代码就自动执行了,这时你说:这个问题应该怎么解决呢?老王问:你知道嵌套函数吗?你回答:知道。老王说:想实现一开始你写的america = login(america)不触发你函数的执行,只需要在这个login里面再定义一层函数,第一次调用america = login(america)只调用到外层login,这个login虽然会执行,但不会触发认证了,因为认证的所有代码被封装在login里层的新定义 的函数里了,login只返回 里层函数的函数名,这样下次再执行america()时, 就会调用里层函数啦,你说:...这是什么意思?我一脸懵逼。老王说:算了,还是给你看代码吧:
user_status = False # 用户登陆了就把这个改成True
def login(func):
def inner():
_username = "admin" # 假如这是数据库里存的用户信息
_password = '123' # 假如这是数据库里存的用户信息
global user_status # 声明全局变量
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password: # 如果输入的是相同的
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
func() # 只要验证通过了,就调用相应功能
return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
def Home():
print("-"*10+"首页"+"-"*10)
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
def Beijing():
print("-"*10+"北京专区"+"-"*10)
America = login(America) # 需要验证就调用loing,把需要验证的功能当作一个参数传递给login
Beijing = login(Beijing)
print(America) # <function login.<locals>.inner at 0x0000020DFEAB91E0>
America()
Beijing()
让我们来看一下代码的执行顺序:
- 1.程序直接到了最下边America = login(America),调用了login,并把函数当作参数传递进去;
- 2.在login函数内部,程序从上到下执行,首先执行inner(),在inner()函数里做了认证;
- 3.inner()函数执行完,返回inner函数,此时return inner即代表返回的是inner函数的内存地址;
- 4.在代码的底部,America = login(America),login(America)返回的是inner的内存地址;
- 5.我们刚刚说:如果打印的是函数名,则是函数的内存地址,如果函数名加上(),那么就是执行函数;
- 6.所以America = login(America)此时左边的America代表的是inner函数的内存地址,我们打印后可以看到;
- 7.America()执行innera函数;
此时问题暂时得到解决,在刚刚的代码中,有人会说,returni inner表示返回一个值代表着程序的终止,那么为什么我还能运行America()呢?那么我们就需要了解一下闭包了:
闭包
关于闭包,即函数定义和函数表达式位于另一个函数的函数体内(嵌套函数)。而且,这些内部函数可以访问他们所在的外部函数中声明的所有局部变量、参数。当其中一个这样的内部函数在包含他们外部函数之外被调用时,就会形成闭包。
也就是说:内部函数会在外部w函数执行后返回。而当这个函内部函数执行时,它仍然必须访问其外部函数的局部、参数以及其他内部的函数。这些局部变量、参数和函数声明(最初时)的值是外部函数返回的值,但也会受到内部函数的影响。
代码如下:
def func():
n = 10
def func2():
print("func2",n)
return func2 # 返回func2 的内存地址
res = func() # 想要接收函数内部的返回值,就需要一个变量来接收
print(res) # <function func.<locals>.func2 at 0x0000018E29200C80>返回的是func2的内存地址
res() # func2 10
在函数里又定义了一层子函数,子函数被返回了,就是在外层函数执行的时候,返回了子函数的内存地址,在外面执行子函数的时候,又引用了外层函数这个变量,这种现象称之为:闭包。
等等,刚刚视频网站还没有完结......
你仔细看了老王的代码,觉得老王真的不是一般人呀,这种姿势很牛逼呀,你独创的吗?
此时你的媳妇嗤嗤的笑出声来,你也不知道她笑个球
老王说:呵呵,这不是我独创的,这是开发中一个常见的语法,叫语法糖,官方名称“装饰器”,其实上面的语法,还可以更简单:
user_status = False # 用户登陆了就把这个改成True
def login(func):
def inner():
_username = "admin" # 假如这是数据库里存的用户信息
_password = '123' # 假如这是数据库里存的用户信息
global user_status # 声明全局变量
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password: # 如果输入的是相同的
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
func() # 只要验证通过了,就调用相应功能
return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
def Home():
print("-"*10+"首页"+"-"*10)
@login # 只需要在装饰的函数上面加上@login,就代表着会自动把下面的America这个函数名传递进来,成为loing(America),然后赋值给America,相同于:America = login(America)
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
@login
def Beijing():
print("-"*10+"北京专区"+"-"*10)
America()
Beijing()
效果是一样的。
你开心的玩着老王交给你的新姿势,玩着玩着就给你的北京专区模块加了个参数,然后,结果就出错了...
@login
def Beijing(style):
print("-"*10+"北京专区"+"-"*10,style)
America()
Beijing("3P")
你说:老王,怎么传递一个参数就不行了呢?
老王回答:那是肯定的呀,你调用Beijing的时候,其实是相当于调用的login,你的Beijing第一次调用时Beijing = login(Beijing),返回的是inner的内存地址,第二次用户自己调用Beijing("3P"),实际上相当于调用的是inner,但你的inner定义的时候并没有设置参数,但你给它传递了一个参数,所以这就报错了,你说对不对?
你说:但是我的版块需要传参数啊,你不让我传不行啊!
老王回答:没说不让你传,稍作改动即可!
user_status = False # 用户登陆了就把这个改成True
def login(func): # 把要执行的模块从这里传递进来
def inner(arg1): # 再定义一层函数,并且接收传递进来的参数
_username = "admin" # 假如这是数据库里存的用户信息
_password = '123' # 假如这是数据库里存的用户信息
global user_status # 声明全局变量
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password: # 如果输入的是相同的
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
func(arg1) # 只要验证通过了,就调用相应功能
return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
def Home():
print("-"*10+"首页"+"-"*10)
@login
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
@login
def Beijing(style): # 调用时传递参数
print("-"*10+"北京专区"+"-"*10,style)
# America()
Beijing('3P')
运行结果如下:
请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
----------北京专区---------- 3P
那么,问题又来了,我有的版块没有其他的频道,那么我就不用传递参数,但是如果不传递参数就会报错,怎么办?这时候我们绞尽脑汁,传一个参数可以,不传参数也可以,眼睛一亮,非固定参数!
user_status = False # 用户登陆了就把这个改成True
def login(func): # 把要执行的模块从这里传递进来
def inner(*args,**kwargs): # 再定义一层函数
_username = "admin" # 假如这是数据库里存的用户信息
_password = '123' # 假如这是数据库里存的用户信息
global user_status # 声明全局变量
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password: # 如果输入的是相同的
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
# 如果加上*程序就不会报错,如果不加*,那么就代表打印的是列表和字典两个对象,但是Beijing只需要一个参数,却传进来了两个参数,包括:空列表,空字典,使用*代表不会当作真的参数去存放
func(*args,**kwargs) # 只要验证通过了,就调用相应功能
return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
def Home():
print("-"*10+"首页"+"-"*10)
@login
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
@login
def Beijing(style):
print("-"*10+"北京专区"+"-"*10,style)
America()
Beijing('3P')
运行结果如下:
请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
----------欧美专区----------
----------北京专区---------- 3P
老王:你再试试就可以了
你:果然好使,大神就是大神啊......
老王说:这种姿势多练练,我就先回去了
你的媳妇为了不让打扰你,提出去她的好姐妹家过夜,你觉得你的媳妇真体贴,最终你搞定了所有需求,完全遵守封闭-开放原则,此时你累的已经不行了,洗洗就抓紧睡了,半夜,上厕所,隐隐听到隔壁老王家有微弱的女人的声音传来,你会心一笑,老王这家伙,不声不响找了女朋友也不带给我看看,改天一定要见下真人。
第二2天早上,产品经理又提了新的需求,要允许用户选择用qq\weibo\weixin认证,此时的你,已深谙装饰器各种装逼技巧,轻松的就实现了新的需求。
user_status = False # 用户登陆了就把这个改成True
def login(auth_type): # 把要执行的模块从这里传递进来
def outer(func):
def inner(*args,**kwargs): # 再定义一层函数
_username = "admin" # 假如这是数据库里存的用户信息
_password = '123' # 假如这是数据库里存的用户信息
global user_status # 声明全局变量
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password: # 如果输入的是相同的
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
# 如果加上*程序就不会报错,如果不加*,那么就代表打印的是列表和字典两个对象,但是Beijing只需要一个参数,却传进来了两个参数,包括:空列表,空字典,使用*代表不会当作真的参数去存放
func(*args,**kwargs) # 只要验证通过了,就调用相应功能
return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
return outer
def Home():
print("-"*10+"首页"+"-"*10)
@login
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
@login('qq')
def Beijing(style):
print("-"*10+"北京专区"+"-"*10,style)
Beijing('3P')
代码运行结果如下:
请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
----------北京专区---------- 3P
为了让大家能够看懂,一步一步来:
备注:此代码纯属用于步骤分析
此时,你的公司想让用户可以qq\weixin\weibo登陆,那么该如何操作呢?源代码如下:
user_status = False
def login(auth_type):
def inner(*args,**kwargs):
_username = "admin"
_password = '123'
global user_status
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password:
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
func(*args,**kwargs)
return inner
def Home():
print("-"*10+"首页"+"-"*10)
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
def Beijing(style):
print("-"*10+"北京专区"+"-"*10,style)
此时我希望Beijing能用qq登陆或者变成@login('qq')的方式,我们可以以一步步进行测试。1.我想把qq传递进去,那么就需要调用login函数传递qq
Beijing = login('qq')
print(Beijing) # <function login.<locals>.inner at 0x00000164B8AD91E0>
这样的话,此时,def login(auth_type):对象中的auth_type变成了qq,程序继续往下运行,继续把3p传递进去,此时 def inner(*args,**kwargs)的参数就变成了3p,那么此时传递参数的代码是这样的:
Beijing = login('qq') # login返回的是inner的内存地址
print(Beijing) # <function login.<locals>.inner at 0x00000164B8AD91E0>
Beijing('3p')
执行一下看看,哟前面还没有报错:
<function login.<locals>.inner at 0x000001B321F991E0>
请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
Traceback (most recent call last):
File "D:/py_study/day09-函数进阶/python装饰器md/装饰器.py", line 44, in <module>
Beijing('3p')
File "D:/py_study/day09-函数进阶/python装饰器md/装饰器.py", line 27, in inner
func(*args,**kwargs)
TypeError: 'str' object is not callable
为啥会报错?请看func(*args,**kwargs)有func吗?或者有传递过func吗?
那好,我们换一种思路:
user_status = False
def login(auth_type,func):
def inner(*args,**kwargs):
_username = "admin"
_password = '123'
global user_status
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password:
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
func(*args,**kwargs)
return inner
def Home():
print("-"*10+"首页"+"-"*10)
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
def Beijing(style):
print("-"*10+"北京专区"+"-"*10,style)
Beijing = login('qq',Beijing)
print(Beijing) # <function login.<locals>.inner at 0x00000164B8AD91E0>
Beijing('3p')
刚刚,我们是少传递了一个参数,现在我把login函数要传递的参数改成两个再看一下运行结果:
<function login.<locals>.inner at 0x0000017F7FD891E0>
请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
----------北京专区---------- 3p
竟然OK了,那究竟是不是大吉大利今晚吃鸡呢?刚刚我们说可以用@login去表示装饰器,现在看看还行不行?
@login('qq',Beijing) # 这里会立马报错NameError: name 'Beijing' is not defined
def Beijing(style):
print("-"*10+"北京专区"+"-"*10,style)
Beijing('3P')
那么!有没有两全其美的方法,可以让我们支持两种方式表示装饰器?有,代码如上,容我慢慢复制过来
user_status = False # 用户登陆了就把这个改成True
def login(auth_type): # 把要执行的模块从这里传递进来
def outer(func):
def inner(*args,**kwargs): # 再定义一层函数
_username = "admin" # 假如这是数据库里存的用户信息
_password = '123' # 假如这是数据库里存的用户信息
global user_status # 声明全局变量
if user_status == False:
username = input("请输入你的姓名:")
password = input("请输入你的密码:")
if username == _username and password == _password: # 如果输入的是相同的
print("Welcome Login...")
user_status = True
else:
print("用户名或密码错误...")
if user_status == True:
# 如果加上*程序就不会报错,如果不加*,那么就代表打印的是列表和字典两个对象,但是Beijing只需要一个参数,却传进来了两个参数,包括:空列表,空字典,使用*代表不会当作真的参数去存放
func(*args,**kwargs) # 只要验证通过了,就调用相应功能
return inner # 用户调用login时,只会返回inner的内存地址,下次再调用时加上()才会执行inner函数
return outer
def Home():
print("-"*10+"首页"+"-"*10)
# @login
def America():
print("-"*10+"欧美专区"+"-"*10)
def Japan():
print("-"*10+"日本专区"+"-"*10)
# @login('qq')
def Beijing(style):
print("-"*10+"北京专区"+"-"*10,style)
res = login('qq')
print(res) # <function login.<locals>.outer at 0x0000014A38CA91E0>
Beijing = res(Beijing)
print(Beijing) # <function login.<locals>.outer.<locals>.inner at 0x00000275149B9268>
Beijing('3P')
现在再来重新讲一下步骤:
- 1.程序开始运行,到最后一步调用login函数,并且把qq这种登陆方式传递进去;
- 2.在Login函数内部从上到下开始运行,最终返回outer的内存地址;
- 3.那么返回了的outer内存地址,如果加上了()就等于去执行outer函数;
- 4.res(Beijing)高阶函数,把函数当作参数传递进去,等于执行了outer()函数,最终返回了inner的内存地址;
- 5.此时Beijing('3P')就等于是inner的内存地址加上()传递参数,就等于执行了inner()函数
程序运行成功
<function login.<locals>.outer at 0x00000135F59091E0>
<function login.<locals>.outer.<locals>.inner at 0x00000135F5909268>
请输入你的姓名:admin
请输入你的密码:123
Welcome Login...
----------北京专区---------- 3P
而此时,@login('qq')同样也是运行成功的
@login('qq')
def Beijing(style):
print("-"*10+"北京专区"+"-"*10,style)
Beijing('3P')