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)

Python的对象协议_python

当然你也可以用内置函数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

运行结果如下:

Python的对象协议_协议_02

希望上面的例子能够帮助你理解部分知识。


(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_03

不过在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 ×××')
print a()
print b()

运行结果如下:

Python的对象协议_python_04

看看上面的例子,我们能够像调用函数一样调用实例

对象通过提供__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