Python菜鸟编程第十一课之迭代器与生成器
1.迭代器
可迭代对象:list,str,tuple,dict,可以用for…in…遍历的序列。(从深层次中理解:指存储了元素的一个容器对象,且容器中的元素可以通过_iter_ ( )方法或 _getitem_( )方法访问。)
迭代器协议:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么引起StopIteration 异常,以终止迭代(只能往下走,不能回退)。
demo:
l1=[1,2,3]
li_iter=iter(l1)
# li_iter=l1.__iter__()#与前一句代码作用相同
print(next(li_iter))
print(next(li_iter))
print(li_iter.__next__())
#print(li_iter.__next__()) 如果再迭代一次,则会报错
运行结果:
1
2
3
demo2:
判断一个对象是否可以迭代。
from collections import Iterable
print(isinstance([],Iterable))
print(isinstance(str(),Iterable))
print(isinstance({},Iterable))
print(isinstance(set(),Iterable))
print(isinstance(123,Iterable))
print(isinstance(True,Iterable))
运行结果:
True
True
True
True
False
False
demo3:
自定义一个容器,判断是否可迭代
from collections import Iterable
class Myclas:
def __init__(self):
self.names=[]
def add(self,name):
self.names.append(name)
my_class=Myclas()
my_class.add('qqq')
my_class.add('www')
my_class.add('eee')
print('是否为可迭代对象:',isinstance(my_class,Iterable))
print(type(my_class))
运行结果:
是否为可迭代对象: False
<class '__main__.Myclas'>
demo4:
自定义一个容器,判断是否可迭代
from collections import Iterable
class Myclas:
def __init__(self):
self.names=[]
def add(self,name):
self.names.append(name)
def __iter__(self):
return self.names.__iter__()
my_class=Myclas()
my_class.add('qqq')
my_class.add('www')
my_class.add('eee')
print('是否为可迭代对象:',isinstance(my_class,Iterable))
print(type(my_class))
运行结果:
是否为可迭代对象: True
<class '__main__.Myclas'>
qqq
www
eee
与demo3相比,增了一个_iter_()方法。
_iter_()方法,可以为我们提供一个迭代器。在迭代一个对象的时候,实际上就是获取该对象提供的一个迭代器,然后通过该迭代器依次获取对象的每一个数据。
由此可见,for…in…循环的本质就是通过iter()函数获取可迭代对象的Iterable迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值,当遇到StopIteration的异常后退出。
迭代器的核心就是通过next()函数调用下一个数据,如果每次返回的数据不是在一个已有的数据集合中读取的,而是通过程序按照一定规律生成的,那么也就意味着可以不用依赖一个已有的数据集合,无需将所有的迭代对象数据一次性缓存下来供后续使用,这样可以节省大量的内存空间。
demo5:
通过迭代来实现斐波那契数列
class Fibonacci(object):
'''斐波那契数列迭代器'''
def __init__(self, n):
# 记录生成的斐波那契数列的项数
self.num1 = 0
self.num2 = 1
self.n = n
self.fbindex = 0
def __next__(self):
'''调用next()函数来获取下一个数'''
if self.fbindex < self.n:
self.fbindex+=1
num = self.num1
self.num1, self.num2 = self.num2, self.num1 + self.num2
return num
else:
raise StopIteration
def __iter__(self):
return self
fib=Fibonacci(10)
for num in fib:
print(num,end=' ')
运行结果:
0 1 1 2 3 5 8 13 21 34
demo6:
class test:
def __init__(self, data=1):
self.data = data
def __iter__(self):
return self
def __next__(self):
if self.data > 5:
raise StopIteration
else:
self.data += 1
return self.data
for i in test(2):
print(i)
运行结果:
3
4
5
6
从data=2开始迭代,直到data大于5。
2.生成器
生成器:利用迭代器,我们可以在每次迭代获取数据时(通过next()方法)按照特定的规律进行生成。但是我们在实现一个迭代器时,关于当前迭代的状态需要我们自己记录,进而才能根据当前的状态生成下一个数据,为了达到记录当前状态,并配合next()函数进行迭代使用,可以采用更简便的语法。
生成器其实是一种特殊的迭代器,比迭代器更加优雅。
demo:
li=[x**2 for x in range(6)]
print(li)
gen=(x**2 for x in range(6))
print(gen)
print('通过next()函数取得下一个值:',next(gen))
print('通过next()函数取得下一个值:',next(gen))
print('通过next()函数取得下一个值:',next(gen))
print('通过next()函数取得下一个值:',next(gen))
print('通过next()函数取得下一个值:',next(gen))
print('通过next()函数取得下一个值:',next(gen))
gen=(x**2 for x in range(6))#假如注释掉这一句代码,则下面的for循环不会输出结果,因为前面的next以及遍历完了。
for i in gen:
print(i,end=' ')
运行结果:
[0, 1, 4, 9, 16, 25]
<generator object <genexpr> at 0x000001CEDEC3A750>
通过next()函数取得下一个值: 0
通过next()函数取得下一个值: 1
通过next()函数取得下一个值: 4
通过next()函数取得下一个值: 9
通过next()函数取得下一个值: 16
通过next()函数取得下一个值: 25
0 1 4 9 16 25
2.1生成器函数
在函数中如果出现了yield关键字,那么该函数就不再是一个普通函数,而是生成器函数。
demo1:
def foo():
yield 11
yield 2
return
yield 3
f = foo()
print(next(f))#程序会停留在对应yield 后的语句
print(next(f))
print(next(f))
运行结果:
11
2
Traceback (most recent call last):
File "D:/PyCharm/BClass/PXClass/2019-7-30.py", line 114, in <module>
print(next(f))
StopIteration
这里报错,是因为程序遇到return ,不再继续执行后续代码。假如 return 后返回一个值,比如 return 50,
报错信息处的StopIteration则会变为 StopIteration: 50。
demo2:
通过函数啦输出无穷个奇数
def odd():
n = 1
while True:
yield n
n += 2
odd_num = odd()
print(next((odd_num)))
print(next((odd_num)))
for i in odd_num:#这里限制,只输出到99
if i <100:
print(i,end=' ')
运行结果:
1
3
5 7 9 11 13 15 17 19 21 23 25 27 29 31 33 35 37 39 41 43 45 47 49 51 53 55 57 59 61 63 65 67 69 71 73 75 77 79 81 83 85 87 89 91 93 95 97 99
demo3:
通过类来生成无穷个奇数。
class OddIter:
def __init__(self):
self.start = -1
def __iter__(self):
return self
def __next__(self):
self.start += 2
return self.start
odd = OddIter()
for i in range(6):#这里限制,只输出到6项
print(next(odd),end=' ')
运行结果:
1 3 5 7 9 11
2.2close()函数
手动关闭生成器,后面调用会引起StopIteration异常。
demo:
def gen():
yield 1
yield 2
yield 3
g=gen()
print(next(g))
print(next(g))
g.clise()
print(next(g))
运行结果:
1
2
Traceback (most recent call last):
File "D:/PyCharm/BClass/PXClass/2019-7-30.py", line 156, in <module>
g.clise()
AttributeError: 'generator' object has no attribute 'clise'
这里本该输出3,但是close()函数存在,使得结果报错。
2.3send()函数
demo:
def gen():
value = 0
while True:
rec = yield value#这里的等式右边相当于一个整体,接受回传值
if rec == 'e':
break
value = 'got : %s' % rec
g = gen()
print(g.send(None))#第一次发送的值必须死None,否则报错,因为没有Python yield语句来接收这个值。
print(g.send('aaa'))
print(g.send('e'))
print(g.send('qwwer'))
运行结果:
0
got : aaa
Traceback (most recent call last):
File "D:/PyCharm/BClass/PXClass/2019-7-30.py", line 171, in <module>
print(g.send('e'))
StopIteration
send执行的顺序:rec = yield value这句话是从右往左执行的。当第一次send(None)时,启动生成器(也可以通过gen.next()语句来启动),从生成器gen()函数的第一行代码开始执行,直到第一次执行完yield(对应第4行)后,跳出生成器函数。这个过程中,n1一直没有定义。
运行到send(‘aaa’)时,进入生成器函数,此时,将yield value看做一个整体,赋值给它并且传回。此时即相当于把’aaa’赋值给value,但是并不执行yield部分,然后继续从yield的下一条语句继续执行。当传人的值为’e’时,退出生成器,引发异常,下面传入的’qwwer’并不会被接受到。send和next相比,只是开始多了一次赋值的动作,其他运行流程是相同的。
demo:
def gen():
i=0
while i<5:
temp=yield i
print(temp)
i+=1
obj=gen()
print(next(obj))
print(next(obj))
print(obj.send('city'))
print(next(obj))
运行结果:
0 #第一次运行结果
None #第二次运行结果
1
city #第三次运行结果
2
None #第四次运行结果
3
然后看不太懂的可以看这里:
https://blog.csdn.net/mieleizhi0522/article/details/82142856
3.闭包
闭包:闭是封闭(函数中的函数),包是包含(该函数对外部函数作用域而非全局作用域变量的引用)。
- 内部函数对外部作用域里的变量的引用
- 函数内的属性,都是由生命周期,都是在函数执行期间
- 闭包内的闭包函数私有化了变量,完成了数据的封装,类似面向对象
demo1:
def foo():
print('in foo()')
def bar():
print('in bar()')
#直接运行内部函数报错
bar()
运行结果:
Traceback (most recent call last):
File "D:/PyCharm/BClass/PXClass/2019-7-30.py", line 204, in <module>
bar()
NameError: name 'bar' is not defined
demo2:
def foo():
print('in foo()')
def bar():
print('in bar()')
#考虑先运行外部函数,再运行内部函数,依然会报错
foo()
bar()
运行结果:
in foo()
Traceback (most recent call last):
File "D:/PyCharm/BClass/PXClass/2019-7-30.py", line 206, in <module>
bar()
NameError: name 'bar' is not defined
由于作用域的问题,函数内的属性都是由生命周期的,只有在函数运行时才会存在。所以,依然会报错
demo3:
假如我们一定要使用bar()函数,只能在foo()内部调用他,比如return bar()即可。
def foo():
print('in foo()')
def bar():
print('in bar()')
return bar()
foo()
运行结果:
in foo()
in bar()
4.装饰器
装饰器本身也是一个函数,作用是为现有存在的函数,在不改变函数的基础上,增加一些功能进行装饰。它是以闭包的形式去实现。在使用装饰器函数时,在被装饰的函数前一行使用@装饰器函数名
形式来进行装饰。
装饰器的意义:
- 不影响原因函数的功能
- 可以添加新功能
假如拿到第三方的API接口,第三方不允许修改这个接口。这个时候就可以使用装饰器了。
demo:
现在在一个项目中,有很多函数,由于我们的项目越来越大,功能也越来越多,会导致程序越来越慢。
其中一个功能函数的实现,是实现一百万次的累加。
import time
def my_count():
s = 0
for i in range(1000001):
s += i
print('sum:', s)
start = time.time()
my_count()
end = time.time()
print('执行时间为:', (end - start))
运行结果:
sum: 500000500000
执行时间为: 0.06248211860656738
一个函数的实现非常简单,假如有几千个这样的函数,每个都写一遍,非常的麻烦,代码量也会非常多。也不符合开发原则,代码过于冗余。
#将上述代码中的执行代码封装成函数。
import time
def my_count():
s = 0
for i in range(1000001):
s += i
print('sum:', s)
def count_time(func):
start = time.time()
func()
end = time.time()
print('执行时间为:', (end - start))
count_time(my_count)
运行结果:
sum: 500000500000
执行时间为: 0.05983471870422363
经过修改,定义一个函数来实现时间计算功能。看上去比之前好很多,只需要将对应的函数传入到时间计算函数中去,但仍影响了原来的使用。
import time
def my_count():
s = 0
for i in range(1000001):
s += i
print('sum:', s)
def count_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print('执行时间为:', (end - start))
return wrapper
test=count_time(my_count)
test()
运行结果:
sum: 500000500000
执行时间为: 0.06083846092224121
这里再次对上述函数做出修改:使用装饰器。
import time
def count_time(func):
def wrapper():
start = time.time()
func()
end = time.time()
print('执行时间为:', (end - start))
return wrapper
@count_time#使用装饰器
def my_count():
s = 0
for i in range(1000001):
s += i
print('sum:', s)
my_count()
运行结果:
sum: 500000500000
执行时间为: 0.0738060474395752
这样实现的好处,定义闭包函数后,主需要通过@装饰器函数名
形式的装饰器语法,就可以将@装饰器函数名
加到要装饰的函数前即可。
这种不改变原有函数功能,对函数进行拓展的形式,就成为装饰器
在执行@装饰器函数名
时,就是将原函数传递到闭包中。然后,原函数的引用指向闭包的装饰过的内部函数的引用。
4.1装饰器的几种形式:
1.无参无返回值
demo:
def setFunc(func):
def wrapper():
print('start')
func()
print('end')
return wrapper
@setFunc
def show():
print('show')
show()
运行结果:
start
show
end
2.无参有返回值
demo:
def setFunc(func):
def wrapper():
print('start')
return func() #return 后不再执行
print('end')
return wrapper
@setFunc
def show():
return 'show'
print(show())
运行结果:
start
show
3.有参无返回值
demo:
def setFunc(func):
def wrapper(s):
print('start')
func(s)
print('end')
return wrapper
@setFunc
def show(s):
print('Hello %s'%s)
show('ss')
运行结果:
start
Hello ss
end
4.有参有返回值
demo:
def setFunc(func):
def wrapper(s,q):
print('start')
return func(s,q) #return 后不再执行
print('end')
return wrapper
@setFunc
def myadd(x,y):
return x+y
# def show(s):
# print('Hello %s'%s)
print(myadd(1,1))
运行结果:
start
2
4.2万能装饰器
demo:
def setFunc(func):
def wrapper(*args, **kwargs):
print('Wrapper context.')
return func(*args, **kwargs)
return wrapper
@setFunc
def func(name, age):
print(name, age)
@setFunc
def demo(a,b,*c,**d):
print((a,b))
print(c)
print(d)
demo('city','college',1991,1,1,school='sss')
func('Tom', 18)
运行结果:
Wrapper context.
('city', 'college')
(1991, 1, 1)
{'school': 'sss'}
Wrapper context.
Tom 18
4.3函数被多个装饰器所装饰
一个函数在使用时,通过一个装饰器来拓展,可能并不能达到预期,这就需要被多个装饰器装饰。
函数可以像普通变量一样,作为函数的参数或者返回值传递,函数的内部可以定义另一个函数,目的就是隐藏函数功能的实现。一个函数可以被多个装饰器所装饰,一个装饰器也可以为多个函数提供装饰功能。
闭包实际上也是函数定义的一种形式,闭包定义的规则:在外部函数内定义一个内部函数,内部函数使用外部函数的变量,并返回内部函数的引用,Python中,装饰器就是用闭包来实现的。装饰器的作用:不改变现有函数的基础上为函数增加功能。
demo:
def setFunc1(func):
def wrapper1(*args, **kwargs):
print('Wrapper Context 1 Start'.center(40, '-'))
func(*args, **kwargs)
print('Wrapper Context 1 End'.center(40, '-'))
return wrapper1
def setFunc2(func):
def wrapper2(*args, **kwargs):
print('Wrapper Context 2 Start'.center(40, '-'))
func(*args, **kwargs)
print('Wrapper Context 2 End'.center(40, '-'))
return wrapper2
@setFunc1 #--->F
@setFunc2 #装饰顺序,由近及远 #--->g
def show(*args, **kwargs): #--->f
print('Show Run.')
show() #F(g(f))
运行结果:
--------Wrapper Context 1 Start---------
--------Wrapper Context 2 Start---------
Show Run.
---------Wrapper Context 2 End----------
---------Wrapper Context 1 End----------