携程
学习路线: 迭代器 -> 生成器 -> yield -> greenlet -> gevent
迭代器
迭代大家都知道, for i in XX, 时访问集合元素的一种方式。迭代器是一个可以记住遍历位置的对象,他从集合的第一个元素开始访问,直到所有的元素被访问完结束。迭代器只会向前进而不是向后退
Python的可迭代对象很普遍,如 list, tuple, str, dict等等。当然不可迭代的类型如int等也很普遍,我们来看一下报错, 清楚的告诉你这个对象not in iterable(不可迭代)
>>> for i in 5:
... print(i)
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: 'int' object is not iterable
如何判断一个数据类型是否可迭代,即判断这个类是不是collection.iterable的子类即可,Iterable对象都具有**iter()方法与next()**方法,可以被for循环 。
# 我们来定义一个可以被迭代的类
class Iter_son():
def __init__(self):
self.name = []
def get_name(self):
return self.name
# 方法A: 如果不想考虑继承的话根据上面说的,我们要自己创建iter方法才可
def __iter__(self):
pass
def __next__(self):
pass
m = Iter_son()
for i in m:
print(i)
# 可以看到,打印的错误信息不一样了,因为我瞎写了iter方法所以报错
TypeError: iter() returned non-iterator of type None
迭代器的实现
for的本质就是通过__next__的迭代获取我们所需要的数据,因此迭代器可以采用以下的方法实现
# 我们来定义一个可以被迭代的类
class Iter_son():
def __init__(self):
self.name = ['老王','老杨','阿炳']
self.num = 3
def new_student(self,name):
self.name.append(name)
self.num += 1
def __iter__(self):
#返回具有Iter方法和Next方法对象的引用, 我的话就一般返回本对象就好了,这样就会直接找他的next方法了
return self
def __next__(self):
if self.a < self.num:
num = self.a
self.a += 1
return num,self.name[num]
# StopIteration 在 next() 方法中设置完成指定循环次数后触发 StopIteration 异常结束迭代。
# StopIteration(教程):https://www.runoob.com/python3/python3-iterator-generator.html
else:
raise StopIteration
m = Iter_son()
for a,b in m:
print(str(a),str(b))
0 老王
1 老杨
2 阿炳
总结, for的逻辑:
- 先判断for的对象是不是 Iterable的子类, 或是否具备 iter方法
- 之后判断iter返回的引用对象中是否有__next__方法,返回next方法的返回值, 否则报错
可以迭代的类称为迭代器: Iterator
拓展: Iterator (https://blog.csdn.net/appleyuchi/article/details/109139809)
Iterator是Iterable的子类,Iterator对象都具有__iter__()和__next__()方法,Iterator可以通过next()获取其下一个数据。Iterator的生成方式是通过iter()方法,或为类定义__iter__()和__next__(),从而使类成为Iterator。
>>> from collections import Iterable
# 判断
>>> isinstance({1:1,2:2},Iterable)
True
# isinstance的意思是“判断类型”;isinstance()是一个内置函数,用于判断一个对象是否是一个已知的类型,类似type()
# isinstance() 与 type() 区别:type() 不会认为子类是一种父类类型,不考虑继承关系。isinstance() 会认为子类是一种父类类型,考虑继承关系。
https://blog.csdn.net/weixin_29358723/article/details/112047579
那迭代器有啥用,用range迭代不香么
先来一个问题镇楼,如果你要进行一个100000000以内的迭代循环,你会怎么做?
基本来说我们做循环的时候喜欢用for i in range(XXXX), 在python3本质上是没毛病的,但是对于python2即以下,我们需要考虑以下range的机制。range的机制是先生成一个列表,再去遍历 直接使用的话就要至少占去 4Byte * len( XXXX)的空间。 这么大的空间对我,们来说是一种极大的边了,所以我们课以考虑 i = 0 while (i<XXXX): i += 1 这样的遍历,将空间缩为4Byte, 但是如果要求强制用for解决怎么办呢,这个时候我们可以选择xrange。 xrange本身就是一个迭代器,在你使用的数据的时候,或者说循环到哪里,生成哪里。当然,python3 range == xrange
这也体现了迭代器的优点: 存储生成迭代的方法而非一个可迭代对象,消耗空间小。
eg. 斐波那契数列迭代器:
class Fibonacci_Iter():
def __init__ (self):
self.num1 = 1
self.num2 = 1
self.round_num = 0
self.maxs_round_num = 10
def set_len(self,lens):
self.maxs_round_num = lens
def __next__(self):
if self.round_num <= 1:
self.round_num += 1
return 1
elif self.round_num < self.maxs_round_num:
self.round_num += 1
num12 = self.num1 + self.num2
if self.num1 < self.num2:
self.num1 = num12
else:
self.num2 = num12
return self.num12
else:
raise StopIteration
def __iter__(self):
return self
lists = Fibonacci_Iter()
lists.set_len(5)
for i in lists:
print(i)
1
1
2
3
5
生成器
利用迭代器,我们按照特定的规律迭代生成数据。但是我们在实现一个迭代器时,关于当前迭代的状态需要我们自己记录,才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合next()函数进行迭代使用,我们可以采用更简便的语法,即生成器(generator)。生成器是一类特殊的迭代器。
可惜俺靠blog : https://www.cnblogs.com/chengxuyuanaa/p/13065794.html
生成器的两种使用方式:
-
小括号法
>>> n1 = [x for x in range(5)] >>> n2 = (x for x in range(5)) >>> n1 [0, 1, 2, 3, 4] >>> n2 <generator object <genexpr> at 0x0000029E1CA56B30>
() 创建了一个生成器,而非一个实体的list()对象,节省了数据空间。不常用
-
定义函数生成器
# 使用生成器完成斐波那契数列 def create_num(all_num): a,b = 0,1 current_num = 0 while current_num < all_num: # 定义生成器的传参, 这会变成生成器 yield a a,b = b, a+b current_num += 1 # 因为函数变成了生成器,所以本身已经不是函数了,而是生成器对象 obj = create_num(5) for num in obj: print(num) 0 1 2 3 5
在创建生成器对象obj时create num函数遇到yiled(这个时候current还是0)时会阻塞,指导for调用的时候生成器就拿出阻塞的a , 并在继续执行到下一个yiled(这个时候current = 1),继续阻塞,而for 去除就继续执行。每次只有一个a生成,同时也没有连边等数据结构占用空间。这样的方式减小了空间的消耗。
同时,我们不管可以用for取值,也可以用next, send 的方式来进行唤醒
def create_num2(all_num): a,b = 0,1 current_num = 0 while current_num < all_num: # 定义生成器的传参, 这会变成生成器 h = yield a if h is None: pass else: print(h) a,b = b, a+b current_num += 1 # 实例化生成器对象 b = create_num(10) #注意next与send调用的区别 print(next(b)) 0 c = b.send('Hello') hello print(c) 1
同时注意,由于生成器是一个类,所以实例化的时候会New对象,调用的时候不需要考虑不同对象之间的参数干涉
yield多任务
上面讲到, yiled可以进行阻塞,那么yiled在一定程度上不久可以进行多任务的平衡了么。
# 一个简单的多任务deemo
import time
def test():
while True:
name = yield None
print("I'm task1")
if __name__ == '__main__':
m = test()
n = test()
while (True):
time.sleep(1)
m.send("I'm task1")
n.send("I'm task2")
TypeError: can't send non-None value to a just-started generators
报错,可以发现yield的要求还是相当严苛的,原因见Bolg https://blog.csdn.net/chen1042246612/article/details/85317341。
简单来说就是第一个yield不允许传参,我们可以在第二个yield 或者先调用一次send方法
# 一个简单的多任务deemo2
import time
def test():
while True:
name = yield None
print(name)
if __name__ == '__main__':
m = test()
next(m)
n = test()
next(n)
while (True):
time.sleep(1)
m.send("I'm task1")
n.send("I'm task2")
I'm task1
I'm task2
I'm task1
I'm task2
然而实际上yield实际上还是串行运行,但是不用创建进程线程,几乎不占用资源。不过是多个循环可以伪一起执行(甚至还是单线程你不能有更高要求)罢了
Greenlet, Gevent 携程多任务
Greenlet 本身与yield 底层无差别,不过greenlet对yield进行了封装,不需要额外在函数中写入yield,而Gevent对Greenlet进行了再次封装,不在赘述: https://blog.csdn.net/appleyuchi/article/details/79078736
# 简单Greenlet用法
from greenlet import greenlet
import time
def A():
while 1:
print('-------A-------')
time.sleep(0.5)
g2.switch()
def B():
while 1:
print('-------B-------')
time.sleep(0.5)
g1.switch()
g1 = greenlet(A) #创建协程g1
g2 = greenlet(B)
g1.switch() #跳转至协程g1
# 简单Gevent(遇到延时才切换,不遇到延时不切换)
import gevent
def A():
while 1:
print('-------A-------')
gevent.sleep(1) #用来模拟一个耗时操作,注意不是time模块中的sleep
def B():
while 1:
print('-------B-------')
gevent.sleep(0.5) #每当碰到耗时操作,会自动跳转至其他协程
g1 = gevent.spawn(A) # 创建一个协程
g2 = gevent.spawn(B)
g1.join() #等待协程执行结束
g2.join()
进程,线程,携程总结
- 多线程,多进程根据CPU的荷属不一样可能时并行的,但是携程在一个线程中所以时并发的。
- 携程切换的资源消耗最少,其次时线程,最大的时进程
- 然而可笑的是,在Python中(除非你编译器特殊)进程是唯一实现并行的,效率应该是最快的。