python必知必会9

作者: Nefelibatas | 来源:发表于2022-03-08 08:15 被阅读0次

    生成器和迭代器

    迭代是处理数据的重要环节,基本上对大量数据的处理上,我们都需要对数据进行迭代操作,如何在节省内存开销且高效地去对数据迭代,这就是生成器存在的意义。

    迭代器实现了next方法,返回序列中的下一个元素;如果没有元素了,那么抛出 StopIteration 异常。另外迭代器实现了iter方法,用于返回迭代器本身。

    所有生成器都是迭代器,会生成传给 yield 关键字的表达式的值。调用生成器函数返回生成器,而生成器可以产出值。同样,当没有值可以产出时,会抛出 StopIteration 异常。

    yield from iterator

    这个语法多用于嵌套生成器。它有两种用法:一,从生成器中读取数据;二,创建通道,把内层生成器直接与外层生成器联系起来,把生成器当协程使用。

    先看看 yield from 的基本用法,通过将生成器拆分为多个生成器,可以轻松地对其进行重构。

    版本一:不使用 yield from

    def generator2():
        for i in range(10):
            yield i
    
    def generator3():
        for j in range(10, 20):
            yield j
    
    def generator():
        for i in generator2():
            yield i
        for j in generator3():
            yield j
    

    版本二:我们用 yield from 来改写上面的 generator():

    def generator():
        yield from generator2() # 对for循环进行重构,这个版本使用`yield from`减少了手动循环
        yield from generator3()
    

    如果你想在生成器中调用其他生成器作为子例程,yield from 这个时候非常有用。
    如果你不使用它的话,那么就必须写额外的 for 循环了。

    当生成器有了 send 方法

    生成器对象有几个重要的 API,send,throw,close,这些在协程中
    先从一个简单的协程例子讲起:

    def accumulator():
        total = 0
        while True:
            print("Total is ", total)
    
            # 生成器的调用方可以使用`.send(...)`方法发送数据,发送的数据会成为`yield`表达式的值。
            # 所以,这里的input是从send传进来的值,而非yield产出的值
            # yield total相当于函数return total, 只不过这个函数不是真正的return,而是在这个位置挂起等待下一次调用。
            input = yield total
            print("Send: ", input)
            total += input
            print("Adding %d ... => Total is %d"%(input, total))
    
    >>> gen = accumulator()
    >>> next(gen)  # 激活协程,计算停在`yield`,也可以使用gen.send(None)激活协程
    Total is 0
    0
    >>> gen.send(1) # 从刚刚停留的位置开始,传入数据开始计算
    Send:  1
    Adding 1 ... => Total is 1
    Total is  1    # 运行又停在`yield`处,等待传入新数值
    0 # 这是yield total的产生结果,就相当于函数最后的return,比方说,你去调用一个用return返回的函数,最后都会输出return的结果。
    >>> gen.send(12)
    Send:  12
    Adding 12 ... => Total is 13
    Total is  13
    13
    >>> try:
    ...    gen.throw(ValueError) # 或调用gen.close()终止协程
    ...except ValueError:
    ...    pass
    >>> gen.send(12)
    StopIteration
    

    使用协程的基本步骤为:

    • 创建协程对象
    • 调用 next 函数,激活协程
    • 调用.send(...) 方法,推动协程执行并产出
    • 调用方可以通过.close(...) 或.throw() 方法终止协程,如果继续推进协程会抛出 StopIteration。
      生成器的调用方可以使用 .send(...) 方法发送数据,它的参数会成为暂停的 yield 整个表达式 的值。

    协程是指一个过程,这个过程与调用方协作,产出由调用方提供的值。

    前面提到 yield from 主要是用于嵌套的生成器,所以,把它当做协程的时候,它的主要功能是打开双向通道,把最外层的调用方与最内层的子生成器连接起来,这样二者可以直接发送和产出值,还可以直接传入异常,而不用在位于中间的协程中添加大量处理异常的样板代码。有了这个结构,协程可以通过以前不可能的方式委托职责。

    子生成器

    def accumulator():
    total = 0
    while True:
    input = yield
    if input is None: # 如果调用方传入None,这里就跳出循环,total重新置零
    return total # 子生成器可以返回结果给外层生成器middleware
    total += input

    yield from所在的函数相当于一个管道,将调用方client和子生成器accumulator串联起来

    它的职责是负责传递信息以及异常处理,而子生成器就专职做自己该做的事。

    def middleware(results):
        while True:
            result = yield from accumulator()
            results.append(result)
    
    # 调用方(客户端)
    def client():
        results = [] # 用于收集结果
        counter = middleware(results)
        next(counter) # 激活协程
        for i in range(5):
            counter.send(i)
        counter.send(None) # 关闭当前的生成器对象,协程重置
    
        for i in range(3):
            counter.send(i)
        counter.send(None)
        print(results)
    >>> client()
    [10, 3]
    

    内置函数 iter ()

    和iter函数用法不一样的地方在于,参数形式变了,而且你可以去控制迭代。

    iter(obj) 传进去是一个可迭代对象,而 iter(func, sentinel) 的第一个参数是可调用对象,多数情况下即函数对象,第二个参数是一个哨符,用于指示迭代器去抛出 StopIteration。如果可迭代对象的返回值等于这个哨符,那么迭代器停止。

    >>> from random import randint
    >>> def get_number(): # 无参
    ...    return randit(1, 6) # 随机返回一个在1~6范围内的整数
    >>> iter_num = iter(get_number, 2)  # iter的第一个参数是一个可调用对象
    >>> for num in iter_num: # 直到get_number()返回2,停止循环
    ...    print(num)
    

    魔法函数call怎么使用

    使用 Python 的魔术方法,可以以一种简单的方法来让对象可以表现的像内置类型一样。比如如果一个类定义了名为getitem() 的方法,并且x为该类的一个实例,则x[i]基本就等同于 type(x).getitem(x, i)。也就是说,你需要在自定义的类中实现一些接口,但是你可以只实现部分接口,这样你就可以去对新序列对象访问单个元素,迭代,in运算。同样,你希望你的新类型创建的实例可以像函数对象一样被调用,那么就必须实现call。这是魔法方法的最大优势。

    掌握一些基本的魔法方法,可以让你创建出与其他 Python 特性无缝集成的类,这是非常有必要的。

    魔法函数call可以模拟可调用对象,这个方法在实例作为一个函数被 “调用” 时被调用;如果定义了此方法,则 x(arg1, arg2, ...) 就相当于 x.call(arg1, arg2, ...) 的快捷方式。
    魔法方法非常强大,但是一般情况下,我们是不需要直接调用魔法方法,只有在定制的类中,你可以去重写它们。

    class MyClass:
        def __call__(self, *args):
            print(*args)
    
    >>> obj = MyClass()
    >>> obj(123) # 可以像调用函数一样去调用obj对象
    

    对于模拟其它内置类型需要使用到的魔法方法,可以参考官方文档 - 魔法方法,这里就不一一展开了。特别提一下几个非常常用的:

    • __new__
    • __str__ , __repr__
    • __iter__
    • __getitem__ , __setitem__ , __delitem__
    • __getattr__ , __setattr__ , __delattr__
    • __call__

    相关文章

      网友评论

        本文标题:python必知必会9

        本文链接:https://www.haomeiwen.com/subject/pvngrrtx.html