Python中的鴨子型別

banq發表於2024-03-05

Duck Typing是一種動態型別的程式設計風格,主要用於物件導向的程式語言中,例如Python。

這種程式設計風格不依賴於物件的實際型別,而是關注物件是否具有特定的方法、屬性或行為。Duck Typing的理念源自於一句格言:“如果它看起來像鴨子,叫起來像鴨子,那麼它就是鴨子。”

簡而言之,Duck Typing關注的是物件的行為而不是物件的型別。如果一個物件能夠像鴨子一樣“走路像鴨子、叫聲像鴨子”,那麼它就可以被視為鴨子。

這種方式可以使得程式碼更加靈活,因為不必關注物件的具體型別,只需要關注物件是否具有所需的行為。

Python程式碼:

# Duck Typing 示例
class Duck:
    def sound(self):
        print(<font>"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常用的方式。

如下:

<font>// 介面定義<i>
interface Animal {
    void sound();
}

// 抽象類定義<i>
abstract class Bird {
    abstract void fly();
}

// Duck類實現Animal介面<i>
class Duck implements Animal {
    @Override
    public void sound() {
        System.out.println(
"Quack");
    }
}

// Sparrow類繼承自Bird抽象類<i>
class Sparrow extends Bird {
    @Override
    void fly() {
        System.out.println(
"Sparrow is flying");
    }
}

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

        duck.sound();
// 輸出:Quack<i>
        sparrow.fly();
// 輸出:Sparrow is flying<i>
    }
}

Java中將sound()行為用介面Animal這種型別標識出來:

interface Animal {
    void sound();
}

而Python的Duck Typing則是節省了介面Animal這一步驟。

  • 在Java中,我們使用了介面和抽象類來定義多型型別,然後在Main類中進行多型引用。
  • 而在Python中,我們直接傳遞具有特定方法的物件,並透過呼叫這些方法來實現多型性。

Python 不需要預先定義介面或抽象類,只要物件具有相應的方法即可。

如果它看起來像鴨子並且嘎嘎叫起來像鴨子,那麼它一定是鴨子。

複雜案例:
這是一個涉及可以游泳和飛行的鳥類的簡單示例:

class Duck:
    def swim(self):
        print(<font>"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 = (<font>"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(<font>"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 示例,演示了鴨子型別的概念:

<font>// 定義一個擁有 quack 方法的物件<i>
let duck = {
    quack: function() {
        console.log(
"Quack");
    }
};

// 定義一個擁有 quack 方法的物件,但它不是一個真正的鴨子物件<i>
let notADuck = {
    quack: function() {
        console.log(
"Not a duck, but quacks like one");
    }
};

// 接受任何具有 quack 方法的物件作為引數的函式<i>
function makeSound(obj) {
    obj.quack();
}

// 使用 duck 物件呼叫 makeSound 函式<i>
makeSound(duck);  
// 輸出:Quack<i>

// 使用 notADuck 物件呼叫 makeSound 函式,它雖然不是鴨子物件,但是具有 quack 方法<i>
makeSound(notADuck);  
// 輸出:Not a duck, but quacks like one<i>

在這個示例中,

  • 我們定義了一個名為 duck 的物件,它具有一個名為 quack 的方法。
  • 我們還定義了一個名為 notADuck 的物件,它也具有 quack 方法,儘管它不是一個真正的鴨子物件。
  • 然後,我們定義了一個 makeSound 函式,它接受任何具有 quack 方法的物件作為引數,並呼叫它們的 quack 方法。

JavaScript 的動態特性使得它很適合使用鴨子型別的程式設計風格,因為它允許物件在執行時根據需要動態地新增或刪除方法和屬性。

相關文章