一、叠加多个装饰器
加载装饰器就是将原函数名偷梁换柱成了装饰器最内层那个wrapper函数
在加载完毕后,调用原函数其实就是在调用wrapper函数当一个被装饰的对象同时叠加多个装饰器时
装饰器的加载顺序是:自下而上
加载顺序,自下而上改变的是各个装饰器内func所表示的装饰器最内层的函数(不同的wrapper),这样的加载顺序在装饰器自上而下的执行顺序执行的时候就能够按照加载时候的按照一定的顺序去执行多个装饰器了
装饰器内wrapper函数的执行顺序是:自上而下
多个装饰器例子:
在遇到有参装饰器时,先调用有参装饰器的函数,再执行@语法糖,
比如在下面这个例子中,原函数是index,使用auth和timmer两个装饰器来对其进行装饰,他的加载顺序为:按照自下而上的装载顺序,首先加载有参装饰器auth(),遇到有参装饰器,先执行auth函数,在执行装饰器的语法糖,实质执行的@xxx装饰器,即将原函数index最初的内存地址使用xxx来装饰,得到新的index(这个index的内存地址就变成了xxx内层函数wrapper2的内存地址了),接着加载第二个装饰器timmer,将新的index进行装饰,然后会得到又一个新的index,这时候的index的指向的内存地址为装饰器timmer返回的wrapper1的内存地址,到此为止装饰器加载完毕,index由最初自己本身的index内存地址变成了被两个装饰器装饰后的全新的wrapper1的内存地址.这样也就实现了两个装饰器的装饰效果.
在加载过程中各个装饰器中func的改变,首先装饰器auth中的func和原函数index指向的内存地址是一致的,接着加载到装饰器timmer时,func就变成了xxx返回的wrapper2的内存地址了,所以这就决定了在执行过程的装饰器的执行顺序
在多个装饰器执行时: 按照自上而下的执行顺序执行,首先执行timmer,执行timmer时,timmer中func的内存地址是wrapper2的内存地址,所以运行到timmer中内层中的func()的时候,就要执行auth中的wrapper2了,在本例子中体现为,执行晚wrapper1中的start=time.time()后就会直接执行wrapper2的第一行程序print()在wrapper2执行过程中的func执行的原函数的功能,在wrapper2执行结束之后程序再回到wrapper1中继续执行stop=time.time(),接着执行wrapper1中的后续代码直到结束,所以在程序执行过程中wrapper1中的计时计算的是wrapper2和原函数index两个函数的运行时间.
import time
def timmer(func): #func=wrapper2的内存地址
def wrapper1(*args, **kwargs):
print('===================================>wrapper1运行了')
start=time.time()
res = func(*args, **kwargs) #===========================>跳到wrapper2去执行了,
stop=time.time()
print('run time is %s' %(stop - start))
return res
return wrapper1
def auth(engine='file'):
def xxx(func): # func=最原始那个index的内存地址
def wrapper2(*args, **kwargs):
print('===================================>wrapper2运行了')
name=input('username>>>: ').strip()
pwd=input('password>>>: ').strip()
if engine == 'file':
print('基于文件的认证')
if name == 'egon' and pwd == '123':
print('login successfull')
res = func(*args, **kwargs)
return res
elif engine == 'mysql':
print('基于mysql的认证')
elif engine == 'ldap':
print('基于ldap的认证')
else:
print('错误的认证源')
return wrapper2
return xxx
@timmer # index=timmer(wrapper2的内存地址) #index=wrapper1的内存地址
@auth(engine='file') #@xxx #index=xxx(最原始那个index的内存地址) #index=wrapper2的内存地址
def index():
print('welcome to index page')
time.sleep(2)
index() #wrapper1的内存地址()
'''
import time
def timmer(func): #func=index最初的内存地址
def wrapper1(*args, **kwargs):
print('===================================>wrapper1运行了')
start=time.time()
res = func(*args, **kwargs) #执行index源程序
stop=time.time()
print('run time is %s' %(stop - start))
return res
return wrapper1 经过装饰之后index内存地址变为wrapper1
def auth(engine='file'):
def xxx(func): # func=wrapper1的内存地址
def wrapper2(*args, **kwargs):
print('===================================>wrapper2运行了')
name=input('username>>>: ').strip()
pwd=input('password>>>: ').strip()
if engine == 'file':
print('基于文件的认证')
if name == 'egon' and pwd == '123':
print('login successfull')
res = func(*args, **kwargs) 运行wrapper1
return res
elif engine == 'mysql':
print('基于mysql的认证')
elif engine == 'ldap':
print('基于ldap的认证')
else:
print('错误的认证源')
return wrapper2
return xxx
@auth(engine='file')
@timmer
def index():
print('welcome to index page')
time.sleep(2)
index() #wrapper2的内存地址()
将上边例子中的装饰器顺序改变之后,装饰器的执行也会发生变化,
最终调用的index()的内存地址发生的变化过程: index最初的内存地址>>经过timmer装饰变为wrapper1的内存地址>>再经过auth的装饰变为wrapper2的内存地址
func的变化:在加载装饰器timmer时,func指向的原函数index的最初的内存地址>>加载装饰器auth时func经过timmer的装饰变成了wrapper1的内存地址,
装饰器执行顺序:首先执行装饰器auth,运行到auth中的func()时,因为这时的func指向的是wrapper1的内存地址,所以这时候会转到timmer中的wrapper1运行,wrapper1中的func指向的有事原函数index的内存地址,所以这时候计算的程序运行时间仅仅是原函数index的运行时间2秒.在wrapper1运行结束后,如果wrapper2中还有代码没有执行完,就会接着执行wrapper2中剩下的代码.
二、迭代器
1. 什么是迭代器
迭代指的是一个重复的过程,每一次重复都是基于上一次的结果而来的
li=['a','b','c','d','e']
li=('a','b','c','d','e')
li='hello'
i=0
while i < len(li):
print(li[i])
i+=1 这是迭代器循环取值的原理
迭代器指的是迭代取值的工具,该工具的特点是可以不依赖于索引取值
2. 为何要用迭代器
为了找出一种通用的&可以不依赖于索引的迭代取值方式
3. 如何用迭代器
可迭代的对象:但凡内置有.__iter__方法的对象都称之为可迭代的对象
迭代器对象:既内置有__iter__方法,又内置有__next__方法
关于__iter__方法:
调用可迭代对象的__iter__会的到一个迭代器对象
调用迭代器对象的__iter__会的到迭代器本身
4. 总结迭代器的优缺点:
优点:
1. 提供了一种通用的&可以不依赖于索引的迭代取值方式
2. 同一时刻在内存中只有一个值,更加节省内存(这里同一时刻在内存中只有一个值指的是,迭代器在运行过程中只要不执行 __next__()命令,迭代器内部是一个值都没有的.在执行了__next__之后才会重复的取值)
缺点:
1. 取指定值不如索引灵活,并且迭代器是一次性的(一次性是指迭代器在取值过程中,取过一个值之后不就必须要往下进行取下一个值,不能回头,直到循环把要取得值取完.)
2. 无法预知迭代器数据的个数
dic={'x':1,'y':2,'z':3}
iter_dic=dic.__iter__() 执行__iter__()命令将dic这个可迭代对象转换成迭代器对象
# print(iter_dic) 迭代器对象打印出来为:<dict_keyiterator object at 0x00000265E5FF6728>
res1=iter_dic.__next__() 再对迭代器对象执行__next__命令进行取值操作,__next__()一次只能取一
个值
print(res1)
res2=iter_dic.__next__()
print(res2)
res3=iter_dic.__next__()
print(res3)
res4=iter_dic.__next__()
print(res4)
print(dic.__iter__().__next__())
print(dic.__iter__().__next__())
print(dic.__iter__().__next__())
iter_dic=open(r'db.txt',mode='rt',encoding='utf-8') 可以看出文件对象本身就是迭代器对象,可以直接执行__next__()取值操作
while True: 在对文件对象执行循环取值的时候,取完值之后程序会进行报错,这时可以使用try来捕获错误,并接受错误然后打断循环,终止错误信息的发出,就可实现完整取值的操作
try:
print(iter_dic.__next__())
except StopIteration:
break
使用try来捕获错误信息:在有错误信息的代码上面加上try,并将本行代码缩进,try的下边使用except接受程序返回的错误信息,最后使用break 打断.
可迭代的对象: str,list,tuple,dict,set,文件对象
迭代器对象: 文件对象可迭代的对象=====》迭代器对象:调用可迭代对象内置的__iter__方法会有一个返回值,该返回值就是对应的迭代器对象
for循环准确地说应该是迭代器循环,for循环的原理如下:
1. 先调用in后面那个值的__iter__方法,得到迭代器对象
2. 执行迭代器.__next__()方法得到一个返回值,然后赋值给一个变量k,运行循环体代码
3, 循环往复,直到迭代器取值完毕抛出异常然后捕捉异常自动结束循环
dic={'x':1,'y':2,'z':3}
iter_dic=dic.__iter__()
print(iter_dic)
print(iter_dic.__iter__()) 在迭代器对象后边多次使用__iter__命令的时候,返回的是迭代器本身
三、自定义迭代器
yield关键字:只能用在函数内
在函数内但凡包含有yield关键字,再去执行函数,就不会立刻运行函数体代码了(即对函数进行暂停,再次调用函数时会从yield下边的代码开始执行,这也是yield可以用来做迭代器的原因)
会得到一个返回值,该返回值称之为生成器对象,生成器本质就是迭代器
总结yield:
1. 提供一种自定义迭代器的解决方案
2. yield可用于返回值
yield VS return
相同点:都可以用于返回值
不同点:yield可以暂停函数,yield可以返回多次值,而return只能返回一次值函数就立刻终止
使用yield来自定义迭代器:
def func():
print('=====>第一次')
yield 1
print('=====>第二次')
yield 2
print('=====>第三次')
yield 3
print('=====>第四次')
print(func) 输出:<function func at 0x0000019090831E18>
g=func()
print(g.__iter__().__iter__().__iter__() is g) Ture(证明多次调用__iter__返回的还是迭代器本身)
iter(g) #g.__iter__() 除了可以使用__iter__命令外,可以使用iter()使得可迭代对象变为迭代器对象
next(g) #g.__next__()
res1=next(g) 第一次取值得到的是 print('=====>第一次')
print(res1) 得到第一个yield的返回值1
res2=next(g) 第二次取值得到的是 print('=====>第二次')
print(res2) 得到第二个yield的返回值2
res3=next(g) 第三次取值得到的是 print('=====>第二次')
print(res3) 得到第三个yield的返回值3
res4=next(g) 第四次取值得到的是 print('=====>第二次')
print(res4) 得到第四个yield的返回值4
这是一个自定义的迭代器就定义成功了
下面自定义一个和rang()功能相同的自定义迭代器
def my_range(start,stop,step=1): 设置三个参数:开始参数,结束参数,步长
while start < stop:
yield start
start+=step
#一个自定义的rang就定义好了
res=my_range(1,5,2) # 1 3
next(res) 第一次取值1
next(res) 第二次取值3
print(next(res)) 在这里再进行取值操作就会进行报错,因为res只有两个值可以取,如果上面注释掉一个取值操作这里就会取到第二个值3,两个都注释掉的话就会取到第一个值1,页证明了迭代器的取值操作在一个循环内是不可逆的,
for item in res:
print(item) 得到值1,3
for item in my_range(1,5,2):
print(item) 得到值1,3 证明咱们自定义的迭代器是可以正常使用的
四、xxx生成器
1.三元表达式
在使用三元表达式的时候如果条件成立会执行左边的语句,不成立则执行if条件右边的语句
def max2(x,y):
if x > y:
return x
else:
return y
x=10
y=20 一元 if(第二个元) 第三个元
res='条件成立的值' if x > y else '条件不成立的值'
print(res)
2.列表生成式
优点:方便,改变了编程习惯,可称之为声明式编程
普通列表的生成方式:创建一个新列表,使用for循环等方式取值之后再使用append向里边传值
l=[]
for i in range(1,11):
if i > 4:
res='egg%s' %i
l.append(res)
print(l)
列表生成式是在类表中直接写输入生成列表的语句, [传入列表中值的格式+循环取值+取值条件]
l=['egg%s' %i for i in range(1,11) if i > 4]
print(l)
names=['egon','lxx','yyx','cw','alex','wxx']
l=[]
for name in names:
if name != 'egon':
res='%s_DSB' %name
l.append(res)
print(l)
l=['%s_DSB' %name for name in names if name != 'egon']
print(l)
3.生成器表达式
1、把列表推导式的[ ]换成()就是生成器表达式
2、优点:省内存,一次只产生一个值在内存中
2、示例:生一筐鸡蛋变成给你一只老母鸡,用的时候就下蛋,这也是生成器的特性
>>> chicken=('鸡蛋%s' %i for i in range(5))
>>> chicken
<generator object <genexpr> at 0x10143f200>
>>> next(chicken)
'鸡蛋0'
>>> list(chicken) #因chicken可迭代,因而可以转成列表
['鸡蛋1', '鸡蛋2', '鸡蛋3', '鸡蛋4',]
生成器实质就是一个迭代器
res=(i**2 for i in range(3))
print(res)
print(next(res))
print(next(res))
print(next(res))
print(next(res)) 第四次取值时就会取不到值了
with open(r'db.txt',mode='rt',encoding='utf-8') as f:
data=f.read()
print(len(data)) #1025 文件的总长度
#使用常规循环方法得到文件总长度
-------------------------------------------------------
res=0
for line in f:
res+=len(line)
print(res)
-------------------------------------------------------
res=sum((len(line) for line in f)) 生成器和求和函数sum配合来完成计算文件总长度
res=sum(len(line) for line in f) 使用生成器时,多余的小括号可以去掉不影响生成器的功能
print(res)
res=max([len(line) for line in f]) 如果里边用的不是小括号,外边的小括号是不可以省略的
res=max((len(line) for line in f)) 使用生成器和max函数来计算文件中最长一行的文件长度
res=max(len(line) for line in f) 同样可以去掉小括号
print(res)
4.字典生成式
items=[('name','egon'),('age',18),('sex','male')]
常规产生一个新字典的方式:首先新建一个空字典,然后使用for循环等方式取值赋给字典的K,V然后来填充自身
然后产生一个新的字典.
dic={}
for k,v in items:
dic[k]=v
print(dic)1
字典生成式:和列表生成式类似,字典生成式是{k:v for k,v in items if+条件}
res={k:v for k,v in items if k != 'sex'}
print(res)
res={i for i in 'hello'}
print(res)