python提升七
Python可重载运算符有哪些
这里给大家举一个与重载运算符相关的实例:
1 | class MyClass: #自定义一个类 |
通过将 myc 进行 repr、str 运算,从输出结果中可以看到,程序调用了重载的操作符方法 repr 和 __str__。而令 myc 和 mycl 进行 < 号的比较运算以及加法运算,从输出结果中可以看出,程序调用了重载 < 号的方法 lt 和 add 方法。
那么,Python 类支持对哪些方法进行重载呢?这个给大家提供一个表格(表 1),列出了 Python 中常用的可重载的运算符,以及各自的含义
Python迭代器及其用法
列表(list)、元组(tuple)、字典(dict)、集合(set)这些序列式容器,有一个共同的特性,
它们都支持使用 for 循环遍历存储的元素,都是可迭代的,因此它们又有一个别称,即迭代器。
从字面来理解,迭代器指的就是支持迭代的容器,更确切的说,是支持迭代的容器类对象,这里的容器可以是列表、元组等这些 Python 提供的基础容器,也可以是自定义的容器类对象,只要该容器支持迭代即可。
如果要自定义实现一个迭代器,则类中必须实现如下 2 个方法:
next(self):返回容器的下一个元素。
iter(self):该方法返回一个迭代器(iterator)。
1 | import sys |
迭代器本身是一个底层的特性和概念,在程序中并不常用,但它为生成器这一更有趣的特性提供了基础。
Python迭代器实现字符串的逆序输出
实现思路是这样的,自定义一个类并重载其 init() 初始化方法,实现为自身私有成员赋值
同时重载 iter() 和 next() 方法,使其具有迭代器功能。在此基础上,如果想实现对用户输入的字符串进行逆序输出,就需要在 next() 方法中实现从后往前返回字符。
1 | class Reverse: |
Python生成器
以 list 容器为例,在使用该容器迭代一组数据时,必须事先将所有数据存储到容器中,才能开始迭代;而生成器却不同,它可以实现在迭代的同时生成元素。
也就是说,对于可以用某种算法推算得到的多个数据,生成器并不会一次性生成它们,而是什么时候需要,才什么时候生成。
不仅如此,生成器的创建方式也比迭代器简单很多,大体分为以下 2 步:
定义一个以 yield 关键字标识返回值的函数;
调用刚刚创建的函数,即可创建一个生成器。
带有 yield 的函数不再是一个普通函数,而是一个生成器generator,可用于迭代,工作原理同上。
yield 是一个类似 return的关键字,迭代一次遇到yield时就返回yield后面的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码开始执行。
简要理解:yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后开始。
1 | def intNum(): |
我们就成功创建了一个 num 生成器对象。显然,和普通函数不同,intNum() 函数的返回值用的是 yield 关键字,而不是 return 关键字,此类函数又成为生成器函数。
和 return 相比,yield 除了可以返回相应的值,还有一个更重要的功能,即每当程序执行完该语句时,程序就会暂停执行。
不仅如此,即便调用生成器函数,Python 解释器也不会执行函数中的代码,它只会返回一个生成器(对象)。
要想使生成器函数得以执行,或者想使执行完 yield 语句立即暂停的程序得以继续执行,有以下 2 种方式:
通过生成器(上面程序中的 num)调用 next() 内置函数或者 next() 方法;
通过 for 循环遍历生成器。
在上面程序的基础上,添加如下语句:
1 | def diedai(): |
这里有必要给读者分析一个程序的执行流程:
首先,在创建有 num 生成器的前提下,通过其调用 next() 内置函数,会使 Python 解释器开始执行 intNum() 生成器函数中的代码,因此会输出“开始执行”,程序会一直执行到yield i,而此时的 i==0,因此 Python 解释器输出“0”。由于受到 yield 的影响,程序会在此处暂停。
然后,我们使用 num 生成器调用 next() 方法,该方法的作用和 next() 函数完全相同(事实上,next() 函数的底层执行的也是 next() 方法),它会是程序继续执行,即输出“继续执行”,程序又会执行到yield i,此时 i==1,因此输出“1”,然后程序暂停。
最后,我们使用 for 循环遍历 num 生成器,之所以能这么做,是因为 for 循环底层会不断地调用 next() 函数,使暂停的程序继续执行,因此会输出后续的结果。
Python生成器send()方法
我们知道,通过调用 next() 或者 next() 方法,可以实现从外界控制生成器的执行。除此之外,通过 send() 方法,还可以向生成器中传值。
值得一提的是,send() 方法可带一个参数,也可以不带任何参数(用 None 表示)。其中,当使用不带参数的 send() 方法时,它和 next() 函数的功能完全相同。
1 | def intNum(): |
带参数的 send(value) 的用法,其具备 next() 函数的部分功能,即将暂停在 yield 语句出的程序继续执行,但与此同时,该函数还会将 value 值作为 yield 语句返回值赋值给接收者。注意,带参数的 send(value) 无法启动执行生成器函数。也就是说,程序中第一次使用生成器调用 next() 或者 send() 函数时,不能使用带参数的 send() 函数。
1 | def foo(): |
分析一下此程序的执行流程:
首先,构建生成器函数,并利用器创建生成器(对象)f 。
使用生成器 f 调用无参的 send() 函数,其功能和 next() 函数完全相同,因此开始执行生成器函数,即执行到第一个 yield “hello” 语句,该语句会返回 “hello” 字符串,然后程序停止到此处(注意,此时还未执行对 bar_a 的赋值操作)。
下面开始使用生成器 f 调用有参的 send() 函数,首先它会将暂停的程序开启,同时还会将其参数“漂亮鬼”赋值给当前 yield 语句的接收者,也就是 bar_a 变量。程序一直执行完 yield bar_a 再次暂停,因此会输出“漂亮鬼”。
4) 最后依旧是调用有参的 send() 函数,同样它会启动餐厅的程序,同时将参数”https://xiaoyangzst.vercel.app"传给 bar_b,然后执行完 yield bar_b 后(输出”https://xiaoyangzst.vercel.app"),程序执行再次暂停。
yield 是一个类似 return 的关键字,迭代一次遇到yield时就返回yield后面(右边)的值。重点是:下一次迭代时,从上一次迭 代遇到的yield后面的代码(下一行)开始执行
Python生成器close()方法
当程序在生成器函数中遇到 yield 语句暂停运行时,此时如果调用 close() 方法,会阻止生成器函数继续执行,该函数会在程序停止运行的位置抛出 GeneratorExit 异常。
1 | def foo(): |
注意,虽然通过捕获 GeneratorExit 异常,可以继续执行生成器函数中剩余的代码,带这部分代码中不能再包含 yield 语句,否则程序会抛出 RuntimeError 异常
1 | def foo(): |
另外,生成器函数一旦使用 close() 函数停止运行,后续将无法再调用 next() 函数或者 next() 方法启动执行,否则会抛出 StopIteration 异常
1 | def foo(): |
Python @函数装饰器及用法
函数装饰器的工作原理
假设用 funA() 函数装饰器去装饰 funB() 函数:
#funA 作为装饰器函数
def funA(fn):
#…
fn() # 执行传入的fn参数
#…
return ‘…’
@funA
def funB():
#…
实际上,上面程序完全等价于下面的程序:
def funA(fn):
#…
fn() # 执行传入的fn参数
#…
return ‘…’
def funB():
#…
funB = funA(funB)
通过比对以上 2 段程序不难发现,使用函数装饰器 A() 去装饰另一个函数 B(),其底层执行了如下 2 步操作:
将 B 作为参数传给 A() 函数;将 A() 函数执行完成的返回值反馈回B。
1 | #funA 作为装饰器函数 |
显然,被“@函数”修饰的函数不再是原来的函数,而是被替换成一个新的东西(取决于装饰器的返回值),
即如果装饰器函数的返回值为普通变量,那么被修饰的函数名就变成了变量名;
同样,如果装饰器返回的是一个函数的名称,那么被修饰的函数名依然表示一个函数。
实际上,所谓函数装饰器,就是通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。
带参数的函数装饰器
在分析 funA() 函数装饰器和 funB() 函数的关系时,细心的读者可能会发现一个问题,即当 funB() 函数无参数时,可以直接将 funB 作为 funA() 的参数传入。但是,如果被修饰的函数本身带有参数,那应该如何传值呢?
比较简单的解决方法就是在函数装饰器中嵌套一个函数,该函数带有的参数个数和被装饰器修饰的函数相同。
1 | def funA(fn): |
其实,它和如下程序是等价的
1 | def funA(fn): |
但还有一个问题需要解决,即如果当前程序中,有多个(≥ 2)函数被同一个装饰器函数修饰,这些函数带有的参数个数并不相等,怎么办呢?
最简单的解决方式是用 *args 和 *kwargs 作为装饰器内部嵌套函数的参数,args 和 **kwargs 表示接受任意数量和类型的参数。举个例子:
def funA(fn):
# 定义一个嵌套函数
def say(*args,**kwargs):
fn(*args,**kwargs)
return say
@funA
def funB(arc):
print(“211212:”,arc)
@funA
def other_funB(name,arc):
print(name,arc)
funB(“2134567”)
other_funB(“324程:”,”234234324234234”)