python必知必会5
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是一个用列表推导式生成的列表
最后用一个图总结一下:
![](https://img.haomeiwen.com/i27579716/202cbc13bd9003a7.png)
生成器工作原理: 函数将从头开始执行直到遇到yield,然后返回yield后的值作为第一次迭代的返回值,在下次调用next之前这个函数执行被挂起。