Python进阶与拾遗4:Python中的运算符重载

  • 运算符重载的相关概念
  • 常见的运算符重载方法
  • 运算符重载的常见例子
  • 索引和分片:\_\_getitem\_\_和\_\_setitem\_\_
  • 迭代器对象:\_\_iter\_\_和\_\_next\_\_
  • 普通迭代器的对象
  • 有多个迭代器的对象
  • 成员关系:\_\_contains\_\_, \_\_iter\_\_和\_\_getitem\_\_
  • 属性引用:\_\_getattr\_\_和\_\_setattr\_\_
  • 返回字符串表达式:\_\_str\_\_和\_\_repr\_\_
  • 加法,右侧加法和原处加法:\_\_add\_\_, \_\_radd\_\_和\_\_iadd\_\_
  • 比较:\_\_lt\_\_,\_\_gt\_\_与其他方法
  • 布尔测试:\_\_bool\_\_和\_\_len\_\_
  • 对象析构函数:\_\_del\_\_
  • Call表达式:\_\_call\_\_



在Python这一门

面向对象的编程语言中,

运算符重载是类程序中常见的技巧与方法。本篇博文总结Python中的运算符重载相关知识,下面开始干货。

运算符重载的相关概念

运算符重载意味着在类方法中拦截内置的操作,这样当类的实例出现在内置操作中,Python会自动调用你的方法,并且方法的返回值变成了相应的操作结果。

  1. 运算符重载使得类拦截常规的Python运算。
  2. 类可以重载所有Python表达式的运算符。
  3. 类也可以重载打印,函数调用,属性点号运算等内置运算。
  4. 重载使得类实例行为像内置类型。
  5. 重载是通过特殊名称的类方法来实现的。
    类中的构造函数,就是使用最多的运算符重载。
class Number:
    def __init__(self, start):
        self.data = start
    def __sub__(self, other):
        return Number(self.data - other)

def main():
    X = Number(5)
    Y = X - 2
    print(Y.data)

if __name__ == "__main__":
    main()
# 输出:
# 3

常见的运算符重载方法

方法

重载

调用

__init__

构造函数

对象建立: X = Class(args)

__del__

析构函数

X对象收回

__add__

运算符+

如果没有__iadd__, X + Y, X += Y

__or__

运算符

(按位或)

__repr__, __str__

打印, 转换

print(X), repr(X), str(X)

__call__

函数调用

X(*args, **kargs)

__getattr__

点号运算

X.undefined

__setattr__

属性赋值语句

X.any = value

__delattr__

属性删除

del X.any

__getattribute__

属性获取

X.any

__getitem__

索引运算

X[key], X[i:j], 没__iter__时的for循环和其他迭代器

__setitem__

索引赋值语句

X[key] = value, X[i:j] = sequence

__delitem__

索引和分片删除

def X[key], def X[i:j]

__len__

长度

len(X), 如果没有bool, 真值测试

__bool__(Python 3.0及之后版本), __nonzero__(Python 2.6及之后版本)

布尔测试

bool(X),nonzero(X)

__lt__, __gt__, __le__, __ge__

大小比较

X < Y, X > Y, X <= Y, X >= Y

__eq__, __ne__(Python 3.0及之后版本), __cmp__(Python 2.6及之后版本)

等值比较

X == Y, X != Y

__radd__

右侧加法

other + X

__iadd__

实地加法

X += Y

__iter__, __next__

迭代环境

I = iter(X) next(I), 循环, map(F, X)

__contains__

成员关系测试

item in X

__index__

整数值

hen(X), bin(X), oct(X), O[X], O[X:]

__enter__, __exit__

环境管理器

with obj as var:

__get__, __set__

描述符属性

X.attr, X.attr = value, del X.attr

__delete__

del操作触发

del(X)时执行

__new__

创建

在__init__之前创建对象

运算符重载的常见例子

索引和分片:__getitem__和__setitem__

  1. 对于实例的索引和分片,会调用__getitem__方法。
  2. 对于索引和分片赋值,会调用__setitem__方法。
  3. 对于索引迭代,成员关系测试in,列表解析,内置函数map,列表和元素赋值运算以及类型构造方法也会自动调用__getitem__。
class Indexer:
    data = [5, 6, 7, 8, 9]
    def __getitem__(self, index):
        return self.data[index]
    def __setitem__(self, key, value):
        self.data[key] = value

def main():
    X = Indexer()
    print(X[0])
    print(X[2:4])
    print(X[1:])
    X[0] = 1
    for item in X:
        print(item)

    print(1 in X)
    print([num for num in X])
    print(list(map(oct, X)))
    (a, b, c, d, e) = X
    print('---------------')
    print(a)
    print(b)
    print(list(X))
    print(tuple(X))
    X.data = 'abcdefg'
    print(''.join(X))


if __name__ == "__main__":
    main()
'''
输出:
5
[7, 8]
[6, 7, 8, 9]
1
6
7
8
9
True
[1, 6, 7, 8, 9]
['0o1', '0o6', '0o7', '0o10', '0o11']
---------------
1
6
[1, 6, 7, 8, 9]
(1, 6, 7, 8, 9)
abcdefg
'''

迭代器对象:__iter__和__next__

普通迭代器的对象

  1. 在Python中,迭代环境是通过调用内置函数iter去尝试寻找__iter__方法实现的,返回一个迭代器对象。
  2. 如果有iter方法,Python就会重复地调用这个迭代器对象的next方法,直到发生StopIteration异常。如果没有这一类方法,才会去寻找__getitem__方法。注意,__iter__只循环一次,而不是循环多次。
class Squares:
    def __init__(self, start, stop):
        self.value = start - 1
        self.stop = stop
    def __iter__(self):
        return self
    def __next__(self):
        if self.value == self.stop:
            raise StopIteration
        self.value += 1
        return self.value ** 2

def main():
    X = Squares(1, 5)
    # I = iter(X) # 手动迭代也有效
    print(next(X))
    # X[1] # TypeError: 'Squares' object is not subscriptable,因为没有实现__getitem__方法
    print([n for n in X])
    print([n for n in X])
    print([n for n in Squares(1,5)])

    print('--------------------------')
    X = Squares(1, 5)
    I1 = iter(X)
    I2 = iter(X)
    print(next(I1))
    print(next(I2))
    print(next(I1))
    print(next(I2))


if __name__ == "__main__":
    main()
'''
输出:
1
[4, 9, 16, 25]
[]
[1, 4, 9, 16, 25]
--------------------------
1
4
9
16
'''

有多个迭代器的对象

  1. 用类编写用户迭代器的时候,可以自己决定是支持单一的还是多个的迭代器对象
class Squares1:
    def __init__(self, start, stop):
        self.value = start - 1
        self.stop = stop
    def __iter__(self):
        return self
    def __next__(self):
        if self.value == self.stop:
            raise StopIteration
        self.value += 1
        return self.value ** 2

class Squares2:
    def __init__(self, start, stop):
        self.value = start - 1
        self.stop = stop
    def __iter__(self):
        return Squares2(self.value + 1, self.stop)
    def __next__(self):
        if self.value == self.stop:
            raise StopIteration
        self.value += 1
        return self.value ** 2

def main():
    X = Squares1(1, 5)
    I1 = iter(X)
    I2 = iter(X)
    print(next(I1))
    print(next(I2))
    print(next(I1))
    print(next(I2))

    print('------------------------------------')

    Y = Squares2(1, 5)
    I1 = iter(Y)
    I2 = iter(Y)
    print(next(I1))
    print(next(I2))
    print(next(I1))
    print(next(I2))


if __name__ == "__main__":
    main()
'''
输出:
1
4
9
16
------------------------------------
1
1
4
4
'''

成员关系:__contains__, __iter__和__getitem__

在Python的迭代领域,类通常把in成员关系运算符实现为一个迭代,使用__iter__方法或者__getitem__方法,也可以编写一个__contains__方法。优先级为__contains__ > __iter__ > __getitem__

class Iters:
    def __init__(self, value):
        self.data = value
    def __getitem__(self, i):
        print('get[%s]: ' % i, end='')
        return self.data[i]
    def __iter__(self):
        print('iter=> ', end='')
        self.ix = 0
        return self
    def __next__(self):
        print('next: ',end='')
        if self.ix == len(self.data): raise StopIteration
        item = self.data[self.ix]
        self.ix += 1
        return item
    def __contains__(self, item):
        print('contains: ', end='')
        return item in self.data

def main():
    X = Iters([1, 2, 3, 4, 5])
    print(3 in X)
    for i in X:
        print(i, end=' | ')
    print()
    print([i ** 2 for i in X])
    print(list(map(bin, X)))

    I = iter(X)
    while True:
        try:
            print(next(I), end=' @ ')
        except StopIteration:
            break

if __name__ == "__main__":
    main()
'''
输出:
contains: True
iter=> next: 1 | next: 2 | next: 3 | next: 4 | next: 5 | next: 
iter=> next: next: next: next: next: next: [1, 4, 9, 16, 25]
iter=> next: next: next: next: next: next: ['0b1', '0b10', '0b11', '0b100', '0b101']
iter=> next: 1 @ next: 2 @ next: 3 @ next: 4 @ next: 5 @ next: 
'''
class Iters:
    def __init__(self, value):
        self.data = value
    def __getitem__(self, i):
        print('get[%s]: ' % i, end='')
        return self.data[i]
    def __iter__(self):
        print('iter=> ', end='')
        self.ix = 0
        return self
    def __next__(self):
        print('next: ',end='')
        if self.ix == len(self.data): raise StopIteration
        item = self.data[self.ix]
        self.ix += 1
        return item
    '''
    def __contains__(self, item):
        print('contains: ', end='')
        return item in self.data
    '''

def main():
    X = Iters([1, 2, 3, 4, 5])
    print(3 in X)
    for i in X:
        print(i, end=' | ')
    print()
    print([i ** 2 for i in X])
    print(list(map(bin, X)))

    I = iter(X)
    while True:
        try:
            print(next(I), end=' @ ')
        except StopIteration:
            break

if __name__ == "__main__":
    main()
'''
输出:
iter=> next: next: next: True
iter=> next: 1 | next: 2 | next: 3 | next: 4 | next: 5 | next: 
iter=> next: next: next: next: next: next: [1, 4, 9, 16, 25]
iter=> next: next: next: next: next: next: ['0b1', '0b10', '0b11', '0b100', '0b101']
iter=> next: 1 @ next: 2 @ next: 3 @ next: 4 @ next: 5 @ next:  
'''
class Iters:
    def __init__(self, value):
        self.data = value
    def __getitem__(self, i):
        print('get[%s]: ' % i, end='')
        return self.data[i]
    def __iter__(self):
        print('iter=> ', end='')
        self.ix = 0
        return self
    def __next__(self):
        print('next: ',end='')
        if self.ix == len(self.data): raise StopIteration
        item = self.data[self.ix]
        self.ix += 1
        return item
    '''
    def __contains__(self, item):
        print('contains: ', end='')
        return item in self.data
    '''

def main():
    X = Iters([1, 2, 3, 4, 5])
    print(3 in X)
    for i in X:
        print(i, end=' | ')
    print()
    print([i ** 2 for i in X])
    print(list(map(bin, X)))

    I = iter(X)
    while True:
        try:
            print(next(I), end=' @ ')
        except StopIteration:
            break

if __name__ == "__main__":
    main()
'''
输出:
iter=> next: next: next: True
iter=> next: 1 | next: 2 | next: 3 | next: 4 | next: 5 | next: 
iter=> next: next: next: next: next: next: [1, 4, 9, 16, 25]
iter=> next: next: next: next: next: next: ['0b1', '0b10', '0b11', '0b100', '0b101']
iter=> next: 1 @ next: 2 @ next: 3 @ next: 4 @ next: 5 @ next:  
'''
class Iters:
    def __init__(self, value):
        self.data = value
    def __getitem__(self, i):
        print('get[%s]: ' % i, end='')
        return self.data[i]
    '''
    def __iter__(self):
        print('iter=> ', end='')
        self.ix = 0
        return self
    def __next__(self):
        print('next: ',end='')
        if self.ix == len(self.data): raise StopIteration
        item = self.data[self.ix]
        self.ix += 1
        return item
    def __contains__(self, item):
        print('contains: ', end='')
        return item in self.data
    '''

def main():
    X = Iters([1, 2, 3, 4, 5])
    print(3 in X)
    for i in X:
        print(i, end=' | ')
    print()
    print([i ** 2 for i in X])
    print(list(map(bin, X)))

    I = iter(X)
    while True:
        try:
            print(next(I), end=' @ ')
        except StopIteration:
            break

if __name__ == "__main__":
    main()
'''
输出:
get[0]: get[1]: get[2]: True
get[0]: 1 | get[1]: 2 | get[2]: 3 | get[3]: 4 | get[4]: 5 | get[5]: 
get[0]: get[1]: get[2]: get[3]: get[4]: get[5]: [1, 4, 9, 16, 25]
get[0]: get[1]: get[2]: get[3]: get[4]: get[5]: ['0b1', '0b10', '0b11', '0b100', '0b101']
get[0]: 1 @ get[1]: 2 @ get[2]: 3 @ get[3]: 4 @ get[4]: 5 @ get[5]: 
'''

属性引用:__getattr__和__setattr__

  1. __getattr__方法拦截属性点号运算,当通过未定义属性名称和实例进行点号运算时,就会用属性名称作为字符串调用这个方法。
  2. __setattr__会拦截所有属性的赋值语句,如果定义了这个方法,self.attr = value会变成self.__setattr__(‘attr’, value)。注意,这导致了对任何self属性做赋值,都会再调用__setattr__,也就是说,在函数中需要对self.__dict__进行赋值运算。使用self.__dict__[‘name’] = x,而不是self.name = x
class accesscontrol:
    def __setattr__(self, key, value):
        if key == 'age':
            self.__dict__[key] = value
        elif key == 'name':
            self.__dict__[key] = value
        else:
            raise AttributeError(key + ' not allowed')
    def __getattr__(self, item):
        if item in self.__dict__:
            return self.__dict__[item]
        elif item == 'age':
            return 0
        elif item == 'name':
            return ''
        else:
            raise AttributeError(item)

def main():
    X = accesscontrol()
    X.age = 100
    X.name = 'Mike'
    print(X.age)
    print(X.name)

    # X.gender = 'mile' # AttributeError: gender not allowed
    X.__dict__['gender'] = 'mile'
    print(X.gender)

if __name__ == "__main__":
    main()
'''
输出:
100
Mike
mile
'''

返回字符串表达式:__str__和__repr__

  1. 打印实例操作会首先尝试__str__和str内置函数。
  2. __repr__用于任何地方,除了当定义了一个__str__之后。如果没有__str__,打印会只用__repr__。
  3. 为了确保一个定制显示在所有环境中都显示而不管容器是什么,建议使用__repr__,而不是__str__。
class Printer:
    def __init__(self, val):
        self.val = val
    def __str__(self):
        return str(self.val)
    #def __repr__(self):
    #    return str(self.val)

def main():
    objs = [Printer(2), Printer(3)]
    for x in objs:
        print(x)

    print(objs)

if __name__ == "__main__":
    main()
'''
输出:
2
3
[<__main__.Printer object at 0x000001F208DB94C0>, <__main__.Printer object at 0x000001F20AA0E370>]
'''
class Printer:
    def __init__(self, val):
        self.val = val
    # def __str__(self):
    #     return str(self.val)
    def __repr__(self):
        return str(self.val)

def main():
    objs = [Printer(2), Printer(3)]
    for x in objs:
        print(x)

    print(objs)

if __name__ == "__main__":
    main()
'''
输出:
2
3
[2, 3]
'''

加法,右侧加法和原处加法:__add__, __radd__和__iadd__

  1. __add__方法支持左侧加法和原处加法。
  2. __radd__支持右侧加法。
  3. __iadd__支持原处加法。注意__iadd__只支持原处加法。
  4. 原处加法的好处是可以直接对实例的属性进行修改,而其他加法则需要返回加法操作的结果
  5. 同样的操作可以推广到乘法,右侧乘法和原处乘法:__mul__, __rmul__和__imul__。
class Add1:
    def __init__(self, val):
        self.val = val
    def __add__(self, other):
        self.val += other
        return self

class Add2:
    def __init__(self, val):
        self.val = val
    def __add__(self, other):
        return self.val + other

class Radd:
    def __init__(self, val):
        self.val = val
    def __radd__(self, other):
        return other + self.val

class Iadd:
    def __init__(self, val):
        self.val = val
    def __iadd__(self, other):
        self.val += other
        return self


def main():
    x = Add1(5)
    x += 1
    print(x.val)
    x = x + 1
    print(x.val)

    x = Add2(5)
    x += 1
    print(x)
    x = x + 1
    print(x)

    x = Radd(5)
    x = 1 + x
    print(x)

    x = Iadd(5)
    x += 1
    print(x.val)


if __name__ == "__main__":
    main()
'''
输出:
6
7
6
7
6
6
'''

比较:__lt__,__gt__与其他方法

  1. 六种比较运算符:
    <, >, <=, >=, ==, !=分别对应__lt__,__gt__, __le__, __ge__, __eq__和__ne__这几种方法。
class Compare:
    data = 'abcde'
    def __lt__(self, other):
        return self.data < other
    def __gt__(self, other):
        return self.data > other
    def __le__(self, other):
        return self.data <= other
    def __ge__(self, other):
        return self.data >= other
    def __eq__(self, other):
        return self.data == other
    def __ne__(self, other):
        return self.data != other

def main():
    X = Compare()

    print('abcde' == X)
    print('xyz' == X)
    print('abcde' != X)
    print('xyz' != X)
    print('abc' < X)
    print('xyz' < X)
    print('abc' > X)
    print('xyz' > X)
    print('abc' <= X)
    print('xyz' <= X)
    print('abc' >= X)
    print('xyz' >= X)

if __name__ == "__main__":
    main()
'''
输出:
True
False
False
True
True
False
False
True
True
False
False
True
'''

布尔测试:__bool__和__len__

  1. 在获取实例的真假信息时,Python 3.0及之后的版本会先尝试__bool__,再尝试__len__,Python 2.6及之后的版本会先尝试__len__,再尝试__bool__。
  2. __len__还可以配合len(X)函数使用,一般用来返回实例对象中字符串,列表,元组,字典等数据的长度。
class Bool:
    data = 'abcde'
    def __bool__(self):
        if self.data:
            return True
        return False

class Len1:
    data = 'abcde'
    def __len__(self):
        if self.data:
            return True
        return False

class Len2:
    data = 'abcde'
    def __len__(self):
        return len(self.data)

def main():
    X = Bool()
    print(bool(X))
    X = Len1()
    print(bool(X))
    X = Len2()
    print(len(X))

if __name__ == "__main__":
    main()
'''
输出:
True
True
5
'''

对象析构函数:__del__

  1. 每当实例空间被收回时,__del__会自动执行,称为析构函数。
  2. 析构函数不常用,因为Python在实例收回时,会自动地收回该实例所拥有的所有空间,对于空间管理来说不需要析构函数。并且,在大的项目中,往往无法轻易地预测实例何时收回。
class Life:
    def __init__(self, name='unknow'):
        print('Hello', name)
        self.name = name
    def __del__(self):
        print('Goodbye', self.name)

def main():
    X = Life('John')
    del(X)

if __name__ == "__main__":
    main()
'''
输出:
Hello John
Goodbye John
'''

Call表达式:__call__

  1. 如果有__call__定义,Python就会为实例应用函数调用表达式运行__call__方法。这使得类实例的用法贴近函数
  2. __call__支持所有的函数参数传递方式,意味着带有一个__call__的类和实例,支持与常规函数和方法完全相同的参数语法和语义。
class Prod:
    def __init__(self, value):
        self.value = value
    def __call__(self, *args):
        return self.value * sum(args)

def main():
    X = Prod(2)
    print(X(3, 4, 5, 6))

if __name__ == "__main__":
    main()
'''
输出:
36
'''

以上,欢迎各位读者朋友提出意见或建议。