迭代器

  • 术语解释

    迭代:迭代表示在原有已存在的事物上追加新的东西

    迭代器:迭代器是一个可以记住遍历的位置的对象,迭代器对象从集合的第一个元素开始访问,直到所有的元素被访问结束,迭代器只能往前不能后退

  • 作用

    迭代器的存在可以改变传统数据调用的方式(先存后取,占用大量空间),而是变为存储数据生成的规则,这样极大的减少了对内存的消耗

  • 可迭代对象

    • 可迭代数据类型:

      字符串、列表、元组、字典、集合

    • 检查对象是否可迭代

      1
      2
      3
      4
      # 利用isinstance(a, b)判断a对象是否由b类创建,是则返回True,否则返回False
      from collections.abc import Iterable
      list_name = [1, 2, 3, 4]
      result = isinstance(list_name, Iterable)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      # 判断一个对象是否可迭代(可迭代对象中必须包含__iter__私有方法)
      from collections.abc import Iterable


      class Classmate(object):
      def __init__(self):
      pass

      def __iter__(self):
      # __iter__必须返回一个迭代器对象,此处假设这个类本身就是可迭代的
      return self # 此处的self表示返回类本身

      def __next__(self):
      pass


      if __name__ == '__main__':
      result = isinstance(Classmate(), Iterable)
      print(result)
    • 检查对象是否是迭代器

      1
      2
      3
      4
      5
      # 利用Iterator判断对象是否是一个迭代器
      # 判断一个对象是否是迭代器(迭代器中必须包含__iter___、__next__私有方法)
      from collections.abc import Iterator
      classmate = Classmate()
      result = isinstance(classmate, Iterator)
    • 创建可迭代的对象

      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
      # 判断一个对象是否可迭代(可迭代对象中必须包含__iter__私有方法)
      from collections.abc import Iterable
      # 判断一个对象是否是迭代器(迭代器中必须包含__iter___、__next__私有方法), 迭代器是一种特殊对象
      from collections.abc import Iterator


      class Classmate(object):
      def __init__(self):
      self.list_name = ["zhangsna", "lisi", "wangwu", "zhaoliu", "rwnqi"]
      self.count_num = 0

      def __iter__(self):
      # 此处必须返回一个可迭代的对象
      return self

      def __next__(self):
      # 该方法用于创建迭代循环,变量通过实例属性来实现
      if self.count_num < len(self.list_name):
      name = self.list_name[self.count_num]
      self.count_num += 1
      return name
      else:
      # 当迭代的元素内容穷尽的时候,此时会报错
      # 为了避免报错,在迭代器中使用StopIteration方法,可以在越界时停止迭代
      raise StopIteration


      if __name__ == '__main__':
      user = Classmate()
      # iter方法可以直接调用可迭代对象中的__iter__方法
      classmate = iter(user)
      # next方法可以直接调用迭代器中的__next__方法
      first_name = next(user)

      for name in user:
      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
      # 利用迭代实现斐波那契数列
      class Fibonacci(object):
      def __init__(self, num):
      self.all_num = num
      self.count_num = 0
      self.a, self.b = 0, 1

      def __iter__(self):
      return self

      def __next__(self):
      if self.count_num < self.all_num:
      # 由于需要回调参数,所以此处先赋值,这个是迭代器的特点之一
      ret = self.a
      # 利用元组直接赋值,更快捷
      self.a, self.b = self.b, self.a + self.b
      self.count_num += 1
      # 将刚刚赋值的内容返回
      return ret
      else:
      raise StopIteration


      fibo = Fibonacci(500)
      for i in fibo:
      print(i)

生成器

  • 术语解释

    生成器:是一种特殊的迭代器。迭代器的状态需要我们去记录和更新,为了达到记录当前状态并配合next()函数进行迭代使用,而创建了更高级的语法:生成器

  • 创建方法:

    • 直接生成:把一个列表生成式的[]改成()即可(不常用)

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      test = (x for x in range(100))  # 此时生成的iter就是一个生成器
      # 只要是生成器,就可以调用iter()方法来直接调用生成器中__iter__私方法
      iter_name = iter(test)
      # 也可以调用next()方法来直接调用生成器中__next__私方法
      iter_name = next(test)
      # print函数中,前后两个元素之间没有用参数引用,而是用逗号,则表示前后两个元素组成了一个元组
      print("迭代器生成的第一个元素为:", iter_name)
      # 利用for循环
      for num in test:
      print(num)
    • 利用yield方法生成 (用于理解gevent)

      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
      # 实现斐波那契数列
      # 只要函数中有yield,则该函数不再叫函数,而是叫生成器模板
      def create_febonacci(num):
      a, b = 0, 1
      count_num = 0
      while count_num < num:
      msg = yield a
      a, b = b, a + b
      count_num += 1
      # 生成器调用中,不会有return的值,如果需要return则需要借助异常来捕获
      return count_num

      # 借用生成器模板,则不是调用函数,而是创建了一个生成器对象
      iter_name = create_febonacci(50)
      for num in iter_name:
      print(num)

      # 利用异常捕获生成器中return语句
      iter_name = create_febonacci(2)
      try:
      while True:
      num = next(iter_name)
      print(num)
      except Exception as msg: # 生成器中return的内容会被当作异常返回
      msg.value
      break

      # 除了next方法可以触发迭代,还可以通过send方法触发迭代
      iter_name = create_febonacci(5)
      # send()的内容会被返回给生成器(可用赋值语句获取),
      # send发送有内容的消息时,一定要放在next调用之后,或者先发送None消息
      iter_name.send(None)
      iter_name.send("message will send to fix")

协程

  • 术语解释

    协程:是指将多个任务通过生成器的方式交替执行。它耗费的内存要比多线程、多进程小很多

  • 实现方式

    通过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
    # yield不一定需要返回值
    import time

    def task1():
    while True:
    print("------------1-------------")
    time.sleep(1)
    yield # 当生成器执行到这里时,程序会暂停,并且回到主程序中执行下一步

    def task2():
    while True:
    print("------------2-------------")
    time.sleep(1)
    yield

    def main():
    t1 = task1()
    t2 = task2()
    while True:
    next(t1)
    next(t2)

    if __name__ == "__main__":
    main()

greenlet实现协程

  • 作用:

    在协程中通过next方法、yield来实现协程,这样的代码比较冗余,不便于管理,因此引入了greenlet

  • 安装

    greenlet是第三方库,需要手动安装

    1
    sudo pip install greenlet
  • greenlet使用

    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
    from greenlet import greenlet
    import time

    g1 = None
    g2 = None

    def f1(x):
    for i in range(x):
    # greenlet调用生成器时只能通过【生成器对象.switch(参数)】传递参数
    print(greenlet.getcurrent(), i)
    time.sleep(1)
    g2.switch(4) # 当程序执行到此处时,会直接跳转至g2处

    def f2(x):
    for i in range(x):
    print(greenlet.getcurrent(), i)
    time.sleep(1)
    g1.switch(4)

    def main():
    global g1
    global g2
    g1 = greenlet(f1) # 此处只是创建一个生成器对象,并未执行迭代
    g2 = greenlet(f2)
    g1.switch(1) # 此处开始跳转至生成器内容中,并进行迭代


    if __name__ == '__main__':
    main()

gevent实现协程

  • 目的

    greenlet使用过程中没有添加协程的部分完全独立出来,因此各代码之间耦合度还是很高,不利于阅读和使用,同时,它并没有完全实现协程的理念(充分利用系统的休眠时间去执行其它的任务),因此在greenlet的基础上又开发处gevent,gevent的优点如下:

    • 构建方便,降低各模块之间的耦合度
    • 能够自动根据系统的休眠情况去执行其它的任务
  • 安装

    gevent是第三方库,需要人工安装

    1
    sudo pip install gevent
  • 实现原理(便于理解)

    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
    import gevent
    from time import sleep

    def f1(x):
    for i in range(x):
    print(i)
    gevent.sleep(1)

    def f2(x):
    for i in range(x):
    print(i)
    gevent.sleep(1)

    def f3(x):
    for i in range(x):
    print(i)
    # gevent无法监听到time中的sleep,只能使用gevent中的sleep,对于其它延时方式也同理
    gevent.sleep(1) # 运行到此,监听到有休眠状态,故开始切换到其它任务

    g1 = gevent.spawn(f1, 5) # 此处只是创建一个迭代器对象,并未执行
    g2 = gevent.spawn(f2, 9) # gevent的迭代器参数直接填写在spawn()方法中
    g3 = gevent.spawn(f3, 11)
    g1.join() # 此处生成迭代器内容,并开始执行
    g2.join()
    g3.join()
  • 真实环境使用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    import gevent
    from gevent import monkey
    import time
    # 利用monkey,可以将代码中其它模块的延时方法自动转成gevent对应的延时方法
    # 遇到延时方法时使用
    monkey.patch_all()


    def test(x, modle):
    for num in range(x):
    print("新增板块:{0},目前处于的阶段:{1}".format(modle, num))
    # 当使用gevent时,遇到延时的,需要将一整个模块导入进来,否则不利于monkey辨认
    time.sleep(1)


    # 使用joinall将所有多任务进协程中
    # gevent与greenlet不同,gevent会将所有任务的内容全部执行完,而greenlet执行时遇到调用方提前结束的,则会因为报错而无法继续调用其它执行的任务
    gevent.joinall([
    gevent.spawn(test, 9, "wolk"),
    gevent.spawn(test, 8, "talk"),
    gevent.spawn(test, 7, "speak")
    ])

总结

  1. 进程是资源分配的单位,线程是操作系统调度的单位
  2. 进程切换需要的资源很大,效率较低,线程切换需要的资源一般,效率一般,协程切换需要的资源少,效率高。
  3. 多进程、多线程依据cpu的核数,不一定是并行,而协程位于一个线程中,所以是并发