什么是鸭子打字?

时间:2010-11-17 14:03:35

标签: programming-languages duck-typing

我在阅读有关在线软件的随机主题时遇到了 duck typing 这个词,并没有完全理解它。

什么是“鸭子打字”?

16 个答案:

答案 0 :(得分:266)

dynamic languages中使用的术语没有strong typing

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

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

Wikipedia提供了更多信息。

答案 1 :(得分:175)

鸭子打字 表示操作未正式指定其操作数必须满足的要求,而只是尝试给出的内容。

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

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

如果没有使用duck-typing,执行此任务的函数f必须事先指定其参数必须支持某种方法Quack。一种常见的方法是使用接口

interface IQuack { 
    void Quack();
}

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

调用f(42)失败,但只要f(donald)donald - 子类型的实例,IQuack就会有效。

另一种方法是structural typing - 但同样,方法Quack()被正式指定为任何事先无法证明它quack的东西都会导致编译器失败。

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

我们甚至可以写

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

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

<小时/> 那么 duck typing 如何改变这个?

好吧,正如我所说,鸭子打字系统没有指定要求,但只是尝试任何工作

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

def f(x):
    x.Quack()

如果f得到x支持Quack(),一切都很好,如果没有,它会在运行时崩溃。

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

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

该函数不会以任何方式告诉它需要x Quack,因此只需在编译时尝试 ,如果一切正常,那就是细

答案 2 :(得分:91)

简单说明(无代码)

讨论这个问题的语义是相当细微的(而且非常学术化),但这是一般性的想法:

鸭子打字

(“如果它像鸭子一样走路,像鸭子那样嘎嘎叫,那就是鸭子。” - 是的!但是这意味着什么?!最好用例子说明:

示例:动态键入的语言

想象一下,我有一根魔杖。它有特殊的权力。如果我挥动魔杖并对汽车说“驾驶!”,那么它就开车了!

它适用于其他事情吗?不确定:所以我在卡车上试试。哇 - 它也开车了!然后我在飞机,火车和1个伍兹(它们是一种人们用来“驾驶”高尔夫球的高尔夫球杆)上尝试。 他们都开车了!

但它会起作用吗,一个茶杯?错误:KAAAA-BOOOOOOM!没那么好的。 ====&GT;茶杯不能开车!!咄!?

这基本上是鸭子打字的概念。这是一个先试后买系统。如果它有效,一切都很好。但如果它失败了,那么它会在你脸上爆炸。

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

示例:静态类型语言

如果我们关注对象实际上是什么,那么我们的魔术技巧将仅适用于预先设定的授权类型 - 在这种情况下是汽车,但将在其他对象上失败它可以驱动:卡车,轻便摩托车,嘟嘟车等。它不适用于卡车,因为我们的魔杖预计 仅适用于汽车

换句话说,在这种情况下,魔杖会非常仔细地看待对象(它是汽车吗?)而不是对象可以做什么 (例如,汽车,卡车等是否可以驾驶)。

你可以让卡车开车的唯一方法就是你能以某种方式获得魔杖以期望卡车汽车(可能是通过“实现一个共同的界面”)。如果您不知道这意味着什么,那么暂时忽略它。

摘要:重点外卖

鸭子打字的重要之处在于对象实际上可以做什么,而不是对象

答案 3 :(得分:22)

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

  1. 这是我的功能,我必须确保它只接受Bird,否则他们的代码将无法编译。如果有人想使用我的功能,他必须知道我只接受Bird s
  2. 我的函数获取objects,我只需调用对象的walk()方法。因此,如果object可以walk()它是正确的,如果它不能,我的功能将失败。所以此处对象是Bird或其他任何内容并不重要,重要的是它可以walk()(这是 duck typing
  3. 必须考虑到 duck typing 在某些情况下可能很有用,例如Python使用 duck typing

    有用的阅读

答案 4 :(得分:16)

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

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

  鸭子打字是一种动态的风格   输入一个对象的当前值   一组方法和属性   确定有效的语义   而不是它从特定的继承   特定的类或实现   接口

重要的注意事项可能是,对于鸭子类型,开发人员更关心的是消耗的对象部分而不是实际的底层类型。

答案 5 :(得分:4)

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

  

如果它看起来像鸭子,而象鸭子一样嘎嘎叫,那就是鸭子

然后深入说明如何使用鸭子打字,或者通过一个示例进一步混淆该概念。

我找不到太多帮助。

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

Duck Typing(鸭子键入)表示对象是由它可以做什么而不是由对象定义的 是什么。

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

答案 6 :(得分:3)

“鸭子打字”:=“尝试方法,不要检查类型”

鸭子输入”的意思是:只需在传入的任何对象上尝试该方法(函数调用),而不是先检查该对象的类型以查看该方法是否是有效调用这种类型。

让我们称之为“尝试方法,不要检查类型”打字,“方法调用类型检查”,或者只是“方法- 简称打字“

在下面更长的解释中,我将更详细地解释这一点,并帮助您理解荒谬、深奥和混淆的术语“鸭子打字”。

注意::= 可以读作“被定义为”


更长的解释:

去死?去死! ?

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

因此诞生了荒谬的短语:

<块引用>

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

使用“鸭子类型”的程序将简单地尝试在对象上调用的任何方法(在上面的示例中:quack()walk()fly())甚至不知道对象的类型!它只是尝试方法!如果它们有效,那就太好了,因为所有“鸭子类型”语言都知道或关心,IT(传递给函数的对象)就是 DUCK!--因为所有(类似鸭子的)方法都适用于它。

(总结我自己的话):

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

那是鸭子打字。

我厌倦了这种荒谬的“鸭子”解释(因为没有这个完整的解释,它根本没有任何意义!),其他人也是如此。示例:来自 BKSpurgeon's answer here(我用粗体强调):

<块引用>

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

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

我将称之为“运行时检查,其中程序只是尝试调用的方法,甚至不知道对象是否具有这些方法,而不是首先检查对象的类型作为了解对象的方法有这些方法”,因为那样更有意义。但是……说得太长了,所以人们宁愿互相混淆多年,而不是说诸如“鸭子打字”之类的可笑但引人入胜的事情。

让我们改为这样称呼:“尝试方法,不要检查类型”打字。或者,也许:“方法调用类型检查”(简称“方法调用类型”),或“方法调用的间接类型检查” ,因为它使用给定方法的调用作为“足以证明”对象是正确类型的证据,而不是直接检查对象的类型。

请注意,这种“方法调用类型检查”(否则容易混淆地称为“鸭子类型”)是一种动态类型。但是,并非所有的动态类型都必须是“方法调用类型检查”,因为动态类型,或运行时的类型检查,也可以由完成实际检查对象的类型,而不是简单地尝试调用函数中对该对象调用的方法,而不知道其类型。

另请阅读:

  1. https://en.wikipedia.org/wiki/Duck_typing --> 在页面上搜索“run”、“run time”和“runtime”。

答案 7 :(得分: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)"

您无需检查参数的类型。如果他们支持&lt;&lt; (在结果的情况下)或标题和艺术家(在歌曲的情况下),一切都将工作。如果他们不这样做,你的方法无论如何都会抛出异常(正如你检查过类型时那样)。但是没有检查,你的方法突然变得更加灵活。你可以传递一个数组,一个字符串,一个文件或任何其他使用&lt;&lt;附加的对象,它就可以工作。

答案 8 :(得分:2)

鸭子打字:

  

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

这通常被称为 abduction abductive 推理或者也称为 retroduction ,我认为这是一个更清晰的定义):

  • 来自 C (结论,我们看到的)和 R (规则,我们所知道的 >),我们接受/决定/假设 P (前提,属性)换言之,给定事实

    ......医学诊断的基础

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

回到编程:

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

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

  • ...

所以,不仅仅是在任何对象上接受 mp1 ...只要它符合 mp1 的某些定义......,编译器/运行时也应该没问题断言 o 的类型为 T

嗯,上面的例子是这样吗? Duck打字基本上没有打字吗?或者我们应该将其称为隐式输入?

答案 9 :(得分:2)

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

duck typing

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

2)duck这个词表达了如何做出决定;它是一种“松散”的决定,如:“如果它像鸭子一样行走......那么它就是一只鸭子”。它是“松散的”,因为它可能是鸭子或者可能不是,但它是否真的是鸭子并不重要;重要的是我可以用它来做我能用鸭子做的事情,并期待鸭子表现出的行为。我可以给面包屑喂食它,这东西可能会朝向我或向我充电或退回......但它不会像灰熊那样吞噬我。

答案 10 :(得分:2)

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

希望它有所帮助;)

答案 11 :(得分:1)

使用鸭子打字技术遍历树木

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

答案 12 :(得分:0)

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

答案 13 :(得分:0)

“鸭子打字”一词是谎言<​​/ strong>。

您会看到这样的成语:“如果它走路像鸭子,嘎嘎叫鸭子,那就是鸭子。”这句话会不时重复出现。

但这不是鸭子输入(或我们通常称为鸭子输入)的含义。我们正在讨论的Duck Typing所要做的只是试图对某事强制执行命令。看看事物是否发出嘎嘎声,无论它说的是什么。但是没有关于物体是否是鸭子的推论。

有关 true 鸭子输入的信息,请参见类型分类。 现在遵循成语“如果它像鸭子一样走路,而像鸭子一样嘎嘎叫,那么它就是鸭子。”对于类型类,如果类型实现了由类型类定义的所有方法,则可以认为它是成员因此,如果有一个类型类Duck定义了某些方法(嘎嘎和类似鸭子的鸭子),则实现这些相同方法的任何东西都可以被视为Duck(而无需继承该类型类)。需要继承鸭子)。

答案 14 :(得分:0)

在鸭子类型中,对象的适用性(例如,要在函数中使用)是根据是否实现某些方法和/或属性而不是基于该对象的类型来确定的。

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

答案 15 :(得分:-2)

我尝试以自己的方式理解著名的句子: “ Python不在乎对象是否是真正的鸭子。 它关心的只是对象是第一个“嘎嘎”,第二个“像鸭子”。”

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

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