在Python官網的Glossary第一次看到「duck typing」這個詞的時候,真的是很疑惑:Python怎麼會跟鴨子扯得上關係?更疑惑的是,那還是隻會打字的鴨子!等到看完裡頭的解釋後,才知道完全會錯意了,duck還是鴨子,不過typing跟打字一點關係都沒有,而是指程式設計中,設定data type的方法,中文有人翻譯成「定型」。
其實看完官網的解釋後,並沒什麼特別的感覺,不過有句話倒是特別有印象,因為實在是太特別了,真不知道這句話跟程式語言是怎麼扯上關係的,這句話原文是:
If it looks like a duck and quacks like a duck, it must be a duck.
翻譯成中文就是:
如果牠看起來像隻鴨子,叫起來像隻鴨子,那牠就一定是隻鴨子。
好嘛!反正牠就是隻鴨子,然後呢?然後就沒有然後了,寫程式時好像也用不著,這個跟鴨子有關的詞,就這樣給拋到九霄雲外去了。偶而會想起跟鴨子有關的事,也就只有烤鴨,直到在寫Stochastic L-system時,才又注意到它,把它搞懂。
在寫Stochastic L-system時,因為每組production rule裡頭,可能包含數條規則,會使用哪條規則,是依據機率來決定。所以囉,在程式裡頭,就應該要去檢查這些規則的機率總和,是不是剛好是1。這個部分倒是不難處理,可是接著又想到,那是不是也需要去檢查這些規則的寫法,是不是符合設定好的格式?這些規則長這樣:
rule = {'F': [('G[-F]+F', 1.0)], 'G': [('GG', 0.5), ('G', 0.2), ('GGG', 0.3)]}
要檢查使用者給的規則是不是符合這樣的格式,第一步顯然應該先檢查rule的data type是不是dictionary,不過這部分的指令不熟,得查一下。
因為rule是要當成argument給送進去function裡頭,所以下關鍵字查詢的時候,就乾脆查看看別人是怎麼檢查argument的。沒想到,不少人說不要檢查argument的data type,然後提到duck typing。
說實在的,會想到要去檢查argument的data type,是因為萬一送進function的東西是有問題的,要能夠抓出來並顯示錯誤訊息。那現在說不要檢查data type是怎樣?有問題的時候怎麼辦?網路上有人的意見是說,其實啊,這個問題也沒那麼難處理,反正argument的data type不對,在某個地方一定會因為錯誤的使用方式而被抓出來。例如某個argument本來應該是一個dictionary,結果卻送了個tuple進來,用使用dictionary的方式來使用tuple,一定會出問題,而且系統也會有錯誤訊息,顯示到底是什麼問題。既然這樣,那有沒有去檢查argument的data type,其實也就沒那麼重要了。
也是啦!反正萬一argument的data type有問題,系統會告訴你,那多一事不如少一事,大可不必自己去檢查。能坐就不站,能躺就不坐,心存偷懶的心態,在寫程式的時候,有時候反而會是一種美德。不過,偷懶歸偷懶,那個跟鴨子有關的duck typing還是應該要把它搞懂。
在網路上有許多關於duck typing的文章,也有許多範例,不過總而言之,其實就是大家常說的:不管黑貓白貓,會抓老鼠的就是好貓。又是鴨子又是貓的,用個例子來說明:假設現在有三隻動物,分別叫做Black、Kitty、Dada,牠們其實是一隻狗、一隻貓,還有一隻鴨子。眾所周知,狗、貓、鴨子都會叫,而且叫聲不一樣;另外,牠們都會走路,不過鴨子除了會走之外,還會飛。這些寫成程式長這樣:
class Dog:
def walk(self):
print("I am a dog. See! I can walk.")
def say(self):
print("Woof! Woof! Woof!")
class Cat:
def walk(self):
print("I am a cat. See! I can walk.")
def say(self):
print("Meow! Meow! Meow!")
class Duck:
def walk(self):
print("I am a duck. See! I can walk.")
def say(self):
print("Quack! Quack! Quack!")
def fly(self):
print("I am a duck. See! I can fly.")
Black = Dog()
Kitty = Cat()
Dada = Duck()
現在問題來了,牠們三個很害羞,都把臉遮起來,讓人搞不清楚誰是誰。為了要搞清楚誰是誰,只好根據牠們會的技能來判別。判斷牠們會不會某項技能的程式長這樣:
def test_walk(animal):
animal.walk()
def test_say(animal):
animal.say()
def test_fly(animal):
animal.fly()
先來看看牠們會不會走路:
test_walk(Black)
test_walk(Kitty)
test_walk(Dada)
結果
I am a dog. See! I can walk.
I am a cat. See! I can walk.
I am a duck. See! I can walk.
每個都會走路,而且都說出來牠是哪種動物。這樣就知道誰是誰了吧?那可未必!萬一牠們說謊呢?既然不確定,那再來看看牠們會不會叫好了:
test_say(Black)
test_say(Kitty)
test_say(Dada)
結果三個都會叫,而且叫聲都不一樣:
Woof! Woof! Woof!
Meow! Meow! Meow!
Quack! Quack! Quack!
這總可以了吧?這……萬一牠們都會二種語言呢?說不定狗狗Black很喜歡貓的文化,所以學會了叫「Meow」,而貓咪Kitty為了想抓鴨子,所以學會了叫「Quack」,這樣才能混入鴨群中。
這也不行、那也不行,那就看看誰會飛吧!至少知道會飛的那個是鴨子,總不可能貓和狗都可以飛上天吧。先來看看Black行不行:
test_fly(Black)
結果出現錯誤訊息,說牠是狗不會飛:
AttributeError: 'Dog' object has no attribute 'fly'
再來是Kitty
test_fly(Kitty)
一樣會有錯誤訊息,說牠是貓不會飛:
AttributeError: 'Cat' object has no attribute 'fly'
最後是Dada
test_fly(Dada)
結果,牠會飛耶!
I am a duck. See! I can fly.
所以囉,現在很確定Dada就是會飛的鴨子。
費盡千辛萬苦,總算知道誰是誰了。從這過程中,可以看得出來,test_walk、test_say、test_fly這三個function,在使用的時候,根本就不管傳給它們的argument是哪種class的instance。它們唯一在乎的,是那個object有沒有它們要用到的method。所以說,duck typing說的,就是如果有隻雞界同時精通易容跟口技的大師,哪天突然心血來潮易容成鴨子,然後改變叫聲開始像鴨子一樣呱呱叫。這時候,即使牠的嘴巴和爪子都是尖尖的,走路也不會大搖小屁屁,牠都會被當成是鴨子。這也就是說,如果目的就是要抓老鼠,不管你是黑貓或白貓,甚至是捕鼠籠或機器人,只要會抓老鼠就好,長相和身份根本就不重要!
各行各業不同領域,總是會有些字面上看來挺奇怪的術語。鴨子跟程式設計居然扯得上關係!還有啊,猜猜看,「出血」是哪個行業的術語?可別猜是醫療業!要有這麼好猜就不好玩了。答案是平面設計。第一次在平面設計的書上看到「出血」兩個字,還真是愣住了,這行業有這麼血汗嗎?等搞清楚了出血是怎麼回事,也只能翻翻白眼苦笑,這行業的人可真是幽默啊!
其實啊,看起來再奇怪的術語,都有它形成的原因和典故,說不定現在習以為常的說法,在以後的人看來也是會覺得奇奇怪怪挺無言的。現在管讓車子加速的東東叫「油門」,可是電動車又不喝油,那玩意兒該叫什麼?看來還是會油門油門一直叫下去。等到以後大家開的、騎的全都是電動車,而油車身影也從大多數人的腦海中消失,那時候應該也會有人覺得很奇怪,明明就是吃電的,怎麼會叫油門啊?