我刚刚阅读了关于duck typing的维基百科文章,我觉得我错过了关于Java习惯的界面概念的重要观点:
"When I see a bird that walks like a duck and swims like a duck and quacks like a duck, I call that bird a duck."
class Duck:
def quack(self):
print("Quaaaaaack!")
def feathers(self):
print("The duck has white and gray feathers.")
def swim(self):
print("Swim seamlessly in the water")
class Person:
def quack(self):
print("The person imitates a duck.")
def feathers(self):
print("The person takes a feather from the ground and shows it.")
def name(self):
print("John Smith")
def in_the_forest(duck):
duck.quack()
duck.feathers()
def game():
donald = Duck()
john = Person()
in_the_forest(donald)
in_the_forest(john)
game()
如果,在in_the_forest
,我写道:
quack
吗?是feathers
吗?是以后,因为我知道它是鸭子,我想要它swim
? john
会下沉!
我不希望我的应用程序在其进程中间(随机)崩溃只是因为John假装成了一只鸭子,但我想在检查对象的每个属性时都不是一个明智的想法。我收到了吗??
答案 0 :(得分:21)
鸭子打字并不是要检查你需要的东西是否存在然后使用它们。鸭子打字只是使用你需要的东西。
in_the_forest
函数是由一位考虑过鸭子的开发人员编写的。它被设计为在Duck
上运行。 Duck
可以quack
和feathers
,因此编码人员使用这些功能来完成手头的工作。在这种情况下,Duck
也可以swim
未被使用,而且不需要。
在像Java这样的静态语言中,in_the_forest
将被声明为Duck
。当编码员后来发现他们有一个Person
(也可能quack
和feathers
)并想要重复使用该功能时,他们就不走运了。 Person
是Duck
的子类吗?不,这似乎并不合适。是否有QuacksAndFeathers
接口?也许,如果我们幸运的话。否则,我们必须制作一个,然后修改Duck
来实现它,并修改in_the_forest
以取QuacksAndFeathers
而不是Duck
。如果Duck
位于外部库中,则可能无法进行此操作。
在Python中,您只需将Person传递给in_the_forest
即可。因为事实证明in_the_forest
不需要Duck
,它只需要一个“鸭子般的”对象,在这种情况下,Person就像鸭子一样。
game
需要一个不同的“鸭子”定义,这个定义稍微强一些。在这里,约翰史密斯运气不好。
现在,Java确实会在编译时捕获到这个错误而Python不会。这可以被视为一个缺点。亲动态类型计数器的论点是说你编写的任何实体代码总是包含没有编译器可以捕获的错误(说实话,Java甚至不是一个特别好的例子)具有强静态检查的编译器来捕获大量错误)因此,您需要测试代码以找到这些错误。如果您正在测试这些错误,那么您将很容易发现将Person
传递给需要Duck
的函数的错误。鉴于此,动态打字员说,一种诱惑你进入不测试的语言,因为它找到一些你的琐碎错误实际上是坏事情。最重要的是,它会阻止您执行一些非常有用的操作,例如重用in_the_forest
上的Person
函数。
就个人而言,我被两个方向撕裂了。我非常喜欢Python,它具有灵活的动态类型。我非常喜欢Haskell和Mercury的强大的静态类型系统。我不是Java或C ++的粉丝;在我看来,他们拥有静态打字的所有不良内容,只有很少的好位。
答案 1 :(得分:5)
不能代替其他语言,但在python中最近(v2.6)引入了Abstract Base Classes (ABC) module。
如果您阅读其介绍背后的理由(PEP 3119),您很快就会意识到部分原因是“将约翰从肯定的死亡中拯救出来”或换句话说,为了方便您检查事实程序到一个接口,所有接口方法都会在那里。来自链接的PEP:
ABCs就是Python类 被添加到对象的继承中 树发出信号的某些特征 该对象是外部检查员。 测试使用isinstance()和 特定ABC的存在意味着什么 测试已经过去了。此外, ABCs定义了一组最小的 建立的方法 该类型的特征行为。 基于区分对象的代码 他们的ABC类型可以相信那些 方法将永远存在。
通常,您可以为自己的代码应用相同的模式。例如:您可以创建一个BasePlugin
类,其中包含插件工作所需的所有方法,然后您可以通过子类化创建几个不同的插件。根据每个插件必须或 是否定义了这些方法,您可以定义BasePlugin
方法以静默方式传递(插件可以定义那些方法)或引发异常(插件必须定义那些方法/覆盖BasePlugin
的一个)。
编辑:在下面的评论主题中,我建议在答案中包含以下内容:
这种功能 - 至少在python中 - 并不是为了人类程序员而实现的(python从不沉默错误,所以已经有很多反馈),而是为了python自己的内省能力(从而更容易编写动态加载,元编程代码等...)。换句话说:我知道John无法飞行......但我希望python翻译也能知道它! :)
答案 2 :(得分:4)
我不希望我的应用程序在其进程中间(随机)崩溃只是因为John假装成了一只鸭子,但我想在检查对象的每个属性时都不是一个明智的想法。我收到了吗??
这通常是动态类型的问题。在像Java这样的静态类型语言中,编译器在编译时检查Person
是否实现IDuck
。在像Python这样的动态类型语言中,如果Person
错过某些特定的鸭子特征(例如swim
),则会出现运行时错误。引用另一篇维基百科文章("Type system", Section "Dynamic Typing"):
动态类型可能会导致运行时类型错误 - 也就是说,在运行时,值可能具有意外类型,并且应用了对该类型无意义的操作。这样的错误可能在编程错误发生的地方很久之后发生 - 也就是说,错误类型的数据传递到它不应该有的地方。这可能使错误很难找到。
动态类型有其缺点(您已经提到过)及其优点。可以在维基百科的类型系统文章的另一部分中找到简要比较:Static and dynamic type checking in practice。
答案 3 :(得分:0)
动态类型的优势,Duck Typing 还有另一张牌,可以实现多个接口,即多个独立的行为特征。
正式接口,使用 ABC 非常好,可以让您提前失败。但是,您仍然一次只能实现一个“接口”(您可以从一个父类继承)。
此外,使用类继承,由于其固有的链,您很快就会陷入混乱,并导致紧耦合和死胡同。由于链上的更改而导致的错误将影响所有相关代码,这些代码可能尚未经过测试,也没有完整的测试覆盖范围。
而使用组合的类反而促进了独立性。 开发人员可以使用必要的接口来规划 UML,以添加多个行为特征(例如 ILogger、IEventGenerator、ICalcArea...),并在代码中使用鸭子类型屏蔽多个接口。当然,缺点是开发人员需要仔细计划以防止名称冲突,并且正如 OP 指出的那样,缺少属性或方法将导致运行时错误而不是编译器警告。
回到旧的优势/劣势交易。