什么是鸭子打字?
Posted
技术标签:
【中文标题】什么是鸭子打字?【英文标题】:What is duck typing? 【发布时间】:2011-05-11 10:42:51 【问题描述】:我在网上阅读软件的随机主题时遇到了duck typing这个词,并没有完全理解它。
什么是“鸭子打字”?
【问题讨论】:
@Mitch 我尝试并得到了某种形式的继承。但不能跟随太多。对不起,如果我问错了问题。 @sushil bharwani:不,不生气。但人们期望作为第一个停靠港(即您做的第一件事)是在发帖之前尝试搜索。 考虑到上面的论点,*** 似乎并不是真正必要的,因为我确信几乎每个人可能想到的问题都在互联网上的某个地方得到了回答,如果没有,可能会得到答案通过向知识渊博的朋友发送电子邮件,更容易且没有批评。我想你们中的许多人都错过了 *** 的要点。 我确定我在某处读到过 SO 旨在成为“规范问题的存储库”,而且我敢肯定,您找不到比这更规范的了。 If it looks like a duck, swims like a duck, and quacks like a duck, then it probably is a duck. 【参考方案1】:这是dynamic languages中使用的一个术语,没有strong typing。
这个想法是,您不需要类型来调用对象上的现有方法 - 如果在其上定义了方法,则可以调用它。
这个名字来源于“如果它看起来像鸭子,叫起来像鸭子,那就是鸭子”这句话。
Wikipedia 有更多信息。
【讨论】:
小心使用强类型。它的定义不是很好。鸭子打字也不是。 Google Go 或 Ocaml 是具有结构子类型构造的静态类型语言。这些是鸭式语言吗? 鸭子打字的一个更好的短语是:“如果它说它是一只鸭子......那对我来说已经足够好了。”见pyvideo.org/video/1669/keynote-3 28:30 或youtube.com/watch?v=NfngrdLv9ZQ#t=1716 鸭式打字不一定只用于动态语言。 Objective-C 不是动态语言,它使用鸭子类型。 Python 和 Ruby 都是强类型语言,并且都有 Duck Typing。字符串类型并不意味着没有鸭子类型。 我对此表示反对。鸭子闪避与类型的强度无关,只是能够使用任何具有方法的对象的能力,无论它是否实现接口。【参考方案2】:鸭子打字 意味着一个操作没有正式指定其操作数必须满足的要求,而只是尝试给定的内容。
与其他人所说的不同,这不一定与动态语言或继承问题有关。
示例任务:在对象上调用某个方法Quack
。
如果没有使用duck-typing,执行此任务的函数f
必须提前指定其参数必须支持某些方法Quack
。一种常见的方式是使用接口
interface IQuack
void Quack();
void f(IQuack x)
x.Quack();
调用f(42)
失败,但只要donald
是IQuack
子类型的实例,f(donald)
就可以工作。
另一种方法是structural typing - 但同样,方法Quack()
被正式指定任何无法提前证明quack
s 的东西都会导致编译器失败。
def f(x : def Quack() : Unit ) = x.Quack()
我们甚至可以写
f :: Quackable a => a -> IO ()
f = quack
在 Haskell 中,Quackable
类型类确保我们的方法存在。
那么**鸭子打字**如何改变这一点?
嗯,正如我所说,鸭子打字系统没有指定要求,但只要有什么工作就尝试。
因此,Python 的动态类型系统总是使用鸭子类型:
def f(x):
x.Quack()
如果f
得到一个支持Quack()
的x
,一切都很好,如果没有,它会在运行时崩溃。
但是鸭子类型根本不意味着动态类型——事实上,有一种非常流行但完全静态的鸭子类型方法,它也没有给出任何要求:
template <typename T>
void f(T x) x.Quack();
该函数并没有以任何方式告诉它它需要一些x
可以Quack
,所以它只是在编译时尝试,如果一切正常,那就没问题了。 p>
【讨论】:
你不是说:void f(IQuak x) x.Quak(); (而不是 K.Quack)因为函数 f 的参数是 IQuack x 而不是 Iquack k,非常小的错误,但我觉得它需要更正:) 根据***,你的最后一个例子是“结构类型”,而不是“鸭子类型”。 好吧,似乎有一个单独的问题需要讨论:***.com/questions/1948069/… 所以如果我理解你所说的,支持鸭子类型的语言和不支持鸭子类型的语言之间的区别仅仅是鸭子类型,你不必指定函数的对象类型接受?def f(x)
而不是 def f(IQuack x)
。【参考方案3】:
简单说明(无代码)
避免学术技术/细微差别并保持简单(脚注^^^)。
什么是鸭式打字?
“如果它走路像鸭子,叫起来像鸭子,那么它就是鸭子。” - 是的!但这是什么意思??!
我们感兴趣的是“对象”可以做什么,而不是它们是什么。让我们用一个例子来解压它:
Duck Typing 功能示例:
想象一下我有一根魔杖。它有特殊的权力。如果我挥动魔杖并对一辆车说“开车!”,那么,它会开车!
它是否适用于其他事情?不确定:所以我在卡车上试了一下。哇——它也能开车!然后我在飞机、火车和 1 Woods(它们是人们用来“驱动”高尔夫球的一种高尔夫球杆)上试一试。 他们都开车!
但是,它可以说是一个茶杯吗?错误:KAAAA-BOOOOOOM!结果不太好。 ====> 茶杯不能开车!!呃!?
这基本上是鸭子类型的概念。这是一个先试后买系统。如果它有效,一切都很好。但如果失败了,就像手榴弹还在你的手中,它会在你的脸上爆炸。
换句话说,我们感兴趣的是对象可以做什么,而不是对象是什么。
示例:如果使用 C# 或 Java 等。
如果我们关心对象实际上是什么,那么我们的魔术将只适用于预先设置的授权类型——在这种情况下是汽车,但会在其他对象上失败可以驾驶:卡车、轻便摩托车、嘟嘟车等。它不适用于卡车,因为我们的魔杖希望它只适用于汽车 .
换句话说,在这种情况下,魔杖非常仔细地观察对象是什么(是汽车吗?),而不是对象可以做什么 (例如汽车、卡车等是否可以驾驶)。
让卡车开起来的唯一方法是,如果你能以某种方式让魔杖同时期待卡车和汽车(也许通过“实现一个通用界面”)。如果您不知道这意味着什么,请暂时忽略它。
总结:钥匙取出
鸭式打字中重要的是对象实际上可以做什么,而不是对象是什么。
Footnote is here
【讨论】:
我觉得有趣的前提是你更关心行为,这就是它的定义。毫无疑问,BDD 在 ruby 等语言中非常成功。 肯定是最好的解释。我已经厌倦了这种“鸭子”的解释,你也一样,它听起来像(我用粗体强调):“(“如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那么它就是一只鸭子。”) - 是的!但那是什么意思??!”现在我明白了:只需在任何传入的对象上尝试该方法,而不是先检查对象的类型。 我对这个感觉太热情了,不得不add my own answer too:)。 另外,我认为您应该将“摘要”放在开头。这是关键的要点。也许把它放在最后,但也把它的副本放在开头。【参考方案4】:假设您正在设计一个简单的函数,该函数获取Bird
类型的对象并调用其walk()
方法。您可以想到两种方法:
-
这是我的函数,我必须确保它只接受
Bird
类型,否则代码将无法编译。如果有人想使用我的功能,他们必须知道我只接受Bird
s。
我的函数得到任何objects
,我只是调用对象的walk()
方法。所以,如果object
可以walk()
那么它是正确的。如果不能,我的功能将失败。所以,这里对象是Bird
或其他任何东西并不重要,重要的是它可以walk()
(这是鸭子打字)。
必须考虑到 duck typing 在某些情况下可能很有用。例如,Python 经常使用 duck typing。
有用的阅读
对于 Java、Python、javascript 有很好的 duck typing 示例 等等https://en.wikipedia.org/wiki/Duck_typing。 这也是一个很好的答案,它描述了 dynamic 的优点 打字及其缺点:What is the supposed productivity gain of dynamic typing?【讨论】:
很好的解释,有什么好处? 这个答案简单明了,可能最适合初学者。阅读此答案及其上方的答案(或者如果它移动,则阅读有关汽车和茶杯的答案)【参考方案5】:我看到很多重复老成语的答案:
如果它长得像鸭子,叫起来像鸭子,那就是鸭子
然后深入解释你可以用鸭子打字做什么,或者一个似乎进一步混淆概念的例子。
我没有找到太多帮助。
这是我发现的关于鸭子打字的简单英文答案的最佳尝试:
Duck Typing 意味着一个对象是由它可以做什么来定义的,而不是由 它是什么。
这意味着我们不太关心对象的类/类型,而更关心可以在其上调用哪些方法以及可以对其执行哪些操作。 我们不关心它的类型,我们关心它可以做什么。
【讨论】:
【参考方案6】:***有相当详细的解释:
http://en.wikipedia.org/wiki/Duck_typing
鸭式打字是一种动态的风格 键入对象的当前 一组方法和属性 确定有效的语义,而不是 比它从一个特定的继承 特定的类或实现 界面。
重要的一点是,对于鸭子类型,开发人员可能更关心对象中被消费的部分,而不是实际的底层类型是什么。
【讨论】:
【参考方案7】:别当庸医;我支持你:
"Duck typing" := "尝试方法,不检查类型"
注意::=
可以读作“被定义为”。
“Duck typing”的意思是:只要在任何进入的对象上尝试方法(函数调用),而不是先检查对象的类型,看看该方法是否是一个有效的调用这样的类型。
我们称之为“尝试方法,不检查类型”打字,“方法调用类型检查”,或者只是“方法-call typing” 简称。
在下面更长的解释中,我将更详细地解释这一点,并帮助您理解“鸭子打字”这个荒谬、深奥和混淆的术语。
更长的解释:
死了?死了! ?
Python 在上面做了这个概念。考虑这个示例函数:
def func(a):
a.method1()
a.method2()
当对象(输入参数a
)进入函数func()
时,函数将尝试(在运行时)调用该对象上指定的任何方法(即:method1()
和method2()
在上面的示例),而不是首先检查 a
是否是具有这些方法的某个“有效类型”。
因此,它是在运行时的基于动作的尝试,而不是在编译时或运行时的基于类型的检查。
现在看看这个愚蠢的例子:
def func(duck_or_duck_like_object):
duck_or_duck_like_object.quack()
duck_or_duck_like_object.walk()
duck_or_duck_like_object.fly()
duck_or_duck_like_object.swim()
因此诞生了这个荒谬的短语:
如果它走路像鸭子,叫起来像鸭子,那么它就是鸭子。
使用“duck typing”的程序应该简单地尝试在对象上调用的任何方法(在上面的示例中:quack()
、walk()
、fly()
和 swim()
)甚至不知道 对象的类型!它只是尝试方法!如果它们有效,那太好了,因为所有“鸭子打字”语言都知道或关心,IT(传递给函数的对象)是一只鸭子!——因为所有(鸭子样)方法都在它上面工作。
(总结我自己的话):
“鸭子类型”语言不应检查其类型(无论是在编译时还是运行时)——它不关心。它只会在运行时尝试这些方法。如果他们工作,那就太好了。如果他们不这样做,那么它将引发运行时错误。
这是鸭式打字。
我厌倦了这种荒谬的“鸭子”解释(因为没有这个完整的解释,它根本没有任何意义!),其他人听起来也是如此。示例:来自BKSpurgeon's answer here(我的重点是粗体):
(“如果它走路像鸭子,叫起来像鸭子,那它就是鸭子。”) - 是的!但这是什么意思??!”
现在我明白了:只要对进来的任何对象尝试该方法,而不是先检查对象的类型。
我称之为“运行时检查程序只是尝试调用的方法,甚至不知道对象是否有这些方法,而不是首先检查对象的类型知道对象的方法有这些方法”,因为这样更有意义。但是……说起来太长了,所以人们宁愿多年来互相混淆,而不是说一些荒谬但朗朗上口的话,比如“鸭子打字”。
让我们改为:“尝试方法,不检查类型” 打字。或者,也许:“方法调用类型检查”(简称“方法调用类型”),或“方法调用的间接类型检查” ,因为它使用给定方法的调用作为“足够证明”对象的类型是正确的,而不是直接检查对象的类型。
请注意,这种“方法调用类型检查”(或者容易混淆地称为“鸭子类型”)是一种动态类型。但是,并不是所有的动态类型都必须是“方法调用类型检查”,因为动态类型,或者说运行时的类型检查,也可以由来完成实际检查对象的类型,而不是简单地尝试在函数中调用对象所调用的方法而不知道其类型。
另请阅读:
-
https://en.wikipedia.org/wiki/Duck_typing --> 在页面中搜索“run”、“run time”和“runtime”。
【讨论】:
谢谢先生!我的意思是“好极了!!”这太棒了,应该得到更多的支持! 谢谢,这更有意义。那么 JavaScript 鸭子类型是什么? @manish,我不懂 JavaScript,所以我不能说。如果它是一种脚本语言,具有弱的、未指定的类型,并且在您传入参数时不会在函数调用中自动检查错误(无论是在编译时还是在运行时),那么答案是“是”。 Python 符合这个描述。当您将错误类型的参数传递给函数时,它不会引发错误,而是会引发运行时错误当您尝试对该对象类型调用某些无效方法并且它失败时。【参考方案8】:我知道我没有给出笼统的答案。在 Ruby 中,我们不声明变量或方法的类型——一切都只是某种对象。 所以规则是“类不是类型”
在 Ruby 中,类从不(好吧,几乎从不)类型。相反,对象的类型更多地由该对象可以做什么来定义。在 Ruby 中,我们称之为鸭子类型。如果一个物体像鸭子一样走路和像鸭子一样说话,那么解释器很乐意把它当作鸭子来对待。
例如,您可能正在编写一个例程来将歌曲信息添加到字符串中。如果你有 C# 或 Java 背景,你可能会想写这个:
def append_song(result, song)
# test we're given the right parameters
unless result.kind_of?(String)
fail TypeError.new("String expected") end
unless song.kind_of?(Song)
fail TypeError.new("Song expected")
end
result << song.title << " (" << song.artist << ")" end
result = ""
append_song(result, song) # => "I Got Rhythm (Gene Kelly)"
拥抱 Ruby 的鸭式打字,你会写出更简单的东西:
def append_song(result, song)
result << song.title << " (" << song.artist << ")"
end
result = ""
append_song(result, song) # => "I Got Rhythm (Gene Kelly)"
您不需要检查参数的类型。如果他们支持
【讨论】:
【参考方案9】:查看语言本身可能会有所帮助;它经常帮助我(我不是以英语为母语的人)。
在duck typing
:
1) typing
这个词并不意味着在键盘上打字(就像我脑海中的永久图像一样),它意味着确定“那个东西是什么类型的东西?”
2) 单词duck
表示确定是如何完成的;这是一种“松散”的确定,例如:“如果它像鸭子一样走路......那么它就是鸭子”。它是“松散的”,因为这东西可能是鸭子,也可能不是,但它是否真的是鸭子并不重要;重要的是我可以用它做我可以用鸭子做的事情,并期待鸭子表现出的行为。我可以喂它面包屑,它可能会朝我冲过来或冲向我或后退……但它不会像灰熊那样吞噬我。
【讨论】:
【参考方案10】:鸭子打字:
如果它说话和走路都像鸭子,那么它就是鸭子
这通常称为abduction(abduction推理或也称为retroduction,我认为这是一个更清晰的定义):
来自 C(结论,我们所看到的)和 R(规则,我们所知道的),我们接受/决定/假设 P(前提,属性),换句话说,一个给定的事实
...医学诊断的基础
与鸭子:C = 走路、说话、R = 像鸭子、P = 这是一只鸭子
回到编程:
对象o有方法/属性mp1和接口/类型T 需要/定义 mp1
对象o有方法/属性mp2和接口/类型T需要/定义mp2
...
所以,除了简单地接受任何对象上的 mp1...,只要它符合 mp1... 的某些定义...,编译器/运行时也应该没问题断言 o 的类型是 T
那么,上面的例子就是这种情况吗? Duck 打字本质上是不打字吗?还是我们应该称之为隐式类型?
【讨论】:
【参考方案11】:Duck Typing 不是 Type Hinting!
基本上,为了使用“鸭子类型”,您不会针对特定类型,而是更广泛的子类型(不是在谈论继承,当我指的是子类型时,我是指适合相同配置文件的“事物”)通过使用一个通用接口。
您可以想象一个存储信息的系统。为了写入/读取信息,您需要某种存储和信息。
存储类型可能是:文件、数据库、会话等
无论存储类型如何,该界面都会让您知道可用的选项(方法),这意味着此时什么都没有实现!换句话说,接口对如何存储信息一无所知。
每个存储系统都必须通过实现相同的方法来知道接口的存在。
interface StorageInterface
public function write(string $key, array $value): bool;
public function read(string $key): array;
class File implements StorageInterface
public function read(string $key): array
//reading from a file
public function write(string $key, array $value): bool
//writing in a file implementation
class Session implements StorageInterface
public function read(string $key): array
//reading from a session
public function write(string $key, array $value): bool
//writing in a session implementation
class Storage implements StorageInterface
private $_storage = null;
function __construct(StorageInterface $storage)
$this->_storage = $storage;
public function read(string $key): array
return $this->_storage->read($key);
public function write(string $key, array $value): bool
return ($this->_storage->write($key, $value)) ? true : false;
所以现在,每次你需要写/读信息时:
$file = new Storage(new File());
$file->write('filename', ['information'] );
echo $file->read('filename');
$session = new Storage(new Session());
$session->write('filename', ['information'] );
echo $session->read('filename');
在此示例中,您最终在 Storage 构造函数中使用 Duck Typing:
function __construct(StorageInterface $storage) ...
希望对您有所帮助;)
【讨论】:
【参考方案12】:使用鸭子类型技术进行树遍历
def traverse(t):
try:
t.label()
except AttributeError:
print(t, end=" ")
else:
# Now we know that t.node is defined
print('(', t.label(), end=" ")
for child in t:
traverse(child)
print(')', end=" ")
【讨论】:
【参考方案13】:我认为混淆了动态类型、静态类型和鸭子类型。 Duck 类型是一个独立的概念,即使像 Go 这样的静态类型语言,也可以有一个实现鸭子类型的类型检查系统。如果一个类型系统会检查(声明的)对象的方法而不是类型,它可以被称为鸭子类型语言。
【讨论】:
【参考方案14】:Duck Typing 这个词是一个谎言。
您会看到“如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那么它就是鸭子。”这句话在这里反复出现。
但这不是鸭式打字(或我们通常所说的鸭式打字)的意义所在。我们正在讨论的所有 Duck Typing 都是关于试图对某些东西强制执行命令。看东西是不是嘎嘎,不管它说的是什么。但是没有推论该对象是否是鸭子。
对于 true 鸭子类型,请参阅类型类。 现在遵循成语“如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那么它就是鸭子。”。对于类型类,如果一个类型实现了一个类型类定义的所有方法,那么它可以被认为是一个成员该类型类的(不必继承类型类)。因此,如果有一个类型类 Duck 定义了某些方法(quack 和 walk-like-duck),那么任何实现这些相同方法的东西都可以被认为是 Duck(没有需要继承 Duck)。
【讨论】:
【参考方案15】:在鸭子类型中,对象的适用性(例如,在函数中使用)取决于是否实现了某些方法和/或属性,而不是基于该对象的类型。
例如,在 Python 中,len
函数可以与任何实现 __len__
方法的对象一起使用。它不关心该对象是否属于某种类型,比如字符串、列表、字典或 MyAwesomeClass,只要这些对象实现了 __len__
方法,len
就可以使用它们。
class MyAwesomeClass:
def __init__(self, str):
self.str = str
def __len__(self):
return len(self.str)
class MyNotSoAwesomeClass:
def __init__(self, str):
self.str = str
a = MyAwesomeClass("hey")
print(len(a)) # Prints 3
b = MyNotSoAwesomeClass("hey")
print(len(b)) # Raises a type error, object of type "MyNotSoAwesomeClass" has no len()
换句话说,MyAwesomeClass
看起来像鸭子,叫起来像鸭子,因此是鸭子,而MyNotSoAwesomeClass
看起来不像鸭子,不叫,因此不是鸭子!
【讨论】:
【参考方案16】:^脚注:善意狩猎——鸭子打字场景
这里的完整场景:https://youtu.be/hIdsjNGCGz4
CHUCKIE:好的,我们会有问题吗?
克拉克:没问题。我只是希望你能给我一些关于鸭子类型实际上是什么的见解?我的论点是鸭式捆绑没有很好的定义,也不是很强大
WILL:[打断]……强类型也不是。当然这是你的论点。你是研究生一年级:你刚读完一些关于鸭子打字的文章,可能是在 *** 上,直到下个月当你到达四人帮时,你才会确信这一点,然后你就会将谈论 Google Go 和 Ocaml 如何成为具有结构子绑定结构的统计类型语言。这将持续到明年,直到你可能会在这里反刍 Matz,谈论你知道的 Pre-Ruby 3.0 乌托邦和子类型对 GC 的内存分配影响。
CLARK:[吃惊]事实上我不会,因为 Matz 严重低估了——
WILL:“Matz 大大低估了 Ruby 3.0 的 GC 对性能的影响。你从 Donald Knuth, The Art of Computer Programming, page 98 中得到了这一点,对吗?是的,我也读到了. 你会为我们剽窃整件事——你有任何想法——你自己在这件事上吗?或者你做——这是你的事,你走进一家酒吧,你读了一些关于 r/ruby 的晦涩的段落,然后你假装,你把它当成你自己的——你自己的想法只是为了给一些女孩留下好印象,让我的朋友难堪?
[克拉克惊呆了]
WILL:看到像你这样的人的可悲之处,大约 50 年后你会开始自己思考,你会想出一个事实,那就是生活中的三个确定性。一,不要那样做。第二,如果它像鸭子一样走路,那么它就是鸭子。第三,通过 Ben Koshy 的 SO 回答,您可以在公共图书馆以 0 美分获得的教育投入了 150 美元。
CLARK:是的,但我将获得学位,并且在我们去滑雪旅行的路上,你会在开车兜风时为我的孩子提供一些便宜的 html(通过 react)。
WILL:[微笑] 是的,也许吧。但至少我不会是非原创的。
(一拍)
WILL:你有什么问题吗?我想我们可以走出去,解决一些代码问题。
克拉克:没问题
一段时间后:
威尔:你喜欢苹果吗?
克拉克一头雾水。嗯?
威尔:你觉得他们的苹果怎么样? (Boom:Will 将一封信砸在窗户上。)我必须得到 Google 的报价! (向克拉克展示他的面试答案的录取通知书:关于什么是鸭子类型的正确回答是:“如果它像鸭子一样走路,那么它就是鸭子”。
结束
(这是old answer here的脚注:)
【讨论】:
【参考方案17】:我尝试以自己的方式理解这句名言: “Python 不在乎一个物体是不是真的鸭子。 它所关心的只是对象是否,首先是'嘎嘎',其次是'像鸭子'。”
有一个很好的网站。 http://www.voidspace.org.uk/python/articles/duck_typing.shtml#id14
作者指出,duck typing 可以让你创建自己的类 它们自己的内部数据结构——但使用普通的 Python 语法访问。
【讨论】:
这个答案是相当鸭打字的答案以上是关于什么是鸭子打字?的主要内容,如果未能解决你的问题,请参考以下文章