【问题标题】:Pythonic way to have code-reuse in game Entity classes在游戏实体类中进行代码重用的 Pythonic 方式
【发布时间】:2012-02-13 18:22:35
【问题描述】:

我开始为我正在编写的游戏定义我的实体类。但是,我想要很多代码重用。我想为不同的功能定义类,然后让类“具有”其中一些类的功能。

例如:

class Collidable:
    def handle_collision(other, incident_vector):
        pass

    def __init__(self, shape):
        self.shape = shape

class Movable:
    def update_position(self):
        self.velocity += self.acceleration
        self.position += self.velocity

    def __init__(self, velocity, acceleration):
        self.velocity, self.acceleration = velocity, acceleration

class Drawable:
    def draw(self):
        pass

    def __init__(self, image):
        self.image = image

class Controllable:
    def key_down(self, key):
        pass

    def __init__(self):
        pass

然后有一个 Player 类,它是 Collidable、Movable、Drawable、Controllable,一个 Invisible Barrier,它只是 Collidable,一个 Background,它只是 Drawable,等等。我听说过许多不同的连接方式多个类,(例如通过组合、(多个)继承、接口等),但我不知道哪种最适合这种情况和/或 Pythonic。

Mix-ins(多重继承的特殊情况)看起来是我正在寻找的(因为 Player 应该是 Collidable、Movable、Drawable 和 Controllable),但在尝试这个时,我'我发现使用 super 将正确的参数传递给正确的 init 函数有困难。

编辑:

我正在使用 python 3.2。

【问题讨论】:

  • 请记住始终从“对象”继承您的类,无论多么短,以便“现代”对象行为适用于它们。有很多有用的恐惧只适用于新样式类,在 Python 2 中是从“对象”派生的类。作为替代方案,如果您有一个包含许多从无到有继承的短类的单个文件,只需设置一个模块级别 __metaclass__=type 变量 - 这与从对象继承每个类具有相同的效果
  • 这对 Python 3.x 仍然有效吗?我没有提到我正在使用 Python 3.2 进行开发,因为我不知道 super 除了语法变化(从直接引用到对父级的间接引用)有任何问题。
  • @Darthfett:在 Py3k 中,类默认继承自 object

标签: python oop mixins


【解决方案1】:

Mixins 是要走的路,但你不想在它们上调用__init__

class CollidableMixin(object):
    #...
    def init_collidable(self, shape):
        self.shape = shape

class MovableMixin(object):
    #...
    def init_movable(self, velocity, acceleration):
        self.velocity, self.acceleration = velocity, acceleration

class DrawableMixin(object):
    #...
    def init_drawable(self, image):
        self.image = image

在我看来,您不需要为Controllable 单独的类,因为它只是定义了继承类应该具有的接口。虽然您在 Java 等静态类型语言中经常这样做,但在 Python 中则不需要。相反,您只需定义一个 key_down 方法并完成它。这称为鸭子打字。

在示例实现中,这将如下所示:

class Player(CollidableMixin, DrawableMixin, MovableMixin):
    def __init__(self):
        self.init_collidable(...)
        self.init_drawable(...)
        self.init_movable(...)

    def key_down(self, key):
        # ...

objects = []
objects.append(Player())
# ... add some more objects. Later we iterate through that collection,
# not knowing which of them is a player:
for o in objects:
    try:
        o.key_down(...)
    except AttributeError:
        pass

【讨论】:

  • 哇,我非常喜欢。也解决了我的游戏的一些问题 :) 不过,我想知道这种方法有多标准。定义“init 的私有版本”是否被接受为一种好习惯?在我看来,代码仍然可以使用标准的 init 方法。你得到的是Collidable.__init__(...),而不是self.init_collidable(...)。不是更清晰并使用更标准的方法吗?干杯
  • 我为什么不想对它们调用 init 呢?这是为了回答为正确的课程提供正确的论据的问题吗?我可以通过使用 **kwargs 参数来解决这个问题吗?
  • @Darthfett:新式类 super(...).__init__(...) 仅在所有方法具有相同签名时才有效。您无法指定调用方法的顺序。您当然可以使用其他答案中的方法,但是这样做会失去一些安全性(而且通常不值得!)。
  • @Morlock:我认为这是旧式课程必须采用的方式,但对于新式课程不鼓励这样做(虽然我不能告诉你原因,抱歉)。
【解决方案2】:

这是使用super() 实现继承的简单方法。为此,您将始终需要使用关键字参数创建 Player 的实例(以及从您的 ***able 类继承的其他类)。每个基类都会从 kwargs 中删除它正在使用的任何关键字参数,并将其余的传递给 mro 中的下一个 __init__(),例如:

class Collidable(object):
    def handle_collision(other, incident_vector):
        pass

    def __init__(self, shape, **kwargs):
        self.shape = shape
        super(Collidable, self).__init__(**kwargs)

class Movable(object):
    def update_position(self):
        self.velocity += self.acceleration
        self.position += self.velocity

    def __init__(self, velocity, acceleration, **kwargs):
        self.velocity, self.acceleration = velocity, acceleration
        super(Movable, self).__init__(**kwargs)

class Drawable(object):
    def draw(self):
        pass

    def __init__(self, image, **kwargs):
        self.image = image
        super(Drawable, self).__init__(**kwargs)

class Controllable(object):
    def key_down(self, key):
        pass

    def __init__(self, **kwargs):
        super(Controllable, self).__init__(**kwargs)

然后你可以定义你的Player 类:

class Player(Collidable, Movable, Drawable, Controllable):
    pass

并像这样使用它:

>>> p = Player(shape='circle', velocity=0.0, acceleration=1.0, image='player.png')
>>> p.shape
'circle'
>>> p.velocity
0.0
>>> p.acceleration
1.0

如果您需要 Player 类的其他实例变量,您可以定义一个类似于其他类的 __init__(),例如:

class Player(Collidable, Movable, Drawable, Controllable):
    def __init__(name, **kwargs):
        self.name = name
        super(Player, self).__init__(**kwargs)

【讨论】:

  • 我同意这是在 Python 中最自然的方式 - 打算使用 OOP 系统的方式。
  • 一旦Player 实例必须将它自己的参数传递给父构造函数,这将变得很糟糕。然后我们需要kwargs.update({...})。如果多个参数同名,那就更难看了……
  • @NiklasB。在调用超级构造函数之后使用 self.* 成员很简单,除非你真的需要在传递它们之前对它们采取行动。不过,您对参数名称是正确的。这是我不喜欢的唯一一件事。这可能是一种窃取模块可选参数的有用方法,但它提供了一种容易出错的方法。
猜你喜欢
  • 1970-01-01
  • 2012-06-28
  • 2013-05-31
  • 1970-01-01
  • 2017-07-11
  • 2012-05-21
  • 1970-01-01
  • 1970-01-01
  • 2011-05-05
相关资源
最近更新 更多