PythonPython必知必会系列

python必知必会5

2022-03-03  本文已影响0人  Nefelibatas

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迭代的时候,它隐式地进行了以下几步:

可迭代对象 vs 迭代器

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

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

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

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

>>> 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之前这个函数执行被挂起。

上一篇下一篇

猜你喜欢

热点阅读