迭代器
可迭代对象:
list,str,tuple etc. ---->for … in …遍历 ---->遍历(迭代)
迭代器协议:对象必须提供一个next方法,执行该方法要么返回迭代中的下一项,要么引起StopIteration 异常,以终止迭代(只能往下走,不可以回退)
现在,我们可以说,实现迭代器协议的对象就是可迭代对象
如何实现?
- 通过在对象内部定义一个
__iter__
方法
from collections import Iterable #使用instance() 来判断一个对象是否可迭代
print(isinstance([],Iterable))
print(isinstance(str(),Iterable))
print(isinstance({},Iterable))
print(isinstance(set(),Iterable))
print(isinstance(123,Iterable))
print(isinstance(True,Iterable))
自定义一个类,可以容纳数据,测试该类的可迭代性
回顾之前说的’_iter_'方法,其实可以为我门提供一个迭代器.
在迭代一个可选对象的时候,实际上就是获取该对象提供的一个迭代器.然后通过该迭代器依次获取对象的每一个数据.
from collections import Iterable
class MyClass:
def __init__(self):
self.names = []
def add(self,name):
self.names.append(name)
my_class = MyClass()
my_class.add("Tom")
my_class.add("jack")
my_class.add("ddd")
print("是否为可迭代对象:",isinstance(my_class,Iterable))
for tmp in my_class:
print(tmp,end=" ")
out
通过迭代器,迭代
print(my_class)
my_class_iter = iter(my_class)
print(my_class_iter)
print(next(my_class_iter))
print(next(my_class_iter))
print(next(my_class_iter))
print(next(my_class_iter))
for … in …循环的本质
就是通过iter()函数获取可迭代对象的Iterable的迭代器,然后对获取到的迭代器不断调用next()方法来获取下一个值并将其赋值,当遇到StopIteration的异常后,退出.
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)
out:
3
4
5
6
应用场景
迭代器的 核心就是通过next()函数调用返回下一个数据值.如果每次返回的数据值不是在一个已有的数据集合中读取的,而是通过程序按照一定规律计算生成.那么也就意味着可以不用依赖一个已有的数据集合,namely,
无需将所有的迭代对象数据一次性缓存下来供后续使用.这样,可以节省大量的存储(内存)空间.
demo:
菲波那契数列
#两个初始值
#n ,不大于n的类似索引的值
class Fib:
'''斐波那契数列迭代器'''
def __init__(self, n):
#记录生成的斐波那契数列的个数
self.n = n
#记录当前记录的索引
self.current_index = 0
#记录两个初始值
self.num1 = 0
self.num2 = 1
self.name = []
def __next__(self):
'''调用next()函数获取下一个数'''
if self.current_index < self.n:
num = self.num1
self.num1, self.num2 = self.num2, self.num1+self.num2
self.current_index += 1
self.name.append(num)
return num
else:
raise StopIteration
def __iter__(self):
return self
fib = Fib(10)
for num in fib:
print(num, end=' ')
a = fib.__iter__()
print(next(a))
print(next(a))
print(next(a))
现在我们希望通过for… in…的方式来遍历斐波拉契数列中的前n个数.
通过迭代来实现,每次迭代都可以通过数学计算生成下一个数.
生成器
生成器,利用迭代器,我们可以在每次迭代获取数据时(通过next()方法)按照特定规律进行生成.但是我们在实现一个迭代器时,关于当前迭代的状态需要我们自己记录,进而才能根据当前状态生成下一个数据.为了达到记录当前状态,并配合next()函数进行迭代使用,可以采用更简便的语法.
即生成器(generator),生成器是一种特殊的迭代器,它比迭代器更优雅.
创建一个生成器
列表[] —>()
li = [x**2 for x in range(6)]
print(li)
gen = (x**2 for x in range(6))
print(gen)
print("通过next()函数取得下一个值")
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print(next(gen))
print("通过for遍历")
gen = (x**2 for x in range(6))
for i in gen:
print(i,end=" ")
out:
[0, 1, 4, 9, 16, 25]
<generator object <genexpr> at 0x0000021D35DDEF68>
通过next()函数取得下一个值
0
1
通过for 遍历
4 9 16 25
生成器函数
在函数中如果出现了yield关键字,那么该函数就不再是一个普通函数,而是一个生成器函数.
demo:
def foo():
yield 1
yield 2
print(next(f)) #1
print(next(f)) #2
#若 加入return
def foo():
yield 1
return 'Error'
yield 2
#1
#StopIteration: Error
#则 1可以迭代输出 进而return返回 2无法进行迭代输出StopIteration
next 和 yield进行匹配,如果遇到return,
return后的语句不会再执行,直接抛出StopIteration,终止迭代
如果return后有返回值,那么这个值就是异常的说明,而不是函数的返回值.
构造一个产生无穷奇数的生成器
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):
print(next(odd))
生成器支持的方法
- close()
- 手动关闭生成器函数,后面调用直接引起StopIteration异常
def gen():
yield 1
yield 2
yield 3
yield 4
g = gen()
print(next(g))
print(next(g))
g.close()
print(next(g)) # 回溯
-
send()
-
x = yield y语句的含义:send()的作用就是使x赋值为其所传送的值(send的参数),然后让生成器执行到下一个yield.
- 如果生成器未启动,则必须在使用send()前启动生成器,而启动的方法可以使gen.next(),也可以是gen.send(None)执行到yield处.之后就可以使用send(para)不断的传入值了.
-
如果是已启动,则send(para)的作用就是给x赋值为发送的值(send的参数),然后,让生成器执行到下一个yield.
-
# def gen():
# i = 0
# while i< 5:
# temp = yield i
# print(temp)
# i+=1
# # 创建生成器对象
# obj = gen()
# # 使用next()唤醒生成器
# print(next(obj))
# print(next(obj))
# print(obj.send("city"))
结果
0
None
1
city
2
def gen():
value = 0
while True:
receive = yield value
if receive == 'e':
break
value = 'got: %s' %receive
#send()的作用就是使 receive赋值,然后生成器执行下一个yield
# 生成器未启动时 启动方法未 gen.next() 或者 gen.send(None)执行到第一个yield处开始生成
g = gen()
print(g.send(None))
print(g.send('aaa'))
print(g.send(123))
print(g.send('e'))
# 0
# got:aaa
# got: 123
报错 StopIteration
解析:
g.send(None)启动生成器, 程序运行到 yield value终止 返回为 0
g.send(‘aaa’) ,程序 从上一步yield value后 If开始运行 send赋值给receive = ‘aaa’ 判断条件后 value=‘got: aaa’ 进而 yield value 返回 got: aaa并终止
同理 g.send(123) 赋值receive = 123, 进入 value = ‘ got: 123’, yield value 终止并返回 got:123
g.send('e) if判断break 此时迭代器没有更多的值,则报错。
- throw()
- 手动抛出异常.
闭包
什么是闭包
闭是封闭(函数中的函数),包时包含(该内部函数对外部函数作用域而非全局作用域变量的引用)
闭包:
-
内部函数对外部函数用域里的变量引用
-
函数类的属性,都是有生命周期
-
闭包内的闭包函数私有化了变量
demo:
def foo():
print('in foo()')
def bar():
print('in bar()')
#1.直接允许内部函数报错
#bar()
#2.考虑先允许外部函数,再允许内部函数,依然报错
# foo()
# bar()
由于作用域的问题,函数内的属性,都是有生命周期,只有在函数执行期间
再考虑这段代码,只有调用foo()是,内部的print()及bar()才能存活.
现在我们为了让foo()内的bar()存在,就是调用bar(),我们该怎么做?
把bar() 函数返回给函数
def foo():
print("in foo()")
def bar():
print("in bar()")
return bar
var = foo()
var()
in foo()
in bar()
前面说,内部函数对外部函数作用域变量的引用,---->如果是变量呢?
def foo():
a = 66
print('in foo()')
def bar(num):
print('in bar()')
print(a + num)
return bar
var = foo()
var(22)
in foo()
in bar()
## 88
li = [1, 2, 3, 4, 5]
def foo(obj):
print('foo:', obj)
def bar():
obj[0] -= 1
print('bar:',obj)
return bar
var = foo(li) # foo: [1, 2, 3, 4, 5]
var() #bar: [0, 2, 3, 4, 5]
var() #bar: [-1, 2, 3, 4, 5]
foo: [1, 2, 3, 4, 5]
bar: [0, 2, 3, 4, 5]
bar: [-1, 2, 3, 4, 5]
装饰器
首先,看一个demo:
@func1
def func():
print('aaa')
装饰器存在的意义
- 不影响原有函数的功能
- 可以添加新功能
一般常见的,比如拿到第三方的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))
修改后
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))
def count_time(func):
start = time.time()
func()
end = time.time()
print("执行那个时间为:",(end-start))
count_time(my_count)
修改后,定义一个函数来实现时间计算功能.
初看上去,比之前好很多,
只是在使用时,需要将对应的函数传入到时间计算函数中
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()
在使用时,让my_count函数重新指向count_time函数返回的函数引用,
这样实现的好处,定义闭包函数后,只需要通过’@装饰器函数名’形式的装饰器语法,就可以将’@装饰器函数名’加到要装饰的函数前即可.
这种不改变原有函数功能,对函数进行拓展形式,就称为’装饰器’
在执行’@装饰器函数名’时,就是将原有函数传递到闭包中,然后,原函数的引用指向闭包返回的装饰过的内部函数引用.