【问题标题】:Correct way of passing a self variable as argument to a mixin parent method将 self 变量作为参数传递给 mixin 父方法的正确方法
【发布时间】:2020-05-23 16:45:46
【问题描述】:

我必须模拟一个战士以及他可以执行的不同类型的攻击。这个想法是使用 mixins 来包含攻击逻辑。我的类是通过以下方式定义的:

class Warrior:
    def __init__(self, energy):
        self.energy = energy


class TemplarKnight(Warrior, HandToHandCombatMixin):
    pass


class CombatMixin:
    def __init__(self):
        self.attacks_cost = {}

    def attack(self, attacker, attack_cost):
        if attacker.energy < attack_cost:
            print('Not enough energy to attack')
        else:
            attacker.energy -= attack_cost
            print('Attack!')


class HandToHandCombatMixin(CombatMixin):
    def __init__(self):
        super().__init__()
        self.attacks_cost['sword_spin'] = 10

    def sword_spin(self, attacker):
        return self.attack(attacker, self.attacks_cost['sword_spin'])

但是当我尝试测试这个设置时问题就来了。当我这样做时

class TestTemplarKnight(unittest.TestCase):
    def setUp(self):
        self.templar = TemplarKnight(energy=100)

    def test_templar_knight_can_sword_spin(self):
        self.templar.sword_spin(self.warrior)
        self.assertEquals(self.templar.energy, 90)

我明白了

    def sword_spin(self, attacker):
        return self.attack(
>           attacker, self.attacks_cost['sword_spin'])
E       AttributeError: 'TemplarKnight' object has no attribute 'attacks_cost'

似乎Python认为参数self.attacks_cost(在HandToHandCombatMixin类的sword_spin()方法内调用self.attack()时)属于TemplarKnight类而不是HandToHandCombatMixin

我应该如何编写此代码以使 Python 在 HandToHandCombatMixin 中查找 self.attacks_cost

【问题讨论】:

  • 所有在这种情况下__init__方法应该使用super()
  • 属性并不真正属于类;它们都属于同一个实例,但只有在调用设置它们的__init__ 方法时才会创建每个实例。

标签: python python-3.x oop inheritance mixins


【解决方案1】:

要正确使用super所有所涉及的类都需要使用它。现在,首先调用Warrior.__init__,但它不使用super,因此永远不会调用HandToHandCombatMixin.__init__

添加以下内容:

class Warrior:
    def __init__(self, energy, **kwargs):
        super().__init__(**kwargs)
        self.energy = energy


class TemplarKnight(Warrior, HandToHandCombatMixin):
    pass


class CombatMixin:
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.attacks_cost = {}

    def attack(self, attacker, attack_cost):
        if attacker.energy < attack_cost:
            print('Not enough energy to attack')
        else:
            attacker.energy -= attack_cost
            print('Attack!')


class HandToHandCombatMixin(CombatMixin):
    def __init__(self, **kwargs):
        super().__init__(**kwargs)
        self.attacks_cost['sword_spin'] = 10

    def sword_spin(self, attacker):
        return self.attack(attacker, self.attacks_cost['sword_spin'])

现在当您实例化TemplarKnight 时,您将保证所有__init__ 方法都被调用,并且顺序正确。最终,对super() 的一次调用将导致object.__init__ 被调用,此时链最终结束。如果您正确处理关键字参数,**kwargs 将在发生时为空。

【讨论】:

  • 唯一需要的更改是在Warrior 类的开头添加super().__init__()。在CombatMixin 的开头添加相同的调用对测试的输出没有影响。为什么会这样?
  • 至少在 this 的情况下,CombatMixinTemplarKnight 的 MRO 中的最后一个类(在 object 之前),而 object.__init__ 并不是真的做任何事情,所以如果它没有被调用也没关系。但是,可能有一些 otherCombatMixin 不知道 可能 有一个 MRO 与 CombatMixin 在不同的位置,所以你仍然需要使用super 避免破坏链。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-09-03
  • 2022-01-05
  • 2019-09-02
  • 2021-11-13
  • 2020-07-07
  • 1970-01-01
  • 2012-02-07
相关资源
最近更新 更多