Python 3:继承和抽象方法的简洁示例?

4
实际代码与完全不同的主题,但我认为这个小例子可能更好,因为我的问题是理解复杂继承场景的关键概念,而不是我的特定领域。
让我们考虑一个基本的Entity类:
from enum import Enum
from abc import abstractmethod

class Condition(Enum):
    ALIVE = 1
    DEAD = 2
    UNDEAD = 3

class Entity(object):

    def __init__(self):
        self.condition = Condition.ALIVE
        self.position = 0
        self.hitpoints = 100

    def move(self):
        self.position += 1

    def changeHitpoints(self, amount):
        self.hitpoints += amount

    @abstractmethod
    def attack(self, otherEntity):
        pass

这是一个其他具体实体继承的基类,并且attack()必须是抽象的,因为每个实体都应该实现自己的攻击方法风格。
现在我们可以实现一些实体:
class Orc(Entity):

    def __init__(self):
        super().__init__()
        self.hitpoints = 150
        self.damage = 10

    def attack(self, otherEntity : Entity):
        otherEntity.changeHitpoints(-self.damage)

class Human(Entity):

    def __init__(self):
        super().__init__()
        self.damage = 8

    def attack(self, otherEntity : Entity):
        otherEntity.changeHitpoints(-self.damage)

class Undead(Entity):

    def __init__(self):
        super().__init__()
        self.condition = Condition.UNDEAD
        self.damage = 5

    def attack(self, otherEntity : Entity):
        # harm enemy
        otherEntity.changeHitpoints(-self.damage)
        # heal yourself
        self.changeHitpoints(1)

这很好用。但是,我正在努力寻找一个好的解决方案(DRY风格),用于实现“能力”等其他内容。 例如,如果“兽人”和“人类”不仅应该移动,而且还应该能够跳跃,那么有这样的东西会很有趣:
class CanJump(Entity):

    def jump(self):
        self.position += 2

class Orc(Entity, CanJump):
    (...)

class Human(Entity, CanJump):
    (...)

这里有两个问题。(1)我们需要在CanJump中访问self.position,因此我们必须继承Entity?但如果这样做,我们就必须在CanJump类中实现抽象方法attack()。这是没有意义的,因为CanJump应该只给予实体一种新的移动方式。(2)将来,我们可能想要实现例如一个修饰器,在执行move()attack()等之前检查实体的条件是否为Condition.DEAD。这也意味着CanJump需要访问self.condition

针对这种类型的问题,有什么简洁的解决方案吗?
如果需要进一步的子类化怎么办?例如,我们可能有兴趣创建一个UndeadHuman,像这样:class UndeadHuman(Undead, Human)。由于线性化(Undead首先按顺序),它应该具有Undeadattack行为,但它还需要HumanCanJump
2个回答

3
“我们需要在CanJump中访问self.position,所以我们必须从Entity继承?!”
不需要。您可以将CanJump视为“mix-in类”,它只是添加功能。任何子类化CanJump的类都应该有一个position属性。而CanJump类本身则不需要从Entity继承。因此,这样做:
class CanJump:
    def jump(self):
        self.position += 2

可以完全没有问题。然后您可以执行:
class Orc(Entity, CanJump):
    (...)

class Human(Entity, CanJump):
    (...)

以下是一个完整的示例,演示了上述内容的工作原理:
from abc import abstractmethod


class A:
    def __init__(self):
        self.a = 0 

    @abstractmethod
    def m(self):
        pass


class C:
    def c(self):
        self.a += 1


class B(A, C):
    def __init__(self):
        super().__init__()

    def m(self):
        print('method in B')


b = B()
print(b.a) # 0
b.c()
print(b.a) # 1
b.m() # method in B

你可以在方法实现中使用当前不存在的属性。这些属性只需要在调用方法时存在即可。让CanJump的子类实现所需的属性效果很好。

如果你想强制用户定义某些属性,可以使用元类。我不会重复解释,我会指向@kindall的答案,它处理得相当优雅。


这是一个有趣的概念。这是 Python/鸭子类型相关的特性吗?我以前从没见过混入类。有没有办法防止直接实例化 CanJump?因为某些情况下可能会不小心这么做,然后 self.position += 2 就会出现未解决的引用错误。此外,我想知道我们是否可以以某种方式对属性进行类型提示? - daniel451
1
@daniel451 完全不是这样,许多其他语言支持混合类。它们可能有不同的名称,比如在PHP中被称为traits。尽管在静态类型语言中实现起来有些棘手。我稍后会更新一个示例,以解决您提到的其他问题。 - Christian Dean
@daniel451 这是一种针对具有多重继承的语言的常见技术。现在在 Java 中可以使用提供默认方法的接口实现相同的功能。 - JAB

1
我会尝试使用组合而不是继承来建模。
您可以创建一个名为 Entity 的类,用于每个生物,并创建一个单独的 RaceModifier 类来设置相关常量/调整和能力。然后,每个生物都可以有一个这些类的列表,以在游戏中建模种族,例如:
class RaceModifier:
    def __init__(self):
        ...
        self.abilities = []

class Orc(RaceModifier):
    def __init__(self):
        super().__init__()
        self.extra_hit_points = 50
        self.extra_damage = 2
        self.abilities = [JUMPING]

class Undead(RaceModifier):
    def __init__(self):
        super().__init__()
        self.abilities = [REGEN_ON_ATTACK]

class Entity:
    ...
    def has_ability(self, ability): # ability = JUMPING etc
        for race in self.race_modifiers:
            if ability in race.abilities:
                return True
        for eq in self.equipment: # wearing a ring of jumping ? :)
            if ability in eq.abilities:
                return True
        return False

这也是一个有趣的概念。你有一些关于Python中组合的更多(好)例子吗? - daniel451
abilities 应该是一个 dict,这样你就可以更快地检查是否有适当的能力。 - chepner

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接