python必知必会5

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

    Python 的 super 方法有什么用?

    面向对象编程的主要优点之一是重用。继承是实现继承的机制之一。 在继承中,一个类(通常称为超类)被另一个类(通常称为子类)继承。子类为超类添加了一些属性。Python有一个其它语言所没有的特点,即支持多重继承。以上这些,super函数都在其中发挥着重要的作用。

    在子类中使用super函数,它会在当前实例的继承树中(MRO列表)搜索下一个基类,把基类方法绑定到当前实例上并调用。

    super函数是2.2版本中为新式类添加的一个函数,它为我们提供了显式引用父类的便利,可以为子类找到合适的可以调用的基类函数。在必须调用基类函数的地方,这基本上很有用。它返回允许我们通过“super”引用父类的代理对象。

    class F:
        def foo(self):
            print("I'm F.foo")
    
    class C(F):
        def foo(self):
            # 子类如果重写父类方法foo,会直接覆盖父类方法
            # 如果我想在这里执行父类的foo方法,该怎么办?
            super().foo() # 《====== 答案:调用super().foo(),
            print("I'm C.foo")
    
    >>> c = C()
    >>> c.foo()
    I'm F.foo
    I'm C.foo
    

    Python 中的 yield 的用法?

    yield表达式主要用在生成器函数和异步生成器函数中:

    在一个函数体内使用yield表达式会使这个函数返回一个生成器。在调用该函数的时候不会执行该函数,而是返回一个生成器对象。

    对这个对象执行for循环时,每次循环都会执行该函数内部的代码,执行到yield时,该函数就返回一个迭代值,下次迭代时,代码从yield的下一条语句继续执行,而函数的本地变量看起来和上次中断执行前是完全一样的,于是函数继续执行,直到再次遇到yield。
    在一个async def定义的函数体内使用yield表达式会让协程函数变成异步的生成器。

    def gen():  # defines a generator function
        yield 123
    
    async def agen(): # defines an asynchronous generator function
        yield 123
    

    一个简单例子:

    >>> def gen_123():
    ...    yield 1
    ...    yield 2
    ...    yield 3
    
    >>> gen = gen_123() # 调用生成器函数,返回一个生成器对象。
    >>> gen
    <generator object gen_123 at 0x102794f00>
    >>> gen_123
    <function __main__.gen_123>
    >>> next(gen) # 执行到第一个 yield 表达式,然后挂起,局部状态保留
    1
    >>> next(gen)
    2
    >>> next(gen)
    3
    >>> next(gen)
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    StopIteration
    

    如果我们使用for对那个生成器对象执行循环,看看会发生什么

    >>> for gen_item in gen_123():
    ...    print(gen_item)
    1
    2
    3
    

    这一次没有像上次那样抛出一个异常,这是因为使用for item in expression迭代的时候,它隐式地进行了以下几步:

    • expression表达式被求值一次,产生一个可迭代对象,然后对它调用iter(),获取一个迭代器
    • 不断在获取到的迭代器上调用next函数得到的每一项,再对每一项执行一次子句体
    • 如果捕获到StopIteration异常就终止循环

    可迭代对象 vs 迭代器

    最常见的可迭代对象,比如一个列表alist=[1,2,3],这是一个可迭代对象。我们可以使用for循环去迭代这个列表的每个成员。

    可以返回一个迭代器的对象都可称之为可迭代对象,也可以这么说,可以使用for... in...的所有对象都是可迭代对象:列表(lists)、字符串、文件。
    可迭代对象的类都必须有一个方法iter,这个方法返回一个迭代器。可使用自省方法dir(list)去查看属性。

    迭代器协议中规定迭代器必须支持两个方法:

    • next:从容器中返回下一项
    • iter: 返回迭代器对象本身

    手动迭代一个列表对象的过程:

    >>> a = [1,2] # <==== 可迭代对象
    >>> ia = iter(a) # <==== 返回的是一个迭代器
    >>> ia
    <listiterator object at 0x10c36df90>
    >>> next(ia) # 对迭代器进行next操作,而不是对可迭代对象操作
    1
    

    生成器 vs 迭代器

    当一个生成器函数被调用的时候,它返回一个生成器对象。它本身就支持next操作,所以,** 生成器其实是一种特殊的迭代器 ** 。不过,相比于自己动手去写一个迭代器类,用生成器往往要简单很多,因为不用去手动去写一个类,然后这个类还必须包含next方法和iter方法,只需要一个yiled关键字。所以,生成器是一种实现迭代器协议的便捷方式。

    先看看迭代两个对象,它们分别是调用下面两种方法返回的对象。

    def my_func1():
        for i in range(5):
            yield i
    
    def my_func2():
        alist = []
        for i in range(5):
            alist.append(i)
    
        return alist
    
    >>> g = my_func1()
    >>> l = my_func2()
    >>> for i in g:
    ...    print(i)
    >>> for i in l:
    ...    print(i)
    

    g是一个生成器对象,l是一个列表对象,它们都是可迭代对象,对它们进行迭代并打印出来的结果是一样的。

    但是,不同的是,我们可以一次性得到l中的所有成员。这就意味着,我们给l开了一段内存去存储它包含的所有成员。

    如果,my_func2返回的一个序列是一个非常大的序列呢?这就会有内存不够的风险,并且函数会一次性计算把得到的结果返回给l,这中间可能会消耗很多时间才能拿到你需要的结果。

    然而,调用my_func1就没有这个问题,for第一次调用从my_func1函数创建的生成器对象,函数将从头开始执行直到遇到yield,然后返回yield后的值作为第一次迭代的返回值,在下次调用next之前这个函数执行被挂起,挂起后,所有局部状态都被保留下来。

    生成器的调用者可以控制生成器函数yield多少次。所以,使用生成器,可以节省内存和高效运行。

    生成器函数 vs 生成器表达式

    • 生成器表达式是列表推倒式的生成器版本。
    • 生成器函数和生成器表达式都可以返回一个生成器对象。
    >>> a = (x*x for x in range(10)) # a是一个生成器对象
    >>> b = [x*x for x in range(10)] # b是一个用列表推导式生成的列表
    

    最后用一个图总结一下:


    image.png

    生成器工作原理: 函数将从头开始执行直到遇到yield,然后返回yield后的值作为第一次迭代的返回值,在下次调用next之前这个函数执行被挂起。

    相关文章

      网友评论

        本文标题:python必知必会5

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