Dictionary в Python
По мотивам Objects/dictnotes.txt
Cyril @notorca Lashkevich
piątek, 30 sierpnia 13
Slide 2
Slide 2 text
Как создать словарь
{}
dict()
PyObject* PyDict_New()
piątek, 30 sierpnia 13
Slide 3
Slide 3 text
Сколько словарей в Hello
World?
$ python -c "print('Hello world')"
| wc -l
piątek, 30 sierpnia 13
Slide 4
Slide 4 text
Сколько словарей в Hello
World?
$ python -c "print('Hello world')"
| wc -l
1642
piątek, 30 sierpnia 13
Slide 5
Slide 5 text
Именованные параметры
функицй
1 запись, 1 чтение
1-3 элемента
Часто встречается в обычных
программах на Python
piątek, 30 sierpnia 13
Slide 6
Slide 6 text
Поиск метода в классе
1 запись, много чтений
8-16 элементов
При наследовании много неудачных
чтений с последующим поиском в
базовом классе
piątek, 30 sierpnia 13
Slide 7
Slide 7 text
Атрибуты и глобальные
пременные
Много записей и чтений
4-10 элементов
piątek, 30 sierpnia 13
Slide 8
Slide 8 text
Builtins
Частые чтение, почти не бывает
записи
~150 строковых ключей (3.3)
По некоторым ключам чтения гораздо
чаще чем по другим
piątek, 30 sierpnia 13
Slide 9
Slide 9 text
Удаление повторов,
подсчет элементов
Одинократное чтение по каждому из
ключей
Произвольное количество элементов
Многократный доступ по одному ключу
подряд
piątek, 30 sierpnia 13
Slide 10
Slide 10 text
Удаление дубликатов
dict.fromkeys(seqn).keys()
Все операции записи при
конструировании
piątek, 30 sierpnia 13
Slide 11
Slide 11 text
Подсчет элементов в
последовательности
for e in seqn:
d[e] = d.get(e,0) + 1
2 последовательных доступа по
одинаковому ключу
piątek, 30 sierpnia 13
Slide 12
Slide 12 text
Создание индекса из
словаря списков
setdefault совмещает 2 поиска в 1м
for pnum, page in enumerate(pages):
for w in page:
d.setdefault(w, []).append(pnum)
piątek, 30 sierpnia 13
Slide 13
Slide 13 text
Проверка принадлежности
Словари произвольных размеров
Создаются 1 раз и затем мало
изменяются
Много вызовов has_key() и
__contains__()
piątek, 30 sierpnia 13
Slide 14
Slide 14 text
Динамические отображения
Чередующиеся добавления, удаления,
чтение и перезапись элементов
piątek, 30 sierpnia 13
Slide 15
Slide 15 text
Реализация (2.7)
Последовательная область памяти с
доступом по индксу
typedef struct {
Py_hash_t me_hash;
PyObject *me_key;
PyObject *me_value;
} PyDictKeyEntry;
piątek, 30 sierpnia 13
Slide 16
Slide 16 text
Пустой dict с размером по умолчанию (8
элементов)
>>> d = {}
piątek, 30 sierpnia 13
Slide 17
Slide 17 text
Хеширование ключа
Ключ преобразуется в индекс с
помощъю функции hash()
hash() возвращает 32/64bit значение
Для индекса берется n младших бит
piątek, 30 sierpnia 13
Slide 18
Slide 18 text
Свойства хеша
Для равных значений хеши всегда
равны
Даже если представление значений
разное: 9, 9.0, complex(9,0)
Похожие значения дают сильно
отличающиеся хеши
piątek, 30 sierpnia 13
Коллизии
Разные ключи пытаются доступиться
по одинаковому индексу
Находим первое свободное место
piątek, 30 sierpnia 13
Slide 37
Slide 37 text
>>> d = {}
piątek, 30 sierpnia 13
Slide 38
Slide 38 text
>>> d['smtp'] = 21
piątek, 30 sierpnia 13
Slide 39
Slide 39 text
>>> d['smtp'] = 21
piątek, 30 sierpnia 13
Slide 40
Slide 40 text
>>> d['dict'] = 2628
piątek, 30 sierpnia 13
Slide 41
Slide 41 text
>>> d['dict'] = 2628
piątek, 30 sierpnia 13
Slide 42
Slide 42 text
>>> d['svn'] = 3690
piątek, 30 sierpnia 13
Slide 43
Slide 43 text
>>> d['svn'] = 3690
piątek, 30 sierpnia 13
Slide 44
Slide 44 text
>>> d['ircd'] = 6667
piątek, 30 sierpnia 13
Slide 45
Slide 45 text
>>> d['ircd'] = 6667
piątek, 30 sierpnia 13
Slide 46
Slide 46 text
>>> d['zope'] = 9673
piątek, 30 sierpnia 13
Slide 47
Slide 47 text
>>> d['zope'] = 9673
# 2 из 5ти элементов на своих
ожидаемых местах
piątek, 30 sierpnia 13
Slide 48
Slide 48 text
Коллизии и очередность
Поскольку из за коллизий элементы
могут находится не по своим
"естественным" индексам порядок
элементов зависит от порядка
добавления
piątek, 30 sierpnia 13
Slide 49
Slide 49 text
Поиск первой свободной
ячейки
Последовательный поиск плох для int
ключей
pertrurb = hash
while (<слот занят>) {
slot = (5*slot) + 1 + perturb;
perturb >>= 5;
}
piątek, 30 sierpnia 13
Slide 50
Slide 50 text
>>> d['svn']
3690
piątek, 30 sierpnia 13
Slide 51
Slide 51 text
>>> d['ircd']
6667
piątek, 30 sierpnia 13
Slide 52
Slide 52 text
>>> d['nsca']
KeyError: 'nsca'
piątek, 30 sierpnia 13
Slide 53
Slide 53 text
>>> d['netstat']
KeyError: 'netstat'
piątek, 30 sierpnia 13
Slide 54
Slide 54 text
Не все поиски одинаковы
Некоторые находят результат сразу
Некоторым нужны несколько итераций
piątek, 30 sierpnia 13
Удаление элементов
Нелзя просто так взять, и пометить
ячейку как пустую
Необходимо вставить специальный
"dummy" элемент
piątek, 30 sierpnia 13
Slide 57
Slide 57 text
del d['smtp']
piątek, 30 sierpnia 13
Slide 58
Slide 58 text
del d['smtp']
d['ircd'] ???
piątek, 30 sierpnia 13
Slide 59
Slide 59 text
del d['smtp']
#Заменяем на "dummy" слот
#Может быть использован снова
piątek, 30 sierpnia 13
Slide 60
Slide 60 text
del d['smtp']
#Заменяем на "dummy" слот
#Может быть использован снова
piątek, 30 sierpnia 13
Slide 61
Slide 61 text
>>> del d['svn'], d['dict'],
d['zope']
>>> d['ircd']
piątek, 30 sierpnia 13
Slide 62
Slide 62 text
Увеличение размера
таблицы
Таблица заполнена максимум на 2/3
2.7: < 50k size × 4
> 50k size × 2
3.3: size × 2
piątek, 30 sierpnia 13
Slide 63
Slide 63 text
>>> d = {}
piątek, 30 sierpnia 13
Slide 64
Slide 64 text
d = dict.fromkeys(words[:5])
# 40% коллизий
# Заполнен на ⅔, resize
piątek, 30 sierpnia 13
Slide 65
Slide 65 text
d['abash'] = None
# размер ×4 до 32
# 0% коллизий
piątek, 30 sierpnia 13
Slide 66
Slide 66 text
d = dict.fromkeys(words[:21])
# 29% коллизий
# Заполнен на ⅔
piątek, 30 sierpnia 13
Slide 67
Slide 67 text
d['abode'] = None
# размер ×4 до 128
# 9% коллизий
piątek, 30 sierpnia 13
Slide 68
Slide 68 text
d = dict.fromkeys(words[:85])
# 33% коллизий
# Заполнен на ⅔
piątek, 30 sierpnia 13
Slide 69
Slide 69 text
Время доступа к элементам
Растет по мере заполнения словаря
Затем резко умешьшается после
изменения размера
Среднее время доступа ОК
piątek, 30 sierpnia 13
Slide 70
Slide 70 text
Поиски vs размер
piątek, 30 sierpnia 13
Slide 71
Slide 71 text
Время vs размер
piątek, 30 sierpnia 13
Slide 72
Slide 72 text
Удаление элементов
Не уменьшает размер таблицы
Таблица может уменьшиться только
при добавлении элементов
piątek, 30 sierpnia 13
Slide 73
Slide 73 text
Порядок элементов
Во время изменения размера порядок
элементов может полностью
поменяться
Добавление элементов во время
итерации запрещено
RuntimeError: dictionary changed size during iteration
piątek, 30 sierpnia 13
Slide 74
Slide 74 text
Свой __hash__()
Хорошо перемешать биты
Равные хеши для равных элементов
__eq__() должен быть
Быстро вычисляется
piątek, 30 sierpnia 13
Slide 75
Slide 75 text
Пример __hash__()
class Point(object):
def __init__(self, x, y):
self.x, self.y = x, y
def __eq__(self, p):
return self.x==p.x and self.y==p.y
def __hash__(self):
return hash(self.x) ^ hash(self.y)
piątek, 30 sierpnia 13
Slide 76
Slide 76 text
oCERT #2011-003
Хэш для str, bytes и datetime
смешивается с "солью" уникальной
для каждого процесса Python
pre 3.3: -R option
3.3: by default
piątek, 30 sierpnia 13
Slide 77
Slide 77 text
Python 3: Split-table словари.
Общая таблица с ключами для разных
таблиц со значениями
piątek, 30 sierpnia 13