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 Dog
s 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
Note: The __init__() function is called automatically every time the class is being used to create a new object.
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.
In Python, we use double underscore (Or __) before the attributes name and those attributes will not be directly visible outside.
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:
vars(inst).update
)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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With