迭代器

可以迭代的数据类型:元组、列表、字典、集合、字符串

判断是否可迭代:

1
2
from collections import Iterable
isinstance(要判断的变量,Iterable) #如果可以迭代,返回 True

判断是否是迭代器:

1
2
from collections import Iterable
isinstance(要判断的变量,Iterator) #如果是迭代器,返回 True

print 的两种书写形式:

1
2
print('number is %d' % 25)
print('number is ', 25)

创建出来的可迭代的实例对象:

  1. 普通的类

  2. 添加 __iter__,使其具有可迭代属性,即:isinstance 返回 True

    1
    2
    def __iter__(self):
    pass

  3. __iter__ 方法中返回一个具有__iter__方法以及__next__对象的引用

一个对象是否可以用 for 来迭代遍历(电脑程序的判断方法):

  1. 根据对象中有无 __iter__ 方法,即:使用 isinstance() 判断

  2. 在第一步成立的前提下,使用 iter 方法 iter(classmate) 来调用该对象的 __iter__ 方法,获取其返回值,也就是迭代器对象的引用

  3. 使用返回值,即:迭代器,调用其自身的 next 方法 next(classmate_iter) 来获取其__next__ 方法,获取其返回值

    注:称一个同时具有 __iter____next__ 方法的类成为迭代器。

  4. 每一次 for 循环通过迭代器中的 __next__ 方法来取其返回值

__next__ 方法输出可迭代对象的东西,需要再将可迭代对象在 __iter__ 方法创建迭代器时,传入自己的 self 引用。此外,在迭代器中写一个 __init__ 接受引用。

在迭代器中设置一个属性,用来标志当前的序号,然后通过比较序号,如果当前序号小于列表的长度,那么接着取,否则会默认返回一个 None 值。

通过使用 raise StopIteration 引发此异常,来使 for 循停止迭代。

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from collections import Iterable
from collections import Iterator
import time

class Classmates(object):
def __init__(self):
self.names = list()

def add(self, name):
self.names.append(name)

def __iter__(self):
return classIter(self)

class classIter(object):
def __init__(self,obj):
self.obj = obj
self.current_num = 0

def __iter__(self):
pass

def __next__(self):
if self.current_num < len(self.obj.names):
ret = self.obj.names[self.current_num]
self.current_num += 1
return ret
else:
raise StopIteration


classmate = Classmates()

classmate.add('wang')
classmate.add('zhang')
classmate.add('li')

# classmate_iter = iter(classmate)
# print(f'the iterability of classmate:{isinstance(classmate,Iterable)}')
# print(f'the iterability of classmate_iter:{isinstance(classmate_iter,Iterator)}')
# print(next(classmate_iter))

for name in classmate:
print(name)

下面将可迭代对象与迭代器合成一个对象(也就是迭代器一定可迭代,但是可迭代的对象不一定是迭代器):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
from collections import Iterable
from collections import Iterator
import time

class Classmates(object):
def __init__(self):
self.names = list()
self.current_num = 0

def add(self, name):
self.names.append(name)

def __iter__(self):
return self

def __next__(self):
if self.current_num < len(self.names):
ret = self.names[self.current_num]
self.current_num += 1
return ret
else:
raise StopIteration


classmate = Classmates()

classmate.add('wang')
classmate.add('zhang')
classmate.add('li')


for name in classmate:
print(name)

迭代器的优点:

  1. 存储指定生成的数据的方式,而不是结果。如果当结果过多时,存储起来会花费大量的空间。

    例如:python 中的 range 与 xrange

使用迭代器写斐波那契数列:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
import sys

class fibonacci(object):
def __init__(self,all_num):
self.all_num = all_num
self.current_num = 0
self.a = 0
self.b = 1

def __iter__(self):
return self

def __next__(self):
if self.current_num < self.all_num:
ret = self.a
self.a, self.b = self.b, self.a+self.b
self.current_num += 1
return ret
else:
raise StopIteration

def main():
try:
number = int(sys.argv[1].strip())
except:
pass
fibo = fibonacci(number)
for num in fibo:
print(num)

if __name__ =="__main__":
main()

并不是只有 for 循环能接收迭代器,list(), tuple() 等也能接收

1
2
li = list(fina(8))
tp=tuple(fina(5))

这并不是简单的类型转换,而是通过迭代器逐个转换。比如:

1
2
3
4
In [29]: a=(11,22,33)

In [30]: list(a)
Out[30]: [11, 22, 33]

首先创建一个空列表,然后找到 a 的迭代器,使用 next 函数找其 __next__ 方法的返回值逐个迭代转换,最后遇到迭代停止异常,进而停止。

列表生成式与生成器表达式

列表生成式:保存生成结果,将结果存到变量。 使用方括号

生成器表达式:保存生成结果的方式,for 循环迭代的时候,逐个计算输出。 使用圆括号

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
In [31]:  num = [x*2 for x in range(10) if x%2 == 0]

In [32]: num
Out[32]: [0, 4, 8, 12, 16]

In [33]: num_generator = (x*2 for x in range(10) if x%2 == 0)

In [34]: num_generator
Out[34]: <generator object <genexpr> at 0x10782a050>

In [35]: for i in num_generator:
...: print(i)
...:
0
4
8
12
16

生成器

  1. 利用迭代器,我们可以在每次迭代获取数据(通过 next 方法)时,按照特定的规律进行生成。但是,我们在实现一个迭代器时,关于当前迭代到的状态需要我们自己记录,进而才能根据当前状态生成下一个数据。为了达到记录当前状态,并配合 next() 函数进行迭代使用,我么可以采用更简便的语法:生成器(generator),生成器是一类特殊的迭代器

  2. 创建生成器的方法

    1. 将列表生成式的 [] 改为 ()

    2. 在普通函数中加上 yield(只要函数里面有 yield,无论是在 if, for 等等里面,这个函数就不是函数了,变成了生成器。)

      如果一个函数中有 yield 语句,那么它就不再是一个函数,而是一个生成器。

      如果在调用函数时,发现这个函数中有 yield, 那么此时不再调用函数,而是创建一个生成器对象。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      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(12)

      # print(next(obj))

      # print(next(obj))

      for num in obj:
      print(num)

for 循环触发迭代器:

第一次:程序从头开始运行,然后运行到 yield,yield 语句会暂停程序,将 yield 后面的值返回给 for 循环的变量,然后开始进行 for 循环。下一次 for 循环,进入生成器后,从 yield 语句的下一条语句开始执行。

通过 next 触发迭代器,开始迭代。

注意:不同的生成器对象有不同的局部变量。互不干扰。

如果想要通过 while True 调用 next 方法来取值,可以通过捕获 StopIteration 异常来结束。

1
2
3
4
5
6
while True:
try:
ret = next(obj)
print(ret)
except StopIteration:
break

如何接受迭代器的返回值?

可以在其执行完之后,通过异常对象 except StopIteration as e: 或者 except Exception as e: 中 e 的 value 属性来打印。

1
2
3
4
5
6
7
while True:
try:
ret = next(obj)
print(ret)
except StopIteration as e:
print(e.value)
break

小结

  • 使用了 yield 关键字的函数不再是函数,而是生成器。(使用了 yield 的函数就是生成器)
  • yield 关键字有两点作用:
    • 保存当前运行状态(断点),然后暂停执行,即将生成器(函数)挂起
    • 将 yield 关键字后面表达式的值作为返回值返回,此时可以理解为起到了 return 的作用
  • 可以使用 next()函数让生成器从断点处继续执行,即唤醒生成器(函数)
  • Python3 中的生成器可以使用 return 返回最终运行的返回值,而 Python2 中的生成器不允许使用 return 返回一个返回值(即可以使用 return 从生成器中退出,但 return 后不能有任何表达式)

使用 send 唤醒 我们除了可以使用next()函数来唤醒生成器继续执行外,还可以使用send()函数来唤醒执行。使用 send()函数的一个好处是可以在唤醒的同时向断点处传入一个附加数据。即:使用 send 唤醒 yield a 的同时,将消息传递给 yield a 的结果。即:temp = yield a, temp 就是当使用 send 唤醒时,传入的结果。

例子:执行到 yield 时,gen 函数作用暂时保存,返回 i 的值; temp 接收下次 c.send("python"),send 发送过来的值,c.next()等价 c.send(None)

1
2
3
4
5
6
7
In [10]: def gen():
....: i = 0
....: while i<5:
....: temp = yield i
....: print(temp)
....: i+=1
....:

注意:

  1. send() 不能作为第一次唤醒使用,因为第一次从生成器的开始执行,而开始往往不是 yield a, 这样就没有东西接受 send 传入的值,因此会抛出异常。
  2. 如果必须第一次使用 send, 那传入消息 None。大多情况下使用 next。
  3. next 是函数,直接使用 next(obj), 而 send 是迭代器对象的方法,需要使用:obj.send('message')
  4. 具有 yield 的函数是生成器对象,需要先创建对象,然后才可以使用。

迭代器和生成器都保存生成数据的方法,而不是数据,节省空间。

迭代器减少内存空间,生成循环。生成器可以让一个函数看上去暂停执行,并返回结果。

yield 完成多任务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import time

def task_one():
while True:
print('-----1-----')
time.sleep(0.1)
yield

def task_two():
while True:
print('-----2-----')
time.sleep(0.1)
yield

def main():
t1 = task_one()
t2 = task_two()
# 先让 yield 运行一会,当 t1 中遇到 yield 的时候,再返回到 24 行
# 然后,执行 t2, 当它遇到 yield 的时候,再次切换到 t1 中
# 这样 t1/t2/t1/t2 的交替运行,最终实现了多任务 ---- 协程

while True:
next(t1)
next(t2)

if __name__ == "__main__":
main()

线程依赖于进程,协程依赖于线程。

协程使用的是单线程,它只是充分利用延时的时间。

random 模块常用函数

random.randrange

random.randrange(stop)

random.randrange(start, stop[, step])

从 range([start], stop[, step]) 中,即:[start, stop),步长为 step 的整数序列中随机选取一个整数。

random.randint

random.randint(a, b)

从 [a, b] 中返回一个随机整数, 同:randrange(a, b+1)

1
2
In [25]: random.randint(1,2)
Out[25]: 2

random.choice

Random.choice(seq)

从序列 seq 中随机选取一个元素(无论类型),如果 seq 为空,抛出异常。

1
2
In [31]: random.choice(['8','6','4',3])
Out[31]: '6'

random.shuffle

Random.shuffle(x[, random])

对序列元素进行随机排序(直接改变原序列)

1
2
3
4
5
6
In [37]: num = [1,2,3,4,5, 6]

In [38]: random.shuffle(num)

In [39]: num
Out[39]: [1, 5, 6, 4, 3, 2]