derezzed

导航

Python的鸭子类型

                                                                     # python与鸭子类型


'''
基本定义



对于熟悉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 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'


'''
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>>
1
print(next(another_iterable_object))
output>>
2

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

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

从上面可以看出,python鸭子类型的灵活性在于它关注的是这个所调用的对象是如何被使用的,
而没有关注对象类型的本身是什么。所以在python中使用isinstance来判断传入参数的类型是不提倡的,
更pythonic的方法是直接使用传入的参数,通过try,except来处理传入参数不符合要求的情况。
我们应该通过传入对象的能力而不是传入对象的类型来使用该对象。


'''




'''
调用不同的子类将会产生不同的行为,而无须明确知道这个子类实际上是什么,
这是多态的重要应用场景。而在python中,因为鸭子类型(duck typing)使得其多态不是那么酷。
鸭子类型是动态类型的一种风格。在这种风格中,一个对象有效的语义,
不是由继承自特定的类或实现特定的接口,而是由"当前方法和属性的集合"决定。
这个概念的名字来源于由James Whitcomb Riley提出的鸭子测试,“鸭子测试”可以这样表述:
“当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子。”
在鸭子类型中,关注的不是对象的类型本身,而是它是如何使用的。例如,在不使用鸭子类型的语言中,
我们可以编写一个函数,它接受一个类型为"鸭子"的对象,并调用它的"走"和"叫"方法。
在使用鸭子类型的语言中,这样的一个函数可以接受一个任意类型的对象,并调用它的"走"和"叫"方法。
如果这些需要被调用的方法不存在,那么将引发一个运行时错误。
任何拥有这样的正确的"走"和"叫"方法的对象都可被函数接受的这种行为引出了以上表述,这种决定类型的方式因此得名。


鸭子类型通常得益于不测试方法和函数中参数的类型,而是依赖文档、清晰的代码和测试来确保正确使用。

'''



# python中的多态


'''
python中的鸭子类型允许我们使用任何提供所需方法的对象,而不需要迫使它成为一个子类。
由于python属于动态语言,当你定义了一个基类和基类中的方法,并编写几个继承该基类的子类时,
由于python在定义变量时不指定变量的类型,
而是由解释器根据变量内容推断变量类型的(也就是说变量的类型取决于所关联的对象),
这就使得python的多态不像是c++或java中那样,定义一个基类类型变量而隐藏了具体子类的细节。

请看下面的例子和说明:

'''

class AudioFile:
    def __init__(self, filename):
        if not filename.endswith(self.ext):
            raise Exception("Invalid file format")
        self.filename = filename

class MP3File(AudioFile):
    ext = "mp3"
    def play(self):
        print("Playing {} as mp3".format(self.filename))

class WavFile(AudioFile):
    ext = "wav"
    def play(self):
        print("Playing {} as wav".format(self.filename))

class OggFile(AudioFile):
    ext = "ogg"
    def play(self):
        print("Playing {} as ogg".format(self.filename))

class FlacFile:
    """
    Though FlacFile class doesn't inherit AudioFile class,
    it also has the same interface as three subclass of AudioFile.

    It is called duck typing.
    """
    def __init__(self, filename):
        if not filename.endswith(".flac"):
            raise Exception("Invalid file format")
        self.filename = filename

    def play(self):
        print("Playing {} as flac".format(self.filename))

'''
上面的代码中,MP3File、WavFile、OggFile三个类型继承了AudioFile这一积累,
而FlacFile没有扩展AudioFile,但是可以在python中使用完全相同的接口与之交互。
因为任何提供正确接口的对象都可以在python中交替使用,它减少了多态的一般超类的需求。
继承仍然可以用来共享代码,但是如果所有被共享的都是公共接口,鸭子类型就是所有所需的。
这减少了继承的需要,同时也减少了多重继承的需要;通常,当多重继承似乎是一个有效方案的时候,
我们只需要使用鸭子类型去模拟多个超类之一(定义和那个超类一样的接口和实现)就可以了。



'''

 

posted on 2018-01-24 16:51  derezzed  阅读(205)  评论(0)    收藏  举报