了解 Python 3.7 中类、namedtuple 和 __slots__ 的大小

Mat*_*att 5 python documentation class python-3.7

在 Pycon2016 上观看 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))}')
Run Code Online (Sandbox Code Playgroud)

终端输出:

$ python3.7 example.py
Class: 56
Slots: 72
Named Tuple: 80
Run Code Online (Sandbox Code Playgroud)

What is going on here? From the docs on Python's Data Model it appears that descriptors are used for __slots__ which would add function overhead to classes implementing it. However, why are the results so heavily skewed towards a normal class?

Channeling my inner Raymond H.: there has to be a harder way!

Ral*_*alf 2

该函数sys.getsizeof()可能没有按照您的想法进行操作;它不适用于复杂的对象,例如自定义类。

查看这个答案,了解计算对象内存大小的方法;也许它对你有帮助。我从此处复制了该答案中的代码,但完整的解释位于我链接的答案中。

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)
Run Code Online (Sandbox Code Playgroud)