Почему именованные кортежи используют меньше памяти, чем словари?

Я спрашиваю это, потому что я нахожу это удивительным - я думал, что namedtuple будет иметь больше накладных расходов.

( .values() фоном является то, что я кэшировал большой запрос Django в памяти и обнаружил, что объекты Django в 100 раз больше размера .values() . Затем я задался вопросом, какими будут namedtuple версии 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 также не понадобится namedtuple таблица атрибутов? pympler не является точным?

Всего 3 ответа


Но разве именованному кортежу также не понадобится хэш-таблица атрибутов?

Нету. Макет экземпляра namedtuple точно такой же, как и у обычного кортежа. Отображение записей кортежей в атрибуты обеспечивается сгенерированными дескрипторами , которые представляют собой механизм, который Python обеспечивает для управления разрешением атрибутов. Дескрипторы хранятся в сгенерированном типе namedtuple, поэтому они стоят за тип, а не за экземпляр. В настоящее время дескрипторы являются объектами property , как вы можете видеть в текущей реализации , но это может измениться (особенно, если что-то из этого будет переписано на C).

Именованный кортеж становится намного более эффективным по памяти, чем диктат, потому что с точки зрения структуры памяти это просто кортеж.


Основной ответ - просто «да»: обычный объект имеет внутренний словарь для хранения атрибутов экземпляра:

class Foo:
    pass

f = Foo()
print(f.__dict__)
# {}

Это должно быть диктом, потому что в Python вы можете назначать новые атрибуты экземпляру, которые не были определены классом:

f.a = 1
print(f.__dict__)
# {'a': 1}

Использование dict позволяет осуществлять быстрый поиск атрибутов, но из-за самой структуры данных происходит перерасход памяти. Кроме того, поскольку для разных экземпляров Foo могут быть определены разные атрибуты, каждому отдельному экземпляру может потребоваться свой собственный dict:

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 генерирует Здесь хранится перевод между именованным атрибутом и индексом кортежа. Когда вы обращаетесь к атрибуту a в экземпляре f , доступ к атрибуту направляется через этот дескриптор:

type(Foo.a)
#<class 'property'>


Есть идеи?

10000