‘那只东西呱呱的叫,有扁扁的嘴巴,走起路来还外八,对!它就是只鸭子’
基本定义
对于熟悉python的开发者来说,相信对于python的鸭子类型比较熟悉,所谓鸭子类型,在维基百科中的准确定义是‘是动态类型的一种风格。在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定’。python中的具体实现
下面的代码就是一个简单的鸭子类型
class duck(): def walk(self): print('I walk like a duck') def swim(self): print('i swim like a duck')class person(): def walk(self): print('this one walk like a duck') def swim(self): print('this man swim like a duck')
对于一个鸭子类型来说,我们并不关心这个对象的类型本身或是这个类继承,而是这个类是如何被使用的。我们可以通过下面的代码来调用这些类的方法。
def watch_duck(animal): animal.walk() animal.swim() small_duck = duck() watch_duck(small_duck) output >> I walk like a duck i swim like a duck duck_like_man = person() watch_duck(duck_like_man) output >> this one walk like a duck this man swim like a duckclass Lame_Foot_Duck(): def swim(self): print('i am lame but i can swim') lame_duck = Lame_Foot_Duck() watch_duck(lame_duck) output >>AttributeError: Lame_Foot_Duck instance has no attribute 'walk'
watch_duck函数接收这个类的对象,然后并没有检查对象的类型,而是直接调用这个对象的走和游的方法,如果所需要的方法不存在就报错。具体在python中鸭子类型的体现如下面的代码所示
class CollectionClass(): lists = [1,2,3,4] def __getitem__(self, index): return self.lists[index] iter_able_object = CollectionClass()class Another_iterAbleClass(): lists=[1,2,3,4] list_position = -1 def __iter__(self): return self def next(self): #还有更简单的实现,使用生成器或迭代器什么的:) self.list_position += 1 if self.list_position >3: raise StopIteration return self.lists[self.list_position] another_iterable_object=Another_iterAbleClass() print(iter_able_object[1]) print(iter_able_object[1:3]) output>>2[2, 3] another_iterable_object[2] output>>Traceback (most recent call last): File "/Users/steinliber/a.py", line 32, in <module> another_iterable_object[2]TypeError: 'Another_iterAbleClass' object does not support indexing print(next(another_iterable_object)) output>>1print(next(another_iterable_object)) output>>2print(next(iter_able_object)) output>>Traceback (most recent call last): File "/Users/steinliber/a.py", line 29, in <module> print(next(iter_able_object))TypeError: IterAbleClass object is not an iterator
在python把上述代码的实现方法叫做protocol(协议),这些protocol可以看作是通知型的接口,它规定了调用方使用该功能要调用对象的哪些方法,被调用方要实现哪些方法才能完成这个功能。它和java中的接口区别在于java中的接口功能实现需要通过继承,继承的类必须实现接口中的所有的抽象方法,所以在Java中强调的是类型的概念,而python中的protocol更多的是通知性的,一个函数规定要实现某个功能需要调用传入对象的哪些方法,所有实现这些方法的类就可以实现这个功能。
具体从上面两个类来说,第一个类实现了__getitem__方法,那python的解释器就会把它当做一个collection,就可以在这个类的对象上使用切片,获取子项等方法,第二个类实现了__iter__和next方法,python就会认为它是一个iterator,就可以在这个类的对象上通过循环来获取各个子项。一个类可以实现它有能力实现的方法,并只能被用于在它有意义的情况下。
这两个类和上面的鸭子类相比较,其实用于切边的[](它其实调用的是python的slice函数)和用于循环的iter()就相当于watch_duck函数,这些函数都接收任意类的对象,并调用实现功能所需要的对象中的方法来实现功能,若该函数中调用的方法对象里面不存在,就报错。
从上面可以看出,python鸭子类型的灵活性在于它关注的是这个所调用的对象是如何被使用的,而没有关注对象类型的本身是什么。所以在python中使用isinstance来判断传入参数的类型是不提倡的,更pythonic的方法是直接使用传入的参数,通过try,except来处理传入参数不符合要求的情况。我们应该通过传入对象的能力而不是传入对象的类型来使用该对象。
作者:steinliber
链接:https://www.jianshu.com/p/d296b9446908