Python面向对象运算符重载

peterlee 2017-05-04

运算符重载的概念如下:

  1. 运算符重载让类拦截常规的Python运算;

  2. 类可重载所有Python表达式运算符;

  3. 类也可重载打印、函数调用、属性点号运算等内置运算;

  4. 重载是类实例的行为想内置类型;

  5. 重载是通过提供特殊名称的类方法来实现的;

常见的运算符重载方法

方法重载调用
__init__构造函数对象建立:X = Class(args)
__del__解析函数X对象收回
__add__运算符+如果没有__iadd__,X+Y,X+=Y
__or__运算符或如果没有__ior__
__repr__,__str__打印、转换print(X)、repr(X)、str(X)
__call__函数调用X(args, *kwargs)
__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:k]=sequence
__delitem__索引和分片删除del X[key], del X[i:j]
__len__长度len(X),如果没有__bool__,真值测试
__bool__布尔测试bool(X),真测试
__lt__,__gt__,__le__,__ge__,__eq__,__ne__特定的比较XY…,x>
__radd__右侧加法Other + X
__iadd__增强的加法X += Y
__iter__,__next__迭代环境I=iter(X),next(I)
__contains__成员关系测试item in X(任何可迭代对象)
__index__整数值hex(X),bin(X),oct(X),o[X],O[X:]
__enter__,__exit__环境管理器with obj as var:
__get__,__set__,__delete__描述符属性X.attr,X.attr=Value,del X.attr
__new__创建在__init__之前创建对象

所有重载方法的名称前后都有两个下划线字符,以便把同类中定义的变量名区别开来。

构造函数和表达式:__init__和__sub__

 

  1. >>> class Number:

  2. ...   def __init__(self, start):

  3. ...     self.data = start

  4. ...   def __sub__(self, other):

  5. ...     return Number(self.data - other)

  6. ...

  7. >>> X = Number(5)

  8. >>> Y = X - 2

  9. >>> Y

  10. <__main__.Number object at 0x10224d550>

  11. >>> Y.data

  12. 3

索引和分片: __getitem__和__setitem__

基本索引

  1. >>> class Index:

  2. ...     def __getitem__(self, item):

  3. ...         return item ** 2

  4. ...

  5. >>>

  6. >>> for i in range(5):

  7. ...     I = Index()

  8. ...     print(I[i], end=' ')

  9. ...

  10. 0 1 4 9 16

切片索引

  1. >>> class Index:

  2. ...   data = [5, 6, 7, 8, 9]

  3. ...   def __getitem__(self, item):

  4. ...     print('getitem: ', item)

  5. ...     return self.data[item]

  6. ...   def __setitem__(self, key, value):

  7. ...     self.data[key] = value

  8. ...

  9. >>> X = Index()

  10. >>> print(X[1:4])

  11. getitem:  slice(1, 4, None)

  12. [6, 7, 8]

  13. >>> X[1:4] = (1, 1, 1)

  14. >>> print(X[1:4])

  15. getitem:  slice(1, 4, None)

  16. [1, 1, 1]

索引迭代:__getitem__

如果重载了这个方法,for循环每次循环时都会调用类的getitem方法;

  1. >>> class stepper:

  2. ...     def __getitem__(self, item):

  3. ...         return self.data[item].upper()

  4. ...

  5. >>>

  6. >>> X = stepper()

  7. >>> X.data = 'ansheng'

  8. >>> for item in X:

  9. ...     print(item)

  10. ...

  11. A

  12. N

  13. S

  14. H

  15. E

  16. N

  17. G

迭代器对象:__iter__和__next__

  1. >>> class Squares:

  2. ...   def __init__(self, start, stop):

  3. ...         self.value = start - 1

  4. ...         self.stop = stop

  5. ...   def __iter__(self):

  6. ...         return self

  7. ...   def __next__(self):

  8. ...         if self.value == self.stop:

  9. ...             raise StopIteration

  10. ...         self.value += 1

  11. ...         return self.value ** 2

  12. ...

  13. >>> for i in Squares(1, 5):

  14. ...   print(i)

  15. ...

  16. 1

  17. 4

  18. 9

  19. 16

  20. 25

成员关系:__contains__、__iter__和__getitem__

  1. class Iters:

  2.    def __init__(self, value):

  3.        self.data = value

  4.    def __getitem__(self, item):

  5.        print('get[%s]' % item, end='')

  6.        return self.data[item]

  7.    def __iter__(self):

  8.        print('iter>==', end='')

  9.        self.ix = 0

  10.        return self

  11.    def __next__(self):

  12.        print('next:', end='')

  13.        if self.ix == len(self.data): raise StopIteration

  14.        item = self.data[self.ix]

  15.        self.ix += 1

  16.        return item

  17.    def __contains__(self, item):

  18.        print('contains: ', end=' ')

  19.        return item in self.data

  20. X = Iters([1, 2, 3, 4, 5])

  21. print(3 in X)

  22. for i in X:

  23.    print(i, end='|')

  24. print([i ** 2 for i in X])

  25. print(list(map(bin, X)))

  26. I = iter(X)

  27. while True:

  28.    try:

  29.        print(next(I), end=' @')

  30.    except StopIteration as e:

  31.        break

属性引用:__getattr__和__setattr__

当通过未定义的属性名称和实例通过点号进行访问时,就会用属性名称作为字符串调用这个方法,但如果类使用了继承,并且在超类中可以找到这个属性,那么就不会触发。

  1. >>> class empty:

  2. ...     def __getattr__(self, item):

  3. ...         if item == 'age':

  4. ...             return 40

  5. ...         else:

  6. ...             raise AttributeError(item)

  7. ...

  8. >>>

  9. >>> x = empty()

  10. >>> print(x.age)

  11. 40

  12. >>> print(x.name)

  13. Traceback (most recent call last):

  14.  File "<stdin>", line 1, in <module>

  15.  File "<stdin>", line 6, in __getattr__

  16. AttributeError: name

 

  1. >>> class accesscontrol:

  2. ...     def __setattr__(self, key, value):

  3. ...         if key == 'age':

  4. ...             self.__dict__[key] = value

  5. ...         else:

  6. ...             raise AttributeError(key + ' not allowed')

  7. ...

  8. >>>

  9. >>> x = accesscontrol()

  10. >>> x.age = 40

  11. >>> print(x.age)

  12. 40

  13. >>> x.name = 'Hello'

  14. Traceback (most recent call last):

  15.  File "<stdin>", line 1, in <module>

  16.  File "<stdin>", line 6, in __setattr__

  17. AttributeError: name not allowed

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

__repr__和__str__都是为了更友好的显示,具体来说,如果在终端下print(Class)则会调用__repr__,非终端下会调用__str__方法,且这两个方法只能返回字符串;

  1. class adder:

  2.    def __init__(self, value=0):

  3.        self.data = value

  4.    def __add__(self, other):

  5.        self.data += other

  6.    def __repr__(self):

  7.        return 'addrepr(%s)' % self.data

  8.    def __str__(self):

  9.        return 'N: %s' % self.data

  10. x = adder(2)

  11. x + 1

  12. print(x)

  13. print((str(x), repr(x)))

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

只有当+右侧的对象是类实例,而左边对象不是类实例的时候,Python才会调用__radd__

  1. class Commuter:

  2.    def __init__(self, val):

  3.        self.val = val

  4.    def __add__(self, other):

  5.        print('add', self.val, other)

  6.        return self.val + other

  7.    def __radd__(self, other):

  8.        print('radd', self.val, other)

  9.        return other + self.val

  10. x = Commuter(88)

  11. y = Commuter(99)

  12. print(x + 1)

  13. print('')

  14. print(1 + y)

  15. print('')

  16. print(x + y)

使用__iadd__进行原处加法

 

  1. class Number:

  2.    def __init__(self, val):

  3.        self.val = val

  4.    def __iadd__(self, other):

  5.        self.val += other

  6.        return self

  7. x = Number(5)

  8. x += 1

  9. x += 1

  10. print(x.val)

  11. class Number:

  12.    def __init__(self, val):

  13.        self.val = val

  14.    def __add__(self, other):

  15.        return Number(self.val + other)

  16. x = Number(5)

  17. x += 1

  18. x += 1

  19. print(x.val)

Call表达式:__call__

当调用类实例时执行__call__方法

  1. class Callee:

  2.    def __call__(self, *args, **kwargs):

  3.        print('Callee:', args, kwargs)

  4. C = Callee()

  5. C(1, 2, 3)

  6. C(1, 2, 3, x=1, y=2, z=3)

  7. class Prod:

  8.    def __init__(self, value):

  9.        self.value = value

  10.    def __call__(self, other):

  11.        return self.value * other

  12. x = Prod(3)

  13. print(x(3))

  14. print(x(4))

比较:__lt__,__gt__和其他方法

类可以定义方法来捕获所有的6种比较运算符:<、>、<=、>=、==和!=

  1. class C:

  2.    data = 'spam'

  3.    def __gt__(self, other):

  4.        return self.data > other

  5.    def __lt__(self, other):

  6.        return self.data < other

  7. x = C()

  8. print(x > 'han')

  9. print(x < 'han')

布尔值测试:bool和len

  1. class Truth:

  2.    def __bool__(self):

  3.        return True

  4. X = Truth()

  5. if X: print('yes')

  6. class Truth:

  7.    def __bool__(self):

  8.        return False

  9. X = Truth()

  10. print(bool(X))

如果没有这个方法,Python退而求其次的求长度,因为一个非空对象看作是真:

  1. >>> class Truth:

  2. ...   def __len__(self): return 0

  3. ...

  4. >>> X = Truth()

  5. >>> if not X: print('no')

  6. ...

  7. no

     

     

如果两个方法都有,__bool__会胜过__len__:

 

  1. >>> class Truth:

  2. ...   def __bool__(self): return True

  3. ...   def __len__(self): return 0

  4. ...

  5. >>> X = Truth()

  6. >>> bool(X)

  7. True

如果两个方法都没有定义,对象毫无疑义的看作为真:

  1. >>> class Truth: pass

  2. ...

  3. >>> bool(Truth)

  4. True

对象解析函数:__del__

每当实例产生时,就会调用init构造函数,每当实例空间被收回时,它的对立面__del__,也就是解析函数,就会自动执行;

  1. class Life:

  2.    def __init__(self, name='unknown'):

  3.        print('Hello, ', name)

  4.        self.name = name

  5.    def __del__(self):

  6.        print('Goodbye', self.name)

  7. brian = Life('Brian')

  8. brian = 'loretta'

相关推荐