鸭子类型是Python的一个基本原则,根据该原则,对象是根据其行为而不是根据其类别层次来考虑的。
此术语源于谚语:“如果某物看起来像鸭子,游泳像鸭子,叫声像鸭子,那么它就是鸭子。”在Python中,对象的行为(其接口)比其所属的类更重要。这实现了“鸭子类型”原则——基于行为的类型化(结构类型化)。
看起来,鸭子类型提供了最大的灵活性。但这增加了隐藏错误的数量:如果对象不支持所需的接口,程序将在运行时“崩溃”。
与其通过isinstance或type检查类型,不如编写尝试调用对象所需方法的函数,假设它支持这些方法——这样就能正常工作。最坏的情况下,捕获AttributeError或TypeError,以处理意外对象。
示例:
def quack_and_walk(duck): duck.quack() duck.walk() class Robot: def quack(self): print("我会呱呱叫!") def walk(self): print("我在走") quack_and_walk(Robot()) # 一切正常!
关键特性:
在Python中是否可以通过isinstance检查类型,并说这符合鸭子类型的标准?
不可以。鸭子类型正好与严格的类型检查相对立。更准确的做法是关注对象的行为,而不是它的血统。
是否可以通过抽象基类(ABC)实现鸭子类型?
部分可以。抽象类引入了静态结构的元素。鸭子类型并不要求声明亲属关系——你只需实现所需的方法。但从Python 3.8开始,引入了typing.Protocol模块,使我们更接近于结构类型化。
鸭子类型能否与魔法方法(len,__getitem__等)一起工作?
可以。如果对象实现了所需的方法(len),可以将其传递给函数,例如len(obj),这将根据鸭子类型正常工作。
开发人员编写一个接受所有具有run()方法的对象的函数,但没有进行检查。代码中出现一个没有该方法的对象——错误只在运行时发生。
优点:
缺点:
使用鸭子类型,但带有try/except和接口文档:
def run_task(obj): try: obj.run() except AttributeError: print("对象不支持运行任务!")
优点:
缺点: