本篇文章,继续为大家介绍使用特殊方法重写实现的 2 个 Python 特性:
- 环境管理器
- 运算符重载
一. 环境管理器
环境管理器用于对资源进行访问的场合,确保使用过程中不管是否发生异常,都会执行必要的清理操作,并释放资源。如:文件使用后自动关闭,线程中锁的自动获取和释放等。
1.1 with 语句
我们使用 with
语句进行管理的对象,其实就是环境管理器了~
下面是 with
语句使用时的几点说明:
- 用
as
子句中的变量绑定生成的对象; -
with
语句并不改变异常的状态; - 使用
with
语句进行管理的对象必须是环境管理器,即实现了__enter__
方法和__exit__
方法。
比如,我们使用 open
函数打开的文件句柄,就是一个环境管理器,可以使用 with
语句进行管理。下面我们以一个文件复制的过程,示意 with
的使用:
src_file = input('请输入原文件:')
dst_file = input('请输入目标文件:')
try:
with open(src_file, 'rb') as src, open(dst_file, 'wb') as dst:
while True:
b = src.read(4096)
if not b:
break
dst.write(b)
except OSError:
print('复制失败!')
运行结果:
1.2 实现自定义的环境管理器
环境管理器也称为上下文管理器,具有以下特点:
- 类内有
__enter__
方法和__exit__
实例方法的类被称为环境管理器; - 能够用
with
语句进行管理的对象必须是环境管理器; -
__enter__
将在进入with
语句时被调用,并返回由as
变量绑定的对象; -
__exit__
将在离开with
语句时被调用,且可以用参数来判断在离开with
语句时是否有异常发生并做出相应处理。
下面,我们就根据上述要求,来实现自己的环境管理器吧~
示例:环境管理器类的定义和使用
class A:
"""此类的对象可以用 with 语句进行管理"""
def __enter__(self):
"""__enter__ 将在进入 with 语句时被调用,并返回由 as 变量绑定的对象"""
print('进入 with 语句...')
return self
def __exit__(self, exc_type, exc_value, exc_tb):
""""__exit__ 将在离开 with 语句时被调用"""
print('离开了 with 语句...')
if exc_type is None:
print('正常离开 with ,没有发生异常')
else:
print('离开 with 语句时发生异常')
print("异常类型: ", exc_type)
print('错误的值是: ', exc_value)
运行结果:
二. 运算符重载
通过运算符重载,能让自定义类创建的对象能够使用运算符进行操作。运算符重载的优势如下:
- 让自定义的类像运算符一样进行运算符操作;
- 让程序简洁易读;
运算符重载方法的参数已经有固定的含义,不建议改变原有的意义。
2.1 算数运算符重载
常用的算数运算符及其特殊方法如下:
方法名 | 运算符和表达式 | 说明 |
---|---|---|
__add__(self, rhs) |
self + rhs |
加法 |
__sub__(self, rhs) |
self - rhs |
减法 |
__mul__(self, rhs) |
self * rhs |
乘法 |
__truediv__(self, rhs) |
self / rhs |
除法 |
__floordiv__(self, rhs) |
self // rhs |
地板除 |
__mod__(self, rhs) |
self % rhs |
取余(求模) |
__pow__(self, rhs) |
self ** rhs |
幂运算 |
备注:rhs
即 right hands side,这里指运算符右手边的对象。
示例:自定义的类通过运算符重载实现算数运算
class MyNumber:
def __init__(self, v):
self.value = v
def __repr__(self):
return 'MyNumber({})'.format(self.value)
def __add__(self, other):
return MyNumber(self.value + other.value)
def __sub__(self, rhs):
return MyNumber(self.value - rhs.value)
def __mul__(self, other):
return MyNumber(self.value * other.value)
def __truediv__(self, other):
return MyNumber(self.value / other.value)
def __floordiv__(self, other):
return MyNumber(self.value // other.value)
def __mod__(self, other):
return MyNumber(self.value % other.value)
运行结果:
>> n1 = MyNumber(5)
>> n2 = MyNumber(2)
>> n1 + n2
MyNumber(7)
>> n1 - n2
MyNumber(3)
>> n1 * n2
MyNumber(10)
>> n1 // n2
MyNumber(2)
>> n1 / n2
MyNumber(2.5)
>> n1 % n2
MyNumber(1)
2.2 反向算数运算符重载
当运算符的左侧为内建类型时,右侧为自定义类型进行算数运算时,会出现 TypeError错误
。
示例:
class MyList:
def __init__(self, iterable=None):
if not iterable:
self.data = []
else:
self.data = [x for x in iterable]
def __repr__(self):
return 'MyList(%r)' % self.data
def __mul__(self, rhs):
return MyList(self.data * rhs)
创建一个自定义列表:
>> L = MyList([1,2,3])
>> L
MyList([1, 2, 3])
>> L * 3
然而,我们自定义的列表是不支持交换律的,即 3 * L
将会抛出异常:
>> 3 * L
...
TypeError: unsupported operand type(s) for *: 'int' and 'MyList'
因无法修改内建类型的代码来实现运算符重载,此时需要实现反向运算符重载。我们的 MyList
只需要实现 __rmul__
即可:
class MyList:
def __init__(self, iterable=()):
self.data = [x for x in iterable]
def __repr__(self):
return 'MyList(%r)' % self.data
def __mul__(self, rhs):
return MyList(self.data * rhs)
def __rmul__(self, lhs):
return MyList(self.data * lhs)
运行结果:
>> L = MyList([1,2,3])
>> L * 2
MyList([1, 2, 3, 1, 2, 3])
>> 2 * L
MyList([1, 2, 3, 1, 2, 3])
反向算数运算符如下:
方法名 | 运算符和表达式 | 说明 |
---|---|---|
__radd__(self,lhs) |
self + lhs |
加法 |
__rsub__(self, lhs) |
self - lhs |
减法 |
__rmul__(self, lhs) |
self * lhs |
乘法 |
__rtruediv__(self, lhs) |
self / lhs |
除法 |
__rfloordiv__(self, lhs) |
self // lhs |
地板除 |
__rmod__(self, lhs) |
self % lhs |
取余(求模) |
__rpow__(self, lhs) |
self ** lhs |
幂运算 |
备注:lhs
即 right hands side,这里指运算符左手边的对象。
2.3 其它运算符的重载
复合赋值运算符
方法名 | 运算符和表达式 | 说明 |
---|---|---|
__iadd__(self, rhs) |
self += rhs |
加法 |
__isub__(self, rhs) |
self -= rhs |
减法 |
__imul__(self, rhs) |
self *= rhs |
乘法 |
__itruediv__(self, rhs) |
self /= rhs |
除法 |
__ifloordiv__(self, rhs) |
self //= rhs |
地板除 |
__imod__(self, rhs) |
self %= rhs |
取余(求模) |
__ipow__(self, rhs) |
self **= rhs |
幂运算 |
比较运算符
方法名 | 运算符和表达式 |
---|---|
__lt__(self, rhs) |
self < rhs |
__le__(self, rhs) |
self <= rhs |
__gt__(self, rhs) |
self > rhs |
__ge__(self, rhs) |
self >= rhs |
__eq__(self, rhs) |
self == rhs |
__ne__(self, rhs) |
self != rhs |
位运算符
方法名 | 运算符和表达式 | 含义 |
---|---|---|
__invert__(self) |
~self |
按位取反 |
__and__(self, rhs) |
self & rhs |
与 |
__or__(self, rhs) |
self | rhs |
或 |
__xor__(self, rhs) |
self ^ rhs |
异或 |
__lshift__(self, rhs) |
self << rhs |
左移 |
__rshift__(self, rhs) |
self >> rhs |
右移 |
2.4 一元运算度的重载
方法名 | 运算符和表达式 |
---|---|
__neg__(self) |
-self |
__pos__(self) |
+self |
__invert__(self) |
~self |
示例:
class MyList:
def __init__(self, iterable=None):
if not iterable:
iterable = []
self.data = [x for x in iterable]
def __repr__(self):
return 'MyList(%r)' % self.data
def __neg__(self):
new = (-x for x in self.data)
return MyList(new)
运行结果:执行 -L
将调用 L.__neg__
特殊方法,将自定义列表中的元素 *-1
>> L = MyList([1, -2, 3, -4, 5, -6])
>> L
MyList([1, -2, 3, -4, 5, -6])
>> -L
MyList([-1, 2, -3, 4, -5, 6])
2.5 in / not in 运算符
表达式 o in L
,将调用 L.__contains__(o)
特殊方法。
示例:
class MyList:
def __init__(self, iterable=None):
if not iterable:
iterable = []
self.data = [x for x in iterable]
def __repr__(self):
return 'MyList(%r)' % self.data
def __contains__(self, e):
return e in self.data
运行结果:
>> L = MyList([1, -2, 3, -4, 5, -6])
>> L
MyList([1, -2, 3, -4, 5, -6])
>> -2 in L
True
>> 2 not in L
True
2.6 索引和切片运算符的重载
实现如下的特殊方法,可以让自定义对象像内置的列表一样支持索引和切片操作:
重载方法 | 运算符和表达式 |
---|---|
__getitem__(self, i) |
x = self[i] |
__setitem__(self, i, val) |
self.[i] = val |
__delitem__(self, i) |
del self[i] |
示例:
class MyList:
def __init__(self, iterable=None):
if not iterable:
self.data = []
else:
self.data = [x for x in iterable]
def __repr__(self):
return 'MyList(%r)' % self.data
def __getitem__(self, i):
if type(i) is slice:
print('切片对象{}的起始值为:{},终止值为:{},步长为:{}'.format(i, i.start, i.stop, i.step))
return self.data[i]
运算结果:
>> L = MyList([1, -2, 3, -4, 5, -6])
>> L[-1]
-6
>> L[1::2]
切片对象slice(1, None, 2)的起始值为:1,终止值为:None,步长为:2
[-2, -4, -6]
>> L[slice(1, -1, 2)]
切片对象slice(1, -1, 2)的起始值为:1,终止值为:-1,步长为:2
[-2, -4]
三. 综合示例
实现有序集合类 OrderSet
,并实现两个 OrderSet
集合的如下运算:
- 交集
&
- 并集
|
- 补集
-
- 对称补集
^
-
==
!=
-
in
not in
要求:内部用 list
存储数据。
class OrderSet:
def __init__(self, iterable):
self.data = list({x for x in iterable})
def __repr__(self):
return 'OrderSet(%s)' % self.data
def __and__(self, another):
return OrderSet(list(set(self.data) & set(another.data)))
def __or__(self, another):
return OrderSet(list(set(self.data) | set(another.data)))
def __xor__(self, another):
return OrderSet(list(set(self.data) ^ set(another.data)))
def __sub__(self, other):
return OrderSet(list(set(self.data) - set(other.data)))
def __ne__(self, another):
return self.data != another.data
def __eq__(self, another):
return self.data == another.data
def __contains__(self, number):
return number in self.data
运行结果:
>> s1 = OrderSet([1, 2, 3, 4])
>> s2 = OrderSet([3, 4, 5])
计算 s1
和 s2
的交集、并集、补集、对称补集:
>> s1 & s2
OrderSet([3, 4])
>> s1 | s2
OrderSet([1, 2, 3, 4, 5])
>> s1 - s2
OrderSet([1, 2])
>> s1 ^ s2
OrderSet([1, 2, 5])
返回 bool
值的运算:
>> s1 != s2
True
>> s1 == s2
False
>> 2 in s1
True
>> 2 not in s1
False