理解Python 3.7中的类,namedtuple和__slots__的大小

问题描述 投票:1回答:3

在Pycon2016(link)观看Nina Zahkarenko的Python内存管理演讲之后,似乎dunder方法__slots__是一个减少对象大小和加速属性查找的工具。

我的期望是正常的班级将是最大的,而__slots__ / namedtuple方法将节省空间。然而,使用sys.getsizeof()的快速实验似乎暗示:

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__,这将为实现它的类增加函数开销。但是,为什么结果如此严重偏向正常的阶级呢?

引导我的内心雷蒙德H:必须有一个更难的方式!

python class documentation python-3.7
3个回答
2
投票

功能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)

0
投票

“引导我的内心雷蒙德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,看起来它可能对你的用例工作得相当好。在槽矩形和常规矩形上运行它分别产生152352

此外,如果您真的想要优化您的代码并最大限度地减少资源使用,那么请转到该房子的rust / c / c ++端。


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字节)根本不需要)。

© www.soinside.com 2019 - 2024. All rights reserved.