0

So I have a parent class called User_1, and User_1's init method looks like this:

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

I then have a class that inherits from the User_1 class called TrendingUsers, and it's init method looks like this:

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

I create several instances of the TrendingUser class and store them in a dictionary like so:

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

Now when I check the memory address of the various instances of TrendingUser using the id() function, I get different values. However, when I check the memory address of the all inherited attributes of each TrendingUser instance, I get the same value across the instances, except for the comments attribute (the list).

A side question: Why is this the case?

The real question is that when I edit one of the inherited attributes of a TrendingUser instance, for example updating the bio, the memory address changes, and only that instance's bio has been updated. This is not the case with the uploads attribute, which is a dictionary. When I insert a key-value pair into what I believe is the uploads attribute of a single TrendingUser instance, it adds the key-value pair to all TrendingUser instances' uploads attribute. When I check if the memory address of the uploads attribute changed after inserting the key-value pair, I realize that it hasn't, which explains the behavior.

I was wondering why is this the case with dictionaries, but not other variable types (I have tried similar exercises with the various inherited attributes), and how to get around this problem when inheriting from a Parent class that has a dictionary attribute you would like to use? I.e. I only want to update one instance's inherited uploads attribute at a time, not all of them at once.

Any help on this matter would be greatly appreciated. Thank you.

EDIT:

This might help:

Before doing anything:

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

After changing the bio of TrendingUser with user_id = 11299389

(66809143, 4446746056, 4458480848, 4441785608, 140675510194976)
(60284557, 4446746056, 4458480560, 4441785608, 140675510194976)
(11299389, 4446746056, 4458667400, 4458804640*, 140675510194976)
  • the memory address changed

After adding a key-value to the uploads attribute of TrendingUser with user_id = 11299389

(66809143, 4446746056*, 4458480848, 4441785608, 140675510194976)
(60284557, 4446746056*, 4458480560, 4441785608, 140675510194976)
(11299389, 4446746056*, 4458667400, 4458804640, 140675510194976)
  • no change, and now all uploads attributes of the thee TrendingUser instances have the inserted key-value pair
user3241894
  • 73
  • 1
  • 5
  • Possible duplicate of ["Least Astonishment" and the Mutable Default Argument](https://stackoverflow.com/questions/1132941/least-astonishment-and-the-mutable-default-argument) – AChampion Jun 01 '17 at 03:58

2 Answers2

5

In Python, the rule is "once created, an object never moves".

That rule refers to the head of the object (the part whose address you see in CPython when calling id()). Internally, mutable container points to the variable length port of the data. As you suspected, the location of that data can move around as more data gets added.

For an overview of how Python dicts work, see my recent PyCon talk and the related slides. To better understand the object model, see Ned Batchelder's blog post.

A key point is that "python containers don't contain anything", they just have references to existing objects. In the case of TrendingUser the underlying instance dictionaries point to the same attribute values (constants created when the method definition was built).

As you update the values, for instances of TrendingUsers, the memory location will point to the new values you've inserted and they will not be that same addresses as the default values (remember, that once created, those objects will never move).

Hope that adds a little clarity :-)

See this Pytutor visualization of what is happening with your code. Note that both instances refer to the same underlying data.

Raymond Hettinger
  • 216,523
  • 63
  • 388
  • 485
  • But why do all instances of the TrendingUser objects' uploads attribute point to the same dictionary, and how can I get around this issue? – user3241894 Jun 01 '17 at 03:54
3

The default argument uploads={} is the problem. It actually creates the dict at class definition time and sets that dict as the default, instead of what you want (create a new empty dict if none is specified). For that, the usual pattern is

def __init__(self, par=None):
  if par is None:
    par = {}
Paul Becotte
  • 9,767
  • 3
  • 34
  • 42