首页 > 代码库 > Python的对象协议
Python的对象协议
Python是一门动态语言,Duck Typing概念遍布其中,所以其中的Concept并不是以类型的约束为载体,而是使用称作为协议的概念。那什么是Duck Typing呢?
Duck Typing是鸭子类型,在动态语言中用的较多,是动态类型语言设计的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口决定,而是由当前方法和属性的集合决定。说白了就是并不关心对象是什么类型,只关心行为。
这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试(见下面的“历史”章节),“鸭子测试”可以这样表述:
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。
看下面例子:
#coding=utf-8 class Duck: def quack(self): print u"嘎嘎嘎!" def feathers(self): print u"这是一只Duck。" class Person: def quack(self): print u"hello,world!" def feathers(self): print u"这是人。" def in_the_forest(duck): duck.quack() duck.feathers() def game(): donald = Duck() john = Person() in_the_forest(donald) in_the_forest(john) game()
运行结果不用贴出来,都知道是什么。那么上面代码从何体现出Duck Typing风格呢?
看看in_the_forest()函数,它对它的参数只有两个要求,那就是这个参数必须要有quack()和feathers()两个方法,不管它是什么类型,只要有这两个方法,都可以作为in_the_forest()函数的参数,而类Duck和Person都实现了这个方法,所以它们的实例都可以作为in_the_forest()的参数。这个就是鸭子类型,不关注对象类型本身,只关心行为。这一点更C++还是有很大区别的,我也学习过C++,也了解C++多态性以及虚函数所以感觉这差别还是蛮大的。
现在来看看什么是协议吧,简单的说,在python中我需要调用你的某个方法,你正好有这个方法,这就是协议,比如在加法运算中,当出现加号(+)时,那么按照数值类型相关的协议,python会自动去调用相应对象的__add__()方法,这就是协议。
python中有很多协议,比如下面代码:
#coding=utf-8 a = 1 print dir(a)
结果如下:
[‘__abs__‘, ‘__add__‘, ‘__and__‘, ‘__class__‘, ‘__cmp__‘, ‘__coerce__‘, ‘__delattr__‘, ‘__div__‘, ‘__divmod__‘, ‘__doc__‘, ‘__float__‘, ‘__floordiv__‘, ‘__format__‘, ‘__getattribute__‘, ‘__getnewargs__‘, ‘__hash__‘, ‘__hex__‘, ‘__index__‘, ‘__init__‘, ‘__int__‘, ‘__invert__‘, ‘__long__‘, ‘__lshift__‘, ‘__mod__‘, ‘__mul__‘, ‘__neg__‘, ‘__new__‘, ‘__nonzero__‘, ‘__oct__‘, ‘__or__‘, ‘__pos__‘, ‘__pow__‘, ‘__radd__‘, ‘__rand__‘, ‘__rdiv__‘, ‘__rdivmod__‘, ‘__reduce__‘, ‘__reduce_ex__‘, ‘__repr__‘, ‘__rfloordiv__‘, ‘__rlshift__‘, ‘__rmod__‘, ‘__rmul__‘, ‘__ror__‘, ‘__rpow__‘, ‘__rrshift__‘, ‘__rshift__‘, ‘__rsub__‘, ‘__rtruediv__‘, ‘__rxor__‘, ‘__setattr__‘, ‘__sizeof__‘, ‘__str__‘, ‘__sub__‘, ‘__subclasshook__‘, ‘__truediv__‘, ‘__trunc__‘, ‘__xor__‘, ‘bit_length‘, ‘conjugate‘, ‘denominator‘, ‘imag‘, ‘numerator‘, ‘real‘]
上面代码中,a是整数对象,我们列出来该对象的所有属性,上面的属性列表中,以"__"开头并且以"__"结尾的基本上都是对象协议依赖的方法。是不是很多呀,我们可以将它们分成以下几类:
(1)类型转换协议
类型转换协议,顾名思义就是用来进行类型转的咯,比如a=1,a是整型,想把它变成float浮点型,我们可以这样:
#coding=utf-8 a = 1 print type(a) b = a.__float__() print b print type(b)
当然你也可以用内置函数float()。
除了__float__()外,还有__str__()、__repr__()、__int__()、__long__()、__nonzero__()等等,这些都是类型转换协议依赖的方法。
在这里提下__nonzero__(),他用t于将类转换为布尔值。通常在用类进行判断和将类转换成布尔值时调用。通常会在if等条件语句会调用该方法。比如:
#coding=utf-8 list1 = [] if list1: print "1"
上面代码在执行过程中,先会调用list1对象的__nonzero__()方法,判读它是不是空,是的话就会转换为False。假设有些对类没有定义__nonzero__()方法,那么将如何判断真假呢?此时对象会获取__len__()方法,如果该方法返回值为0,则表示为假。
如果一个类即没有定义__nonzero__()方法,也没有定义__len__()方法,那么该类的所有实例用if判断的话全为真。
比如看下面例子:
#coding=utf-8 class A: def __nonzero__(self): print u"定义了__nonzero__()方法" return 1 class B: def __len__(self): print u"没定义__nonzero__()方法,但定义了__len__()方法" return 1 class C: pass if A(): a = 1 print a if B(): a = 2 print a if C(): a = 3 print a
运行结果如下:
希望上面的例子能够帮助你理解部分知识。
(2)用以比较大小的协议
这个协议依赖于__cmp__()方法,当两者相等时返回0,self<other时返回负值,反之返回正值。但是这种返回有点复杂,Python又定义了__eq__()、__ne__()、__lt__()、__gt__()等方法来实现相等、不等、小于和大于的判定。这也是Python对==,!=,<,>等操作符进行重载支撑的机制,也就是说重载这些操作符,就是要重新定义对应的方法。
(3)与数值类型相关的协议
这一类方法比较多,主要是数值之间的一些操作。
分类 | 方法 | 操作符/函数 | 说明 |
数值运算符 | __add__ | + | 加 |
__sub__ | - | 减 | |
__mul__ | * | 乘 | |
__div__ | / | 除 | |
__floordiv__ | // | 整除 | |
__truediv__ | / | 真除法 | |
__pow__ | ** | 幂运算 | |
__mod__ | % | 取余 | |
__divmod__ | divmod() | 余、除 | |
位运算符 | __lshift__ | << | 向左移位 |
__rshift__ | >> | 向右移位 | |
__and__ | & | 与 | |
__or__ | | | 或 | |
__xor__ | ^ | 异或 | |
__invert__ | ~ | 非 | |
运算赋值符 | __iadd__ | += | |
__isub__ | -= | ||
__imul__ | *= | ||
__idiv__ | /= | ||
__ifloordiv__ | //= | ||
__itruediv__ | /= | ||
__ipow__ | **= | ||
__imod__ | %= | ||
__ilshift__ | <<= | ||
__irshift__ | >>= | ||
__iand__ | &= | ||
__ior__ | |= | ||
__ixor__ | ^= | ||
其它 | __pos__ | + | 正 |
__neg__ | - | 负 | |
__abs__ | abs() | 绝对值 |
举个例子,比如C++中,"<<"可表示输出流,我们也可以将python中"<<"重载为具有输出流功能。代码如下:
#coding=utf-8 class endl(object): pass class Cout(object): def __lshift__(self, other): if other is endl: print return print other return self cout = Cout() cout << "hello" << "world" << endl
运行结果如下:
不过在python中,将"<<"操作符重载为具有输出流功能,基本上是没有意义,在这里只是举例说明。
另外Python中还有一个特有的概念:反运算。例如a + b,调用的是a的__add__()方法,如果a没有__add__()方法,但b有,可是这种写法调用b.__add__()又不对的,这时候Python有了一个反运算协议,它会去检测b中有没有__radd__(),如果有的话,那么a为参数调用之。类似__radd__()方法,所有数值运算和位运算都之处,规则一律在前面加前缀r。
(4)容器类协议
既然为容器,那么肯定会有查询、取值、删除、赋值、求长度等等一系列动作行为,那么必有对应的方法与这些操作对应。打印出dir(list)的值,结果如下:
[‘__add__‘, ‘__class__‘, ‘__contains__‘, ‘__delattr__‘, ‘__delitem__‘, ‘__delslice__‘, ‘__doc__‘, ‘__eq__‘, ‘__format__‘, ‘__ge__‘, ‘__getattribute__‘, ‘__getitem__‘, ‘__getslice__‘, ‘__gt__‘, ‘__hash__‘, ‘__iadd__‘, ‘__imul__‘, ‘__init__‘, ‘__iter__‘, ‘__le__‘, ‘__len__‘, ‘__lt__‘, ‘__mul__‘, ‘__ne__‘, ‘__new__‘, ‘__reduce__‘, ‘__reduce_ex__‘, ‘__repr__‘, ‘__reversed__‘, ‘__rmul__‘, ‘__setattr__‘, ‘__setitem__‘, ‘__setslice__‘, ‘__sizeof__‘, ‘__str__‘, ‘__subclasshook__‘, ‘append‘, ‘count‘, ‘extend‘, ‘index‘, ‘insert‘, ‘pop‘, ‘remove‘, ‘reverse‘, ‘sort‘]
其中:
求长度,也就是查询容器内有多少个对象,用__len__()方法,内置函数len()就是通过该方法实现的。
取值、删除、赋值依次对应__getitem__()、__delitem__()、__setitem__(),有些容器没有__setitem__(),比如字符串,应为字符串是不可变对象
__iter__()实现了迭代协议,__reversed__()则提供了对内置函数reversed()的支持,用来排序。
还有成员关系判定符in和not in,对应的方法是__contains__()
(5)可调用对象协议
可调用对象,也就是类似函数对象,能够让类实例表现的像函数一样,这样可以让每一个函数调用都有所不同。怎么理解这句话呢?还是看例子吧。
#coding=utf-8 class A(object): def __init__(self,name): self.name = name def __call__(self): print "dongn something with %s"%(self.name) a = A(‘li lei‘) b = A(‘han meimei‘) print a() print b()
运行结果如下:
看看上面的例子,我们能够像调用函数一样调用实例
对象通过提供__call__(slef, [,*args [,**kwargs]])方法可以模拟函数的行为,如果一个对象x提供了该方法,就可以像函数一样使用它,也就是说x(arg1, arg2...) 等同于调用x.__call__(self, arg1, arg2) 。
(6)哈希协议
如果对象有__hash__()方法,表示是一个可哈希对象。__hash__()方法支持这hash()这个内置函数。按照文档里面的解释“如果一个对象是可哈希的,那么在它的生存期内必须不可变(需要一个哈希函数),而且可以和其他对象比较(需要比较方法).比较值相同的对象一定有相同的哈希值”。
这也就是说所有不可变的内置类型t都是可哈希的,比如string,tuple。所有可变的内置类型都是不可哈希的,比如list,dict(即没有__hash__()方法)。字典的key必须是可哈希的,所以tuple,string可以做key,而list不能做key。
(7)上下文管理器协议
这个在之前的博文中讲过,在此不做过多解释,可参考之前的相关博文。http://11026142.blog.51cto.com/11016142/1845862
Python的对象协议