Python 入门基础进阶
迭代器&生成器
前引
假如我现在有一个列表l=[‘a’,‘b’,‘c’,‘d’,‘e’],我想取列表中的内容,有几种方式?
首先,我可以通过索引取值l[0],其次我们是不是还可以用for循环来取值呀?
你有没有仔细思考过,用索引取值和for循环取值是有着微妙区别的。
如果用索引取值,你可以取到任意位置的值,前提是你要知道这个值在什么位置。。
如果用for循环来取值,我们把每一个值都取到,不需要关心每一个值的位置,因为只能顺序的取值,并不能跳过任何一个直接去取其他位置的值。
但你有没有想过,我们为什么可以使用for循环来取值?
for循环内部是怎么工作的呢?
python中for循环的机制
首先,我们对一个列表进行for循环。
for i in [1,2,3,4]:
print(i)
上面的代码肯定没有问题,我们换一种情况试试,来循环数字1234.
for i in 1234
print(i)
结果:
Traceback (most recent call last):
File "test.py", line 4, in <module>
for i in 1234:
TypeError: 'int' object is not iterable
看,报错了!报了什么错呢?“TypeError: ‘int’ object is not iterable”,说int类型不是一个iterable,那这个iterable是个啥?
迭代器和可迭代协议
现在,我们已经获得了一个新线索,有一个叫做**“可迭代的”概念**。
首先,我们从报错来分析,好像之所以1234不可以for循环,是因为它不可迭代。那么如果“可迭代”,就应该可以被for循环了。
这个我们知道呀,字符串、列表、元组、字典、集合都可以被for循环,说明他们都是可迭代的。
我们怎么来证明这一点呢?
from collections import Iterable
l = [1,2,3,4]
t = (1,2,3,4)
d = {1:2,3:4}
s = {1,2,3,4}
print(isinstance(l,Iterable)) #判断 l 数据类型是否可迭代
print(isinstance(t,Iterable))
print(isinstance(d,Iterable))
print(isinstance(s,Iterable))
结合我们使用for循环取值的现象,再从字面上理解一下,其实迭代就是我们刚刚说的,可以将某个数据集内的数据“一个挨着一个的取出来”,就叫做迭代。
可迭代协议
能被for循环的对象,就是可迭代对象,那我们怎么知道一个对象能不能被for循环呢?
其实某一个数据类型能被for循环,本质上是因为满足一个要求,这个要求就是可迭代协议
可迭代协议:对象内部实现了__iter__方法。
总结一下我们现在所知道的:可以被for循环的都是可迭代的,要想可迭代,内部必须有一个__iter__方法。
接着分析,__iter__方法做了什么事情呢?
print([1,2].__iter__())
结果
<list_iterator object at 0x1024784a8>
我们执行了列表[1,2]的__iter__方法,获得了一个list_iterator,我们得到了一个新的名词——iterator
这个就是计算机中的专属名词,叫迭代器。
迭代器协议
既什么叫“可迭代”之后,又一个历史新难题,什么叫“迭代器”?
虽然我们不知道什么叫迭代器,但是我们现在已经有一个迭代器了,这个迭代器是一个列表的迭代器。
我们来看看这个列表的迭代器比起列表来说实现了哪些新方法,这样就能揭开迭代器的神秘面纱了吧?
'''
dir([1,2].__iter__())是列表迭代器中实现的所有方法,dir([1,2])是列表中实现的所有方法,都是以列表的形式返回给我们的,为了看的更清楚,我们分别把他们转换成集合,
然后取差集。
'''
#print(dir([1,2].__iter__()))
#print(dir([1,2]))
print(set(dir([1,2].__iter__()))-set(dir([1,2])))
结果:
{'__length_hint__', '__next__', '__setstate__'}
我们看到在列表迭代器中多了三个方法,那么这三个方法都分别做了什么事呢?
方法1
iter_l = [1,2,3,4,5,6].__iter__()
#获取迭代器中元素的长度
print(iter_l.__length_hint__())
#根据索引值指定从哪里开始迭代
print('*',iter_l.__setstate__(0))
#一个一个的取值
print('**',iter_l.__next__())
print('***',iter_l.__next__())
print('****',iter_l.__next__())
6
* None
** 1
*** 2
**** 3
这三个方法中,能让我们一个一个取值的就是next方法
在for循环中,就是在内部调用了一个__next__方法才能一个接一个取到值,
我们用next方法写一个不依赖for的遍历
方法2
l = [1,2,3,4]
l_iter = l.__iter__()
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
item = l_iter.__next__()
print(item)
1
Traceback (most recent call last):
2
3
File "/Users/xujiazeng/python/practice/practice/practice_python.py", line 11, in <module>
4
item = l_iter.__next__()
StopIteration
我们发现,这段代码会报错,这是因为我们一直next把迭代器这两年个的元素取完了,此时如果我们再取,则会抛出一个异常StopIteration,告诉我们,列表中已经没有有效的元素了。
这个时候,我们就要使用异常处理机制来把这个异常处理掉。
方法3
l = [1,2,3,4]
l_iter = l.__iter__()
while True:
try:
item = l_iter.__next__()
print(item)
except StopIteration:
break
这段代码就是for循环运行的本质,我们从l_iter中一个一个取值,l_iter就是一个迭代器
迭代器遵循迭代器协议:必须拥有__iter__方法和__next__方法。
range返回的就是一个可迭代对象
print('__next__' in dir(range(12))) #查看'__next__'是不是在range()方法执行之后内部是否有__next__
print('__iter__' in dir(range(12))) #查看'__next__'是不是在range()方法执行之后内部是否有__next__
from collections import Iterator
print(isinstance(range(100000000),Iterator)) #验证range执行之后得到的结果不是一个迭代器
为什么要使用for循环
l=[1,2,3]
index=0
while index < len(l):
print(l[index])
index+=1
#要毛线for循环,要毛线可迭代,要毛线迭代器
生成器
如果在某些情况下,我们也需要节省内存,就只能自己写。我们自己写的这个能实现迭代器功能的东西就叫生成器。
定义:在Python中,一边循环一边计算的机制,称为生成器:generator。
特点:满足迭代器协议,使用生成器函数或者生成器表达式可以获得生成器。
Python中提供的生成器:
1.生成器函数:常规函数定义,但是,使用yield语句而不是return语句返回结果。yield语句一次返回一个结果,在每个结果中间,挂起函数的状态,以便下次重它离开的地方继续执行
2.生成器表达式:类似于列表推导,但是,生成器返回按需产生结果的一个对象,而不是一次构建一个结果列表
生成器Generator:
本质:迭代器(所以自带了__iter__方法和__next__方法,不需要我们去实现)
特点:惰性运算,开发者自定义
生成器函数
生成器函数的认识:一个包含yield关键字的函数就是一个生成器函数。
import time
def genrator_fun1():
a = 1
print('现在定义了a变量')
yield a
b = 2
print('现在又定义了b变量')
yield b
g1 = genrator_fun1()
print('g1 : ',g1) #打印g1可以发现g1就是一个生成器
print('-'*20) #我是华丽的分割线
print(next(g1))
time.sleep(1) #sleep一秒看清执行过程
print(next(g1))
初识生成器函数
生成器的优点:
1.节省内存
2.python使用生成器对延迟操作提供了支持,所谓延迟操作,是指在需要的时候采取产生结果,而不是立即产生结果。
yield关键字:
1.将函数变成生成器函数
2.返回函数的结果
3.保存/挂机函数的运行状态,以便下次继续往后执行
4.接收send方法传入的值
5.与return的区别,yield在函数中可以多次执行
send方法:
接受用户输入的值,并传给yield,可以通过yield赋值给变量
def generator():
print(123)
content = yield 1
print('=======',content)
print(456)
yield 2
g = generator() 123
ret = g.__next__()
print('***',ret)*** 1
ret = g.send('hello') #send的效果和next一样 ======= hello
print('***',ret)456
*** 2
#send 获取下一个值的效果和next基本一致
#只是在获取下一个值的时候,给上一yield的位置传递一个数据
#使用send的注意事项
# 第一次使用生成器的时候 是用next获取下一个值
# 最后一个yield不能接受外部的值
yield from方法
yield from == for i in
:从一个可迭代对象中获得生成器的返回值
def gen1():
for c in 'AB':
yield c
for i in range(3):
yield i
print(list(gen1()))
def gen2():
for c in 'AB':
return c #return 表示函数工作终止
for i in range(3):
return i
print(list(gen2()))
def gen3():
yield from 'AB'
yield from range(3)
print(list(gen3()))
yield from==for i in
### ['A', 'B', 0, 1, 2]
### ['A']
### ['A', 'B', 0, 1, 2]
列表推导式和生成器表达式
总结:
1.把列表解析的[]换成()得到的就是生成器表达式
2.列表解析与生成器表达式都是一种便利的编程方式,只不过生成器表达式更节省内存
3.Python不但使用迭代器协议,让for循环变得更加通用。大部分内置函数,也是使用迭代器协议访问对象的。例如, sum函数是Python的内置函数,该函数使用迭代器协议访问对象,而生成器实现了迭代器协议,所以,我们可以直接这样计算一系列值的和:
i=sum(x ** 2 for x in range(4))
print(i) # 0~3 平方和为14
推导式的套路
variable = [out_exp_res for out_exp in input_list if out_exp == 2]
out_exp_res: 列表生成元素表达式,可以是有返回值的函数。
for out_exp in input_list: 迭代input_list将out_exp传入out_exp_res表达式中。
if out_exp == 2: 根据条件过滤哪些值可以。
列表推导式
multiples = [i for i in range(30) if i % 3 is 0]
print(multiples)
# Output: [0, 3, 6, 9, 12, 15, 18, 21, 24, 27]
字典推导式
mcase = {'a': 10, 'b': 34}
mcase_frequency = {mcase[k]: k for k in mcase}
print(mcase_frequency)
#{10: 'a', 34: 'b'}
集合推导式
squared = {x**2 for x in [1, -1, 2]}
print(squared)
# Output: set([1, 4])
注:元组没有推导式,元组类型的推导式就是生成器表达式
装饰器
想象一下,有一天领导提了一个需求,统计某个函数执行时间,这个时候你要怎么做呢?
你说这好办啊,
import time
# 导入时间模块
def fun():
time.sleep(2)
print('from fun')
def timer(fun):
start_time = time.time()
fun()
stop_time = time.time()
print(f'函数的运行时间是{stop_time - start_time}')
timer()
#from fun
#函数的运行时间是2.0005362033843994
三下五除二,你就把函数写出来了。
但是接着需求又来了,有一万个函数都要计算运行时间,怎么办呢,这时候你是不是傻眼呢?
import time
# 导入时间模块
def fun():
time.sleep(2)
print('from fun')
def timer():
start_time = time.time()
fun()
stop_time = time.time()
print(f'函数的运行时间是{stop_time - start_time}')
timer()
装饰器初识
概念
本质就是函数(器),能够为其他函数添加附加功能的函数。
装饰器 = 高阶函数 + 函数嵌套 + 闭包
装饰器遵守原则:开放封闭原则
- 不修改被修饰函数的源代码
- 不修改呗装饰函数的调用方式
def timer(func):
def wrapper(*args, **kwargs):
start_time = time.time()
res = func(*args, **kwargs)
stop_time = time.time()
print(f'函数的运行时间是{stop_time - start_time}')
return res
return wrapper
@timmer # 相当于calc = timmer(calc)
def calc(n):
res = 0
for i in range(n):
time.sleep(0.02)
res += i
return res
print(calc(100))
函数的运行时间是2.313875913619995
4950
装饰器的形成过程
:理解装饰器本质**
还是回到之前的计算函数运行时间上:首先我们要计算函数的运行时间,重新定义一个计算时间的函数,将原函数放入计时函数中算运行时间,这就是高阶函数的知识。我们回忆一下高阶函数:
高阶函数:
1.函数接收的参数是一个函数
2.函数的返回值是一个函数
3.满足任意一个条件,就可以称之为高阶函数。
装饰器推导过程
第一步:把函数当做参数传入
'''
将函数当参数传入,计时功能实现了,但是函数的调用方式改变了
'''
def foo():
time.sleep(2)
print('hello word')
def test(func):
print(func)
start_time = time.time()
func() # 在函数另个一函数调用函数,解决不修改原函数代码的功能
stop_time = time.time()
print(f'函数运行时间是{stop_time-start_time}')
test(foo)
<function foo at 0x10b3cce18>
hello word
函数运行时间是2.001737356185913
我们写完上述代码,计时功能是实现了,但是仔细看发现函数的调用方式改变了,怎么办呢?我们可以利用高阶函数中,将函数当参数返回来,看下面代码
函数嵌套:
1.函数定义内定义子函数
2.局部作用域。
闭包函数:嵌套函数中的内部函数有对外部作用域而非全剧作用域变量的引用,该内部函数称为闭包函数.
第二步:利用闭包函数,解决重复运行问题
将函数封装在wrapper中返回,避免在函数体内执行。
import time
def timmer(func):
def wrapper():
print(func)
start_time = time.time()
func()
'''
运行test,此时是把函数封装在闭包函数wrapper()中,
闭包函数wrapper使用上级作用域传进来的func参数。
解决了函数func多次调用的问题。
'''
stop_time = time.time()
print('运行时间是%s'%(stop_time-start_time))
return wrapper
def foo():
time.sleep(3)
print('foo函数运行完毕')
foo = timmer(foo)
# 返回wrapper的地址,将其wrapper函数赋值给foo函数
foo()
<function foo at 0x10ac8e400>
foo函数运行完毕
运行时间是3.0048670768737793
这一步,我们将foo作为参数传入,并封装在wrapper中,在wrapper中计算foo的运行时间,利用了闭包的原理,将wrapper返回出去赋值给foo,避免了foo两次调用,同时也解决了改变调用方式的问题,这个就是装饰器的雏形。
第三步:提炼赋值表达式,定义语法糖
到这里完美了吗?并不是,在python中,为了避免每次都要调用赋值,为我们提供了赋值步骤的缩写方式,这就是语法糖@的作用:
语法糖@:
@装饰器函数名 <==> <被装饰函数> = <装饰器函数>(<被装饰函数>)
在上面应用就是:@timer 等效于 foo = timer(foo)
这时候我们就完成了装饰器最基本的框架
def timmer(func):
def wrapper():
print(func)
start_time = time.time()
func()
stop_time = time.time()
print('运行时间是%s'%(stop_time-start_time))
return wrapper
@timmer # 等效于 test=timmer(test)
def foo():
time.sleep(3)
print('test函数运行完毕')
foo()
还有最后一个问题要解决,刚刚我们讨论的装饰器都是装饰不带参数的函数,现在要装饰一个带参数的函数怎么办呢?
def timer(func):
def inner(a):
start = time.time()
func(a)
stop_time = time.time()
print('运行时间是%s'%(stop_time-stop_time))
return inner
@timer
def foo(a):
print(a)
foo(1)
装饰器——带参数的装饰器
现在参数的问题已经完美的解决了,可是如果你的函数是有返回值的呢?
import time
def timer(func):
def inner(a):
start_time = time.time()
res = func(a)
stop_time = time.time()
print('运行时间是%s'%(stop_time-start_time))
return res
return inner
@timer
def foo(a):
time.sleep(2)
print(a)
return a # 不返回 上面装饰器就接收不到 那么打印foo(1)就是None
foo(1)
print(foo(1))
其实装饰带参的函数并不是什么难事,但假如你有两个函数,需要传递的参数不一样呢?
import time
import time
def timer(func):
def inner(*args,**kwargs):
start = time.time()
re = func(*args,**kwargs)
print(time.time() - start)
return re
return inner
@timer #==> foo1 = timer(foo1)
def foo1(a,b):
time.sleep(1)
print('in foo1')
@timer #==> foo2 = timer(foo2)
def foo2(*args,**kwargs):
time.sleep(2)
print(args,kwargs)
return 'foo2 over'
foo1(1,2)
foo2(1,2,c=3,d=4)
in foo1
1.00132417678833
(1, 2) {'c': 3, 'd': 4}
2.0033130645751953
给装饰器加上参数功能
真实的登录系统,账号信息是从数据库提取,不是提前定好的,每个功能函数也有相应的数据库。
这时候需要在装饰器外层在套一层函数,接受不同参数,进入不同的数据库提取数据。
思路
def outer(flag):
def timer(func):
def inner(*args,**kwargs):
if flag:
print('''执行函数之前要做的''')
re = func(*args,**kwargs)
if flag:
print('''执行函数之后要做的''')
return re
return inner
return timer
@outer(False)
def func():
print(111)
func()
带参数的装饰器
练习
import time
import time
username_list = [
{'name': 'alex', 'passwd': '123456'},
{'name': 'linhaifeng', 'passwd': '1234567'},
{'name': 'wupeiqi', 'passwd': '12345678'}
]
current_info = {'username': None, 'login': False}
def auth(auth_type='filedb'):
def auth_func(func):
def wrapper(*args,**kwargs):
print('认证类型是%s'%auth_type)
if auth_type == 'filedb':
if current_info['username'] and current_info['login']:
res = func(*args,**kwargs)
return res
username = input('请输入账号:')
passwd = input('请输入密码:')
for user_info in username_list:
if username == user_info['name'] and passwd == user_info['passwd']:
current_info['username'] = username
current_info['login'] = True
res = func(*args,**kwargs)
return res
else:
print('账号或密码错误!')
elif auth_type == 'ldap':
print('鬼才他么会玩')
res = func(*args, **kwargs)
return res
else:
print('鬼知道你用的什么认证方式')
res = func(*args, **kwargs)
return res
return wrapper
return auth_func
'''
直接运行auth(auth_type='filedb')返回auth_func,在对auth_func使用@语法糖
'''
@auth(auth_type='filedb')
# auth_func =auth(auth_type='filedb') ---> @auth_func 附加了一个auth_type
def index():
print('欢迎来到京东主页')
@auth(auth_type='ldap')
def home(name):
print(f'欢迎{name}回家')
@auth(auth_type='sssss')
def shopping_car(name):
print(f'{name}的购物车中有["奶茶","妹妹"]')
index()
home('self')
shopping_car('self')
请输入账号:alex
请输入密码:123456
欢迎来到京东主页
认证类型是ldap
鬼才他么会玩
欢迎self回家
认证类型是sssss
鬼知道你用的什么认证方式
self的购物车中有["奶茶","妹妹"]
刚刚那个装饰器已经非常完美了,但是正常我们情况下查看函数的一些信息的方法在此处都会失效
def index():
'''这是一个主页信息'''
print('from index')
print(index.__doc__) #查看函数注释的方法
print(index.__name__) #查看函数名的方法
这是一个主页信息
index
装饰器开放封闭原则
- 对扩展是开放的
为什么要对扩展开放呢?
我们说,任何一个程序,不可能在设计之初就已经想好了所有的功能并且未来不做任何更新和修改。所以我们必须允许代码扩展、添加新功能。
- 对修改是封闭的
为什么要对修改封闭呢?
就像我们刚刚提到的,因为我们写的一个函数,很有可能已经交付给其他人使用了,如果这个时候我们对其进行了修改,很有可能影响其他已经在使用该函数的用户。
装饰器完美的遵循了这个开放封闭原则。
装饰器的主要功能和固定结构
1.在不改变函数调用方式的基础上在函数的前、后添加功能
2.固定结构
def timer(func):
def inner(*args,**kwargs):
'''执行函数之前要做的'''
re = func(*args,**kwargs)
'''执行函数之后要做的'''
return re
return inner
装饰器的固定格式
多个装饰器装饰同一个函数
有些时候,我们也会用到多个装饰器装饰同一个函数的情况。
'''
多层装饰器的套用:
@deco1
@deco2
@deco3
func()
加载时:
从上往下依次加载
执行时:
从下往上依次执行
最开始:
func = deco3(func)
然后:
func = deco2(func),此时func实际上是deco3(func)
所以==> func = deco2(deco3(func))
最后:
func = deco1(func),此时func实际是deco2(deco3(func))
所以==> func = deco1(deco2(deco3(func)))
'''
'''
加载时:
从上往下依次加载
执行时:
从下往上依次执行
'''
def wrapper1(func):
def inner():
print('wrapper1 ,before func') #6
func()#7 func()=foo()
print('wrapper1 ,after func')#9
return inner
def wrapper2(func):#2
def inner():
print('wrapper2 ,before func') #4
func() #5 func()=f()=inner() 由@wrapper1 传递
print('wrapper2 ,after func')#10
return inner
@wrapper2 # 加载1
@wrapper1 # 加载
def f():
print('in f')#8
f()#3
加载 从上到下
print('wrapper2 ,before func')
print('wrapper1 ,before func')
print('in f')
执行 从下到上
print('wrapper1 ,after func')#9
print('wrapper2 ,after func')#10
多层装饰器嵌套问题
多层装饰器嵌套,靠近被装饰函数先被装饰。逐级往外装饰,外层装饰器装饰的是里层装饰器返回函数。装饰完后逐层向内执行。