Python可重载运算符有哪些

这里给大家举一个与重载运算符相关的实例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class MyClass: #自定义一个类
def __init__(self, name , age): #定义该类的初始化函数
self.name = name #将传入的参数值赋值给成员交量
self.age = age
def __str__(self): #用于将值转化为字符串形式,等同于 str(obj)
return "name:"+self.name+";age:"+str(self.age)

__repr__ = __str__ #转化为供解释器读取的形式

def __lt__(self, record): #重载 self<record 运算符
if self.age < record.age:
return True
else:
return False

def __add__(self, record): #重载 + 号运算符
return MyClass(self.name, self.age+record.age)

myc = MyClass("Anna", 42) #实例化一个对象 Anna,并为其初始化
mycl = MyClass("Gary", 23) #实例化一个对象 Gary,并为其初始化
print(repr(myc)) #格式化对象 myc,
print(myc) #解释器读取对象 myc,调用 repr
print (str (myc)) #格式化对象 myc ,输出"name:Anna;age:42"
print(myc < mycl) #比较 myc<mycl 的结果,输出 False
print (myc+mycl) #进行两个 MyClass 对象的相加运算,输出 "name:Anna;age:65"

通过将 myc 进行 repr、str 运算,从输出结果中可以看到,程序调用了重载的操作符方法 repr 和 __str__。而令 myc 和 mycl 进行 < 号的比较运算以及加法运算,从输出结果中可以看出,程序调用了重载 < 号的方法 ltadd 方法。

那么,Python 类支持对哪些方法进行重载呢?这个给大家提供一个表格(表 1),列出了 Python 中常用的可重载的运算符,以及各自的含义

Python迭代器及其用法

列表(list)、元组(tuple)、字典(dict)、集合(set)这些序列式容器,有一个共同的特性,
它们都支持使用 for 循环遍历存储的元素,都是可迭代的,因此它们又有一个别称,即迭代器。

从字面来理解,迭代器指的就是支持迭代的容器,更确切的说,是支持迭代的容器类对象,这里的容器可以是列表、元组等这些 Python 提供的基础容器,也可以是自定义的容器类对象,只要该容器支持迭代即可。

如果要自定义实现一个迭代器,则类中必须实现如下 2 个方法:
next(self):返回容器的下一个元素。
iter(self):该方法返回一个迭代器(iterator)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sys
class reverse:
def __init__(self,string):
self.index= len(string)
self.string= string
def __iter__(self):
return self
def __next__(self):
if self.index==0:
sys.exit(0)
self.index -= 1
return self.string[self.index]
res= reverse("xiaoyang")
for i in res:
print(i,end=' ')

迭代器本身是一个底层的特性和概念,在程序中并不常用,但它为生成器这一更有趣的特性提供了基础。

Python迭代器实现字符串的逆序输出

实现思路是这样的,自定义一个类并重载其 init() 初始化方法,实现为自身私有成员赋值
同时重载 iter() 和 next() 方法,使其具有迭代器功能。在此基础上,如果想实现对用户输入的字符串进行逆序输出,就需要在 next() 方法中实现从后往前返回字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Reverse:
def __init__(self, string):
self.__string = string
self.__index = len(string)
def __iter__(self):
return self
def __next__(self):
if self.__index == 0:
raise(StopIteration)
self.__index -= 1
return self.__string[self.__index]
revstr = Reverse('Python')
for c in revstr:
print(c,end=" ")
Python生成器

以 list 容器为例,在使用该容器迭代一组数据时,必须事先将所有数据存储到容器中,才能开始迭代;而生成器却不同,它可以实现在迭代的同时生成元素。
也就是说,对于可以用某种算法推算得到的多个数据,生成器并不会一次性生成它们,而是什么时候需要,才什么时候生成。
不仅如此,生成器的创建方式也比迭代器简单很多,大体分为以下 2 步:

定义一个以 yield 关键字标识返回值的函数;
调用刚刚创建的函数,即可创建一个生成器。
带有 yield 的函数不再是一个普通函数,而是一个生成器generator,可用于迭代,工作原理同上。

yield 是一个类似 return的关键字,迭代一次遇到yield时就返回yield后面的值。重点是:下一次迭代时,从上一次迭代遇到的yield后面的代码开始执行。

简要理解:yield就是 return 返回一个值,并且记住这个返回的位置,下次迭代就从这个位置后开始。

1
2
3
4
5
6
def intNum():
print("开始执行")
for i in range(5):
yield i
print("继续执行")
num = intNum()

我们就成功创建了一个 num 生成器对象。显然,和普通函数不同,intNum() 函数的返回值用的是 yield 关键字,而不是 return 关键字,此类函数又成为生成器函数。

和 return 相比,yield 除了可以返回相应的值,还有一个更重要的功能,即每当程序执行完该语句时,程序就会暂停执行。

不仅如此,即便调用生成器函数,Python 解释器也不会执行函数中的代码,它只会返回一个生成器(对象)。

要想使生成器函数得以执行,或者想使执行完 yield 语句立即暂停的程序得以继续执行,有以下 2 种方式:

通过生成器(上面程序中的 num)调用 next() 内置函数或者 next() 方法;
通过 for 循环遍历生成器。

在上面程序的基础上,添加如下语句:

1
2
3
4
5
6
7
8
9
10
11
def diedai():
print("开始使用")
for i in range(0,11):
yield i
num = diedai()
# print(diedai())
#调用 next() 内置函数
print(next(num))#调用 __next__() 方法
print(num.__next__())#通过for循环遍历生成器
for i in num:
print(i)

这里有必要给读者分析一个程序的执行流程:

  1. 首先,在创建有 num 生成器的前提下,通过其调用 next() 内置函数,会使 Python 解释器开始执行 intNum() 生成器函数中的代码,因此会输出“开始执行”,程序会一直执行到yield i,而此时的 i==0,因此 Python 解释器输出“0”。由于受到 yield 的影响,程序会在此处暂停。

  2. 然后,我们使用 num 生成器调用 next() 方法,该方法的作用和 next() 函数完全相同(事实上,next() 函数的底层执行的也是 next() 方法),它会是程序继续执行,即输出“继续执行”,程序又会执行到yield i,此时 i==1,因此输出“1”,然后程序暂停。

  3. 最后,我们使用 for 循环遍历 num 生成器,之所以能这么做,是因为 for 循环底层会不断地调用 next() 函数,使暂停的程序继续执行,因此会输出后续的结果。

Python生成器send()方法

我们知道,通过调用 next() 或者 next() 方法,可以实现从外界控制生成器的执行。除此之外,通过 send() 方法,还可以向生成器中传值。
值得一提的是,send() 方法可带一个参数,也可以不带任何参数(用 None 表示)。其中,当使用不带参数的 send() 方法时,它和 next() 函数的功能完全相同。

1
2
3
4
5
6
7
8
def intNum():
print("开始执行")
for i in range(5):
yield i
print("继续执行")
num = intNum()
print(num.send(None))
print(num.send(None))

带参数的 send(value) 的用法,其具备 next() 函数的部分功能,即将暂停在 yield 语句出的程序继续执行,但与此同时,该函数还会将 value 值作为 yield 语句返回值赋值给接收者。注意,带参数的 send(value) 无法启动执行生成器函数。也就是说,程序中第一次使用生成器调用 next() 或者 send() 函数时,不能使用带参数的 send() 函数。

1
2
3
4
5
6
7
8
def foo():
bar_a = yield "hello"
bar_b = yield bar_a
yield bar_b
f = foo()
print(f.send(None))
print(f.send("漂亮鬼"))
print(f.send("https://xiaoyangzst.vercel.app"))

分析一下此程序的执行流程:

  1. 首先,构建生成器函数,并利用器创建生成器(对象)f 。

  2. 使用生成器 f 调用无参的 send() 函数,其功能和 next() 函数完全相同,因此开始执行生成器函数,即执行到第一个 yield “hello” 语句,该语句会返回 “hello” 字符串,然后程序停止到此处(注意,此时还未执行对 bar_a 的赋值操作)。

  3. 下面开始使用生成器 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
2
3
4
5
6
7
8
def foo():
try:
yield 1
except GeneratorExit:
print('捕获到 GeneratorExit')
f = foo()
print(next(f))
f.close()

注意,虽然通过捕获 GeneratorExit 异常,可以继续执行生成器函数中剩余的代码,带这部分代码中不能再包含 yield 语句,否则程序会抛出 RuntimeError 异常

1
2
3
4
5
6
7
8
9
10
def foo():
try:
yield 1
except GeneratorExit:
print('捕获到 GeneratorExit')
yield 2 #抛出 RuntimeError 异常

f = foo()
print(next(f))
f.close()

另外,生成器函数一旦使用 close() 函数停止运行,后续将无法再调用 next() 函数或者 next() 方法启动执行,否则会抛出 StopIteration 异常

1
2
3
4
5
6
7
8
def foo():
yield "c.biancheng.net"
print("生成器停止执行")

f = foo()
print(next(f)) #输出 "c.biancheng.net"
f.close()
next(f) #原本应输出"生成器停止执行"
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
2
3
4
5
6
7
8
9
10
11
#funA 作为装饰器函数
def funA(fn):
print("漂亮鬼")
fn() # 执行传入的fn参数
print("https://xiaoyangzst.vercel.app")
return "装饰器函数的返回值"

@funA
def funB():
print("学习 Python")
print(funB)

显然,被“@函数”修饰的函数不再是原来的函数,而是被替换成一个新的东西(取决于装饰器的返回值),

即如果装饰器函数的返回值为普通变量,那么被修饰的函数名就变成了变量名;

同样,如果装饰器返回的是一个函数的名称,那么被修饰的函数名依然表示一个函数。
实际上,所谓函数装饰器,就是通过装饰器函数,在不修改原函数的前提下,来对函数的功能进行合理的扩充。

带参数的函数装饰器

在分析 funA() 函数装饰器和 funB() 函数的关系时,细心的读者可能会发现一个问题,即当 funB() 函数无参数时,可以直接将 funB 作为 funA() 的参数传入。但是,如果被修饰的函数本身带有参数,那应该如何传值呢?
比较简单的解决方法就是在函数装饰器中嵌套一个函数,该函数带有的参数个数和被装饰器修饰的函数相同。

1
2
3
4
5
6
7
8
9
10
def funA(fn):
# 定义一个嵌套函数
def say(arc):
print("Python教程:",arc)
return say

@funA
def funB(arc):
print("funB():", a)
funB("1d23456789")

其实,它和如下程序是等价的

1
2
3
4
5
6
7
8
9
10
11
def funA(fn):
# 定义一个嵌套函数
def say(arc):
print("Python教程:",arc)
return say

def funB(arc):
print("funB():", arc)

funB = funA(funB)
funB("http://c.biancheng.net/python")

但还有一个问题需要解决,即如果当前程序中,有多个(≥ 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”)