如何使用 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" 只是将它系在腰带上或握在手中,你可以用苹果和剑来做到这一点;在您尝试在战斗中实际使用苹果之前,您不会发现任何不对劲。
我在决定方法在 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" 只是将它系在腰带上或握在手中,你可以用苹果和剑来做到这一点;在您尝试在战斗中实际使用苹果之前,您不会发现任何不对劲。