类中有一些可自定义的特殊方法,它们中的一些有预定义的默认行为,而其它一些则没有,留到需要的时候去实现。这些特殊方法是Python中用来扩充类的强有力的方式。它们可以实现模拟标准类型和重载操作符等。比如__init__()和__del__()就分别充当了构造器和析够器的功能。
这些特殊这些方法都是以双下划线(__)开始及结尾的。下表进行了总结:
基本定制型
C.__init__(self[, arg1, ...])
构造器(带一些可选的参数)
C.__new__(self[, arg1, ...])
构造器(带一些可选的参数);通常用在设置不变数据类型的子类。
C.__del__(self)
解构器
C.__str__(self)
可打印的字符输出;内建str()及print 语句
C.__repr__(self)
运行时的字符串输出;内建repr() 和``操作符
C.__unicode__(self)
Unicode 字符串输出;内建unicode()
C.__call__(self, *args)
表示可调用的实例
C.__nonzero__(self)
为object 定义False 值;内建bool() (从2.2 版开始)
C.__len__(self)
“长度”(可用于类);内建len()
对象(值)比较
C.__cmp__(self, obj)
对象比较;内建cmp()
C.__lt__(self, obj)
C.__le__(self, obj)
小于/小于或等于;对应
C.__gt__(self, obj)
C.__ge__(self, obj)
大于/大于或等于;对应>及>=操作符
C.__eq__(self, obj)
C.__ne__(self, obj)
等于/不等于;对应==,!=及<>操作符
属性
C.__getattr__(self, attr)
获取属性;内建getattr();仅当属性没有找到时调用
C.__setattr__(self, attr, val)
设置属性
C.__delattr__(self, attr)
删除属性
C.__getattribute__(self, attr)
获取属性;内建getattr();总是被调用
C.__get__(self, attr)
(描述符)获取属性
C.__set__(self, attr, val)
(描述符)设置属性
C.__delete__(self, attr)
(描述符)删除属性
数值类型:二进制操作符
C.__*add__(self, obj)
加;+操作符
C.__*sub__(self, obj)
减;-操作符
C.__*mul__(self, obj)
乘;*操作符
C.__*div__(self, obj)
除;/操作符
C.__*truediv__(self, obj)
True 除;/操作符
C.__*floordiv__(self, obj)
Floor 除;//操作符
C.__*mod__(self, obj)
取模/取余;%操作符
C.__*divmod__(self, obj)
除和取模;内建divmod()
C.__*pow__(self, obj[, mod])
乘幂;内建pow();**操作符
C.__*lshift__(self, obj)
左移位;<
数值类型:二进制操作符
C.__*rshift__(self, obj)
右移;>>操作符
C.__*and__(self, obj)
按位与;&操作符
C.__*or__(self, obj)
按位或;|操作符
C.__*xor__(self, obj)
按位与或;^操作符
数值类型:一元操作符
C.__neg__(self)
一元负
C.__pos__(self)
一元正
C.__abs__(self)
绝对值;内建abs()
C.__invert__(self)
按位求反;~操作符
数值类型:数值转换
C.__complex__(self, com)
转为complex(复数);内建complex()
C.__int__(self)
转为int;内建int()
C.__long__(self)
转 .long;内建long()
C.__float__(self)
转为float;内建float()
数值类型:基本表示法(String)
C.__oct__(self)
八进制表示;内建oct()
C.__hex__(self)
十六进制表示;内建hex()
数值类型:数值压缩
C.__coerce__(self, num)
压缩成同样的数值类型;内建coerce()
C.__index__(self)
在有必要时,压缩可选的数值类型为整型(比如:用于切片索引等等)
序列类型
C.__len__(self)
序列中项的数目
C.__getitem__(self, ind)
得到单个序列元素
C.__setitem__(self, ind,val)
设置单个序列元素
C.__delitem__(self, ind)
删除单个序列元素
C.__getslice__(self, ind1,ind2)
得到序列片断
C.__setslice__(self, i1, i2,val)
设置序列片断
C.__delslice__(self, ind1,ind2)
删除序列片断
C.__contains__(self, val)
测试序列成员;内建in 关键字
C.__*add__(self,obj)
串连;+操作符
C.__*mul__(self,obj)
重复;*操作符
C.__iter__(self)
创建迭代类;内建iter()
映射类型
C.__len__(self)
mapping 中的项的数目
C.__hash__(self)
散列(hash)函数值
C.__getitem__(self,key)
得到给定键(key)的值
C.__setitem__(self,key,val)
设置给定键(key)的值
C.__delitem__(self,key)
删除给定键(key)的值
C.__missing__(self,key)
给定键如果不存在字典中,则提供一个默认值
一:简单定制
classRoundFloatManual(object):def __init__(self, val):assert isinstance(val, float), "Value must be a float!"self.value= round(val, 2)>>> rfm =RoundFloatManual(42)
Traceback (mostrecent call last):
File"", line 1, in?
File"roundFloat2.py", line 5, in __init__assertisinstance(val, float), \ AssertionError: Value must be a float!>>> rfm =RoundFloatManual(4.2)>>>rfm
>>> printrfm
它因输入非法而异常,但如果输入正确时,就没有任何输出了。在解释器中,我们得到一些信息,却不是我们想要的。print(使用str())和真正的字符串对象表示(使用repr())都没能显示更多有关我们对象的信息。这就需要实现__str__()和__repr__()二者之一,或者两者都实现。加入下面的方法:
def __str__(self):return str(self.value)
现在我们得到下面的:
>>> rfm = RoundFloatManual(5.590464)>>>rfm
>>> printrfm5.59
>>> rfm = RoundFloatManual(5.5964)>>> printrfm5.6
但是在解释器中转储(dump)对象时,仍然显示的是默认对象符号,要修复它,只需要覆盖__repr__()。可以让__repr__()和__str__()具有相同的代码,但最好的方案是:__repr__ = __str__
在带参数5.5964的第二个例子中,我们看到它舍入值刚好为5.6,但我们还是想显示带两位小数的数。可以这样修改:
def __str__(self):return '%.2f' % self.value
这里就同时具备str()和repr()的输出了:
>>> rfm =RoundFloatManual(5.5964)>>>rfm5.60
>>>printrfm5.60
所有代码如下:
classRoundFloatManual(object):def __init__(self,val):assert isinstance(val, float), "Valuemust be a float!"self.value= round(val, 2)def __str__(self):return '%.2f' %self.value__repr__ = __str__
二:数值定制
定义一个Time60,其中,将整数的小时和分钟作为输入传给构造器:
classTime60(object):def __init__(self, hr, min):
self.hr=hr
self.min= min
1:显示
需要在显示实例的时候,得到一个有意义的输出,那么就要覆盖__str__()(如果有必要的话,__repr__()也要覆盖):
def __str__(self):return '%d:%d' % (self.hr, self.min)
比如:
>>> mon =Time60(10, 30)>>> tue =Time60(11, 15)>>>
>>> printmon, tue10:30 11:15
2:加法
Python中的重载操作符很简单。像加号(+),只需要重载__add__()方法,如果合适,还可以用__radd__()及__iadd__()。注意,实现__add__()的时候,必须认识到它返回另一个Time60对象,而不修改原mon或tue:
def __add__(self, other):return self.__class__(self.hr + other.hr, self.min + other.min)
在类中,一般不直接调用类名,而是使用self 的__class__属性,即实例化self 的那个类,并调用它。调用self.__class__()与调用Time60()是一回事。但self.__class__()的方式更好。
>>> mon = Time60(10, 30)>>> tue = Time60(11, 15)>>> mon +tue
>>> print mon +tue21:45
如果没有定义相对应的特殊方法,但是却使用了该方法对应的运算,则会引起一个TypeError异常:
>>> mon -tue
Traceback (mostrecent call last): File"", line 1, in?
TypeError:unsupported operand type(s)for -: 'Time60' and 'Time60'
3:原位加法
__iadd__(),是用来支持像mon += tue 这样的操作符,并把正确的结果赋给mon。重载一个__i*__()方法的唯一秘密是它必须返回self:
def __iadd__(self, other):
self.hr+=other.hr
self.min+=other.minreturn self
下面是结果输出:
>>> mon = Time60(10,30)>>> tue = Time60(11,15)>>>mon10:30
>>>id(mon)401872
>>> mon +=tue>>>id(mon)401872
>>>mon21:45
下面是Time60的类的完全定义:
classTime60(object):'Time60 - track hours and minutes'
def __init__(self,hr, min):'Time60 constructor - takes hours andminutes'self.hr=hr
self.min=mindef __str__(self):'Time60 - string representation'
return '%d:%d' %(self.hr, self.min)__repr__ = __str__
def __add__(self, other):'Time60 - overloading the additionoperator'
return self.__class__(self.hr + other.hr,self.min +other.min)def __iadd__(self,other):'Time60 - overloading in-place addition'self.hr+=other.hr
self.min+=other.minreturn self
4:升华
在这个类中,还有很多需要优化和改良的地方。首先看下面的例子:
>>> wed =Time60(12, 5)>>>wed12:5 #正确的显示应该是:“12:05”
>>> thu =Time60(10, 30)>>> fri =Time60(8, 45)>>> thu +fri18:75 #正确的显示应该是:19:15
可以做出如下修改:
def __str__(self):return '%02d:%02d'%(self.hr, self.min)__repr__ = __str__
def __add__(self, othertime):
tmin= self.min +othertime.min
thr= self.hr +othertime.hrreturn self.__class__(thr + tmin/60, tmin%60)def __iadd__(self, othertime):
self.min+=othertime.min
self.hr+=othertime.hr
self.hr+= self.min/60self.min%= 60
return self
三:迭代器
迭代器对象本身需要支持以下两种方法,它们组合在一起形成迭代器协议:
iterator.__iter__() 返回迭代器对象本身。
iterator.next() 从容器中返回下一个元素。
实现了__iter__()和next()方法的类就是一个迭代器。自定义迭代器的例子如下:
RandSeq(Random Sequence),传入一个初始序列,__init__()方法执行前述的赋值操作。__iter__()仅返回self,这就是如何将一个对象声明为迭代器的方式,最后,调用next()来得到迭代器中连续的值。这个迭代器唯一的亮点是它没有终点。代码如下:
classRandSeq(object):def __init__(self, seq):
self.data=seqdef __iter__(self):returnselfdefnext(self):return choice(self.data)
运行它,将会看到下面的输出:
>>> from randseq importRandSeq>>> for eachItem in RandSeq(('rock', 'paper', 'scissors')):
...printeachItem
...
scissors
scissors
rock
paper
paper
scissors
......
四:多类型定制
现在创建另一个新类,NumStr,由一个数字-字符对组成,记为n和s,数值类型使用整型(integer)。用[n::s]来表示它,这两个数据元素构成一个整体。NumStr有下面的特征:
初始化: 类应当对数字和字符串进行初始化;如果其中一个(或两)没有初始化,则使用0和空字符串,也就是, n=0 且s=''作为默认。
加法: 定义加法操作符,功能是把数字加起来,把字符连在一起;比如,NumStr1=[n1::s1]且NumStr2=[n2::s2]。则NumStr1+NumStr2 表示[n1+n2::s1+s2],其中,+代表数字相加及字符相连接。
乘法: 类似的, 定义乘法操作符的功能为, 数字相乘,字符累积相连, 也就是,NumStr1*NumStr2=[n1*n::s1*n]。
False 值:当数字的数值为 0 且字符串为空时,也就是当NumStr=[0::'']时,这个实体即有一个false值。
比较: 比较一对NumStr对象,比如,[n1::s1] vs. [n2::s2],有九种不同的组合。对数字和字符串,按照标准的数值和字典顺序的进行比较。
如果obj1< obj2,则cmp(obj1, obj2)的返回值是一个小于0 的整数, 当obj1 > obj2 时,比较的返回值大于0, 当两个对象有相同的值时, 比较的返回值等于0。
我们的类的解决方案是把这些值相加,然后返回结果。为了能够正确的比较对象,我们需要让__cmp__()在 (n1>n2) 且 (s1>s2)时,返回 1,在(n1s2),或相反),返回0. 反之亦然。代码如下:
classNumStr(object):def __init__(self, num=0, string=''):
self.__num =num
self.__string =stringdef __str__(self):return '[%d :: %r]' % (self.__num, self.__string)__repr__ = __str__
def __add__(self, other):ifisinstance(other, NumStr):return self.__class__(self.__num + other.__num, self.__string + other.__string)else:raise TypeError, 'Illegal argument type for built-in operation'
def __mul__(self, num):ifisinstance(num, int):return self.__class__(self.__num * num, self.__string *num)else:raise TypeError, 'Illegal argument type for built-inoperation'
def __nonzero__(self):return self.__num or len(self.__string)def __norm_cval(self, cmpres):returncmp(cmpres, 0)def __cmp__(self, other):return self.__norm_cval(cmp(self.__num, other.__num))+\
self.__norm_cval(cmp(self.__string,other.__string))
执行一些例子:
>>> a =NumStr(3, 'foo')>>> b =NumStr(3, 'goo')>>> c =NumStr(2, 'foo')>>> d =NumStr()>>> e =NumStr(string='boo')>>> f =NumStr(1)>>>a
[3 :: 'foo']>>>b
[3 :: 'goo']>>>c
[2 :: 'foo']>>>d
[0 ::'']>>>e
[0 ::'boo']>>>f
[1 :: '']>>> a
True>>> b
False>>> a ==a
True>>> b * 2[6 :: 'googoo']>>> a * 3[9 :: 'foofoofoo']>>> b +e
[3 :: 'gooboo']>>> e +b
[3 :: 'boogoo']>>> if d: 'not false'...>>> if e: 'not false'...'not false'
>>>cmp(a, b)-1
>>>cmp(a, c)1
>>>cmp(a, a)
0
如果在__str__中使用“%s”,将导致字符串没有引号:
return '[%d :: %s]' % (self.__num, self.__string)>>> printa
[3 :: foo]
第二个元素是一个字符串,如果用户看到由引号标记的字符串时,会更加直观。要做到这点,使用“repr()”表示法对代码进行转换,把“%s”替换成“%r”。这相当于调用repr()或者使用单反引号来给出字符串的可求值版本--可求值版本的确要有引号:
>>> printa
[3 :: 'foo']
__norm_cval()不是一个特殊方法。它是一个帮助我们重载__cmp__()的助手函数:唯一的目的就是把cmp()返回的正值转为1,负值转为-1。cmp()基于比较的结果,通常返回任意的正数或负数(或0),但为了我们的目的,需要严格规定返回值为-1,0 和1。
对整数调用cmp()及与 0 比较,结果即是我们所需要的,相当于如下代码片断:
def __norm_cval(self, cmpres):if cmpres<0:return -1
elif cmpres>0:return 1
else:return 0
两个相似对象的实际比较是比较数字,比较字符串,然后返回这两个比较结果的和。