Dictionary в Python. По мотивам Objects/dictnotes.txt

MinskPythonMeetup 3,796 views 78 slides Sep 16, 2013
Slide 1
Slide 1 of 78
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43
Slide 44
44
Slide 45
45
Slide 46
46
Slide 47
47
Slide 48
48
Slide 49
49
Slide 50
50
Slide 51
51
Slide 52
52
Slide 53
53
Slide 54
54
Slide 55
55
Slide 56
56
Slide 57
57
Slide 58
58
Slide 59
59
Slide 60
60
Slide 61
61
Slide 62
62
Slide 63
63
Slide 64
64
Slide 65
65
Slide 66
66
Slide 67
67
Slide 68
68
Slide 69
69
Slide 70
70
Slide 71
71
Slide 72
72
Slide 73
73
Slide 74
74
Slide 75
75
Slide 76
76
Slide 77
77
Slide 78
78

About This Presentation

Dictionary в Python. По мотивам Objects/dictnotes.txt
Автор: Кирилл Лашкевич (Viber)


Slide Content

Dictionary в Python
По мотивам Objects/dictnotes.txt Cyril @notorca Lashkevich piątek, 30 sierpnia 13

Как создать словарь {}
dict()
PyObject* PyDict_New() piątek, 30 sierpnia 13

Сколько словарей в Hello
World? $ python -c "print('Hello world')"
| wc -l piątek, 30 sierpnia 13

Сколько словарей в Hello
World? $ python -c "print('Hello world')"
| wc -l
1642 piątek, 30 sierpnia 13

Именованные параметры
функицй 1 запись, 1 чтение
1-3 элемента
Часто встречается в обычных
программах на Python piątek, 30 sierpnia 13

Поиск метода в классе 1 запись, много чтений
8-16 элементов
При наследовании много неудачных
чтений с последующим поиском в
базовом классе piątek, 30 sierpnia 13

Атрибуты и глобальные
пременные Много записей и чтений
4-10 элементов piątek, 30 sierpnia 13

Builtins Частые чтение, почти не бывает
записи
~150 строковых ключей (3.3)
По некоторым ключам чтения гораздо
чаще чем по другим piątek, 30 sierpnia 13

Удаление повторов,
подсчет элементов Одинократное чтение по каждому из
ключей
Произвольное количество элементов
Многократный доступ по одному ключу
подряд piątek, 30 sierpnia 13

Удаление дубликатов dict.fromkeys(seqn).keys()Все операции записи при
конструировании
piątek, 30 sierpnia 13

Подсчет элементов в
последовательности for e in seqn:
d[e] = d.get(e,0) + 12 последовательных доступа по
одинаковому ключу
piątek, 30 sierpnia 13

Создание индекса из
словаря списков setdefault совмещает 2 поиска в 1м for pnum, page in enumerate(pages):
for w in page:
d.setdefault(w, []).append(pnum)
piątek, 30 sierpnia 13

Проверка принадлежности Словари произвольных размеров
Создаются 1 раз и затем мало
изменяются
Много вызовов has_key() и
__contains__() piątek, 30 sierpnia 13

Динамические отображения Чередующиеся добавления, удаления,
чтение и перезапись элементов piątek, 30 sierpnia 13

Реализация (2.7) Последовательная область памяти с
доступом по индксу typedef struct {
Py_hash_t me_hash;
PyObject *me_key;
PyObject *me_value;
} PyDictKeyEntry;
piątek, 30 sierpnia 13

Пустой dict с размером по умолчанию (8
элементов)
>>> d = {}
piątek, 30 sierpnia 13

Хеширование ключа Ключ преобразуется в индекс с
помощъю функции hash()
hash() возвращает 32/64bit значение
Для индекса берется n младших бит piątek, 30 sierpnia 13

Свойства хеша Для равных значений хеши всегда
равны
Даже если представление значений
разное: 9, 9.0, complex(9,0)
Похожие значения дают сильно
отличающиеся хеши piątek, 30 sierpnia 13

>>> d['ftp'] = 21
>>> bits(hash('ftp'))[-8:]
10100001
piątek, 30 sierpnia 13

>>> d['ftp'] = 21
>>> bits(hash('ftp'))[-8:]
10100001
piątek, 30 sierpnia 13

>>> d['ssh'] = 22
>>> bits(hash('ssh'))[-3:]
101
piątek, 30 sierpnia 13

>>> d['ssh'] = 22
>>> bits(hash('ssh'))[-3:]
101
piątek, 30 sierpnia 13

>>> d['smtp'] = 25
>>> bits(hash('smtp'))[-3:]
100
piątek, 30 sierpnia 13

>>> d['smtp'] = 25
>>> bits(hash('smtp'))[-3:]
100
piątek, 30 sierpnia 13

>>> d['time'] = 37
>>> bits(hash('time'))[-3:]
111
piątek, 30 sierpnia 13

>>> d['time'] = 37
>>> bits(hash('time'))[-3:]
111
piątek, 30 sierpnia 13

>>> d['www'] = 80
>>> bits(hash('www'))[-3:]
010
piątek, 30 sierpnia 13

>>> d['www'] = 80
>>> bits(hash('www'))[-3:]
010
piątek, 30 sierpnia 13

d = {'ftp': 21, 'ssh': 22,
'smtp': 25, 'time': 37,
'www': 80}
piątek, 30 sierpnia 13

Поиск в словаре Вычислить хеш от ключа
Обрезать старшие биты
Взять значение из слота по индексу piątek, 30 sierpnia 13

>>> d['smtp']
25
>>> bits(hash('smtp'))[-3:]
100
piątek, 30 sierpnia 13

Перебор всех элементов Словари возвращают свои ключи или
значения в порядке отличном от
порядка добавления piątek, 30 sierpnia 13

>>> print d
{'ftp': 21, 'www': 80, 'smtp':
25, 'ssh': 22, 'time': 37}
piątek, 30 sierpnia 13

>>> d.keys()
['ftp', 'www', 'smtp', 'ssh',
'time']
piątek, 30 sierpnia 13

>>> d.values()
[21, 80, 25, 22, 37]
piątek, 30 sierpnia 13

Коллизии Разные ключи пытаются доступиться
по одинаковому индексу
Находим первое свободное место piątek, 30 sierpnia 13

>>> d = {}
piątek, 30 sierpnia 13

>>> d['smtp'] = 21
piątek, 30 sierpnia 13

>>> d['smtp'] = 21
piątek, 30 sierpnia 13

>>> d['dict'] = 2628
piątek, 30 sierpnia 13

>>> d['dict'] = 2628
piątek, 30 sierpnia 13

>>> d['svn'] = 3690
piątek, 30 sierpnia 13

>>> d['svn'] = 3690
piątek, 30 sierpnia 13

>>> d['ircd'] = 6667
piątek, 30 sierpnia 13

>>> d['ircd'] = 6667
piątek, 30 sierpnia 13

>>> d['zope'] = 9673
piątek, 30 sierpnia 13

>>> d['zope'] = 9673
# 2 из 5ти элементов на своих
ожидаемых местах
piątek, 30 sierpnia 13

Коллизии и очередность Поскольку из за коллизий элементы
могут находится не по своим
"естественным" индексам порядок
элементов зависит от порядка
добавления piątek, 30 sierpnia 13

Поиск первой свободной
ячейки Последовательный поиск плох для int
ключей pertrurb = hash
while (<слот занят>) {
slot = (5*slot) + 1 + perturb;
perturb >>= 5;
}
piątek, 30 sierpnia 13

>>> d['svn']
3690
piątek, 30 sierpnia 13

>>> d['ircd']
6667
piątek, 30 sierpnia 13

>>> d['nsca']
KeyError: 'nsca'
piątek, 30 sierpnia 13

>>> d['netstat']
KeyError: 'netstat'
piątek, 30 sierpnia 13

Не все поиски одинаковы Некоторые находят результат сразу
Некоторым нужны несколько итераций piątek, 30 sierpnia 13

threes = {3: 1, 3+8: 2, 3+16: 3,
3+24: 4, 3+32: 5}
piątek, 30 sierpnia 13

Удаление элементов Нелзя просто так взять, и пометить
ячейку как пустую
Необходимо вставить специальный
"dummy" элемент piątek, 30 sierpnia 13

del d['smtp']
piątek, 30 sierpnia 13

del d['smtp']
d['ircd'] ???
piątek, 30 sierpnia 13

del d['smtp']
#Заменяем на "dummy" слот
#Может быть использован снова
piątek, 30 sierpnia 13

del d['smtp']
#Заменяем на "dummy" слот
#Может быть использован снова
piątek, 30 sierpnia 13

>>> del d['svn'], d['dict'],
d['zope']
>>> d['ircd']
piątek, 30 sierpnia 13

Увеличение размера
таблицы Таблица заполнена максимум на 2/3
2.7: < 50k size × 4
> 50k size × 2
3.3: size × 2 piątek, 30 sierpnia 13

>>> d = {}
piątek, 30 sierpnia 13

d = dict.fromkeys(words[:5])
# 40% коллизий
# Заполнен на ⅔, resize
piątek, 30 sierpnia 13

d['abash'] = None
# размер ×4 до 32
# 0% коллизий
piątek, 30 sierpnia 13

d = dict.fromkeys(words[:21])
# 29% коллизий
# Заполнен на ⅔
piątek, 30 sierpnia 13

d['abode'] = None
# размер ×4 до 128
# 9% коллизий
piątek, 30 sierpnia 13

d = dict.fromkeys(words[:85])
# 33% коллизий
# Заполнен на ⅔
piątek, 30 sierpnia 13

Время доступа к элементам Растет по мере заполнения словаря
Затем резко умешьшается после
изменения размера
Среднее время доступа ОК piątek, 30 sierpnia 13

Поиски vs размер
piątek, 30 sierpnia 13

Время vs размер
piątek, 30 sierpnia 13

Удаление элементов Не уменьшает размер таблицы
Таблица может уменьшиться только
при добавлении элементов piątek, 30 sierpnia 13

Порядок элементов Во время изменения размера порядок
элементов может полностью
поменяться
Добавление элементов во время
итерации запрещено
RuntimeError: dictionary changed size during iteration piątek, 30 sierpnia 13

Свой __hash__() Хорошо перемешать биты
Равные хеши для равных элементов
__eq__() должен быть
Быстро вычисляется piątek, 30 sierpnia 13

Пример __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

oCERT #2011-003 Хэш для str, bytes и datetime
смешивается с "солью" уникальной
для каждого процесса Python
pre 3.3: -R option
3.3: by default piątek, 30 sierpnia 13

Python 3: Split-table словари.
Общая таблица с ключами для разных
таблиц со значениями
piątek, 30 sierpnia 13

Спасибо http://blip.tv/pycon-us-
videos-2009-2010-2011/pycon-2010-
the-mighty-dictionary-55-3352147
Python source code piątek, 30 sierpnia 13