Python中的鸭子类型

Duck Typing是一种动态类型的编程风格,主要用于面向对象的编程语言中,例如Python。

这种编程风格不依赖于对象的实际类型,而是关注对象是否具有特定的方法、属性或行为。Duck Typing的理念源自于一句格言:“如果它看起来像鸭子,叫起来像鸭子,那么它就是鸭子。”

简而言之,Duck Typing关注的是对象的行为而不是对象的类型。如果一个对象能够像鸭子一样“走路像鸭子、叫声像鸭子”,那么它就可以被视为鸭子。

这种方式可以使得代码更加灵活,因为不必关注对象的具体类型,只需要关注对象是否具有所需的行为。

Python代码:

# Duck Typing 示例
class Duck:
    def sound(self):
        print("Quack")

class Sparrow:
    def fly(self):
        print(
"Sparrow is flying")

# 函数接受任何具有 sound 方法的对象作为参数
def make_sound(obj):
    obj.sound()

# 函数接受任何具有 fly 方法的对象作为参数
def make_fly(obj):
    obj.fly()

if __name__ ==
"__main__":
    duck = Duck()
    sparrow = Sparrow()

    make_sound(duck)  # 输出:Quack
    make_fly(sparrow)  # 输出:Sparrow is flying

代码中:
def make_sound(obj):
    obj.sound()

是通过sound()行为来判断其类型,不是查看obj这个对象的类型来确定它是否具有正确的接口,例如检查obj是否实现了某个具有sound()行为的接口,后者是OOP语言如Java常用的方式。

如下:

// 接口定义
interface Animal {
    void sound();
}

// 抽象类定义
abstract class Bird {
    abstract void fly();
}

// Duck类实现Animal接口
class Duck implements Animal {
    @Override
    public void sound() {
        System.out.println(
"Quack");
    }
}

// Sparrow类继承自Bird抽象类
class Sparrow extends Bird {
    @Override
    void fly() {
        System.out.println(
"Sparrow is flying");
    }
}

public class Main {
    public static void main(String[] args) {
       
// 多态引用
        Animal duck = new Duck();
        Bird sparrow = new Sparrow();

        duck.sound();
// 输出:Quack
        sparrow.fly();
// 输出:Sparrow is flying
    }
}

Java中将sound()行为用接口Animal这种类型标识出来:

interface Animal {
    void sound();
}

而Python的Duck Typing则是节省了接口Animal这一步骤。

  • 在Java中,我们使用了接口和抽象类来定义多态类型,然后在Main类中进行多态引用。
  • 而在Python中,我们直接传递具有特定方法的对象,并通过调用这些方法来实现多态性。

Python 不需要预先定义接口或抽象类,只要对象具有相应的方法即可。

如果它看起来像鸭子并且嘎嘎叫起来像鸭子,那么它一定是鸭子。

复杂案例:
这是一个涉及可以游泳和飞行的鸟类的简单示例:

class Duck:
    def swim(self):
        print("The duck is swimming")

    def fly(self):
        print(
"The duck is flying")

class Swan:
    def swim(self):
        print(
"The swan is swimming")

    def fly(self):
        print(
"The swan is flying")

class Albatross:
    def swim(self):
        print(
"The albatross is swimming")

    def fly(self):
        print(
"The albatross is flying")

在此示例中,您的三只鸟可以游泳和飞行。然而,它们是完全独立的类。但是有相同行为,好像实现相同接口一样。

>>> from birds_v1 import Duck, Swan, Albatross

>>> birds = [Duck(), Swan(), Albatross()]

>>> for bird in birds:
...     bird.fly()
...     bird.swim()
...
The duck is flying
The duck is swimming
The swan is flying
The swan is swimming
The albatross is flying
The albatross is swimming

Python 不关心在bird给定时间持有什么对象。它只是调用预期的方法。如果对象提供了该方法,那么代码就可以正常工作而不会中断。这就是鸭子类型Duck Typing提供的灵活性。

鸭子类型的优点和缺点
鸭子类型为程序员提供了很大的灵活性,主要是因为您不必考虑复杂的概念,例如继承、类层次结构以及类之间的关系。它在 Python 中如此受欢迎是有原因的!

以下是它的一些优点:

  • 灵活性:您可以根据对象的行为互换使用不同的对象,而不必担心它们的类型。这可以提高代码的模块化和可扩展性。
  • 简单性:您可以通过关注所需的行为而不是考虑特定类型、类以及它们之间的关系来简化代码。这使得代码更加简洁和富有表现力。
  • 代码重用:您可以在其他应用程序中重用一个或多个类,而无需导出复杂的类层次结构以使这些类正常工作。这有利于代码重用。
  • 更简单的原型设计:您可以快速创建表现出必要行为的对象,而无需复杂的类型定义。这在开发的初始阶段非常有用,此时您可能还没有完全充实类层次结构或接口。

以下是鸭子类型的一些缺点:

  • 潜在的运行时错误:您可能会遇到与缺少方法或属性相关的错误,这些错误可能只在运行时出现。如果对象不符合预期行为,这可能会导致意外行为或崩溃。
  • 缺乏明确性:您可能会使代码不那么明确并且更难以理解。缺乏显式接口定义可能会使掌握对象必须表现出的行为变得更加困难。
  • 潜在的维护问题:您可能在跟踪哪些对象必须表现出某些行为时遇到问题。某些对象的行为更改可能会影响代码的其他部分,从而使其更难以维护、推理和调试。


Python 内置工具中的鸭子类型
鸭子类型是 Python 中的一个核心概念,它存在于该语言的核心组件中。这种键入方法使 Python 成为一种高度灵活的语言,它不依赖于严格的类型检查,而是依赖于行为和功能。

Python 中有很多支持和使用鸭子类型的例子。最著名的事实之一是内置类型(例如列表、元组、字符串和字典)支持迭代、排序和反转等操作。

因为鸭子类型都是关于对象的行为,所以您会发现有些通用行为在不止一种类型中很有用。当谈到内置类型时,例如列表、元组、字符串、字典和集合,您很快就会意识到它们都支持迭代。您可以直接在for循环中使用它们:

>>> numbers = [1, 2, 3]
>>> person = ("Jane", 25, "Python Dev")
>>> letters =
"abc"
>>> ordinals = {
"one": "first", "two": "second", "three": "third"}
>>> even_digits = {2, 4, 6, 8}
>>> collections = [numbers, person, letters, ordinals, even_digits]

>>> for collection in collections:
...     for value in collection:
...         print(value)
...
1
2
3
Jane
25
Python Dev
a
b
c
one
two
three
8
2
4
6

python中协议与鸭子类型
在Python中,协议(Protocol)与鸭子类型(Duck Typing)之间存在密切的关系,但它们并不完全相同。

  • 鸭子类型(Duck Typing):鸭子类型是一种动态类型的编程风格,关注对象是否具有特定的方法、属性或行为,而不关注对象的具体类型。这种编程风格使得代码更加灵活,因为只需要关注对象的行为即可,而不需要关注对象的类型。
  • 协议(Protocol):协议是一种编程约定,描述了对象应该具有的方法、属性等行为,以便在特定情况下进行交互。Python中并没有正式的接口机制,因此协议通常用于描述对象应该具有的行为,而不是限制对象的类型。如果对象符合某个协议,那么它就可以在特定的上下文中被视为具有某种特定的类型。

在Python中,鸭子类型通常与协议一起使用。即使没有显式的接口或基类,只要对象具有特定的行为(方法、属性),就可以在某些情况下被视为满足某个协议。因此,Python中的协议更加灵活,而鸭子类型则是一种实现这种灵活性的编程风格。

总的来说,Python中的协议描述了对象应该具有的行为,而鸭子类型则是一种实现这种协议的动态类型编程风格。

下面是一个示例代码,展示了Python中如何使用协议和鸭子类型。

# 定义一个协议
class QuackableProtocol:
    def quack(self):
        raise NotImplementedError("Subclasses must implement quack method")

# 定义一个鸭子类,满足QuackableProtocol协议
class Duck(QuackableProtocol):
    def quack(self):
        print(
"Quack")

# 定义一个狗类,虽然不是鸭子,但是也满足QuackableProtocol协议
class Dog(QuackableProtocol):
    def quack(self):
        print(
"Woof")

# 定义一个函数,接受满足QuackableProtocol协议的对象作为参数
def make_sound(obj):
    obj.quack()

if __name__ ==
"__main__":
    duck = Duck()
    dog = Dog()

    make_sound(duck)  # 输出:Quack
    make_sound(dog)   # 输出:Woof

在这个示例中:

  • 我们定义了一个名为QuackableProtocol的协议,其中包含一个quack方法。
  • 然后,我们定义了两个类Duck和Dog,它们都实现了QuackableProtocol协议中的quack方法。
  • 尽管Dog类实际上不是鸭子,但它仍然可以被传递给make_sound函数,因为它满足了QuackableProtocol协议。

这就是鸭子类型的体现,即只要对象具有特定的行为(方法),就可以被视为满足某种协议。

其他动态语言的鸭子类型
在 JavaScript 中,鸭子类型(Duck Typing)是一种常见的编程理念。JavaScript 是一种动态类型语言,它不强制要求对象的类型,而是关注对象是否具有特定的方法或属性。如果一个对象具有特定的方法或属性,那么它就可以被视为具有某种类型,无需显式声明。

以下是一个简单的 JavaScript 示例,演示了鸭子类型的概念:

// 定义一个拥有 quack 方法的对象
let duck = {
    quack: function() {
        console.log(
"Quack");
    }
};

// 定义一个拥有 quack 方法的对象,但它不是一个真正的鸭子对象
let notADuck = {
    quack: function() {
        console.log(
"Not a duck, but quacks like one");
    }
};

// 接受任何具有 quack 方法的对象作为参数的函数
function makeSound(obj) {
    obj.quack();
}

// 使用 duck 对象调用 makeSound 函数
makeSound(duck);  
// 输出:Quack

// 使用 notADuck 对象调用 makeSound 函数,它虽然不是鸭子对象,但是具有 quack 方法
makeSound(notADuck);  
// 输出:Not a duck, but quacks like one

在这个示例中,

  • 我们定义了一个名为 duck 的对象,它具有一个名为 quack 的方法。
  • 我们还定义了一个名为 notADuck 的对象,它也具有 quack 方法,尽管它不是一个真正的鸭子对象。
  • 然后,我们定义了一个 makeSound 函数,它接受任何具有 quack 方法的对象作为参数,并调用它们的 quack 方法。

JavaScript 的动态特性使得它很适合使用鸭子类型的编程风格,因为它允许对象在运行时根据需要动态地添加或删除方法和属性。