标签:
运算符重载=======================================================================
构造函数和表达式:__init__和__sub__
看一个简单的重载例子。下述Number类提供了一个方法来拦截实例的构造函数(__init__),此外还有一个方法捕捉减法表达式(__sub__)。这种特殊的方法是钩子,可与内置运算绑定。
>>> class Number: def __init__(self,start): self.data = start def __sub__(self,other): return Number(self.data - other) >>> X = Number(5) >>> X.data 5 >>> Y = X - 2 >>> Y.data 3=======================================================================
常见的运算符重载方法
在类中,对内置对象(例如,整数和列表)所能做的事,几乎都有相应的特殊名称的重载方法。下表列出其中一些最常用的重载方法。
方法 | 重载 | 调用 |
__init__ | 构造函数 | 对象建立:X = Class(args) |
__del__ | 析构函数 | X对象收回 |
__add__ | 运算符+ | 如果没有_iadd_,X+Y,X+=Y |
__or__ | 运算符|(位OR) | 如果没有_ior_,X|Y,X|=Y |
__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__ | 索引和分片删除 | del X[key],del X[i:j] |
__len__ | 长度 | len(X),如果没有__bool__,真值测试 |
__bool__ | 布尔测试 | bool(X),真测试 |
__lt__,__gt__, | 特定的比较 | X < Y,X > Y |
__le__,__ge__, | X<=Y,X >= Y | |
__eq__,__ne__ | X == Y,X != Y | |
__radd__ | 右侧加法 | Other+X |
__iadd__ | 实地(增强的)加法 | X += Y (or else __add__) |
__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__ | 描述符属性 | X.attr,X.attr = value,del X.attr |
__new__ | 创建 | 在__init__之前创建对象 |
所有重载方法的名称前后都有两个下划线字符,以便把同类中定义的变量名区别开来。
运算符重载方法都是可选的——如果没有编写或继承一个方法,你的类直接不支持这些运算,并且试图使用它们会引发一个异常。
多数重载方法只用在需要对象行为表现的就像内置类型一样的高级程序中。然而,__init__构造函数常出现在绝大多数类中。
下面介绍一些运算符重载的用法示例。
=======================================================================
索引和分片:__getitem__和__setitem__
如果在类中定义了的话,则对于实例的索引运算,会自动调用__getitem__,把X作为第一个参数传递,并且方括号内的索引值传给第二个参数。例如,下面的类将返回索引值的平方。
>>> class Indexer: def __getitem__(self,index): return index**2 >>> X = Indexer() >>> X[2] 4 >>> for i in range(5): print(X[i],end = ' ') 0 1 4 9 16------------------------------------------------------------------------------------------------------------------
拦截分片
有趣的是,除了索引,对于分片表达式也调用__getitem__。正式地讲,内置类型以同样的方式处理分片。例如,下面是一个内置列表上工作的分片,使用了上边界和下边界以及一个stride。(分片的知识可以回顾这里)
>>> L = [5,6,7,8,9] >>> L[2:4] [7, 8] >>> L[1:] [6, 7, 8, 9] >>> L[:-1] [5, 6, 7, 8] >>> L[::2] [5, 7, 9]实际上,分片边界绑定到了一个分片对象中(即slice对象),并且传递给索引的列表实现。实际上,我们总是可以手动地传递一个分片对象——分片语法主要是用一个分片对象进行索引的语法糖:
>>> L[slice(2,4)] [7, 8] >>> L[slice(1,None)] [6, 7, 8, 9] >>> L[slice(None,-1)] [5, 6, 7, 8] >>> L[slice(None,None,2)] [5, 7, 9]__getitem__既针对基本索引调用,又针对分片调用。我们前面的类没有处理分片,但是,如下类将会处理分片。当针对基本索引调用时,参数像前面一样是一个整数。
>>> class Indexer: data = [5,6,7,8,9] def __getitem__(self,index): print('getitem:',index) return self.data[index] >>> X = Indexer() >>> X[0] getitem: 0 5 >>> X[1] getitem: 1 6 >>> X[-1] getitem: -1 9然而,当针对分片调用时,方法接收一个分片对象,它将一个新的索引表达式直接传递给嵌套的列表索引:
>>> X[2:4] getitem: slice(2, 4, None) [7, 8] >>> X[1:] getitem: slice(1, None, None) [6, 7, 8, 9] >>> X[:-1] getitem: slice(None, -1, None) [5, 6, 7, 8] >>> X[::2] getitem: slice(None, None, 2) [5, 7, 9]如果使用的话,__setitem__索引赋值方法类似地拦截索引和分片赋值——它为后者接收了一个分片对象,它可能以同样的方式传递到另一个索引赋值中:
def __setitem__(self,index,value): ... self.data[index] = value------------------------------------------------------------------------------------------------------------------
>>> class stepper: def __getitem__(self,i): return self.data[i] >>> X = stepper() >>> X.data = 'Spam' >>> X[1] 'p' >>> for item in X: print(item,end = ' ') S p a m我们知道,任何支持for循环的类也会自动支持Python所有的迭代环境,比如成员关系测试in、列表解析、内置函数map、列表和元祖赋值运算以及类型构造方法等,如下示例:
>>> 'p' in X True >>> [c for c in X] ['S', 'p', 'a', 'm'] >>> list(map(str.upper,X)) ['S', 'P', 'A', 'M'] >>> a,b,c,d = X >>> a,c,d ('S', 'a', 'm') >>> list(X),tuple(X),''.join(X) (['S', 'p', 'a', 'm'], ('S', 'p', 'a', 'm'), 'Spam') >>> X <__main__.stepper object at 0x0376A6B0>在实际应用中,这个技巧可以用于建立提供序列接口的对象,并新增逻辑到内置的序列的类型运算。
迭代器对象:__iter__和__next__
尽管上述介绍的__getitem__技术有效,但它只是迭代的一种退而求其次的方法。一般Python中的迭代环境都会先尝试__iter__方法,再尝试__getitem__方法。
迭代环境是通过调用内置函数iter去尝试寻找__iter__方法来实现的,而这种方法应该返回一个迭代器对象。如果已经提供了,Python就会重复调用这个迭代器对象的next方法,直到发生StopIteration异常。如果没有找到这类__iter__方法,Python会改用__getitem__机制,就像之前那样通过偏移量重复索引,直到引发IndexError异常。
------------------------------------------------------------------------------------------------------------------
用户定义的迭代器
在__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 >>> for i in Squares(1,5): print(i,end = ' ') 1 4 9 16 25在这个例子中,迭代器对象就是实例self,因为next方法是这个类的一部分。
>>> X = Squares(1,5) >>> [n for n in X] [1, 4, 9, 16, 25] >>> [n for n in X] [] >>> [n for n in Squares(1,5)] [1, 4, 9, 16, 25]------------------------------------------------------------------------------------------------------------------
有多个迭代器的对象
之前提到过迭代器对象可以定义成一个独立的类,有其自己的状态信息,从而能够支持相同数据的多个迭代。看下例:
>>> s = 'ace' >>> for x in s: for y in s: print(x+y,end=' ') aa ac ae ca cc ce ea ec ee在这里,外层循环调用iter从字符串中取得迭代器,而每个嵌套的循环也做相同的事来获得独立的迭代器。
>>> class SkipIterator: def __init__(self,wrapped): self.wrapped = wrapped self.offset = 0 def __next__(self): if self.offset >= len(self.wrapped): raise StopIteration else: item = self.wrapped[self.offset] self.offset += 2 return item >>> class SkipObject: def __init__(self,wrapped): self.wrapped = wrapped def __iter__(self): return SkipIterator(self.wrapped) >>> alpha = 'abcdef' >>> skipper = SkipObject(alpha) >>> I = iter(skipper) >>> print(next(I),next(I),next(I)) a c e >>> for x in skipper: for y in skipper: print(x+y,end = ' ') aa ac ae ca cc ce ea ec ee这个例子工作起来就像是对内置字符串进行嵌套循环一样,因为每个循环都会获得独立的迭代器对象来记录自己的状信息,所以每个激活状态下的循环都有自己在字符串中的位置。
=======================================================================
成员关系:__contains__、__iter__和__getitem__
在迭代领域,类通常把in成员关系运算符实现为一个迭代,使用__iter__方法或__getitem__方法。要支持更加特定的成员关系,类可能编写一个__contains__方法——当出现的时候,该方法优先于__iter__方法,__iter__方法优先于__getitem__方法。
__contains__方法应该把成员关系定义为对一个映射应用键,以及用于序列的搜索。
考虑下面的类,它编写了3个方法和测试成员关系以及应用于一个实例的各种迭代环境。调用的时候,其方法会打印出跟踪消息。
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,x): print('contains:',end='') return x in self.data if __name__ == '__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 1: try: print(next(I),end = ' @ ') except StopIteration: break这段脚本运行的时候,其输出如下所示:
contains:True iter=> next:1next:2next:3next:4next:5next: 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:可以看到,特定的__contains__拦截成员关系,通用的__iter__捕获其他的迭代环境以致__next__被重复调用,而__getitem__不会被调用。
iter=> next:next:next:True iter=> next:1next:2next:3next:4next:5next: 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:但是,如果__contains__和__iter__都注释掉的话,其输出如下——索引__getitem__替代方法会被调用,针对成员关系和其他迭代环境使用连续较高的索引:
get[0]:get[1]:get[2]:True get[0]:1get[1]:2get[2]:3get[3]:4get[4]:5get[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]:正如我们看到的,__getitem__方法更加通用。
标签:
原文地址:http://blog.csdn.net/gavin_john/article/details/50717695