Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does declaring variables in a function called from __init__ still use a key-sharing dictionary?

I've tried to always declare class attributes inside the __init__ for clarity and organizational reasons. Recently, I've learned that strictly following this practice has extra non-aesthetic benefits too thanks to PEP 412 being added for Python 3.3. Specifically, if all attributes are defined in the __init__, then the objects can reduce space by sharing their keys and hashes.

My question is, does object key-sharing happen when attributes are declared in a function that is called by __init__?

Here's an example:

class Dog:
    def __init__(self):
        self.height = 5
        self.weight = 25

class Cat:
    def __init__(self):
        self.set_shape()

    def set_shape(self):
        self.height = 2
        self.weight = 10

In this case, all instances of Dog would share the keys height and weight. Do instances of Cat also share the keys height and weight (among each other, not with Dogs of course).

As an aside, how would you test this?

Note that Brandon Rhodes said this about key-sharing in his Dictionary Even Mightier talk:

If a single key is added that is not in the prototypical set of keys, you loose the key sharing

like image 249
Nathaniel Rivera Saul Avatar asked Jun 22 '17 17:06

Nathaniel Rivera Saul


People also ask

Is __ init __ automatically called?

Note: The __init__() function is called automatically every time the class is being used to create a new object.

What is __ dict __ in Python?

All objects in Python have an attribute __dict__, which is a dictionary object containing all attributes defined for that object itself. The mapping of attributes with its values is done to generate a dictionary.

Which symbol precedes the attribute of function name when you use an object?

In Python, we use double underscore (Or __) before the attributes name and those attributes will not be directly visible outside.


1 Answers

does object key-sharing happen when attributes are declared in a function that is called by __init__?

Yes, regardless of where you set the attributes from, granted that after initialization both have the same set of keys, instance dictionaries use a shared-key dictionary implementation. Both cases presented have a reduced memory footprint.

You can test this by using sys.getsizeof to grab the size of the instance dictionary and then compare it with a similar dict created from it. dict.__sizeof__'s implementation discriminates based on this to return different sizes:

# on 64bit version of Python 3.6.1
print(sys.getsizeof(vars(c)))
112
print(getsizeof(dict(vars(c))))
240

so, to find out, all you need to do is compare these.

As for your edit:

"If a single key is added that is not in the prototypical set of keys, you loose the key sharing"

Correct, this is one of the two things I've (currently) found that break the shared-key usage:

  1. Using a non-string key in the instance dict. This can only be done in silly ways. (You could do it using vars(inst).update)
  2. The contents of the dictionaries of two instances of the same class deviating, this can be done by altering instance dictionaries. (single key added to that is not in the prototypical set of keys)

    I'm not certain if this happens when a single key is added, this is an implementation detail that might change. (addendum: see Martijn's comments)

For a related discussion on this see a Q&A I did here: Why is the __dict__ of instances so small in Python 3?

Both these things will cause CPython to use a 'normal' dictionary instead. This, of course, is an implementation detail that shouldn't be relied upon. You might or might not find it in other implementations of Python and or future versions of CPython.

like image 83
Dimitris Fasarakis Hilliard Avatar answered Oct 05 '22 04:10

Dimitris Fasarakis Hilliard