假设我们有以下课程:
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没有共同的实现,但是通过构造,它们都将返回True for isinstance(...,Duck)。
这并不完美,但我认为这可能有助于尊重鸭子打字,并且(通过空的mixin继承)允许isinstance()。从本质上讲,它提供了一个满足接口的契约,所以它不是基于完成所有工作的类调用isinstance(),而是基于任何人都可以选择加入的接口。
我看过基于“isinstance()被认为有害的文章”,因为它打破了鸭子打字。但是,我至少作为程序员想知道,如果不一定是对象从哪里获取函数,但它是否实现了接口。
这种方法是否有用,如果可以,可以改进吗?
答案 0 :(得分:6)
我看过基于“isinstance()被认为有害的文章”,因为它打破了鸭子打字。但是,我至少作为程序员想知道,如果不一定是对象从哪里获取函数,但它是否实现了接口。
我认为你错过了这一点。
当我们谈论“鸭子打字”时,我们真正的意思是没有正式化我们的界面。因此,您尝试做的事情归结为试图回答“我怎样才能使我的界面正式化,同时仍然没有正式化我的界面?”。
我们期望一个给我们的对象实现一个接口 - 我们描述的那个,不是通过创建基类,而是通过编写一堆文档和描述行为(如果我们感觉特别活泼,设置某种测试套件) - 因为我们说这是我们所期望的(再次在我们的文档中)。我们通过尝试使用它来验证对象是否实现了接口,并将任何产生的错误视为调用者的责任。调用给我们错误对象的代码是顽皮的,而这就是bug需要修复的地方。 (同样,测试可以帮助我们跟踪这些事情。)
简而言之,测试isinstance(this_fuzzball_that_was_handed_to_me, Duck)
并不是真正有用的事情:
它可以通过isinstance
检查,但是会以违反我们期望的方式实施这些方法(或者说,return NotImplemented
)。这里只做真正的测试。
它可以通过检查,但实际上完全无法实现一个或多个方法;毕竟,基类Duck
不包含任何实现,Python没有理由在派生类中检查它们。
也许更重要的是,它可能无法通过检查,即使它是一个完全可用的鸭子模糊球。也许它是一些不相关的对象直接有quack
,walk
和look
函数,手动作为属性附加到它(而不是它们是它的类的属性,它们在查找时成为方法)。
好吧,所以,“不要那样做”,你说。但现在你正在为每个人做更多的工作;如果客户总是选择加入,那么使用鸭子代码进行检查是没用的,也是危险的。与此同时,你获得了什么?
这与EAFP原则有关:不要试图通过观察它来弄清楚某些东西是否是鸭子;弄清楚如果它是一只鸭子,将其视为鸭子,如果不是那么处理血腥的混乱。
但是如果你不关心鸭子的打字理念,并且绝对必须对事物强加一些严谨性,你可能会对标准库abc
模块感兴趣。
答案 1 :(得分:1)
即使我不想高估这个词,但是:你的方法不是pythonic。做鸭子打字,或者不做。
如果你想确保你的“接口”的实现实现它应该做的一切:测试它!
对于较小的项目,很容易记住您的需求。你可以试试吧。
我同意对于大型项目,甚至团队合作,最好确保您的类型具有所需的一切。在这种情况下,您肯定应该使用单元测试来确保您的类型完整。即使没有鸭子打字,你也需要测试,所以你可能不需要任何额外的测试。
Guido van Rossum谈到了this talk中关于鸭子打字的一些有趣想法。它非常鼓舞人心,值得一看。