观看了Nina Zahkarenko在Pycon2016(link)上的Python内存管理演讲之后,似乎dunder方法__slots__
是减少对象大小并加快属性查找速度的工具。
我期望普通班级人数最多,而__slots__
/ namedtuple
方法可以节省空间。但是,快速实验证明我是错的:
from collections import namedtuple
from sys import getsizeof
class Rectangle:
'''A class based Rectangle, with a full __dict__'''
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
class SlotsRectangle:
'''A class based Rectangle with __slots__ defined for attributes'''
__slots__ = ('x', 'y', 'width', 'height')
def __init__(self, x, y, width, height):
self.x = x
self.y = y
self.width = width
self.height = height
NamedTupleRectangle = namedtuple('Rectangle', ('x', 'y', 'width', 'height'))
NamedTupleRectangle.__doc__ = 'A rectangle as an immutable namedtuple'
print(f'Class: {getsizeof(Rectangle(1,2,3,4))}')
print(f'Slots: {getsizeof(SlotsRectangle(1,2,3,4))}')
print(f'Named Tuple: {getsizeof(NamedTupleRectangle(1,2,3,4))}')
端子输出:
$ python3.7 example.py
Class: 56
Slots: 72
Named Tuple: 80
这是怎么回事?从Python Data Model上的文档看来,__slots__
使用了描述符,这会给实现它的类增加函数开销。但是,为什么结果如此偏向正常的班级呢?
引导我内在的Raymond H .:必须有更艰难的方法!
答案 0 :(得分:1)
函数sys.getsizeof()
可能没有按照您的想象做。它不适用于复杂对象,例如自定义类。
请看this answer,以计算对象的内存大小;也许对您有帮助。 我在此处复制了该答案的代码,但完整的解释在我链接的答案中。
import sys
from numbers import Number
from collections import Set, Mapping, deque
try: # Python 2
zero_depth_bases = (basestring, Number, xrange, bytearray)
iteritems = 'iteritems'
except NameError: # Python 3
zero_depth_bases = (str, bytes, Number, range, bytearray)
iteritems = 'items'
def getsize(obj_0):
"""Recursively iterate to sum size of object & members."""
_seen_ids = set()
def inner(obj):
obj_id = id(obj)
if obj_id in _seen_ids:
return 0
_seen_ids.add(obj_id)
size = sys.getsizeof(obj)
if isinstance(obj, zero_depth_bases):
pass # bypass remaining control flow and return
elif isinstance(obj, (tuple, list, Set, deque)):
size += sum(inner(i) for i in obj)
elif isinstance(obj, Mapping) or hasattr(obj, iteritems):
size += sum(inner(k) + inner(v) for k, v in getattr(obj, iteritems)())
# Check for custom object instances - may subclass above too
if hasattr(obj, '__dict__'):
size += inner(vars(obj))
if hasattr(obj, '__slots__'): # can have __slots__ with __dict__
size += sum(inner(getattr(obj, s)) for s in obj.__slots__ if hasattr(obj, s))
return size
return inner(obj_0)
答案 1 :(得分:0)
“引导我内在的Raymond H,” +1
关于插槽的事情是,您必须阅读about slots。
另一件事是,它们确实会影响class
的大小:
print(f'(Class) Class: {getsizeof(Rectangle)}') # 1056
print(f'(Class) Slots: {getsizeof(SlotsRectangle)}') # 888
很酷。现在,假设我们向Rectangle类添加了一个字段:
rect = Rectangle(1,2,3,4)
rect.extra_field = dict() # wild right?
print(f'(Object) Class: {getsizeof(rect)}') # still 56
因此,您可以“使用”(以实例变量的形式)“计数”资源,并且插槽矩形将为112
,非插槽矩形也将为112 ... >
但是,我们知道情况并非如此,因为我们期望常规矩形至少为352
,因为我们在其中添加了dict
。
插槽阻止您执行此操作,因此提供了一种限制资源使用的方式。
请查看此answer here,对于您的用例来说,它似乎工作得很好。在插槽矩形和常规矩形上运行它分别产生152
和352
。
此外,如果您真的想优化代码并最大程度地减少资源使用,那就来看看rust / c / c ++。
答案 2 :(得分:0)
具有recordclass库的更紧凑的变体:
from recordclass import dataobject
class Rectangle(dataobject):
x:int
y:int
width:int
height:int
>>> r = Rectangle(1,2,3,4)
>>> print(sys.getsizeof(r))
48
它的内存占用量少于基于__slots__
的内存占用量,因为它不参与循环垃圾回收(未设置Py_TPFLAGS_HAVE_GC标志,因此PyGC_Head
(24字节)不需要全部)。