补充:
callable 代表可调用的,加括号可以执行。(函数或者类)
import this 查看python之禅
一。闭包函数
所谓闭包函数,就是定义在函数内部的函数,也就是函数定义的嵌套。而在其内部函数中可以引用外部函数作用域的名字。
闭包的直接问题是传参。
一般的传参方法都是将参数直接传递给函数,函数内部就可以引用,如:
def foo(oop): print(oop) foo(123) #输出结果>>>123
foo收到参数123后直接打印,而在闭包函数中需要从内函数调用外函数的参数,进行运算,如下:
def outter(): x=1 y=2 def inner(): print(x+y) return x+y return inner res=outter() res() #输出结果>>>3
像内函数inner()就调用了外函数的x与y参数。这里复习一下闭包函数的运行过程。
当outter被调用时,执行return inner,返回的是inner的内存地址,所以outter()就是outter函数的返回值,所以res就被赋予inner的内存地址,加上()后才能执行print(x+y)。
res仅仅代表着1与2的值,在运行时并不能对其进行传参,所以一下代码等同于以上代码:
def outter(x,y): def inner(): print(x+y) return x+y return inner res=outter(1,2) res() res=outter(3,4) res() #输出结果>>>3 7
x,y是形参,在创建和传参时就相当于使得x=1,y=2,在外函数中始终有效,所以outter变成了一个有参函数,
可以对res进行重新赋值改变其return的值。
对于闭合函数的应用,res可以反复使用而不需要重复对其传参,当参数改变的时侯可以重新对outter进行传参,这种 方便更多的体现在爬虫中(了解):
import requests def outter(url): def inner(): res=requests.get(url)#把url网站请求赋值给res print(res.text) print(res) return inner res=outter('https://i.cnblogs.com/EditPosts.aspx?opt=1') res()
res.text代表的就是输出网站所有内容,当不使用以上办法时,需要每次使用时都要对同一函数进行相同的赋值,而闭包函数只需要res即可,对于爬虫来说非常方便。
二。装饰器
装饰器就是可以给被装饰的对象添加新的功能。
装饰器满足开放封闭原则,开放就是对外扩展原则,可以对原先的功能进行扩展,封闭:不能修改原来的函数。
做成一个装饰器必须要满足两个条件:
1,不改变被装饰对象的源码。
2,不改变装饰对象的调用方式。
复习:外函数返回的值是内函数的内存地址,内存地址+()可执行代码,带着这个概念和两个条件,开始接触装饰器:
补充:
inport time 模块,time.time()是时间戳,可以返回现在时间距离1970-1-1日的相差秒数(1970.1.1是unix系统诞生的日子)
time.sheep(3)
有一个函数,index,需要判断其程序的运行时间,如何使用装饰器对其进行功能的拓展。
首先用time模块可以对其进行功能上的实现:
import time def index(): time.sleep(3) print('我是登录程序') time1=time.time() index() time2=time.time() print(time2-time1) #输出结果>>> #我是登录程序 #3.0007030963897705
然而并没有使用函数进行封装,所以使用函数版:
def get_time(): time1=time.time() index() time2=time.time() print(time2-time1) get_time()
get_time函数只能对index函数进行功能拓展,所以并没有实现装饰器功能,调用的时候没有使用原来的调用名index,所以,为了达到这一目的,可以使用闭包函数,将get_time()的内存地址返回给外函数,再将外函数的值传给原函数,就可以使用原函数的名字调用get_time函数了。
def outter(): def get_time(): time1=time.time() index() time2=time.time() print(time2-time1) return get_time res=outter() res()
这样一个针对index的函数的装饰器差不多构成了,但还是不满足调用原函数实现拓展功能的目标,而且这里不能直接将get_time的内存地址赋给内函数传给原函数,否则执行index=get_time()函数时会将get_time里的函数都执行一遍,给外函数就不会出现这个问题。
对于上代码中只是针对index做的装饰器,换做其他的函数就不可用,所以要将装饰器中的index()当成参数传入,其形参就定义再外函数中被传入。
def register(): time.sleep(3) print('我是注册程序') def outter(func): def get_time(): time1=time.time() func() time2=time.time() print(time2-time1) return get_time res=outter(index) index=res index() register=outter(register) register() #输出结果>>>我是登录程序 #3.000358819961548 #我是注册程序 #3.0000927448272705
这样将函数名代表的内存地址传入内函数中,加上()就可以被执行,将外函数返回的内函数内存地址赋值给原函数名,再加()就可以执行,效果和装饰器一致,所以,outter满足了无参函数的装饰器所有规定。
既然有无参函数,就有有参函数,当有参函数被传入的时后,其参数不能被内函数接受,所以,要装饰有参函数,必须要将参数以某种形式传入,这里选择了可以接受多余参数的*和** 做为形参。
def print_name(name): time.sleep(3) print('我是%s'%name)
return 'name' def outter(func): def get_time(*args,**kwargs): time1=time.time() func(*args,**kwargs) time2=time.time() print(time2-time1) return get_time print_name('lzx') print_name=outter(print_name) print_name('lzx') #输出结果>>>我是lzx #我是lzx #3.0001375675201416
这样的装饰器,有一个小小的问题,当用户需要返回print_name的返回值时,会出现不一样的情况
print(print_name('lzx')) print_name=outter(print_name) print_name('lzx') print(print_name('lzx')) #输出结果>>>我是lzx #name #我是lzx #3.0016050338745117 #我是lzx #3.0001797676086426 #None
在装饰器之前打印次函数的返回值时时name原返回值,但是当装饰之后只会返回none,那返回值去哪了返回的是谁的返回值呢,这里需要短暂的分析
当程序运行时,编译好outter函数后,再将其返回的get_time内存地址返回给print_name也就是原函数,再运行原函数时实际就是运行内函数get_time所以再执行第三步时其实是返回的get_time的返回值,而其中真正的返回值只要函数func拥有,所以为了实现完美装饰器功能,必须将get_name的函数返回值与原函数的返回值一致。
def register(): time.sleep(3) print('我是注册程序') def print_name(name): time.sleep(3) print('我是%s'%name) return 'name' def outter(func): def get_time(*args,**kwargs): time1=time.time() res=func(*args,**kwargs) time2=time.time() print(time2-time1) return res return get_time # print(print_name('lzx')) print_name=outter(print_name) # print_name('lzx') print(print_name('lzx')) #输出结果>>>我是lzx #3.0002002716064453 #name
如上便是一个完整功能的装饰器。
装饰器语法糖
在每次需要调用装饰器时,都要执行原函数=装饰器(原函数)这样的操作,很麻烦,所以可以使用装饰器语法糖
def outter(func): def get_time(*args,**kwargs): time1=time.time() res=func(*args,**kwargs) time2=time.time() print(time2-time1) return res return get_time @outter def register(): time.sleep(3) print('我是注册程序') register() #输出结果>>>我是注册程序 #3.000058889389038
@outter对于register相当于register=outter(register),将紧挨着的那个对象当作参数赋值给它,和上述语法差不多。
装饰器模板
def outter(func): def inner(*args,**kwargs): print('执行被装饰函数之前 你可以做的操作') res = func(*args,**kwargs) print('执行被装饰函数之后 你可以做的操作') return res return inner
这便是装饰器的模板
有参装饰器
当装饰器中需要调用额外的参数时,如何将参数传递呢,在内函数中,参数时提供给原函数使用,如果改变,势必要改变原函数,与装饰器原则不符,而外函数的参数是传递原函数,如果增加,在使用语法糖时会报错(参数不足),所以,需要在外函数外再报一个函数:
def outter2(choose): def outter(func): def get_time(*args,**kwargs): if choose==1: time1=time.time() res=func(*args,**kwargs) time2=time.time() print(time2-time1) return res elif choose==2: return 0 return get_time return outter @outter2(1) def register(): time.sleep(3) print('我是注册程序') #输出结果>>>我是注册程序 #3.0003597736358643
语法糖是可以传入参数的,当传如1时会执行语句,传入0 时什么都不执行,这个参数可以传入多个,所有多余参数都可以写入。
装饰器修复:
当用户对被装饰后的函数去内存地址和备注时,并不会返回原来的内存地址,而是返回内函数的,所以为了以假乱真,可以使用from functools import wraps,再再外函数里使用@wrap(func)语句就可以返回原来的内存地址及注释
from functools import wraps def outter2(choose): def outter(func): @wraps(func) def get_time(*args,**kwargs): if choose==1: time1=time.time() res=func(*args,**kwargs) time2=time.time() print(time2-time1) return res elif choose==2: return 0 return get_time return outter
多个装饰器
当多个装饰器装饰一个函数时他们的顺序是规定 的
@outter1 # index = outter1(wapper2) @outter2 # wrapper2 = outter2(wrapper3) @outter3 # wrapper3 = outter3(最原始的index函数内存地址) def index(): print('from index')
其执行顺序是
加载了outter3
加载了outter2
加载了outter1
执行了wrapper1
执行了wrapper2
执行了wrapper3
from index
所以它们的执行规则应该是装饰顺序为从下往上,执行顺序为从上往下。