Python Slots To Dict

Posted : admin On 4/3/2022
Slots

Python Slots Vs Dict

P: n/a

Classes using __slots__ seem to be quite a bit smaller and faster
to instantiate than regular Python classes using __dict__.
Below are the results for the __slots__ and __dict__ version of a
specific class with 16 attributes. Each line in the tables shows the
number of instances created so far, the total memory usage in Bytes,
the CPU time in secs, the average size per instance in Bytes and the
average CPU time per instance in micseconds.
Instances of this particular class with __slots__ are almost 6x
smaller and nearly 3x faster to create than intances of the __dict__
version. Results for other classes will vary, obviously.
Comments?
/Jean Brouwers
ProphICy Semiconductor, Inc.
PS) The tests were run on a dual 2.4 GHz Xeon system with RedHat
8.0 and Python 2.3.2. The test script is attached but keep in mind
that it only has been tested on Linux. It will not work elsewhere
due to the implementation of the memory() function.
testing __slots__ version ...
4096 insts so far: 3.0e+05 B 0.030 sec 73.0 B/i 7.3 usec/i
8192 insts so far: 8.8e+05 B 0.070 sec 107.5 B/i 8.5 usec/i
16384 insts so far: 1.5e+06 B 0.150 sec 92.2 B/i 9.2 usec/i
32768 insts so far: 3.3e+06 B 0.280 sec 101.0 B/i 8.5 usec/i
65536 insts so far: 6.6e+06 B 0.560 sec 101.2 B/i 8.5 usec/i
131072 insts so far: 1.4e+07 B 1.200 sec 103.4 B/i 9.2 usec/i
262144 insts so far: 2.7e+07 B 2.480 sec 103.4 B/i 9.5 usec/i
524288 insts so far: 5.5e+07 B 5.630 sec 104.0 B/i 10.7 usec/i
1048576 insts so far: 1.1e+08 B 13.980 sec 104.0 B/i 13.3 usec/i
1050000 insts total: 1.1e+08 B 14.000 sec 103.9 B/i 13.3 usec/i
testing __dict__ version ...
4096 insts so far: 2.4e+06 B 0.050 sec 595.0 B/i 12.2 usec/i
8192 insts so far: 4.6e+06 B 0.090 sec 564.5 B/i 11.0 usec/i
16384 insts so far: 9.5e+06 B 0.180 sec 581.8 B/i 11.0 usec/i
32768 insts so far: 1.9e+07 B 0.370 sec 582.2 B/i 11.3 usec/i
65536 insts so far: 3.8e+07 B 0.830 sec 582.6 B/i 12.7 usec/i
131072 insts so far: 7.6e+07 B 1.760 sec 582.7 B/i 13.4 usec/i
262144 insts so far: 1.5e+08 B 4.510 sec 582.8 B/i 17.2 usec/i
524288 insts so far: 3.1e+08 B 12.820 sec 582.8 B/i 24.5 usec/i
1048576 insts so far: 6.1e+08 B 38.370 sec 583.1 B/i 36.6 usec/i
1050000 insts total: 6.1e+08 B 38.380 sec 583.1 B/i 36.6 usec/i
-------------------------------slots.py-------------------------------
<pre>
from time import clock as time_clock
def cputime(since=0.0):
''Return CPU in secs.
''
return time_clock() - since
import os
_proc_status = '/proc/%d/status' % os.getpid() # Linux only
_scale = {'kB': 1024.0, 'mB': 1024.0*1024.0,
'KB': 1024.0, 'MB': 1024.0*1024.0}
def _VmB(VmKey):
global _scale
try: # get the /proc/<pid>/status pseudo file
t = open(_proc_status)
v = [v for v in t.readlines() if v.startswith(VmKey)]
t.close()
# convert Vm value to bytes
if len(v) 1:
t = v[0].split() # e.g. 'VmRSS: 9999 kB'
if len(t) 3: ## and t[0] VmKey:
return float(t[1]) * _scale.get(t[2], 0.0)
except:
pass
return 0.0
def memory(since=0.0):
''Return process memory usage in bytes.
''
return _VmB('VmSize:') - since
def stacksize(since=0.0):
''Return process stack size in bytes.
''
return _VmB('VmStk:') - since
def slots(**kwds):
''Return the slots names as sequence.
''
return tuple(kwds.keys())
# __slots__ version
class SlotsClass(object):
__slots__ = slots(_attr1= False,
_attr2= None,
_attr3= None,
_attr4= None,
_attr5= None,
_attr6= None,
_attr7= 0,
_attr8= None,
_attr9= None,
_attr10=None,
_attr11=None,
_attr12=None,
_attr13=None,
_attr14=None,
_attr15=None,
_attr16=None)
def __init__(self, tuple4, parent):
self._attr1 = False
self._attr2 = None
self._attr3 = None
self._attr4 = None
self._attr5 = None
self._attr6 = None
if parent:
self._attr7 = parent._attr7 + 1
self._attr8 = parent._attr8
self._attr9 = parent._attr9
self._attr10 = parent
self._attr11 = parent._attr11
self._attr12 = parent._attr12
else:
self._attr7 = 0
self._attr8 = None
self._attr9 = None
self._attr10 = None
self._attr11 = self
self._attr12 = None
self._attr13, self._attr14, self._attr15, self._attr16 = tuple4
# __dict__ version
class DictClass(object):
_attr1 = None
_attr2 = None
_attr3 = None
_attr4 = None
_attr5 = None
_attr6 = None
_attr7 = 0
_attr8 = None
_attr9 = None
_attr10 = None
_attr11 = None
_attr12 = None
_attr13 = None
_attr14 = None
_attr15 = None
_attr16 = None
def __init__(self, tuple4, parent):
if parent:
self._attr7 = parent._attr7 + 1
self._attr8 = parent._attr8
self._attr9 = parent._attr9
self._attr10 = parent
self._attr11 = parent._attr11
self._attr12 = parent._attr12
else:
self._attr11 = self
self._attr13, self._attr14, self._attr15, self._attr16 = tuple4
if __name__ '__main__':
import sys
def report(txt, n, b0, c0):
c = cputime(c0);
b = memory(b0)
print '%8d insts %s: %8.1e B %7.3f sec %6.1f B/i %6.1f usec/i'
% (n, txt, b, c, b/n, 1.0e6*c/n)
if not sys.platform.startswith('linux'):
raise NotImplementedError, '%r not supported' % sys.platform
if 'dict' in sys.argv[1:]:
print 'testing __dict__ version ...'
testClass = DictClass
else:
print 'testing __slots__ version ...'
testClass = SlotsClass
t4 = (', 0, 0, [])
b0 = memory()
c0 = cputime()
p = testClass(t4, None)
n, m = 1, 4096
# generate 1+ M instances
while n < 1050000: # 1048576:
p = testClass(t4, p)
n += 1
if n >= m: # occasionally print stats
m += m
report('so far', n, b0, c0)
report(' total', n, b0, c0)
</pre>

Python Slots To Dictionary

In contrast, a class instance with slots declared to be (no data) is only 16 bytes, and 56 total bytes with one item in slots, 64 with two. For 64 bit Python, I illustrate the memory consumption in bytes in Python 2.7 and 3.6, for slots and dict (no slots defined) for each point where the dict grows in 3.6 (except for 0, 1, and 2.

Python Slots To Dict
  • As of Python 3.8, you can now type hint dictionaries per key. This makes all options equivalent in this respect. With older versions of Python, you will not get type hints for dictionaries beyond a union of expected value types. Dictstr, Unionfloat, str PyCharm will offer hints when creating namedtuples and the user-defined object types.
  • It still has dict.update, dict.items, item getters, setters, and deletors. To get the same with an ABC, you'd have to add each function to the class like you did. This is good for cases when you want something that is a mapping, but doesn't have all the attributes or functions that dict has.