Python 3:继承和抽象方法的干净示例?

Python 3: clean example for inheritance & abstract methods?

实际代码大不相同,而且主题完全不同,但我觉得这个小例子可能更好,因为我的问题是理解复杂继承场景(而不是我的特定领域)的关键概念。

假设我们有一个基本的 Entity class:

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

这是一个基础 class 其他具体实体继承自并且 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 风格)来实现 "abilities" 和其他东西。

例如,如果OrcHuman不仅要移动,还要能够跳跃,那么有这样的东西会很有趣:

class CanJump(Entity):

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

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

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

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

对于此类问题,什么是干净的解决方案?

如果需要进一步子class怎么办?例如。我们可能有兴趣创建一个像 class UndeadHuman(Undead, Human) 这样的 UndeadHuman。由于线性化(Undead 首先),它应该具有 Undeadattack 行为,但它还需要 HumanCanJump

[W]e need access to self.position in CanJump, thus we have to inherit from Entity?!

不,你不知道。您可以将 CanJump 视为 混入 class,它只是增加了功能。 subclass CanJump 的任何 classes 都应具有 position 属性。 CanJump class 本身不需要继承自 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 的子 class 实现所需的属性效果很好。

如果您想强制 class 的用户定义某些属性,您可以使用元 class。我不会重复信息,而是会向您指出 @kindall's answer,它处理这个问题相当优雅。

我很想通过组合而不是继承来对此建模。

你可以有一个用于每个生物的 Entity class 和一个单独的 RaceModifier class 来设置相关的 constants/adjustments 和能力。然后每个生物都可以有一个列表来模拟游戏中的种族,例如:

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