如何使用 duck typing 编写 OOP 一致的代码?

How to write OOP consistent code with duck typing?

我在决定方法在 python 程序中的位置时遇到了麻烦,我过去依赖的鸭子打字方法似乎与我的 OOP 直觉不一致。

为了说明,假设我们有三个 classes:Hero、Sword 和 Apple。英雄出剑,吃苹果。

如果我遵循我的 OOP 直觉,我认为代码将如下所示:

duckless.py

class Hero:
    def __init__(self):
        self.weapon = None
        self.inTummy = None

    def equip(self, weapon):
        weapon.define()
        print("I shall equip it.")
        self.weapon = weapon

    def eat(self, food):
        food.define()
        print("I shall consume it.")
        self.inTummy = food

class Sword:
    def define(self):
        print("'tis a shiny sword")

class Apple:
    def define(self):
        print("'tis a plump apple")

hero = Hero()
swd = Sword()
apl = Apple()

hero.equip(swd)
hero.eat(apl)

感觉非常直观和可读。

但是,如果我是 duck-type,我觉得代码应该是这样的:

duckfull.py

class Hero:
    def __init__(self):
        self.weapon = None
        self.inTummy = None

    def onEquip(self):
        print("I shall equip it.")

    def onEat(self):
        print("I shall eat it.")

class Sword:
    def define(self):
        print("'tis a shiny sword")

    def equip(self, hero):
        self.define()
        hero.onEquip()
        hero.weapon = self


class Apple:
    def define(self):
        print("'tis a plump apple")

    def eat(self, hero):
        self.define()
        hero.onEat()
        hero.inTummy = self

hero = Hero()
swd = Sword()
apl = Apple()

swd.equip(hero)
apl.eat(hero)

duck 类型的代码具有明显的优势,我可以随时执行 try-except 以确定我是否正在执行 "legal" 操作:

try:
    apl.equip()
except AttributeError:
    print("I can't equip that!")

感觉非常 pythonic,而替代方案需要我执行可怕的 类型检查

但是,从OOP的角度来看,负责装备自己,而它接收英雄[=48]感觉很奇怪=] 作为参数。装备这个动作好像是英雄的动作,所以我觉得这个方法应该属于英雄 class.

的完整语法
def eat(self, hero):
    self.define()
    hero.onEat()
    hero.inTummy = self

感觉很陌生。

这两种方法都更 pythonic 吗?是否更符合 OOP?我应该考虑完全不同的解决方案吗?

提前致谢。

没有明确的答案;这取决于您的 classes 做什么。在 Hero.equip 中检查 isinstance(weapon, Weapon) 来检查物品是否是武器并没有那么可怕。此外,如果您要像第二个示例中那样涉及两个对象,则可以将更多处理移至 Hero 中:

class Hero:
    def __init__(self):
        self.weapon = None
        self.inTummy = None

    def equip(self, weapon):
        print("I shall equip it.")
        self.weapon = weapon

class Sword:
    def equip(self, hero):
        hero.equip(self)

这可能看起来有点奇怪,但是在一个 class 上使用一个方法只是委托给另一个 class 上的相关方法并不一定是坏事(因为,在这里,调用 sword.equip 只是调用 hero.equip)。你也可以反过来做,让 Hero.equip 调用 weapon.equip()weapon.ready() 或其他任何东西,如果物品不是武器所以没有这样的东西,这将失败一个属性。

另一件事是,您在第一个示例中仍然可以有鸭子打字行为,只是直到稍后您尝试用武器做其他事情时才会出现错误。类似于:

hero.equip(apple)  # no error
hero.weapon.calculateDamage() # AttributeError: Apple object has no attribute `damage`

这可能不理想,因为你直到后来才知道你装备了一把无效的武器。但这就是鸭子打字的工作原理:在您实际尝试触发该错误的操作之前,您不知道自己是否做错了。

如果你对一个物体要做的只是扔它,保龄球和鸭子一样有效。只有当您尝试让它游泳或飞行时,您才会注意到保龄球不是鸭子。同样,如果你要装备 "weapon" 只是将它系在腰带上或握在手中,你可以用苹果和剑来做到这一点;在您尝试在战斗中实际使用苹果之前,您不会发现任何不对劲。