我之所以这样问,是因为我感到惊讶-我认为namedtuple
会产生更多开销。
(背景是我正在内存中缓存一个大型Django查询,发现Django对象的大小是.values()
的100倍。然后我想知道对象的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))
# https://stackoverflow.com/questions/43921240/pythonic-way-to-convert-dictionary-to-namedtuple-or-another-hashable-dict-like
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)))
正在运行
$ ./foo.py
100000 dicts: 75107672
100000 namedtuples: 56707472
Ratio: 0.8
单个元组甚至更少,这可能是由于list
的开销所致:
$ ./foo.py
1 dicts: 1072
1 namedtuples: 688
Ratio: 0.6
是哈希表数组的开销吗?但是namedtuple
也不需要属性的哈希表吗? pympler
不够准确吗?
答案 0 :(得分:4)
基本答案就是“是”:一个普通对象有一个内部字典来存储实例的属性:
class Foo:
pass
f = Foo()
print(f.__dict__)
# {}
这必须是一个dict,因为在Python中,您可以在类未定义的实例上分配新属性:
f.a = 1
print(f.__dict__)
# {'a': 1}
使用字典可以快速查找属性,但是由于数据结构本身,因此存在内存开销。另外,由于Foo
的不同实例可能具有不同的定义属性,因此每个实例都可能需要自己的字典:
g = Foo()
print(g.__dict__)
# {}
print(f.__dict_ == g.__dict__)
# False
namedtuple
不允许在运行时添加属性。因此,namedtuple
的特定实例可以将其所有属性存储在由所有实例共享的单个实例中。
给出一个namedtuple
和一个实例:
Foo = collections.namedtuple("Foo", 'a,b')
f = Foo(1,2)
每个字段的namedtuple
构造函数generates a descriptor并将其存储在类中;这是存储命名属性和元组索引之间的转换的位置。当您访问实例a
上的属性f
时,属性访问将通过以下描述符进行路由:
type(Foo.a)
#<class 'property'>
答案 1 :(得分:3)
但是namedtuple也不需要属性的哈希表吗?
不。 namedtuple的实例布局与常规元组完全相同。元组条目到属性的映射由生成的descriptors提供,这是Python提供的用于控制属性解析的机制。描述符存储在生成的namedtuple类型中,因此它们是每个类型的开销,而不是每个实例的开销。当前,描述符是property
objects,正如您在current implementation中看到的那样,但这可能会发生变化(特别是其中的任何一个都用C重写)。
namedtuple的存储效率要比dict高,因为就内存布局而言,它只是一个tuple。
答案 2 :(得分:0)
一个有理由的理由是,一个命名的元组对于所有实例(名称->索引)仅需要一次其映射。哈希表可能位于某些位于中心的元数据(名称空间)中,而不是在对象本身中,因此不计入每个实例的内存分配中。