假设我们有以下类:
class Duck(object):
pass
class OldFashionedDuck(Organism, Duck):
def look(self):
self.display_biological_appearance()
def walk(self):
self.keep_balance_on_two_feet()
def quack(self):
self.make_noise_with_lungs("Quack!")
class ArtificialDuck(Robot, Duck):
def look(self):
self.display_imitation_biological_appearance()
def walk(self):
self.engage_leg_clockwork()
def quack(self):
self.play_sound("quack.au")
在这个例子中,OldFashionedDuck 和 ArtificialDuck 没有共同的实现,但通过构造它们都会为 isinstance(..., Duck) 返回 True。
这并不完美,但我认为它可能有助于尊重鸭子类型(duck typing),并且(通过空混合继承)允许 isinstance()。从本质上讲,它提供了满足接口(interface)的契约,因此它并不是真正基于完成所有工作的类调用 isinstance(),而是基于任何人都可以选择加入的接口(interface)。
我看过基于“isinstance() 被认为是有害的”的文章,因为它破坏了 duck 类型。但是,至少作为一名程序员,我想知道,如果对象不一定从哪里获得函数,而是它是否实现了接口(interface)。
这种方法是否有用,如果有用,是否可以对其进行改进?
请您参考如下方法:
I've seen articles based on "isinstance() considered harmful," because it breaks duck typing. However, I at least as a programmer would like to know, if not necessarily where an object gets a function from, but whether it implements an interface.
我认为你没有捕获重点。
当我们谈论“鸭子类型(duck typing)”时,我们真正的意思是没有形式化我们的接口(interface)。因此,您尝试做的事情归结为尝试回答“我如何在仍未形式化我的界面的情况下形式化我的界面?”这个问题。
我们期望给我们一个对象来实现一个接口(interface)——我们描述的一个接口(interface),不是通过创建一个基类,而是通过编写一堆文档和描述行为(如果我们感觉特别活泼,设置建立某种测试套件)——因为我们说过这是我们所期望的(同样在我们的文档中)。我们通过尝试像使用它一样使用它来验证对象是否实现了接口(interface),并将任何由此产生的错误视为调用者的责任。调用给我们错误对象的代码是顽皮的,这就是需要修复错误的地方。 (同样,测试可以帮助我们追踪这些事情。)
简而言之,测试 isinstance(this_fuzzball_that_was_handed_to_me, Duck) 并没有真正的帮助:
它可以通过
isinstance检查,但以违反我们预期的方式实现方法(或者说,return NotImplemented)。只有真正的测试才会在这里进行。可以通过检查,但实际上完全没有实现其中的一个或多个方法;毕竟,基础
Duck不包含任何实现,Python 没有理由在派生类中检查它们。也许更重要的是,它可能无法通过检查,即使它是一个完全可以用作鸭子类型(duck typing)的毛绒球。也许它是一些不相关的对象,它们具有
quack、walk和look函数,直接作为属性手动附加到它(而不是它们是它的类,在查找时成为方法)。
好吧,所以,“不要那样做”,你说。但是现在你正在为每个人做更多的工作;如果客户端不总是选择加入,那么使用鸭子类型(duck typing)的代码进行检查是无用且危险的。同时,您获得了什么?
这与 EAFP 原则有关:不要试图通过观察来判断某物是否是 Duck;通过将它视为鸭子类型(duck typing)来判断它是否是鸭子类型(duck typing),如果不是,则处理血淋淋的困惑。
但是,如果您不关心鸭子类型(duck typing)哲学,并且绝对必须强加一些表面上的严谨性,您可能会对标准库 abc 模块感兴趣。
