ChatGPT解决这个技术问题 Extra ChatGPT

什么是鸭子打字?

duck typing 在软件开发中意味着什么?

@Mitch 我尝试并得到了某种形式的继承。但不能跟随太多。对不起,如果我问错了问题。
@sushil bharwani:不,不生气。但是人们期望作为第一个停靠港(即您做的第一件事)是在发布之前尝试搜索。
考虑到上面的论点,stackoverflow 似乎实际上并不是必需的,因为我确信几乎每个人可能想到的问题都在互联网上的某个地方得到了回答,如果不是这样,答案可能会更容易获得,而且不会通过电子邮件进行批评知识渊博的朋友。我想你们中的许多人都错过了 stackoverflow 的重点。
我确定我在某处读过 SO 旨在成为“规范问题的存储库”,而且我很确定您不会比这个更规范。

O
Oded

它是 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。 String Typing 并不意味着没有 Duck Typing。
我对此表示反对。鸭子闪避与类型的强度无关,只是能够使用任何具有方法的对象的能力,无论它是否实现接口。
P
Pedro Massango

鸭子类型意味着一个操作没有正式指定其操作数必须满足的要求,而只是用给定的内容进行尝试。

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

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

如果不使用鸭子类型,执行此任务的函数 f 必须事先指定其参数必须支持某些方法 Quack。一种常见的方式是使用接口

interface IQuack { 
    void Quack();
}

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

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

另一种方法是 structural typing - 但同样,方法 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 得到一个支持 Quack()x,那么一切都很好,如果没有,它将在运行时崩溃。

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

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

该函数不会以任何方式表明它需要一些可以Quackx,因此它只是在编译时尝试,如果一切正常,那就没问题了。


你不是说: void f(IQuak x) { x.Quak(); (而不是 K.Quack)因为函数 f 的参数是 IQuack x 而不是 Iquack k,非常小的错误,但我觉得它需要更正:)
根据维基百科,你的最后一个例子是“结构类型”,而不是“鸭子类型”。
好吧,该讨论似乎有一个单独的问题:stackoverflow.com/questions/1948069/…
因此,如果我理解您所说的话,支持鸭子类型的语言与不支持鸭子类型的语言之间的区别仅在于鸭子类型,您不必指定函数接受的对象类型吗? def f(x) 而不是 def f(IQuack x)
B
BenKoshy

简单说明

什么是鸭子打字?

“如果它像鸭子一样走路,像……等等一样嘎嘎叫” - 是的,但这意味着什么??!

我们感兴趣的是“对象”可以做什么,而不是它们是什么。

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

https://i.stack.imgur.com/DNeRD.jpg

请参阅下文了解更多详情:

Duck Typing 功能示例:

想象一下,我有一根魔杖。它有特殊的权力。如果我挥动魔杖说“开车!”到一辆车,那么,它开车!

它适用于其他事情吗?不确定:所以我在卡车上试了一下。哇——它也能开车!然后我在飞机、火车和 1 Woods(它们是人们用来“驱动”高尔夫球的一种高尔夫球杆)上尝试它。他们都开车!

但它可以说,一个茶杯吗?错误:KAAAA-BOOOOOOM!结果不太好。 ====> 茶杯不能开车!!呃!?

这基本上是鸭子类型的概念。这是一个先试后买的系统。如果它有效,一切都很好。但如果它失败了,就像你手中的手榴弹一样,它会在你的脸上爆炸。

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

C# 或 Java 等语言呢?

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

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

让卡车开起来的唯一方法是,如果你能以某种方式让魔杖同时期待卡车汽车(也许通过“实现一个通用界面”)。如果您不知道这意味着什么,请查看我的 cartoon on interfaces

摘要:钥匙取出

在鸭子类型中重要的是对象实际上可以做什么,而不是对象是什么。

序幕

我试图保持简单,去掉迂腐的细微差别。

如果您想要更精确的定义,请查看 wikipedia article to duck typing,或 Matt Damon 对避免输入 Good Will Hunting 的解释


我发现有趣的前提是您更关心行为,这就是它的定义。毫无疑问,BDD 在 ruby 等语言中非常成功。
最好的解释是肯定的。我厌倦了这种“鸭子”的解释,你也一样,它听起来像(我用粗体强调):“(“如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那么它就是鸭子。”) -是的!但那是什么意思??!”现在我明白了:只需在任何进入的对象上尝试该方法,而不是先检查对象的类型。
我对此感到太热情了,不得不add my own answer too :)。
另外,我认为您应该将“摘要”放在开头。这是关键的要点。也许把它放在最后,但也把它的副本放在开头。
@GabrielStaples 我听取了您的建议并在开头添加了它。希望它应该超级清晰、简短,最重要的是超级简单。
G
Gabriel Staples

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

这是我的函数,我必须确保它只接受 Bird 类型,否则代码将无法编译。如果有人想使用我的功能,他们必须知道我只接受鸟类。我的函数获取任何对象,我只是调用对象的 walk() 方法。因此,如果对象可以 walk() 则它是正确的。如果不能,我的功能将失败。因此,这里对象是 Bird 或其他任何东西并不重要,重要的是它可以 walk() (这是鸭子类型)。

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

有用的阅读

在 https://en.wikipedia.org/wiki/Duck_typing 上有很好的 Java、Python、JavaScript 等的鸭子类型示例。

这也是一个很好的答案,它描述了动态类型的优点和缺点:动态类型的假设生产力增益是多少?


很好的解释,有什么好处?
这个答案简单明了,可能是最适合初学者的答案。阅读此答案及其上方的答案(或者如果它移动,则有关汽车和茶杯的答案)
G
Gerard Simpson

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

如果它长得像鸭子,叫起来像鸭子,那就是鸭子

然后深入解释你可以用鸭子打字做什么,或者一个似乎进一步混淆这个概念的例子。

我找不到那么多帮助。

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

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

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


C
Chris Baxter

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

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

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

重要的一点很可能是,对于鸭子类型,开发人员更关心对象中被消耗的部分,而不是实际的底层类型是什么。


G
Gabriel Staples

不要做庸医;我支持你:

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

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

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

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

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

更长的解释:

死🦆死! 🍗

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()),甚至不知道 type< /em> 的对象!它只是尝试方法!如果它们有效,那太好了,因为所有“鸭子打字”语言都知道或关心,IT(传递给函数的对象)是一只鸭子!--因为所有(类似鸭子的)方法都在它上面工作。

(总结我自己的话):

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

那是鸭子打字。

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

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

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

我将称之为“运行时检查程序只是在不知道对象是否具有这些方法的情况下尝试调用的方法,而不是首先检查对象的类型作为知道对象具有这些方法的手段”,因为那只是更有意义。但是……说起来太长了,所以人们宁愿多年来互相混淆,而不是说一些荒谬但朗朗上口的话,比如“鸭子打字”。

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

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

另请阅读:

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


谢谢先生!我的意思是“好极了!!”这太棒了,应该得到更多的支持!
谢谢,这更有意义。那么 JavaScript 鸭子类型是什么?
@manish,我不知道 JavaScript,所以我不能说。如果它是一种脚本语言,具有弱的、未指定的类型,并且在您传入参数时不会在函数调用中自动检查错误(无论是在编译时还是在运行时),那么答案是“是”。 Python 符合这个描述。当您将错误类型的参数传递给函数时,它不会引发错误,而是当您尝试对该对象类型调用某些无效方法并且失败时,它会引发运行时错误。
t
timato

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

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


A
Arta

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

duck typing 中:

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

2) duck 一词表示确定是如何完成的;这是一种“松散”的决定,例如:“如果它像鸭子一样走路......那么它就是鸭子”。它是“松散的”,因为这东西可能是鸭子,也可能不是,但它是否真的是鸭子并不重要;重要的是我可以用它做我可以用鸭子做的事情,并期待鸭子表现出的行为。我可以喂它面包屑,这东西可能会朝我冲过来或冲向我或后退……但它不会像灰熊那样吞噬我。


D
Djee

鸭打字:

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

这通常称为溯因(溯因推理或也称为追溯,我认为这是一个更清晰的定义):

从 C(结论,我们看到的)和 R(规则,我们知道的),我们接受/决定/假设 P(前提,属性),换句话说,一个给定的事实……鸭子医学诊断的基础:C = 走路,说话,R = 像一只鸭子,P = 它是一只鸭子

回到编程:

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

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

...

因此,不仅仅是简单地接受任何对象上的 mp1...,只要它符合 mp1... 的某些定义,编译器/运行时也应该可以接受断言 o 是类型 T

那么,上面的例子就是这样吗? Duck 打字本质上是不打字吗?或者我们应该称之为隐式类型?


o
obinoob

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

希望它有所帮助;)


R
Rajat

使用鸭子类型技术的树遍历

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

i
icee

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


d
dstibbe

鸭子打字这个词是一个谎言。

您会看到“如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那么它就是鸭子。”这句话在这里一次又一次地重复。

但这不是鸭式打字(或我们通常所说的鸭式打字)的意义所在。我们正在讨论的所有 Duck Typing 都是关于试图对某些东西强制执行命令。看东西是不是嘎嘎,不管它说的是什么。但是没有推论该对象是否是鸭子。

有关真正的鸭子类型,请参阅类型类。现在遵循成语“如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那么它就是鸭子。”。对于类型类,如果一个类型实现了一个类型类定义的所有方法,那么它可以被认为是一个成员该类型类(无需继承类型类)。因此,如果有一个类型类 Duck 定义了某些方法(quack 和 walk-like-duck),则任何实现这些相同方法的东西都可以被视为 Duck(没有需要继承鸭子)。


R
Rafael

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

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


R
Rahul Rathod

鸭子打字:

let anAnimal 

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

anAnimal.walk()

上面的函数调用在结构类型中不起作用

以下将适用于结构类型:

IAnimal anAnimal

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

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


B
BenKoshy

善意狩猎 - 马特达蒙鸭打字场景

CHUCKIE:好吧,我们会有问题吗?

克拉克:没问题。我只是希望你能给我一些关于什么是鸭子打字的见解?我的论点是打鸭式没有很好的定义,也不是很牢固

威尔:[打断]……强类型也不是。当然这是你的论点。你是一名一年级的研究生:你刚刚读完一些关于鸭子打字的文章,可能是在 StackOverflow 上,直到下个月当你到达四人帮时,你才会相信这一点,然后你就会将谈论 Google Go 和 Ocaml 如何成为具有结构子绑定结构的统计类型语言。这将持续到明年,直到你可能会在这里反刍 Matz,谈论你知道的 Pre-Ruby 3.0 乌托邦和子类型对 GC 的内存分配影响。

克拉克:[吃惊]事实上我不会,因为马茨严重低估了——

WILL:“Matz 大大低估了 Ruby 3.0 的 GC 对性能的影响。你从 Donald Knuth,计算机编程的艺术,第 98 页得到的,对吗?是的,我也读过。你会为我们剽窃整个事情吗? ——你对这件事有什么——你自己的想法吗?或者——那是你的事吗,你陷入了一个松弛的线程,你读了一些关于 r/ruby 的晦涩的段落,然后你假装,你把它当成你自己的——你自己的想法只是为了给一些女孩留下好印象,让我的朋友难堪?

[克拉克惊呆了]

威尔:看到像你这样的人的可悲之处在于,大约 50 年后你会开始自己思考,你会想出生活中有三个确定性的事实。一,不要那样做。第二,如果它像鸭子一样走路,那么它就是鸭子。第三,您通过 Ben Koshy 的堆栈溢出答案以零美分获得的教育投入了 150 美元。

克拉克:是的,但是我将获得学位,并且您将通过在我们去滑雪旅行的路上通过汽车的反应为我的孩子提供一些便宜的 html。

威尔:[微笑]是的,也许吧。但至少我不会是非原创的。

(一拍)

威尔:你对此有意见吗?我想我们可以走出去做一些代码练习吗?

克拉克:没问题

一段时间以后:

威尔:你喜欢苹果吗?

克拉克不知所措。嗯?

威尔:你喜欢他们的苹果吗? (Boom:Will 将一封信砸在窗户上。)我必须得到 Google 的报价! (给克拉克的录取通知书显示了他的面试答案:对冒泡排序算法的正确回答和鸭子走路的图片)

结束

(这是 old answer here 的脚注:)


R
Robin

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

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

作者指出,duck typing 让您可以创建自己的类,这些类具有自己的内部数据结构——但可以使用普通的 Python 语法进行访问。


这个答案是相当鸭子打字的答案