Python: 为什么添加键值对后继承的字典属性的内存位置没有改变

Python: Why doesn't the memory location of an inherited dictionary attribute change once you have added a key value pair

所以我有一个名为 User_1 的父 class,User_1 的初始化方法如下所示:

class User_1(object):
    def __init__(self, user_id, bio="", likes=0, uploads={}, follows=0, dateJoined=None, accountType=""):
        self.user_id = user_id
        self.bio = bio
        self.likes = likes
        self.uploads = uploads
        self.follows = follows
        self.dateJoined = dateJoined
        self.accountType = accountType
        self.comments = []
        self.responseCommentsCount = 0

然后我有一个从 User_1 class 继承的 class 称为 TrendingUsers,它的初始化方法如下所示:

class TrendingUser(User_1):
    def __init__(self, user_id):
        User_1.__init__(self, user_id)

        self.averageTSWords = 0.0
        self.averageSSWords = 0.0
        self.percOfClipTitlesUntitled = 0.0
        self.percOfClipsWithCaptions = 0.0
        self.percOfClipsWithTags = 0.0
        self.percOfClipsWithComments = 0.0
        self.percOfPurgatoryClips = 0.0
        self.averageTimeDifferenceBetweenUploaded = 0.0

我创建了几个 TrendingUser class 实例并将它们存储在字典中,如下所示:

for user_id in user_ids:
    dic[user_id] = TrendingUser(user_id)

现在,当我使用 id() 函数检查 TrendingUser 的各个实例的内存地址时,我得到了不同的值。但是,当我检查每个 TrendingUser 实例的所有继承属性的内存地址时,我在所有实例中得到相同的值,除了 comments 属性(列表)。

附带问题:为什么会这样?

真正的问题是,当我编辑 TrendingUser 实例的一个继承属性时,例如更新 bio,内存地址发生变化,并且只有该实例的 bio 被更新。 uploads 属性不是这种情况,它是一个字典。当我将键值对插入我认为是单个 TrendingUser 实例的上传属性时,它会将键值对添加到所有 TrendingUser 实例的上传属性。当我检查插入键值对后上传属性的内存地址是否发生变化时,我意识到它没有,这解释了行为。

我想知道为什么字典会出现这种情况,而其他变量类型却不会出现这种情况(我尝试过对各种继承属性进行类似练习),以及从父类继承时如何解决这个问题class 具有您想使用的字典属性? IE。我只想一次更新一个实例的继承上传属性,而不是一次全部更新。

如能就此事提供任何帮助,我们将不胜感激。谢谢你。

编辑:

这可能有帮助:

做任何事情之前:

('user_id', 'memory_address_uploads_attribute', 'memory_address_comments_attribute', 'memory_address_bio_attribute', 'memory_address_follows_attribute')
(66809143, 4446746056, 4458480848, 4441785608, 140675510194976)
(60284557, 4446746056, 4458480560, 4441785608, 140675510194976)
(11299389, 4446746056, 4458667400, 4441785608, 140675510194976)

将 TrendingUser 的简介更改为 user_id = 11299389

(66809143, 4446746056, 4458480848, 4441785608, 140675510194976)
(60284557, 4446746056, 4458480560, 4441785608, 140675510194976)
(11299389, 4446746056, 4458667400, 4458804640*, 140675510194976)

将键值添加到 TrendingUser 的上传属性后 user_id = 11299389

(66809143, 4446746056*, 4458480848, 4441785608, 140675510194976)
(60284557, 4446746056*, 4458480560, 4441785608, 140675510194976)
(11299389, 4446746056*, 4458667400, 4458804640, 140675510194976)

在Python中,规则是"once created, an object never moves"。

该规则指的是对象的头部(调用id()时在CPython中看到其地址的部分)。在内部,可变容器指向数据的可变长度端口。正如您所怀疑的那样,随着添加更多数据,该数据的位置可能会移动。

有关 Python 听写如何工作的概述,请参阅我最近的 PyCon talk and the related slides. To better understand the object model, see Ned Batchelder's blog post

一个关键点是"python containers don't contain anything",它们只是对现有对象的引用。在 TrendingUser 的情况下,底层实例字典指向相同的属性值(在构建方法定义时创建的常量)。

当您更新值时,对于 TrendingUsers 的实例,内存位置将指向您插入的新值,并且它们将不是与默认值相同的地址(请记住,一旦创建,这些对象永远不会移动)。

希望增加一点清晰度:-)

查看此 Pytutor visualization 您的代码所发生的情况。请注意,这两个实例都引用了相同的基础数据。

默认参数 uploads={} 是问题所在。它实际上在 class 定义时创建字典并将该字典设置为默认值,而不是您想要的(如果指定 none 则创建一个新的空字典)。为此,通常的模式是

def __init__(self, par=None):
  if par is None:
    par = {}