我在在线阅读软件上的随机主题时遇到了鸭子打字一词,并没有完全理解它。
什么是“鸭打字”?
鸭子类型 意味着一个操作没有正式指定其操作数必须满足的要求,而只是用给定的内容进行尝试。
与其他人所说的不同,这不一定与动态语言或继承问题有关。
示例任务:Quack
在对象上调用某些方法。
在不使用鸭子类型的情况下,f
执行此任务的函数必须提前指定其参数必须支持某种方法Quack
。一种常见的方式是使用接口
interface IQuack {
void Quack();
}
void f(IQuack x) {
x.Quack();
}
调用f(42)
失败,但f(donald)
只要donald
是IQuack
-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
得到一个x
支持 a Quack()
,一切都很好,如果没有,它会在运行时崩溃。
但是鸭子类型根本不意味着动态类型——事实上,有一种非常流行但完全静态的鸭子类型方法,它也没有给出任何要求:
template <typename T>
void f(T x) { x.Quack(); }
该函数并没有以任何方式告诉它它想要一些x
can Quack
,所以它只是在编译时尝试,如果一切正常,那就没问题了。
Avoid the academic technicalities/nuances and keep it simple (footnote^^^).
“If it walks like a duck and quacks like a duck then it is a duck.” - YES! but what does that mean??!
We're interested in what "objects" can do, rather than what they are. Let's unpack it with an example:
Imagine I have a magic wand. It has special powers. If I wave the wand and say "Drive!" to a car, well then, it drives!
Does it work on other things? Not sure: so I try it on a truck. Wow - it drives too! I then try it on planes, trains and 1 Woods (they are a type of golf club which people use to 'drive' a golf ball). They all drive!
But would it work on say, a teacup? Error: KAAAA-BOOOOOOM! that didn't work out so good. ====> Teacups can't drive!! duh!?
This is basically the concept of duck typing. It's a try-before-you-buy system. If it works, all is well. But if it fails, like a grenade still in your hand, it's gonna blow up in your face.
In other words, we are interested in what the object can do, rather than with what the object is.
If we were concerned with what the object actually was, then our magic trick will work only on pre-set, authorised types - in this case cars, but will fail on other objects which can drive: trucks, mopeds, tuk-tuks etc. It won't work on trucks because our magic wand is expecting it to only work on cars.
In other words, in this scenario, the magic wand looks very closely at what the object is (is it a car?) rather than what the object can do (e.g. whether cars, trucks etc. can drive).
The only way you can get a truck to drive is if you can somehow get the magic wand to expect both trucks and cars (perhaps by "implementing a common interface"). If you don't know what that means then just ignore it for the moment.
What's important in duck typing is what the object can actually do, rather than what the object is.
Full scene here: https://youtu.be/hIdsjNGCGz4
CHUCKIE: All right, are we gonna have a problem?
CLARK: There's no problem. I was just hoping you could give me some insight into what duck typing is actually is? My contention is that duck tying is not well defined, and neither is strong
WILL: [interrupting] …and neither is strong typing. Of course that's your contention. You're a first year grad student: you just got finished reading some article on duck typing, probably on StackOverflow, and you’re gonna be convinced of that until next month when you get to the Gang of Four, and then you’re gonna be talking about how Google Go and Ocaml are statistically typed languages with structural sub-tying construction. That's going to last until next year, till you're probably gonna be in here regurgitating Matz, talkin’ about, you know, the Pre-Ruby 3.0 utopia and the memory allocating effects of sub-typing on the GC.
CLARK: [taken aback] Well as a matter of fact I won't, because Matz drastically underestimates the impact of —</p>
WILL: "Matz dramatically underestimates the impact of Ruby 3.0's GC on performance. You got that from Donald Knuth, The Art of Computer Programming, page 98, right? Yeah I read that too. Were you gonna plagiarize the whole thing for us—you have any thoughts of—of your own on this matter? Or do—is that your thing, you come into a bar, you read some obscure passage on r/ruby and then you pretend, you pawn it off as your own—your own idea just to impress some girls, embarrass my friend?
[Clark is stunned]
WILL: See the sad thing about a guy like you is in about 50 years you’re gonna start doing some thinking on your own and you’re gonna come up with the fact that there are three certainties in life. One, don't do that. And two, if it walks like a duck then it is a duck. And three, you dropped a hundred and fifty grand on an education you coulda got for zero cents at the public library via a SO answer by Ben Koshy.
CLARK: Yeah, but I will have a degree, and you'll be serving my kids some cheap html (via react) at a drive-thru on our way to a skiing trip.
WILL: [smiles] Yeah, maybe. But at least I won't be unoriginal.
(a beat)
WILL: you got a problem with that? I guess we can step outside and do some advent of code problems.
Clark: there's no problem
Some time later:
WILL: Do you like apples?
Clark is nonplussed. Huh?
WILL: How do you like them apples? (Boom: Will slams a letter up against a window.) I gotta offer from Google! (Shows the letter of acceptance to Clark showing his interview answer: a bubble sort algorithm and an answer on duck typing)
The End
考虑你正在设计一个简单的函数,它获取一个类型的对象Bird
并调用它的walk()
方法。您可以想到两种方法:
Bird
类型,否则代码将无法编译。如果有人想使用我的功能,他们必须知道我只接受Bird
s。objects
,我只是调用对象的walk()
方法。所以,如果object
可以,walk()
那么它是正确的。如果不能,我的功能将失败。所以,在这里,对象是 aBird
还是其他东西并不重要,重要的是它可以walk()
(这是鸭子打字)。必须考虑鸭类型在某些情况下可能有用。例如,Python 大量使用鸭子类型。
我看到很多重复旧习语的答案:
如果它长得像鸭子,叫起来像鸭子,那就是鸭子
然后深入解释你可以用鸭子打字做什么,或者一个似乎进一步混淆这个概念的例子。
我找不到那么多帮助。
这是我发现的关于鸭子类型的简单英语答案的最佳尝试:
Duck Typing 意味着一个对象是由它可以做什么来定义的,而不是由它是什么来定义的。
这意味着我们不太关心对象的类/类型,而更关心可以在其上调用哪些方法以及可以对其执行哪些操作。我们不关心它的类型,我们关心它可以做什么。
维基百科有一个相当详细的解释:
http://en.wikipedia.org/wiki/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 typing”的程序将简单地尝试在对象上调用的任何方法(在上面的示例中:quack()
、walk()
和fly()
),甚至不知道对象的类型!它只是尝试方法!如果它们有效,那太好了,因为所有“鸭子打字”语言都知道或关心,IT(传递给函数的对象)是一只鸭子!--因为所有(类似鸭子的)方法都在它上面工作。
(总结我自己的话):
“鸭子类型”语言不应检查其类型(无论是在编译时还是运行时)-它不在乎。它只会在运行时尝试这些方法。如果他们工作,那就太好了。如果他们不这样做,那么它将引发运行时错误。
那是鸭子打字。
我厌倦了这种荒谬的“鸭子”解释(因为没有这个完整的解释,它根本没有任何意义!),其他人听起来也是如此。示例:来自BKSpureon 在这里的回答(我的重点是粗体):
(“如果它走路像鸭子,叫起来像鸭子,那它就是鸭子。”)——是的!但是,这是什么意思??!”
现在我明白了:只需在任何进入的对象上尝试该方法,而不是先检查对象的类型。
我将称之为“运行时检查程序只是在不知道对象是否具有这些方法的情况下尝试调用的方法,而不是首先检查对象的类型作为知道对象具有这些方法的手段”,因为那只是更有意义。但是……说起来太长了,所以人们宁愿多年来互相混淆,而不是说一些荒谬但朗朗上口的话,比如“鸭子打字”。
让我们称之为:“尝试方法,不检查类型”打字。或者,也许:“方法调用类型检查”(简称“方法调用类型”)或“方法调用的间接类型检查”,因为它使用给定方法的调用作为“足够的证据”,即object 是正确的类型,而不是直接检查对象的类型。
请注意,这种“方法调用类型检查”(或者容易混淆地称为“鸭子类型”)是一种动态类型。但是,并非所有动态类型都必须是“方法调用类型检查”,因为动态类型或运行时的类型检查也可以通过实际检查对象的类型来完成,而不是简单地尝试调用在函数中的对象而不知道其类型。
我知道我没有给出笼统的答案。在 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)"
您不需要检查参数的类型。如果他们支持 <<(在结果的情况下)或标题和艺术家(在歌曲的情况下),一切都会正常工作。如果他们不这样做,您的方法无论如何都会抛出异常(就像您检查类型时所做的那样)。但是如果没有检查,您的方法会突然变得更加灵活。您可以将数组、字符串、文件或任何其他使用 << 附加的对象传递给它,它会正常工作。
查看语言本身可能会有所帮助;它经常帮助我(我不是以英语为母语的人)。
在duck typing
:
1)这个词typing
并不意味着在键盘上打字(就像我脑海中的持久形象),它意味着确定“那是什么类型的东西? ”
2)该词duck
表示确定是如何完成的;这是一种“松散”的确定,例如:“如果它像鸭子一样走路……那么它就是鸭子”。它是“松散的”,因为这东西可能是鸭子,也可能不是,但它是否真的是鸭子并不重要;重要的是我可以用它做我可以用鸭子做的事情,并期待鸭子表现出的行为。我可以喂它面包屑,这东西可能会朝我冲过来或冲向我或后退……但它不会像灰熊那样吞噬我。
鸭打字:
如果它说话和走路都像鸭子,那么它就是鸭子
这通常称为溯因推理(溯因推理或也称为逆向推理,我认为这是一个更清晰的定义):
从C(结论,我们所看到的)和R(规则,我们所知道的),我们接受/决定/假设P(前提,属性),换句话说,一个给定的事实
... 医学诊断的基础
有鸭子:C =走路、说话、R =像鸭子、P =它是鸭子
回到编程:
对象o具有方法/属性mp1和接口/类型T 需要/定义mp1
对象o具有方法/属性mp2 和接口/类型T需要/定义mp2
...
所以,不仅仅是简单地接受任何对象上的mp1 ...,只要它符合mp1 ...的某些定义,编译器/运行时也应该可以接受断言o是类型T
那么,上面的例子就是这样吗?Duck 打字本质上是不打字吗?或者我们应该称之为隐式类型?
使用鸭子类型技术的树遍历
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 不是 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) ...
希望它有所帮助;)
在鸭子类型中,对象的适用性(例如,在函数中使用)是基于是否实现了某些方法和/或属性而不是基于该对象的类型来确定的。
例如,在 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
长得不像鸭子,不叫,所以不是鸭子!
术语 Duck Typing 是一个谎言。
您会看到“如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那么它就是鸭子。”这句话在这里一次又一次地重复。
但这不是鸭式打字(或我们通常所说的鸭式打字)的意义所在。我们正在讨论的所有 Duck Typing 都是关于试图对某些东西强制执行命令。看东西是不是嘎嘎,不管它说的是什么。但是没有推论该对象是否是鸭子。
有关真正的鸭子类型,请参阅类型类。现在遵循成语“如果它像鸭子一样走路,像鸭子一样嘎嘎叫,那么它就是鸭子。”。对于类型类,如果一个类型实现了一个类型类定义的所有方法,那么它可以被认为是一个成员该类型类的(不必继承类型类)。因此,如果有一个类型类 Duck 定义了某些方法(quack 和 walk-like-duck),那么任何实现这些相同方法的东西都可以被认为是 Duck(没有需要继承鸭子)。
我认为混淆动态类型、静态类型和鸭子类型是很混乱的。鸭子类型是一个独立的概念,甚至像 Go 这样的静态类型语言也可以有一个实现鸭子类型的类型检查系统。如果类型系统将检查(声明的)对象的方法而不检查类型,则可以将其称为鸭子类型语言。
我试图以我的方式理解这句著名的句子:“Python 不在乎一个对象是否是真正的鸭子。它只关心对象是否,首先是“嘎嘎”,其次是“像鸭子”。
有一个很好的网站。http://www.voidspace.org.uk/python/articles/duck_typing.shtml#id14
作者指出,duck typing 让您可以创建自己的类,这些类具有自己的内部数据结构——但可以使用普通的 Python 语法进行访问。