鸭子打字是什么?

鸭子打字在软件开发中意味着什么?

172889 次浏览

它是在动态语言中使用的术语,没有强类型

其思想是,为了调用对象上的现有方法,您不需要指定类型——如果在对象上定义了方法,则可以调用它。

这个名字来自短语“如果它看起来像鸭子,叫起来像鸭子,它就是鸭子”。

维基百科有更多的信息。

维基百科有相当详细的解释:

http://en.wikipedia.org/wiki/Duck_typing

鸭子打字是一种动态的风格 输入一个对象的电流 方法和属性集 而是确定有效的语义 比它从一个特定的继承 类的实现 接口。< / p >

重要的是,使用duck类型时,开发人员可能更关心被使用的对象部分,而不是实际的底层类型是什么。

< em > Duck typing 这意味着一个操作满足操作数必须满足的没有正式指定要求,但只满足给定的尝试一下要求

与其他人所说的不同,这并不一定与动态语言或继承问题有关。

在对象上调用方法Quack

没有使用duck-typing,执行此任务的函数f必须预先指定它的参数必须支持一些方法Quack。常用的方法是使用接口

interface IQuack {
void Quack();
}


void f(IQuack x) {
x.Quack();
}

调用f(42)失败,但是只要donald是# eyz3子类型的实例,f(donald)就可以工作。

另一种方法是结构输入< em > < / em > -但同样,Quack()方法是正式指定的,任何不能提前证明quacks的方法都会导致编译器失败。

def f(x : { def Quack() : Unit }) = x.Quack()

我们甚至可以写成

f :: Quackable a => a -> IO ()
f = quack

在Haskell中,Quackable类型类确保了方法的存在。


那么**鸭子输入**是如何改变这种情况的呢?

好吧,正如我所说,鸭子输入系统不指定需求,而是试试看有没有什么有用的

因此,像Python这样的动态类型系统总是使用duck类型:

def f(x):
x.Quack()

如果f得到一个支持Quack()x,一切都很好,如果不是,它将在运行时崩溃。

但是鸭子类型并不意味着动态类型——事实上,有一种非常流行但完全静态的鸭子类型方法,它也没有给出任何要求:

template <typename T>
void f(T x) { x.Quack(); }

该函数没有以任何方式告诉它想要一些x,可以Quack,所以它只是尝试在编译时,如果一切正常,就没问题。

我知道我不是在给出一般化的答案。在Ruby中,我们不声明变量或方法的类型——所有东西都是某种类型的对象。 所以规则是“类不是类型”

在Ruby中,类从来都不是(好吧,几乎从来都不是)类型。相反,对象的类型更多地由该对象可以做什么来定义。在Ruby中,我们称之为duck typing。如果一个对象像鸭子一样走路,像鸭子一样说话,那么解释器很乐意把它当作鸭子来对待。

例如,您可能正在编写一个例程,将歌曲信息添加到字符串中。如果你有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)"

你不需要检查参数的类型。如果他们支持<<(在结果的情况下)或标题和艺术家(在歌曲的情况下),一切都会正常工作。如果没有,你的方法无论如何都会抛出一个异常(就像你检查了类型一样)。但如果没有检查,你的方法突然变得灵活多了。你可以给它传递一个数组,一个字符串,一个文件,或者任何其他使用<<追加的对象,它就可以工作了。

假设您正在设计一个简单的函数,该函数获取Bird类型的对象并调用它的walk()方法。你可以考虑两种方法:

  1. 这是我的函数,我必须确保它只接受Bird类型,否则代码将无法编译。如果有人想使用我的函数,他们必须知道我只接受Birds。
  2. 我的函数得到任何objects,我只是调用对象的walk()方法。所以,如果object可以walk(),那么它是正确的。如果不能,我的函数就会失败。所以,在这里,对象是Bird或其他东西并不重要,重要的是它可以walk()(这是duck typing)。

必须考虑到duck typing在某些情况下可能有用。例如,Python经常使用duck typing


读有用的书

简单的解释

鸭子打字是什么?

“如果它走路像鸭子,嘎嘎叫像....- 是的,但那是什么意思??!

我们感兴趣的是什么“对象”;< >强能做< / >强,而不是他们<强> < / >强

让我们用一个例子来分解它:

 Duck Typing的解释

详情见下文:

Duck Typing功能示例:

想象我有一根魔杖。它有特殊的力量。如果我挥舞魔杖,对一辆车说“开车!”,那么,它就会开车!

它对其他东西有用吗?不确定,所以我在卡车上试了试。哇,它也能开车!然后我在飞机上、火车上和1 Woods(这是一种人们用来“驾驶”高尔夫球的高尔夫球杆)上尝试了一下。# EYZ0

但它能用在茶杯上吗?错误:KAAAA-BOOOOOOM !结果不太好。= = = =比;茶杯不能开车!!咄! ?

这就是duck typing的基本概念。这是一个先试系统。如果有效,一切都好。但如果失败了,就像手榴弹还在你手上一样,它会在你脸上爆炸。

换句话说,我们感兴趣的是对象可以做,而不是对象是什么

那么像c#或Java等语言呢?

如果我们关心的是这个物体到底是什么,那么我们的魔术只会对预先设置的,授权的类型起作用-在这种情况下,汽车,但会失败的其他对象可以驱动:卡车,轻便摩托车,突突车等。它不会在卡车上工作,因为我们的魔杖期待它只适用于汽车

换句话说,在这个场景中,魔杖非常仔细地观察对象(它是一辆汽车吗?),而不是对象可以做(例如,汽车、卡车等是否可以驾驶)。

让卡车驾驶的唯一方法是,如果你能以某种方式让魔杖期待两辆卡车而且汽车(也许是通过“实现一个公共接口”)。这可以通过一种叫做“多态性”的技术来实现。或者使用“接口”;-它们是一样的。如果你喜欢动画片,想要一个解释,看看我的界面上的卡通

总结:关键外卖

鸭子输入中最重要的是对象实际上可以做什么,而不是对象

序言

我试图通过去除迂腐的细微差别和学术语言来保持它的简单/有趣。这种方法并不适合所有人——如果你更喜欢学术定义,可以看看维基百科文章,以避免输入,或者马特·达蒙(Matt Damon)对避免输入心灵捕手的解释;)

Duck typing:

如果它像鸭子一样说话和走路,那么它就是一只鸭子

这通常被称为绑架 (诱导的推理或也称为retroduction,我认为一个更清晰的定义):

  • C(结论,我们所看到的)和R(规则,我们所知道的),我们接受/决定/假设P(前提,财产)换句话说,一个给定的事实

    ... 医学诊断的基础

    与鸭子:C = 走路、谈话R = 像鸭子一样P = 这是一只鸭子

回到编程:

  • 对象o有方法/属性mp1和接口/类型T 要求/定义# EYZ0 < / p > < /李>

  • object o有方法/属性mp2和接口/类型T要求/定义mp2

  • ...

因此,不仅仅是简单地接受mp1…在任何对象上,只要它满足mp1的一些定义…,编译器/运行时也应该是正确的断言o是类型T

上面的例子是这样的吗?Duck输入实质上就是没有输入吗?或者我们应该称之为隐式类型?

看看语言本身可能会有所帮助;它经常帮助我(我的母语不是英语)。

在# EYZ0:

1)单词typing并不意味着在键盘上输入(因为在我脑海中挥之不去的图像),它意味着确定“那是什么类型的东西?

2)单词duck表示如何决定;这是一种“松散”的决定,比如:“如果它像鸭子一样走路……这是一只鸭子”。之所以说“松散”,是因为这个东西可能是一只鸭子,也可能不是,但它是否真的是一只鸭子并不重要;重要的是我能像对待鸭子一样对待它,期待鸭子表现出的行为。我可以喂它面包屑,它可能会向我扑来,向我冲来,或者后退……但它不会像灰熊那样把我吃掉。

鸭子打字不是类型提示!

基本上,为了使用“duck typing”,你不会针对特定的类型,而是通过使用公共接口来针对更广泛的子类型(不是谈论继承,当我指的子类型时,我指的是适合相同配置文件的“事物”)。

你可以想象一个存储信息的系统。为了读写信息,你需要某种存储空间和信息。

存储类型可以是:文件、数据库、会话等。

无论存储类型是什么,该接口都会让您知道可用的选项(方法),这意味着在这一点上什么都没有实现!换句话说,接口不知道如何存储信息。

每个存储系统都必须通过实现接口的相同方法来知道接口的存在。

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');

在这个例子中,你最终在存储构造函数中使用Duck Typing:

function __construct(StorageInterface $storage) ...

希望能有所帮助;)

用鸭子打字技术的树遍历

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=" ")

我看到很多答案都在重复这句老话:

如果它长得像鸭子,叫声也像鸭子,那它就是鸭子

然后深入解释可以使用duck typing做什么,或者一个似乎进一步混淆了概念的示例。

我觉得没什么用。

这是我发现的关于鸭子打字的最好的简单英语回答:

Duck Typing意味着一个对象是由它能做什么来定义的,而不是由

这意味着我们不太关心对象的类/类型,而更关心可以对其调用哪些方法以及可以对其执行哪些操作。# EYZ0。

我认为把动态类型、静态类型和鸭子类型混在一起很容易混淆。Duck typing是一个独立的概念,即使是像Go这样的静态类型语言,也可以有一个实现Duck typing的类型检查系统。如果类型系统检查(已声明的)对象的方法而不检查类型,则可以将其称为duck类型语言。

我试图用自己的方式理解这句名言: “Python并不关心对象是否是真正的鸭子。 它只关心这个物体,首先“嘎嘎”,其次“像鸭子一样”。" < / p >

有一个很好的网站。# EYZ0

作者指出duck typing允许你创建自己的类 它们自己的内部数据结构-但使用正常的Python语法访问

Duck Typing这个术语是谎言

你看到过这个成语“如果它走路像鸭子,叫起来像鸭子,那么它就是鸭子”。这在这里一次又一次地重复。

但这并不是鸭子打字(或者我们通常所说的鸭子打字)的意思。所有我们正在讨论的鸭子打字,是试图强制命令的东西。看看有什么东西是不是嘎嘎叫,不管它说什么。但是并没有推论出这个物体是不是鸭子。

对于真正的鸭子打字,见类型的类。 这句话遵循了成语“如果它走路像鸭子,叫起来像鸭子,那么它就是鸭子”。使用类型类,如果一个类型实现了由类型类定义的所有方法,那么它可以被认为是该类型类的成员(而不必继承类型类)。因此,如果有一个类型类Duck定义了某些方法(quack和walk-like-duck),那么任何实现这些相同方法的东西都可以被认为是Duck(不需要继承Duck)

在duck类型中,对象的适用性(例如,在函数中使用)取决于是否实现了某些方法和/或属性,而不是基于该对象的类型。

例如,在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看起来不像鸭子,也不嘎嘎叫,因此不是一只鸭子!

不要做庸医;我支持你:

“鸭typing":=“尝试方法,不要检查类型”;

注意::=可以读作"定义为"

“# EYZ0"意思是:# EYZ1

让我们将其称为尝试方法,不要检查类型;键入,“方法调用type-checking",或简称为“方法调用typing"

在下面的更长的解释中,我将更详细地解释这一点,并帮助您理解荒谬、深奥和令人困惑的术语“duck 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()

于是就有了这个荒谬的短语:

如果它走路像鸭子,叫起来像鸭子,那么它就是鸭子。

使用“鸭子打字”的程序;应该简单地尝试对象上调用的任何方法(在上面的例子中:quack()walk()fly()swim()),甚至不知道对象的类型 !它只是尝试方法!如果他们工作,很好,所有的“鸭子打字”;语言知道或关心,它(传递给函数的对象)是一只鸭子!——因为所有(类似鸭子的)方法都管用。

(总结我自己的话):

“鸭型”;语言不应该检查它的类型(无论是在编译时还是运行时)——它不关心。它只会在运行时尝试这些方法。如果有效,那就太好了。如果没有,那么它将抛出一个运行时错误。

这就是鸭子打字。

我受够了这个可笑的“鸭子”解释(因为如果没有这个完整的解释,它就没有任何意义!),其他人听起来也是如此。示例:来自BKSpurgeon的答案在这里(我用粗体强调):

(“如果它走路像鸭子,叫起来像鸭子,那么它就是鸭子。”)- 是的!但这是什么意思??!

现在我明白了:只要在传入的任何对象上尝试该方法,而不是首先检查对象的类型。

我将其命名为“运行时检查,程序只是尝试调用的方法,甚至不知道对象是否具有这些方法,而不是首先检查对象的类型作为知道对象具有这些方法的手段”;,因为这样更有意义。但是…这太长了,所以人们宁愿彼此混淆多年,而不是说一些荒谬但朗朗上口的东西,比如“鸭子打字”。

让我们将其改为:尝试方法,不要检查类型; typing。或者,可能是:“方法调用type-checking"(简称“方法调用typing"),或间接类型检查方法calls",因为它使用给定方法的调用作为“足够的证明”;对象的类型是正确的,而不是直接检查对象的类型。

注意,这个“方法-调用类型检查”;(或者叫“duck typing”)是动态类型类型。但是,并不是所有的动态类型都必须是“方法调用类型检查”,因为动态类型,或运行时的类型检查,也可以由实际上检查对象的类型来完成,而不是简单地尝试在不知道它的类型的情况下调用函数中对象上调用的方法。

读也:

  1. # EYZ0——比;在页面中搜索“运行时间”、“运行时间”和“运行时间”。

马特·达蒙解释了《心灵捕手》中的鸭子打字

笔录如下:

好吧,我们会有问题吗?

没有问题。我只是希望你能告诉我鸭子打字到底是什么?我的观点是,鸭系没有很好的定义,也不强

(打断)……强输入也不是。当然这是你的论点。如果你是一年级的研究生:你刚刚读完了一些关于duck typing的文章,可能是在StackOverflow上,你会一直相信这一点,直到下个月当你读到四人帮时,然后你会谈论谷歌Go和Ocaml是如何使用结构子绑定结构的统计类型语言。这将持续到明年,直到你可能会在这里重复Matz,谈论,你知道,Pre-Ruby 3.0乌托邦和GC上的子类型的内存分配效果。

克拉克:(吓了一跳)事实上我不会,因为马茨大大低估了——的影响

Matz严重低估了Ruby 3.0的GC对性能的影响。你是从Donald Knuth的《计算机编程艺术》第98页学到的,对吧?是的,我也读过。你打算为我们剽窃整篇文章吗——你对这件事有什么想法吗?或者,这是你的做法吗,你进入堆栈溢出,你读了r/ruby上一些晦涩的段落,然后你假装,你把它当作你自己的——你自己的想法,只是为了取悦一些女孩,让我的朋友难堪?

[克拉克惊呆了]

:像你这样的人的可悲之处在于,大约50年后,你会开始自己思考,你会发现生活中有三件事是确定的。第一,不要那样做。第二,如果它走路像鸭子,那它就是鸭子。第三,你花了十五万美元接受本·科西本可以零美分就能得到的教育。

是啊,但我会有学位,而你会在我们去滑雪的路上,在汽车餐厅给我的孩子们提供一些廉价的html。

:[微笑]是的,也许吧。但至少我不会没有创意。

(打)

威尔:你有意见吗?我想我们可以出去做一些关于代码的练习?

克拉克:没有问题

一段时间后:

你喜欢苹果吗?

克拉克说,嗯?

你觉得这些苹果怎么样?(砰:威尔把一封信重重地贴在窗户上。)我要谷歌的报价!(给克拉克看录取通知书,上面有他的面试答案:一张鸭子走路的照片。

< h2 > # EYZ0。 最后。< / h2 >

(这是老答案在这里的一个脚注:)

Duck Typing:

let anAnimal


if (some condition)
anAnimal = getHorse()
else
anAnimal = getDog()


anAnimal.walk()

上述函数调用在结构类型中无效

以下将适用于结构类型:

IAnimal anAnimal


if (some condition)
anAnimal = getHorse()
else
anAnimal = getDog()
    

anAnimal.walk()

这就是所有的,我们中的许多人已经直观地知道鸭子打字。

在编程中,类型可以分为名义类型和结构类型。名义类型考虑类型的整个结构。从谁那里继承的等等。它们显然比结构类型更复杂。例如,在c#和Java中使用名义类型。

结构类型根本不考虑这些问题。对于结构类型,只有单一类型的结构是重要的。这是什么样子。在一个类的例子中。它是否具有相同的参数和预期的类型。这叫做鸭子打字。Duck Typing起源于Duck Test。

Dugtest的意思是:如果某物看起来像鸭子。如果它像鸭子一样游泳。如果它像鸭子一样嘎嘎叫。然后它是一只鸭子。相反地:如果一个测试用例不应用,那么它就不是鸭子。(# EYZ0)