什么是鸭打字?


429

我在网上阅读随机主题时遇到了鸭子打字一词,但并没有完全理解。

什么是“鸭子打字”?


1
@Mitch我尝试并得到了某种形式的继承。但是跟不上。对不起,如果我问错了问题。
sushil bharwani

3
@sushil bharwani:不,不生气。但是人们希望,作为第一个拨打电话的港口(即,您要做的第一件事)是尝试在此处发布之前进行搜索。
米奇·

104
鉴于上述论点,似乎并不一定需要stackoverflow,因为我确信几乎每个人可能想到的问题都会在互联网上某个地方得到解答,如果没有,则可以通过电子邮件将其轻松获得,而无需提出批评,知识渊博的朋友。我想你们中的许多人都错过了stackoverflow的要点。
rhody 2014年

41
我确信我已经读过某处SO旨在成为“规范问题的资料库”,而且我敢肯定,您获得的规范不能超过此规范。
heltonbiker

Answers:


302

它是没有强类型输入的动态语言中使用的术语。

这个想法是您不需要类型来调用对象上的现有方法-如果在对象上定义了方法,则可以调用它。

该名称来自短语“如果它看起来像鸭子,而像鸭子一样嘎嘎叫,那是鸭子”。

维基百科有更多信息。


25
警惕使用强类型。它的定义不是很好。鸭打字也不是。Google Go或Ocaml是具有结构子类型构造的静态类型语言。这些是鸭式语言吗?
我给予解答答案2010年

7
鸭子打字的一个更好的说法是:“如果说它是鸭子,那对我来说已经足够了。” 参见pyvideo.org/video/1669/keynote-3 28:30或youtube.com/watch?v=NfngrdLv9ZQ#t=1716
tovmeod

7
鸭子类型不一定只在动态语言中使用。Objective-C不是动态语言,它使用鸭子类型。
eyuelt

12
Python和Ruby都是强类型语言,并且都具有Duck Typing。字符串输入并不意味着没有鸭子输入。
alanjds 2014年

8
我对此表示反对。Duck Ducking与类型的强度无关,只是能够使用具有方法的任何对象(无论是否实现接口)的能力。
e-satis 2015年

209

鸭子类型 意味着操作不会正式指定其操作数必须满足的要求,而只是使用给定的值进行尝试

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

示例任务:Quack在对象上调用某些方法。

在不使用鸭子模式的情况下,f执行此任务的函数必须预先指定其参数必须支持某种方法Quack。常见的方法是使用接口

interface IQuack { 
    void Quack();
}

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

调用f(42)失败,但是f(donald)只要donaldIQuack-subtype 的实例,调用就可以工作。

另一种方法是结构类型化 -但同样,该方法Quack()被正式指定为任何无法quack提前证明的内容都会导致编译器失败。

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

我们甚至可以写

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

在Haskell中,Quackable类型类可确保我们方法的存在。


那么鸭子的打字如何改变呢?

好吧,就像我说的那样,鸭子输入系统并没有指定要求,而是尝试一切正常

因此,Python的动态类型系统始终使用鸭子类型:

def f(x):
    x.Quack()

如果f得到a的x支持Quack(),那么一切都很好,否则,它将在运行时崩溃。

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

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

该函数不会以任何方式告知x需要的对象Quack,因此它只是在编译时尝试如果一切正常,就可以了。


5
您的意思不是:void f(IQuak x){x.Quak(); }(而不是K.Quack),因为函数f的参数是IQuack x而不是Iquack k,很小的错误,但我觉得需要纠正:)
dominicbri7

根据Wikipedia的说法,您的最后一个示例是“结构类型”,而不是“鸭子类型”。
Brilliand 2014年

那么,它似乎有一个为这一讨论另外一个问题:stackoverflow.com/questions/1948069/...
Brilliand

1
因此,如果我理解您的意思,支持鸭子类型的语言与不支持鸭子类型的语言之间的区别仅在于鸭子类型,您不必指定函数接受的对象类型吗?def f(x)代替def f(IQuack x)
PProteus

124

简单说明(无代码)

关于问题的语义的讨论相当细微(也是非常学术性的),但这是一般的想法:

鸭打字

(“如果它走路像鸭子,而嘎嘎像鸭子,那就是鸭子。”)- 是的!但是,这是什么意思??!最好通过示例说明:

鸭打字功能示例:

想象一下我有一根魔杖。它具有特殊的力量。如果我挥动魔杖说“开车!” 上车,然后开车!

它可以在其他方面起作用吗?不确定:所以我在卡车上尝试了。哇-它也开车!然后,我在飞机,火车和1 Woods(它们是人们用来“驱动”高尔夫球的一种高尔夫俱乐部)上尝试一下。他们都开车!

但这可以用来制作茶杯吗?错误:KAAAA-BOOOOOOM!效果不是很好。====>茶杯不能开车!h !?

这基本上就是鸭子打字的概念。这是一个先试后买的 系统。如果可行,一切都很好。但是,如果失败了,就像手榴弹仍在您的手中,它将在您的脸上炸毁。

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

示例:静态类型语言

如果我们关注的是物体的真正含义,那么我们的魔术将仅适用于预先设定的授权类型(在这种情况下为汽车),但对其他可以行驶的物体(卡车,轻便摩托车,嘟嘟车等)则无效。它不能在卡车上使用,因为我们的魔杖期望它只能在汽车上使用

换句话说,在这种情况下,魔术棒非常仔细地看着物体什么(是汽车吗?),而不是物体可以做什么(例如,汽车,卡车等是否可以驾驶)。

驾驶卡车的唯一方法是,如果您能以某种方式让魔术棒同时指望卡车汽车(也许通过“实现通用接口”)。如果您不知道这意味着什么,请暂时将其忽略。

摘要:钥匙取出

是什么在鸭打字重要的是什么对象其实可以做的,而不是对象是什么


我发现有趣的前提是,您更在乎行为,这就是定义。毫无疑问,BDD在诸如ruby之类的语言中是如此成功。
Pablo Olmos de Aguilera C.

27

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

  1. 这是我的功能,我必须确保它仅接受Bird,否则它们的代码将无法编译。如果有人想使用我的功能,他必须知道我只接受Birds
  2. 我的函数可以得到任何值,objects而我只调用对象的walk()方法。因此,如果object可以walk(),则不能,我的功能将失败。因此,这里的对象是a Bird或其他任何东西并不重要,它可以是很重要的walk() (这是鸭子类型

必须考虑到在某些情况下鸭子类型可能有用,例如Python 大量使用了鸭子类型


有用的阅读


1
很好的解释,有什么好处?
sushil bharwani 2015年

2
这个答案很简单,清晰,可能是初学者的最佳选择。阅读此答案以及其上方的答案(或者,如果它移动了,则涉及汽车和茶杯的答案)
DORRITO

18

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

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

鸭子类型是动态类型的一种类型,其中对象的当前方法和属性集确定有效的语义,而不是其从特定类或特定接口的实现继承。

重要的提示可能是,使用鸭子类型时,开发人员会更加关注对象的消耗部分,而不是实际的基础类型。


13

我看到很多重复旧习语的答案:

如果它看起来像鸭子而嘎嘎像鸭子,那就是鸭子

然后深入说明如何使用鸭打字进行解释,或者深入研究一个可能进一步混淆该概念的示例。

我找不到太多帮助。

这是对我发现的关于鸭类打字的简单英语答案的最佳尝试:

Duck Typing表示对象是由它可以做什么而不是由它是什么定义的。

这意味着我们不太关心对象的类/类型,而更关心可以在对象上调用什么方法以及可以对其执行什么操作。我们不在乎它的类型,我们在乎它可以做什么


3

鸭子打字:

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

这通常被称为诱拐溯因推理或也称为,更明确的定义,我认为):

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

    ...医学诊断的基础

    鸭子:C = 走路,说话R = 像鸭子P = 是鸭子

返回编程:

  • 对象o具有方法/属性mp1和接口/类型T 要求/定义mp1

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

  • ...

因此,只要满足某个mp1 ...的定义,就不仅仅在任何对象上接受mp1 ... ,编译器/运行时也应该可以接受断言oT类型的

很好,上面的示例就是这种情况吗?鸭子打字本质上根本不是打字吗?还是应该称其为隐式键入?


3

查看语言本身可能会有所帮助;它通常对我有帮助(我不是说英语的人)。

duck typing

1)这个词typing并不意味着在键盘上打字(就像我脑海中持久的形象一样),而是意味着确定“ 那是什么类型的东西?

2)这个词duck表示确定是如何完成的;这是一种“松散”的确定,例如:“ 如果它走路像鸭子一样……那是鸭子 ”。之所以“宽松”,是因为它可能是鸭子,也可能不是鸭子,但实际上它是否是鸭子并不重要。重要的是我可以用它来做鸭子,也可以期望鸭子所表现出的行为。我可以给它喂面包屑,东西可能会冲向我或冲向我或让它退缩……但是它不会像灰熊一样吞噬我。


2

我知道我没有给出广义的答案。在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)"

您不需要检查参数的类型。如果他们支持<<(对于结果而言)或标题和艺术家(对于歌曲而言),那么一切将正常进行。如果没有,则您的方法仍然会引发异常(就像检查类型一样)。但是如果没有检查,您的方法会突然变得灵活得多。您可以向其传递一个数组,一个字符串,一个文件或其他任何使用<<附加的对象,它就可以正常工作。


2

鸭打字不是提示类型!

基本上,为了使用“鸭子类型”,您将不使用特定类型,而是使用更广泛的子类型(不是在讨论继承,当我指的是子类型时,我指的是适合同一配置文件的“事物”)。 。

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

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

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

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

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) ...

希望它有所帮助;)


2

鸭子打字技术遍历树

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

0

我认为混淆动态输入,静态输入和鸭子输入是很困惑的。鸭子输入是一个独立的概念,甚至像Go这样的静态类型语言也可以具有实现鸭子输入的类型检查系统。如果类型系统将检查(声明的)对象的方法而不是类型,则可以将其称为鸭子输入语言。


-1

我试图以我的方式理解这句著名的句子:“ Python不在乎对象是否是真正的鸭子。它所关心的只是对象,首先是“嘎嘎”,其次是“像鸭子”。

有一个很好的网站。http://www.voidspace.org.uk/python/articles/duck_typing.shtml#id14

作者指出,鸭子类型可以让您创建自己的类,这些类具有自己的内部数据结构-但可以使用常规Python语法进行访问。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.