【问题标题】:Python: assigning to variable also assigns to class attribute?Python:分配给变量也分配给类属性?
【发布时间】:2021-09-20 01:18:07
【问题描述】:

我没有发现任何类似的问题,我正在努力理解我的代码现在在做什么。

我正在使用 Pygame 编写一个小型益智游戏,这也是我第一次涉足面向对象编程。 Pygame 提供Rect 对象来定义屏幕的一部分(具有xywidthheight 等属性)。由于我的游戏文本较多,因此我创建了一个对象Region 来将Rect 对象与font 对象和一些颜色信息组合在一起,以便我需要在屏幕上进行blit 时使用:

    def __init__(self, rect, bg_color, font, font_color):
        self.rect = rect
        self.bg_color = bg_color
        self.font = font
        self.font_color = font_color

然后我创建了一个Layout 类,该类将包含Region 对象的集合,以及一个集中的调色板和一些用于重绘屏幕元素的方法。由于这个游戏只有一个“棋盘”,我只需要一个Layout对象,所以我做了Region对象类属性:

   ...
   progress_box: region(pygame.Rect(5, 340, 275, 10),
                                   colors["white"],
                                   pygame.font.SysFont('arialblack', 12),
                                   colors["black"])

(为了便于阅读,剪掉了其他 Region 对象。)

在代码的后面,我有一个在progress_box 中绘制进度条的函数,如下所示:

def display_progress(score, viz, max_score):
    progress = round(score/max_score, 4)
    progress_bar = region(viz.boxes["progress_box"].rect, "gold", None, None)
    progress_bar.rect.width = int(progress * progress_box.rect.width)
    viz.box_blit(progress_bar) ##viz is my layout object

据我所知,应该发生的是progress_barprogress_box 对象复制rect 属性,但随后我将width 属性重置为progress_box.rect 宽度的百分比。我没有为progress_box 本身分配任何东西,而progress_bar 是一个未连接到Layout 类的Region 对象,所以我假设progress_box.rect.width 在每个循环中都保持不变。

相反,我分配给progress_bar.rect.width 的任何值也会分配给progress_box.rect.width。当然,这意味着在第一次通过循环时,这两个值都设置为 0,因为玩家的得分为 0,之后它们就永远不会改变。

如果我将progress_bar.rect.width = int(progress * progress_box.rect.width) 更改为progress_bar.rect.width = int(progress * 275),该函数将按预期工作,但这并不能向我解释为什么当我没有直接分配任何东西时变量会发生变化。我知道这些属性只初始化一次,所以对类属性的任何更改都会在每个循环中持续存在——我只是不确定为什么它首先会发生变化。

【问题讨论】:

  • “据我所知,应该发生的是 progress_bar 从 progress_box 对象复制 rect 属性” 不,这样的直接对象分配不会执行“深度复制”,因此修改一个对象也会改变另一个对象。 Relevant
  • 谢谢@CoryKramer,从我使用的参考资料中看不清楚!

标签: python class pygame variable-assignment class-attributes


【解决方案1】:

看起来Cory Kramer 已经在他的评论中指出了这一点,但想为遇到此问题的人稍微扩展一下。

官方文档的Data Model 部分很好地解释了对象和对象引用在 Python 中的工作方式。

一个对象的身份一旦被创建就永远不会改变;您可能会将其视为对象在内存中的地址。 “is”运算符比较两个对象的身份; id() 函数返回一个表示其身份的整数。

因此您可以考虑,当您将对象分配给不同的变量或属性时,您实际上是在分配对对象的引用,而不是对象的副本。


你可以在 repl 中做一个简单的例子来展示这一点:

class Region:
    def __init__(self, width, height):
        self.width = width
        self.height = height

class Layout:
    def __init__(self, region):
        self.region = region
my_region = Region(width=5, height=5)
layout1 = Layout(my_region)
layout2 = Layout(my_region)

所以我创建了一个 Region 实例并将其传递给两个单独的 Layout 实例。正如预期的那样,宽度最初是相同的:

layout1.region.width
Out: 5

layout2.region.width
Out: 5

然后更改layout1中区域的宽度:

layout1.region.width = 10

再次检查宽度,您会看到两者都发生了变化:

layout1.region.width
Out: 10

layout2.region.width
Out: 10

您还可以检查每个布局区域的内存地址,以确认它们实际上指向内存中的同一个插槽(它们本质上是同一个对象):

id(layout1.region)
Out: 140631994345120

id(layout2.region)
Out: 140631994345120

复制和相等can be a big topic,但总之你应该看看copy module

例如,如果您想要在上面的示例中真正唯一的对象,您可以为每个 Layout 实例创建唯一的 Region 实例,或者如果您无法实例化新对象,则可以制作一个副本,只需要复制一个现有的:

"""
Unique instances of Region.
"""

layout1 = Layout(Region(width=5, height=5))
layout2 = Layout(Region(width=5, height=5))

# unique references in memory

id(layout1.region)
Out: 140631995857072

id(layout2.region)
Out: 140631995436384
"""
Copy's of an existing Region instance.
"""

import copy

region = Region(width=5, height=5)

layout1 = Layout(copy.copy(region))
layout2 = Layout(copy.copy(region))

# unique references in memory

id(region)
Out: 140631993964480

id(layout1.region)
Out: 140631995585920

id(layout2.region)
Out: 140631995588176

【讨论】:

    猜你喜欢
    • 2021-12-02
    • 2016-09-27
    • 1970-01-01
    • 2013-05-09
    • 1970-01-01
    • 1970-01-01
    • 2016-12-24
    • 2015-09-30
    • 2012-01-28
    相关资源
    最近更新 更多