一、返回函数
1、学习闭包之前,先了解python的返回函数:
看代码:
def fun1(n):
def fun2(x):
return pow(x,n)
return fun2
p = fun1(2)
print(p) # <function fun1.<locals>.fun2 at 0x0000017F85438F28>
print(p(2)) # 4
解析:
1、关于函数: 先定义函数 fun1() ,然后在函数fun1() 下定义函数 fun2() ,最后返回 fun2() 的地址
2、关于传参调用:先使用 p 变量调用函数 fun1(),并传参 2 ,打印返回的是 fun2的地址;只有当运行 p(2) 的时候,才会运行定义在 fun1() 内的 fun2() ,即 fun1(2)(2)
细品,细品,细品 ~
2、当返回函数中,一个内部函数里对外部作用域(但不是全局作用域)的变量进行引用,内部函数称为闭包(closure)。其中用到了循环
def count():
fs=[]
for i in range(1,4):
def f():
return i*i
#把函数f地址追加至列表fs
fs.append(f)
#返回为一个包含3个函数f地址的列表
return fs
print('count函数返回一个列表,列表分别为函数f地址',count())
f1,f2,f3 = count()
print(f1,'f1执行结果是',f1())
print(f2,'f2执行结果是',f2())
print(f3,'f3执行结果是',f3())
# 注意返回的是列表
count函数返回一个列表,列表分别为函数f地址 [<function count.<locals>.f at 0x7fc5d30cac80>, <function count.<locals>.f at 0x7fc5d30cad08>, <function count.<locals>.f at 0x7fc5d30cad90>]
<function count.<locals>.f at 0x7fc5d30cac80> f1执行结果是 9
<function count.<locals>.f at 0x7fc5d30cad08> f2执行结果是 9
<function count.<locals>.f at 0x7fc5d30cad90> f3执行结果是 9
解析:
返回函数不要引用任何循环变量,或者后续会发生变化的变量
- 结果全是 9,而不是所希望的 1,4,9
- 在这个函数中 i 是count函数的局部变量
- 当 i = 1 时,i 指向 1,函数返回的地址,放在 fs 的第一个位置。
- 当 i = 2 时, i 指向 2,函数返回的地址,放在 fs 的第二个位置上。
- 当 i = 3 时,i 指向 3,函数返回的地址,放在 fs 的第三个位置上。
- 所以当调用函数显示的时候,i 已经是3,所以得到的结果是一样的。
- 返回的这几个函数中,都引用了函数 count 中的变量 i(不是用的 i 变化过程中的值,而是引用的 i ,也就是,真正执行函数的时候 i 是多少,就是多少),后面执行print的时候,count函数已经执行结束了,count函数执行结束后 i 的值为 3 ,所以到后面 print 的时候,真正执行 返回的几个函数的时候,i 已经是3了。
通俗点说,f 表明 会用count中 i 变量(是用 i 这个变量,不是用 i 当前的值!!!),但没说现在用,同时 f 不会阻止 i 的变化,当count 执行结束后, i 已经是3了。
- 其实就是存入数组的内容由函数名改成了函数。就原始代码把fs.append(f)改成fs.append(f()),打印f1,f2,f3 就不会有 999 的情况了
- 在执行这句代码时:f1, f2, f3 = count()
这个时候进行for循环(range(1,4)产生[1,2,3]),循环结束后i=3,且返回了三个f函数,但没有调用f函数,此时并不进行return i*i这个计算,当执行这句代码时:print f1(), f2(), f3(),i已经是3了
修改代码:
def count():
fs = []
for i in range(1, 4):
def f(j):
def g():
return j * j
return g
fs.append(f(i))
# 返回为一个包含3个函数f地址的列表
return fs
print('count函数返回一个列表,列表分别为函数f地址', count())
f1, f2, f3 = count()
print(f1, 'f1执行结果是', f1())
print(f2, 'f2执行结果是', f2())
print(f3, 'f3执行结果是', f3())
count函数返回一个列表,列表分别为函数f地址 [<function count.<locals>.f.<locals>.g at 0x00000155CFE18EA0>, <function count.<locals>.f.<locals>.g at 0x00000155CFE18F28>, <function count.<locals>.f.<locals>.g at 0x00000155CFE1C2F0>]
<function count.<locals>.f.<locals>.g at 0x00000155CFE18F28> f1执行结果是 1
<function count.<locals>.f.<locals>.g at 0x00000155CFE18EA0> f2执行结果是 4
<function count.<locals>.f.<locals>.g at 0x00000155CFE1C2F0> f3执行结果是 9
二、闭包
首先,学习装饰器要了解什么是闭包!
闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例。
emmm,简单来说,闭包的组成:
1、外函数
2、内函数
3、内函数调用外函数的变量
4、外函数返回内函数的引用
如果一个函数结束,函数的内部所有东西都会释放掉,还给内存,局部变量都会消失。但是闭包是一种特殊情况,如果外函数在结束的时候发现有自己的临时变量将来会在内部函数中用到,就把这个临时变量绑定给了内部函数,然后自己再结束。
代码:
#闭包函数的实例
# outer是外部函数 a和b都是外函数的临时变量
def outer( a ):
b = 10
# inner是内函数
def inner():
#在内函数中 用到了外函数的临时变量
print(a+b)
# 外函数的返回值是内函数的引用
return inner
if __name__ == '__main__':
# 在这里我们调用外函数传入参数5
#此时外函数两个临时变量 a是5 b是10 ,并创建了内函数,然后把内函数的引用返回存给了demo
# 外函数结束的时候发现内部函数将会用到自己的临时变量,这两个临时变量就不会释放,会绑定给这个内部函数
demo = outer(5)
# 我们调用内部函数,看一看内部函数是不是能使用外部函数的临时变量
# demo存了外函数的返回值,也就是inner函数的引用,这里相当于执行inner函数
demo() # 15
demo2 = outer(7)
demo2()#17
三、装饰器
装饰器(decorator)也称装饰函数,是一种闭包的应用,其主要是用于某些函数需要拓展功能,但又不希望修改原函数,它就是语法糖,使用它可以简化代码、增强其可读性,当然装饰器不是必须要求被使用的,不使用也是可以的,Python 中装饰器通过 @ 符号来进行标识。
import time
def sugar(func):
def sugar2(*args,**kwargs):
res = func(*args,**kwargs)
print('Hello')
time.sleep(1)
return res
return sugar2
@sugar
def fun1(a,b):
return 'DD = {}'.format(a+b)
print(fun1(1,2))
@sugar
def fun2(a,b,c):
return 'EE = {}'.format(a+b+c)
print(fun2(1,2,3))
这个直接就是带参数的装饰器了~
更牛逼的 ------> 详解Python的装饰器
花里胡哨给装饰器传参数,哈哈哈哈哈哈
def bag(*method):
def sugar(func):
def sugar2(*args,**kwargs):
if not method:
print(method)
return func(*args,**kwargs)
else:
if 'post' in method:
print(method)
print('Hello1')
return func(*args,**kwargs)
else:
print(method)
print('Hello2')
return func(*args,**kwargs)
return sugar2
return sugar
@bag('get')
def fun1(a,b):
return 'DD = {}'.format(a+b)
print(fun1(1,2))
@bag('get','post')
def fun2(a,b,c):
return 'EE = {}'.format(a+b+c)
print(fun2(1,2,3))
个人小记:
通过打印,看看这些参数,到底传了个啥,就知道装饰器怎么运行的了
装饰器,装饰过的函数,打印函数名会有变化,如下展示
# 普通函数
def f1(x):
pass
print(f1.__name__) # 输出 f1
# 装饰器装饰过函数
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
@log
def f2(x):
pass
print(f2.__name__) # 输出 wrapper
可见,由于装饰返回的新函数函数名已经不是’f2’,而是@log内部定义的’wrapper’。这对于那些依赖函数名的代码就会失效。decorator还改变了函数的__doc__等其它属性。如果要让调用者看不出一个函数经过了@decorator的“改造”,就需要把原函数的一些属性复制到新函数中:
def log(f):
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
wrapper.__name__ = f.__name__
wrapper.__doc__ = f.__doc__
return wrapper
这样写decorator很不方便,因为我们也很难把原函数的所有必要属性都一个一个复制到新函数上,所以Python内置的functools可以用来自动化完成这个“复制”的任务:
import functools
def log(f):
@functools.wraps(f)
def wrapper(*args, **kw):
print 'call...'
return f(*args, **kw)
return wrapper
最后需要指出,由于我们把原函数签名改成了(*args, **kw),因此,无法获得原函数的原始参数信息。即便我们采用固定参数来装饰只有一个参数的函数:
def log(f):
@functools.wraps(f)
def wrapper(x):
print 'call...'
return f(x)
return wrapper
实操小记:
# 2020/8/18 16:50 小雨 北京
from tools.return_json_response import json_response
import requests
def check_logging(func):
def sugar(request,*args,**kwargs):
auth_key = request.META.get('HTTP_AUTHORIZATION')
if not auth_key:
print('抱歉,没有传token,拜拜您嘞')
return json_response(code='003',msg='必填项为空或信息不全')
else:
url = 'http://39.98.59.164:6797/api/system/w/user'
headers = {'Authorization': auth_key}
html = requests.post(url=url,headers=headers).json()
# html.get('code') 假如键不存在,可以不报错,可以给这个不存在的键默认值
if html.get('code') == '000':
print('恭喜,token正确,欢迎光临')
return func(request,*args,**kwargs)
else:
print('抱歉,token错误,拜拜您嘞')
return json_response(code='904',msg='验证码错误或已过期')
return sugar