• Day 22:Python 迭代器和生成器小记


    迭代器对象

    像列表、字典、集合他们可以迭代,但是他们都是非迭代器对象,区别在于

    • 有没有经过内置函数 iter 包装
    • 列表的表头始终指向第一个元素,迭代器遍历结束后,不返回指向原来的位置,而是指向最后一个元素的下一个位置,有人说那不是扯淡吗都最后一个元素了,没毛病,所以就会抛出 StopIteration 异常。
    a = [1,2,3,4,5,6,8]
    
    a_iter = iter(a) # a_iter 就是迭代器
    from collections.abc import Iterator
    print(isinstance(a_iter,Iterator))
    # 遍历a 和 a_iter
    for _ in a:
        print(_)
    print("========================华丽的分割线========================")
    for _ in a_iter:
        print(_)
        
    print("再次遍历")
    for _ in a:
        print(_)
    print("========================华丽的分割线========================")
    for _ in a_iter:
        print(_)
    
    output:
    True
    1
    2
    3
    4
    5
    6
    8
    ========================华丽的分割线========================
    1
    2
    3
    4
    5
    6
    8
    再次遍历
    1
    2
    3
    4
    5
    6
    8
    ========================华丽的分割线========================

    这不是没有抛出StopIteration 吗,其实这样的遍历是不会,而是需要迭代器对象才有的next()内置函数想读取最后一个元素的下一个元素才会:

    # 重新建一个a的迭代器,指向a的第一个元素
    a_iter_copy = iter(a)
    # a_iter_copy.len()  无法通过调用 len 获得迭代器的长度
    print(next(a_iter_copy))
    print(next(a_iter_copy))
    print(next(a_iter_copy))
    print(next(a_iter_copy))
    print(next(a_iter_copy))
    print(next(a_iter_copy))
    print(next(a_iter_copy))
    print(next(a_iter_copy))
    
    output:
    1
    2
    3
    4
    5
    6
    8
    ---------------------------------------------------------------------------
    StopIteration                             Traceback (most recent call last)
    <ipython-input-10-52c4d4ddde71> in <module>()
          9 print(next(a_iter_copy))
         10 print(next(a_iter_copy))
    ---> 11 print(next(a_iter_copy))
    
    StopIteration: 

    如何捕获迭代器达到尾部抛出的异常(就此也可以获得迭代器长度):

    a = [1,2,3,4,5,6,8]
    a_iter_copy2 = iter(a)
    iter_len = 0
    try:
        while True:
            i = next(a_iter_copy2)
            print(i)
            iter_len += 1
    except:
        print('iterator stoped!')
        
    print('length of iterator is %d' % (iter_len,))
    
    output:
    1
    2
    3
    4
    5
    6
    8
    iterator stoped!
    length of iterator is 7

    顺便说句,-->python中的异常处理<--

    内置模块 itertools 中使用生成器的 9 个节省内存的案例

    使用生成器(迭代器)来节省内存是通往python高阶使用的必经之路,加油!

    带 yield 的函数是生成器,而生成器也是一种迭代器

    输出结果需要结合 for 或 next 和捕获 StopIteration。

    下面主要说下生成器带来哪些好处,实际的使用场景在哪里:

    节省内存

    看一个例子,空间复杂度O(n),开辟了[1, 2, 6, 24, 120, 720]长的空间

    def accumulate_div(a):
        if a is None or len(a) == 0:
            return []
        rtn = [a[0]] 
        for i in a[1:]:
            rtn.append(i*rtn[-1])
        return rtn
    
    rtn = accumulate_div([1, 2, 3, 4, 5, 6])
    print(rtn) 
    
    output:
    [1, 2, 6, 24, 120, 720]

    使用yield生成器,实现空间复杂度O(1):

    def accumulate_div(a):
        if a is None or len(a) == 0:
            return []
        it = iter(a)
        total = next(it)
        yield total
        for i in it:
            total = total * i
            yield total
            
    rtn = list(accumulate_div([1, 2, 3, 4, 5, 6]))
    print(rtn)
    
    output:
    [1, 2, 6, 24, 120, 720]

    当输入的数组 [1, 2, 3, 4, 5, 6],只有 6 个元素时,这种内存浪费可以忽视,但是当处理几个 G 的数据时,这种内存空间的浪费就是致命的,尤其对于单机处理。

    Python 内置的 itertools 模块,有许多关于yield的使用方法,多加练习。

    拼接迭代器  chain(*iterables)

    实现元素拼接,可以使用chain;有点像join()。但是也同,join只能一个串联一个序列对象,而chain能一次串联多个可迭代对象,形成一个大的可迭代对象。

    chain函数原型:

    chain(*iterables) # * 可变个数的位置参数

    例子:

    from itertools import *
    chain_it = chain(['I','love'],['Flower Dance'],['very', 'much'])
    for _ in chain_it:
        print(_)
        
    from collections.abc import Iterator
    print(isinstance(chain_it,Iterator))
    
    output:
    I
    love
    Flower Dance
    very
    much
    
    True # 是一个迭代器(Iterator)

    chain有没有节省内存?也可以,chain 是一个生成器函数,在迭代时,每次吐出一个元素,所以做到最高效的节省内存

    # chain 主要实现代码
    def my_chain(*iterables):
        for it in iterables:
            for element in it:
                yield element
                
    chain_it = my_chain(['I','love'],['Flower Dance'],['very', 'much',['very', 'much']])
    for _ in chain_it:
        print(_)
    
    output:
    I
    love
    Flower Dance
    very
    much
    ['very', 'much']

    累积迭代器 accumulate(iterable[, func, *, initial=None])

    返回可迭代对象的累积迭代器,函数原型如下:

    accumulate(iterable[, func, *, initial=None])
    • 返回的是一个迭代器,通过结合 for 打印出来

    如果不提供function,那么就默认求和,累积和:

    accu_iterator = accumulate([1,2,3,4,5,6])
    for _ in accu_iterator:
        print(_)
    
    output:
    1
    3
    6
    10
    15
    21

    提供函数:

    accu_iterator = accumulate([1,2,3,4,5,6],lambda x: x**2)
    for _ in accu_iterator:
        print(_)
    
    output:
    1
    ---------------------------------------------------------------------------
    TypeError                                 Traceback (most recent call last)
    <ipython-input-22-e2b3bc560f59> in <module>()
          1 accu_iterator = accumulate([1,2,3,4,5,6],lambda x: x**2)
    ----> 2 for _ in accu_iterator:
          3     print(_)
    
    TypeError: <lambda>() takes 1 positional argument but 2 were given



    # 修正例子lambda x,y: x*y

    accu_iterator = accumulate([1,2,3,4,5,6],lambda x,y: x*y)
    for _ in accu_iterator:
        print(_)

    output:

    1
    2
    6
    24
    120
    720
     

    如果 func 提供,func 的参数个数要求为 2,根据 func 的累积行为返回结果。

    accumulate 主要的实现代码:

    def accumulate(iterable, func=operator.add, *, initial=None):
        it = iter(iterable)
        total = initial
        if initial is None:
            try:
                total = next(it)
            except StopIteration:
                return
        yield total
        for element in it:
            total = func(total, element)
            yield total
    • 包装 iterable 为迭代器
    • 如果它的初始值为 None,迭代器向前移动求出下一个元素,并赋值给 total,然后 yield;如果初始值被赋值,直接 yield
    • func(total, element) 后,求出 total 的下个取值,yield 后,得到返回结果的下个元素。直到迭代结束
    • 从func也可以看出,func接受参数为2个
    • 理解累积

    漏斗迭代器  compress(data, selectors)

    字面意思,肯定有筛选小的意思?函数原型:

    compress(data, selectors)

    确实,经过 selectors 过滤后,返回一个更小的迭代器,例子:

    compress_iter = compress('abcdefg',[2,1,0,0,'1',-1,1,1,1])
    print(isinstance(compress_iter,Iterator))
    for _ in compress_iter:
        print(_)
    
    output:
    True
    a
    b
    e
    f
    g
    • compress 返回元素个数,至多等于两个参数中较短序列的长度。
    • 当selectors有效位置为0时,才会被过滤掉

    compress主要实现代码:

    def compress(data, selectors):
        return (d for d, s in zip(data, selectors) if s)

    drop 迭代器  dropwhile(predicate, iterable)

    扫描可迭代对象 iterable,从不满足条件往后全部保留,返回一个更小的迭代器。函数原型:

    dropwhile(predicate, iterable) # dropwhile(条件,对象)

    注意,是不满足条件处往后开始(包含这处不满足条件的元素),例子:

    drop_iterator = dropwhile(lambda x: x<5,[1,0,2,4,1,1,3,5,-5])
    for _ in drop_iterator:
        print(_)
    
    output:
    5
    -5

    dropwhile主要实现代码:

    def dropwhile(predicate, iterable):
        iterable = iter(iterable)
        for x in iterable:
            if not predicate(x):
                yield x
                break
        for x in iterable:
            yield x
    • 如果不满足条件 predicate,yield x,然后跳出第一个for迭代,进入下一个for迭代完 iterable 剩余所有元素。
    • 如果满足条件 predicate,就继续迭代,如果所有都满足,则返回空的迭代器。

    take 迭代器  takewhile(predicate, iterable)

    扫描列表,只要满足条件就从可迭代对象中返回元素直到不满足条件为止,函数原型:

    takewhile(predicate, iterable) # takewhile(条件,对象(列表))

    一定要是列表吗?nope,支持for in 遍历即可:

    take_iterator = takewhile(lambda x: x<5, (1,4,6,4,1))
    for _ in take_iterator:
        print(_)
    
    output:
    1
    4

    看主要实现代码也知道:

    def takewhile(predicate, iterable):
        for x in iterable:
            if predicate(x):
                yield x
            else:
                break #不满足条件立即返回

    克隆迭代器  tee(iterable, n=2)

    tee 实现对原迭代器的复制,原型:

    tee(iterable, n=2) # tee(目标迭代器,克隆次数)

    例子:

    a = tee([1,2,3,4,5,6,8],3)
    print(isinstance(a,Iterator))
    a
    print(type(a))
    print(next(a[0]))
    print(next(a[1]))
    iter_len = 0
    try:
        while True:
            i = next(a[2])
            iter_len += 1
    except:
        print('iterator stoped!')
        
    print('length of iterator a[2] is %d' % (iter_len,))
    
    output:
    False
    <class 'tuple'>
    1
    1
    iterator stoped!
    length of iterator a[2] is 7
    • 这种应用场景,需要用到迭代器至少两次的场合,一次迭代器用完后,再使用另一个克隆出的迭代器。
    • tee返回的是元组,tee的元组才是我们需要的迭代器
    • 克隆出的迭代器之间相互独立

    tee主要实现代码(随意看看,会用tee就很不错啦):

    from collections import deque
    
    def tee(iterable, n=2):
        it = iter(iterable)
        deques = [deque() for i in range(n)]
        def gen(mydeque):
            while True:
                if not mydeque:            
                    try:
                        newval = next(it)   
                    except StopIteration:
                        return
                    for d in deques:     
                        d.append(newval)
                yield mydeque.popleft()
        return tuple(gen(d) for d in deques)

    复制元素  repeat(object[, times])

    repeat 实现复制元素 n 次,原型如下:

    repeat(object[, times]) #repeat(任意对象,次数)

    例子:

    list(repeat([7,7,7],7))
    
    output:
    [[7, 7, 7], [7, 7, 7], [7, 7, 7], [7, 7, 7], [7, 7, 7], [7, 7, 7], [7, 7, 7]]

    repeat主要实现代码:

    def repeat(object, times=None):
        if times is None:
            while True: 
                yield object
        else:
            for i in range(times):
                yield object

    笛卡尔积  product(*args, repeat=1)

    当repeat参数为1时,等同于实现   ((x,y) for x in A for y in B)

    主要实现代码:

    def product(*args, repeat=1):
        pools = [tuple(pool) for pool in args] * repeat
       result
    = [[]] for pool in pools: result = [x+[y] for x in result for y in pool] for prod in result: yield tuple(prod)
    • yield返回为元组
    • repeat  = 1两个集合的所有交叉情况
    • repeat  = 2呢?  扩展repeat次的两个输入中的所有元素 也许可能大概maybe是这样

    例子:

    def my_product(*args, repeat=1):
        pools = [tuple(pool) for pool in args] * repeat
        print(pools)
        result = [[]]
        for pool in pools:
            result = [x+[y] for x in result for y in pool]
            print(result,'
    ')
            
        for prod in result:
            yield tuple(prod)
    
    # 当repeat = 1
    list(my_product('AB', 'xy',repeat = 1))
    
    output:
    [('A', 'B'), ('x', 'y')]
    [['A'], ['B']] 
    
    [['A', 'x'], ['A', 'y'], ['B', 'x'], ['B', 'y']] 
    
    [('A', 'x'), ('A', 'y'), ('B', 'x'), ('B', 'y')]
    
    # 当repeat = 2
    list(my_product('AB', 'xy',repeat = 2))
    
    output:
    [('A', 'B'), ('x', 'y'), ('A', 'B'), ('x', 'y')]
    [['A'], ['B']] 
    
    [['A', 'x'], ['A', 'y'], ['B', 'x'], ['B', 'y']] 
    
    [['A', 'x', 'A'], ['A', 'x', 'B'], ['A', 'y', 'A'], ['A', 'y', 'B'], ['B', 'x', 'A'], ['B', 'x', 'B'], ['B', 'y', 'A'], ['B', 'y', 'B']] 
    
    [['A', 'x', 'A', 'x'], ['A', 'x', 'A', 'y'], ['A', 'x', 'B', 'x'], ['A', 'x', 'B', 'y'], ['A', 'y', 'A', 'x'], ['A', 'y', 'A', 'y'], ['A', 'y', 'B', 'x'], ['A', 'y', 'B', 'y'], ['B', 'x', 'A', 'x'], ['B', 'x', 'A', 'y'], ['B', 'x', 'B', 'x'], ['B', 'x', 'B', 'y'], ['B', 'y', 'A', 'x'], ['B', 'y', 'A', 'y'], ['B', 'y', 'B', 'x'], ['B', 'y', 'B', 'y']] 
    
    [('A', 'x', 'A', 'x'),
     ('A', 'x', 'A', 'y'),
     ('A', 'x', 'B', 'x'),
     ('A', 'x', 'B', 'y'),
     ('A', 'y', 'A', 'x'),
     ('A', 'y', 'A', 'y'),
     ('A', 'y', 'B', 'x'),
     ('A', 'y', 'B', 'y'),
     ('B', 'x', 'A', 'x'),
     ('B', 'x', 'A', 'y'),
     ('B', 'x', 'B', 'x'),
     ('B', 'x', 'B', 'y'),
     ('B', 'y', 'A', 'x'),
     ('B', 'y', 'A', 'y'),
     ('B', 'y', 'B', 'x'),
     ('B', 'y', 'B', 'y')]

    加强版 zip函数  zip_longest(*args, fillvalue=None)

    若可迭代对象的长度未对齐,将根据 fillvalue 填充缺失值,返回结果的长度等于更长的序列长度。

    主要实现代码:

    def zip_longest(*args, fillvalue=None):
        iterators = [iter(it) for it in args]
        num_active = len(iterators)
        if not num_active:
            return
        while True:
            values = []
            for i, it in enumerate(iterators):
                try:
                    value = next(it)
                except StopIteration:
                    num_active -= 1
                    if not num_active:
                        return
                    iterators[i] = repeat(fillvalue)
                    value = fillvalue
                values.append(value)
            yield tuple(values)

    用法例子:

    list(zip_longest('ABCD', 'xy', fillvalue='-'))
    output:
    [('A', 'x'), ('B', 'y'), ('C', '-'), ('D', '-')]
    
    
    # 如果使用zip的话
    list(zip('ABCD', 'xy'))
    output:
    [('A', 'x'), ('B', 'y')]
  • 相关阅读:
    Python3+Selenium3+webdriver学习笔记5(模拟常用键盘和鼠标事件)
    安装python3后,没有Scripts目录的解决办法
    Selenium3+webdriver学习笔记4(css方式元素定位)
    Selenium3+webdriver学习笔记3(xpath方式元素定位)
    Selenium3+webdriver学习笔记2(常用元素定位方式,定位单个元素共8种,总共有23种)
    selenium3+webdriver学习笔记1(访问常用请求)
    LR使用流程简介之调试脚本(运行设置、编写说明)
    LR创建脚本和场景流程
    LR脚本示例之常用函数
    LR使用流程简介之录制方式说明
  • 原文地址:https://www.cnblogs.com/PiaYie/p/14998632.html
Copyright © 2020-2023  润新知