python 面试题11 python 鸭子类型

鸭子类型(duck typing):是编程语言中动态类型语言中的一种设计风格,

在这种风格中,一个对象有效的语义,不是由继承自特定的类或实现特定的接口,而是由”当前方法和属性的集合”决定。

鸭子类型中关注的不是对象的类型本身,而是他如何使用。

这个概念的名字来源于James Whitcomb Riley提出的鸭子测试:“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”

我们可以这样理解:
在不使用鸭子类型的语言中,我们可以编写一个函数,它接受一个类型为鸭的对象,并调用它的走和叫方法。
在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的走和叫方法。

也许你对这段话还不理解,那么请看下面的例子:

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)#调用watch_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 duck
---------------------------------------------------------

class 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'
------------------------------------------------------------------

上面可以很明显的看出,
Person类拥有跟Duck类一样的方法,当有一个函数调用Duck类,并用到了两个方法walk()和swim()。

我们传入Person类也一样可以运行,函数并不会检查对象的类型是不是Duck,只要他拥有walk()和swim()方法,就可以正确的被调用。

对于一个鸭子类型来说,我们并不关心这个对象的类型本身或是这个类继承,而是这个类是如何被使用的。我们可以通过`watch_duck`函数的代码来调用这些类的方法,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()

#01
print(iter_able_object[1])
print(iter_able_object[1:3])
--------------------------------------------------------------------
output>>
2
[2, 3]
--------------------------------------------------------------------


#02
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
--------------------------------------------------------------------



#03
print(next(another_iterable_object))
---------------------------------------------------------------
output>>
1

----------------------------------------------------------------


#04
print(next(another_iterable_object))
--------------------------------------------------------------
output>>
2
---------------------------------------------------------------


#05
print(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,就可以在这个类的对象上通过循环来获取各个子项。一个类可以实现它有能力实现的方法,并只能被用于在它有意义的情况下。

这两个类和上面的鸭子类相比较,其实用于切边的和用于循环的iter()就相当于watch_duck函数,这些函数都接收任意类的对象,并调用实现功能所需要的对象中的方法来实现功能,若该函数中调用的方法对象里面不存在,就报错。

从上面可以看出,
python鸭子类型的灵活性在于它关注的是这个所调用的对象是如何被使用的,而没有关注对象类型的本身是什么。

所以

  • 在python中使用isinstance来判断传入参数的类型是不提倡的,更pythonic的方法是直接使用传入的参数,通过try,except来处理传入参数不符合要求的情况。

  • 我们应该通过传入对象的能力而不是传入对象的类型来使用该对象。

总结:鸭子类型的特点是:

1、变量绑定的类型具有不确定性

2、函数可以接收任意类型的参数

3、调用方法时不检查提供的参数类型

4、调用是否成功由参数的方法和属性确定

5、调用不成功抛出错误(也就是对象中没有实现该功能的方法)

文章参考
https://www.jianshu.com/p/d296b9446908
https://www.jianshu.com/p/650485b78d11

https://blog.csdn.net/jin__9981/article/details/79160239
用来拓展 ——面向对象 封装 继承 多态(鸭子类型)
https://www.cnblogs.com/xiugeng/p/8930733.html#undefined
https://blog.csdn.net/zhchs2012/article/details/79273109
————-谢谢大佬!

猜你喜欢

转载自blog.csdn.net/weixin_41853490/article/details/81264817
今日推荐