为什么命名元组比字典使用更少的内存?

rra*_*nza 3 python python-2.7

我问这个是因为我觉得这很令人惊讶——我认为 anamedtuple会有更多的开销。

(背景是我在内存中缓存了一个大的 Django 查询,发现 Django 对象的大小是 的 100 倍.values()。然后我想知道namedtuple这些对象的开销版本是什么,允许我仍然使用.对项目的访问作为属性。较小的是不是我所期望的。)

#!/usr/bin/env python                                                           

from pympler.asizeof import asizeof                                             
from collections import namedtuple                                              

import random                                                                   
import string                                                                   

QTY = 100000                                                                    


class Foz(object):                                                              
    pass                                                                        

dicts = [{'foo': random.randint(0, 10000),                                      
          'bar': ''.join([random.choice(string.ascii_letters + string.digits) for n in xrange(32)]),
          'baz': random.randrange(10000),                                       
          'faz': random.choice([True, False]),                                  
          'foz': Foz()} for _ in range(QTY)]                                    

print "%d dicts: %d" % (len(dicts), asizeof(dicts))                             

# /sf/ask/3074486831/

MyTuple = namedtuple('MyTuple', sorted(dicts[0]))                               

tuples = [MyTuple(**d) for d in dicts]                                          

print "%d namedtuples: %d" % (len(tuples), asizeof(tuples))                     

print "Ratio: %.01f" % (float(asizeof(tuples)) / float(asizeof(dicts))) 
Run Code Online (Sandbox Code Playgroud)

跑步,

$ ./foo.py    
100000 dicts: 75107672
100000 namedtuples: 56707472
Ratio: 0.8
Run Code Online (Sandbox Code Playgroud)

单个元组甚至更少,可能是由于以下开销list

$ ./foo.py    
1 dicts: 1072
1 namedtuples: 688
Ratio: 0.6
Run Code Online (Sandbox Code Playgroud)

是哈希表数组的开销吗?但是不是namedtuple也需要属性的哈希表吗?是pympler不是准确?

use*_*968 6

基本答案是“是”:普通对象有一个内部字典来存储实例的属性:

class Foo:
    pass

f = Foo()
print(f.__dict__)
# {}
Run Code Online (Sandbox Code Playgroud)

它必须是一个字典,因为在 Python 中,您可以为类未定义的实例分配新属性:

f.a = 1
print(f.__dict__)
# {'a': 1}
Run Code Online (Sandbox Code Playgroud)

使用 dict 允许快速属性查找,但由于数据结构本身存在内存开销。此外,由于 的不同实例Foo可能定义了不同的属性,因此每个实例可能需要自己的字典:

g = Foo()
print(g.__dict__)
# {}
print(f.__dict_ == g.__dict__)
# False
Run Code Online (Sandbox Code Playgroud)

Anamedtuple不允许在运行时添加属性。namedtuple因此, 的特定实例可以将其所有属性存储在由所有实例共享的单个实例中。

给定一个namedtuple和一个实例:

Foo = collections.namedtuple("Foo", 'a,b')
f = Foo(1,2)
Run Code Online (Sandbox Code Playgroud)

所述namedtuple-constructor产生一个描述符的每个字段,并把它存储在类; 这里是存储命名属性和元组索引之间的转换的地方。当您访问ainstance 上f的属性时,属性访问将通过此描述符路由:

type(Foo.a)
#<class 'property'>
Run Code Online (Sandbox Code Playgroud)

  • 在类中,而不是实例中 (3认同)