Python:为什么一旦添加了键值对

时间:2017-06-01 03:49:32

标签: python python-2.7 dictionary inheritance memory-address

所以我有一个名为User_1的父类,User_1的init方法如下所示:

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类的名为TrendingUsers的类,它的init方法如下所示:

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类的几个实例,并将它们存储在字典中,如下所示:

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

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

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

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

我想知道为什么这是字典的情况,而不是其他变量类型(我尝试过使用各种继承属性的类似练习),以及如何在从具有字典属性的Parent类继承时解决这个问题你想用吗?即我只想一次更新一个实例的继承uploads属性,而不是一次更新所有这些属性。

对此事的任何帮助将不胜感激。谢谢。

编辑:

这可能会有所帮助:

在做任何事之前:

('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)

使用user_id = 11299389

更改TrendingUser的bio后
(66809143, 4446746056, 4458480848, 4441785608, 140675510194976)
(60284557, 4446746056, 4458480560, 4441785608, 140675510194976)
(11299389, 4446746056, 4458667400, 4458804640*, 140675510194976)
  • 内存地址已更改

使用user_id = 11299389向TrendingUser的uploads属性添加键值后

(66809143, 4446746056*, 4458480848, 4441785608, 140675510194976)
(60284557, 4446746056*, 4458480560, 4441785608, 140675510194976)
(11299389, 4446746056*, 4458667400, 4458804640, 140675510194976)
  • 没有变化,现在所有TrendingUser实例的上传属性都有插入的键值对

2 个答案:

答案 0 :(得分:4)

在Python中,规则是“一旦创建,对象永远不会移动”。

该规则引用对象的头部(在调用id()时在CPython中看到的地址的部分)。在内部,可变容器指向数据的可变长度端口。如您所料,随着更多数据的添加,该数据的位置可以随处移动。

有关Python dicts如何工作的概述,请参阅我最近的PyCon talk和相关的slides。要更好地理解对象模型,请参阅Ned Batchelder's blog post

关键是“python容器不包含任何东西”,它们只是对现有对象的引用。对于 TrendingUser ,底层实例字典指向相同的属性值(构建方法定义时创建的常量)。

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

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

请参阅此Pytutor visualization了解您的代码所发生的事情。请注意,两个实例都引用相同的基础数据。

答案 1 :(得分:3)

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

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