0
点赞
收藏
分享

微信扫一扫

Python 面向对象编程(五)

RockYoungTalk 2021-09-28 阅读 45

本篇文章,继续为大家介绍使用特殊方法重写实现的 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])

计算 s1s2 的交集、并集、补集、对称补集:

>> 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
举报

相关推荐

0 条评论