繁体   English   中英

当我尝试存储和使用实例(委托)而不是创建子类(继承)时,为什么库 class 会中断?

[英]Why does a library class break when I try to store and use an instance (delegation) instead of making a subclass (inheritance)?

我正在使用turtle图形标准库模块制作游戏。 我有创建Turtle子类的工作代码,如下所示:

import random

class Food(Turtle):
    def __init__(self):
        super().__init__()
        # more code...

但是,我想看看是否可以在不使用 inheritance 的情况下使其工作。 这是我的尝试:

from turtle import Turtle
from random import randint

class Food():
    def __init__(self):
        self.food = Turtle()
        # more code, but now modifying `self.food` instead of `self`

在程序的其他地方,我有一个Food class 的实例food ,我尝试在food和另一只乌龟snake.head之间进行碰撞检测:

if snake.head.distance(food) < 15:
    ...

在原始代码中,它工作正常,但在新版本中我收到此错误消息:

Traceback (most recent call last):
  File "D:\PycharmProjects\Various stuff beginning\Snake_retry\main.py", line 29, in <module>
    if snake.head.distance(food) < 15:
  File "C:\Users\palliativo\AppData\Local\Programs\Python\Python310\lib\turtle.py", line 1858, in distance
    return abs(pos - self._position)
UnboundLocalError: local variable 'pos' referenced before assignment
Process finished with exit code 1

为什么会发生这种情况,我该如何解决?

我想你必须做

if snake.head.distance(food.food) < 15:
    food.generate_food()

一般来说:当使用组合而不是 inheritance (第二次尝试所做的)时,有必要委托所有需要使用内部 object 的东西——包括对库代码的任何调用。 使用组合而不是 inheritance 意味着不使用 inheritance 因此,class 不再是任何库 class 的子类型; 因此,其他代码不能以同样的方式使用它。


当在snake.head上调用.distance方法时,需要给 Turtle class 一些它知道如何找到其 position 的东西。 它具有以下规则(引自来源):

if y is not None:
    pos = Vec2D(x, y)
if isinstance(x, Vec2D):
    pos = x
elif isinstance(x, tuple):
    pos = Vec2D(*x)
elif isinstance(x, TNavigator):
    pos = x._position

(由于 Python 版本之间的变化,这与报告的错误消息略有不同。)

换句话说,它知道如何使用:

  • 两个独立的整数xy (即直接告诉它位置);
  • 一个Vec2D (直接告诉它 position,但使用库提供的 class 来表示位置);
  • 一个元组(直接告诉它 position,但使用存储在元组中的值);
  • TNavigator (实际上,这意味着 Turtle,但还有其他可能性)。

Food继承自Turtle时,它就是一个Turtle 它有内置的 position 跟踪,其他Turtle可以使用:访问隐藏的._position属性。 (前导下划线意味着其他类不应该知道或使用它;但Python 没有真正的隐私。)

Food存储Turtle时,它不是Turtle 虽然逻辑对程序员来说是显而易见的——获取存储在.food属性中的海龟,然后获取它的 position——已经编写的Turtle代码无法知道这一点。

为了解决这个问题,我们可以在调用方法的地方提取底层的Turtle

if snake.head.distance(food.food) < 15:
    ...

或者我们可以实现库代码想要使用的接口。 在这种特定情况下,这是不可行的; 它明确地检查类型,所以我们需要 class 是这些类型之一——在这种情况下,我们不妨首先使用 inheritance。

但是考虑另一个例子,其他人已经写了一个距离function (不是方法),它需要两个海龟:

def public_distance(t1, t2):
    # this version uses the interface that the Turtle class provides
    # for other code to get the position: calling the `pos` method
    return abs(t1.pos() - t2.pos())

def private_distance(t1, t2):
    # this version directly (naughtily) accesses the "private" attribute
    return abs(t1._position - t2._position)

然后我们可以调整 class 来满足该接口。 对于缺少的方法,实现该方法,并让它的逻辑检查包装的 object。对于缺少的属性,使用(只读)属性检查包装的 object 中的相应信息。这是一个显示两者(并使用实现方法的属性):

class Food:
    # other stuff as before...

    @property
    def _position(self):
        return self.food._position

    def pos(self):
        return self._position

(有人可能会问,为什么 Turtle class 使用方法pos而不是property ?那是因为它是遵循旧设计的旧代码,在property支持被添加到 Python 之前。更新这样的事情对于 Python 开发团队来说是低优先级的;它有破坏旧代码的风险;它涉及编写新文档——并希望教程作者也能得到提示。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM