简体   繁体   English

Python:分配给变量也分配给类属性?

[英]Python: assigning to variable also assigns to class attribute?

I haven't found any similar questions and I'm struggling to understand what my code is doing right now.我没有发现任何类似的问题,我正在努力理解我的代码现在在做什么。

I'm writing a small puzzle game with Pygame, which is also my first foray into object-oriented programming.我正在用 Pygame 编写一个小型益智游戏,这也是我第一次涉足面向对象编程。 Pygame provides Rect objects to define a portion of the screen (with attributes like x , y , width , height , and so on). Pygame 提供Rect对象来定义屏幕的一部分(具有xywidthheight等属性)。 Since my game is text-heavy, I created an object Region to group a Rect object with a font object and some color information for when I need to blit to screen:由于我的游戏是大量文本,我创建了一个对象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

I then created a Layout class that would hold a collection of Region objects, along with a centralized color palette and some methods for redrawing screen elements.然后我创建了一个Layout类,它包含一个Region对象的集合,以及一个集中的调色板和一些重绘屏幕元素的方法。 Since this game only has one "board," I only need one Layout object, so I made the Region objects class attributes:由于这个游戏只有一个“棋盘”,所以我只需要一个Layout对象,所以我制作了Region对象类属性:

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

(Snipped out the other Region objects for readability.) (为了可读性,剪掉了其他 Region 对象。)

Later in the code, I have a function that draws a progress bar in progress_box like so:在代码的后面,我有一个函数在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

What should happen, as I understand it, is that progress_bar copies the rect attribute from progress_box object, but then I reset the width attribute to a percentage of the progress_box.rect width.据我了解,应该发生的是progress_barprogress_box对象复制rect属性,但随后我将width属性重置为progress_box.rect宽度的百分比。 I'm not assigning anything to progress_box itself, and progress_bar is a Region object not connected to the Layout class, so I assumed that progress_box.rect.width would remain constant over every loop.我没有为progress_box本身分配任何东西, progress_bar是一个未连接到Layout类的Region对象,所以我假设progress_box.rect.width在每个循环中都保持不变。

Instead, whatever value I assign to progress_bar.rect.width is also assigned to progress_box.rect.width .相反,我分配给progress_bar.rect.width任何值也分配给progress_box.rect.width That means, of course, that on the first pass through the loop, both those values are set to 0 because the player's score is 0, and after that they can never change.当然,这意味着在第一次通过循环时,这两个值都设置为 0,因为玩家的分数是 0,之后它们就永远不会改变。

The function works as intended if I change progress_bar.rect.width = int(progress * progress_box.rect.width) to progress_bar.rect.width = int(progress * 275) , but that doesn't explain to me why the variable is changing when I don't assign anything directly to it.如果我将progress_bar.rect.width = int(progress * progress_box.rect.width)更改为progress_bar.rect.width = int(progress * 275) ,该函数将按预期工作,但这并没有向我解释为什么该变量是当我不直接为其分配任何内容时发生变化。 I understand that these attributes are only initialized once, so any change to a class attribute will persist through every loop -- I'm just not sure why it's changing in the first place.我知道这些属性只初始化一次,因此对类属性的任何更改都将在每个循环中持续存在——我只是不确定为什么它首先会发生变化。

Looks like Cory Kramer already pointed this out in his comment, but wanted to expand on it a bit for anyone who comes across this.看起来Cory Kramer已经在他的评论中指出了这一点,但想为遇到此问题的任何人扩展一下。

The Data Model section of the official docs gives a good explanation of how objects and object references work in Python.官方文档的数据模型部分很好地解释了对象和对象引用在 Python 中的工作方式。

An object's identity never changes once it has been created;一个对象的身份一旦被创建就永远不会改变; you may think of it as the object's address in memory.您可以将其视为对象在内存中的地址。 The 'is' operator compares the identity of two objects; “is”运算符比较两个对象的身份; the id() function returns an integer representing its identity. id() 函数返回一个表示其身份的整数。

So you can think about when you're assigning objects to different variables or attributes, you are actually assigning a reference to the object, not a copy of the object.因此,您可以考虑将对象分配给不同的变量或属性时,您实际上是在分配对对象的引用,而不是对象的副本。


You can make a quick example in the repl to show this off:您可以在 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)

So I created a single Region instance and passed it into two separate Layout instances.所以我创建了一个Region实例并将它传递给两个单独的Layout实例。 As expected, the widths are the same initially:正如预期的那样,宽度最初是相同的:

layout1.region.width
Out: 5

layout2.region.width
Out: 5

Then change the width of the region in layout1 :然后更改layout1区域的宽度:

layout1.region.width = 10

And checking the widths again, you'll see both changed:再次检查宽度,您会看到两者都发生了变化:

layout1.region.width
Out: 10

layout2.region.width
Out: 10

You can also check the memory addresses of each layout region to confirm that they are actually pointing to the same slot in memory (they are the same object essentially):您还可以检查每个布局区域的内存地址,以确认它们实际上指向内存中的同一个插槽(它们本质上是同一个对象):

id(layout1.region)
Out: 140631994345120

id(layout2.region)
Out: 140631994345120

Copying and equality can be a big topic , but in short you should check out the copy module .复制和相等可能是一个很大的话题,但简而言之,您应该查看copy模块

For example, if you wanted truly unique objects in that example above, you could either create unique Region instances for each Layout instance, or you could make a copy if you aren't able to instantiate new objects, needing to just duplicate an existing one:例如,如果你想要在上面的例子中真正独特的对象,你可以为每个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

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

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