python:类vs元组巨大的内存开销(?)

python:类vs元组巨大的内存开销(?),python,list,class,data-structures,tuples,Python,List,Class,Data Structures,Tuples,我在元组/列表中存储了大量复杂的数据,但是我更喜欢使用小的包装类来使数据结构更容易理解,例如 class Person: def __init__(self, first, last): self.first = first self.last = last p = Person('foo', 'bar') print(p.last) ... 最好是 p = ['foo', 'bar'] print(p[1]) ... 但是,似乎存在可怕的内存开销:

我在元组/列表中存储了大量复杂的数据,但是我更喜欢使用小的包装类来使数据结构更容易理解,例如

class Person:
    def __init__(self, first, last):
        self.first = first
        self.last = last

p = Person('foo', 'bar')
print(p.last)
...
最好是

p = ['foo', 'bar']
print(p[1])
...
但是,似乎存在可怕的内存开销:

l = [Person('foo', 'bar') for i in range(10000000)]
# ipython now taks 1.7 GB RAM

为什么??有没有我没有想到的明显的替代方案

谢谢


(我知道,在这个例子中,“包装器”类看起来很傻。但是当数据变得更复杂和嵌套时,它就更有用了)

使用
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu>可以大大减少内存占用(在我的测试中从1.7GB减少到625MB)

class Person:
    __slots__ = ['first', 'last']
    def __init__(self, first, last):
        self.first = first
        self.last = last

缺点是,在创建实例后,不能再向实例添加属性;该类仅为
\uuuu slots\uuuu
属性中列出的属性提供内存。

在第二个示例中,您只创建一个对象,因为元组是常量

>>> l = [('foo', 'bar') for i in range(10000000)]
>>> id(l[0])
4330463176
>>> id(l[1])
4330463176

类具有开销,即属性保存在字典中。因此,命名的耦合只需要一半的内存。

正如其他人在回答中所说的那样,您必须生成不同的对象,以便进行比较

那么,让我们比较一些方法

tuple
class-Person
namedtuple
tuple
+
\uuuuuuuuuuuuuuuu
namedtuple
基本上是一个类,它扩展了
tuple
,并对所有命名字段使用
\uuuuuuuuuuuuuuuuuuu
,但它添加了字段getter和一些其他助手方法(如果使用
verbose=True
调用,您可以看到生成的确切代码)

班级人员
+
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

这是上面的
namedtuple
的精简版本。一个明显的赢家,甚至比纯元组更好。

除了关闭
\uuuu dict\uuuu
\uuuu weakref\uu
之外,还有另一种方法可以通过关闭对循环垃圾收集的支持来减少对象占用的内存量。它在库中实现:

创建类:

class Person(dataobject):
   first:str
   last:str

因此:

>>> print(sys.getsizeof(Person(100,100)))
32
对于基于
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu
的类,我们有:

class Person:
    __slots__ = ['first', 'last']
    def __init__(self, first, last):
        self.first = first
        self.last = last

>>> print(sys.getsizeof(Person(100,100)))
64
因此,可以节省更多的内存

对于基于数据对象的

l = [Person(i, i) for i in range(10000000)]
memory size: 681 Mb
  l = [Person(i, i) for i in range(10000000)]
  memory size: 921 Mb
对于基于
\uuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuuu

l = [Person(i, i) for i in range(10000000)]
memory size: 681 Mb
  l = [Person(i, i) for i in range(10000000)]
  memory size: 921 Mb

collections.namedtuple
似乎就是为了这个目的而设计的,但就您的示例而言,它们大约需要
1.1GB
。没有太多好的地方。查看
\uuuuu插槽\uuuuu
或移动到Python 3。对于元组,我相信它只引用了相同的元组1000万次。当你创建一个对象,无论是类还是一个新的元组,它都会使用更多的内存。答案中指出,元组示例只会创建一个元组对象。您应该创建一个测试用例,在其中创建许多不同的元组和自定义对象,并查看性能如何。尝试随机化这些值,您应该会得到不同的结果。虽然元组是常量,但这并不能解释这里的差异
[tuple(['foo',bar'])for i in range(N)]
创建了N个常量(但不同的)tuple对象。我没有否决投票,但原因不仅仅是因为“tuple是常量”。它基本上是一个CPython优化,可以处理某种元组文本,例如
(1,2,3/1)
在CPython 2中不会产生相同的ID,因为在CPython 2中3/1不能被常数折叠。感谢您的精彩概述!如果有人想知道2*10M整数怎么会占用1000M内存,这似乎是由于包含列表和引用:
import numpy as np
l=np。数组([(I,I)表示范围内的I(10000000)])
将只占用189MB(在构建过程中占用1GB的时间之后)。但是,这不适用于类实例(引用?)。实际上,
np.array([(i,i)表示范围(10000000)])
将创建一个
dtype('int64')
的同质二维数组,
1000000x2
,这意味着这样的数组的大小是
~8 x N\u elem
字节,或者在本例中,
~160MB
。我已经更正了您的答案中我认为是“输入错误”的地方,如果不是,请返回并表示歉意。不,更正是有效的。它是
Person
的实例,您不能再向其添加新属性。您可能无法将属性添加到
first
last
,但原因完全不同:)
>>> Person = make_dataclass('Person', 'first last')
>>> print(sys.getsizeof(Person(100,100)))
32
class Person:
    __slots__ = ['first', 'last']
    def __init__(self, first, last):
        self.first = first
        self.last = last

>>> print(sys.getsizeof(Person(100,100)))
64
l = [Person(i, i) for i in range(10000000)]
memory size: 681 Mb
  l = [Person(i, i) for i in range(10000000)]
  memory size: 921 Mb