Перейти к содержимому

Как работать со словарями

  • автор:

Словари (dict) и работа с ними. Методы словарей

Python 3 логотип

Сегодня я расскажу о таком типе данных, как словари, о работе со словарями, операциях над ними, методах, о генераторах словарей.

Словари в Python — неупорядоченные коллекции произвольных объектов с доступом по ключу. Их иногда ещё называют ассоциативными массивами или хеш-таблицами.

Чтобы работать со словарём, его нужно создать. Сделать это можно несколькими способами. Во-первых, с помощью литерала:

Во-вторых, с помощью функции dict:

В-третьих, с помощью метода fromkeys:

В-четвертых, с помощью генераторов словарей, которые очень похожи на генераторы списков.

Теперь попробуем добавить записей в словарь и извлечь значения ключей:

  : Как видно из примера, присвоение по новому ключу расширяет словарь, присвоение по существующему ключу перезаписывает его, а попытка извлечения несуществующего ключа порождает исключение. Для избежания исключения есть специальный метод (см. ниже), или можно перехватывать исключение.

Что же можно еще делать со словарями? Да то же самое, что и с другими объектами: встроенные функции, ключевые слова (например, циклы for и while), а также специальные методы словарей.

Методы словарей

dict.clear() — очищает словарь.

dict.copy() — возвращает копию словаря.

classmethod dict.fromkeys(seq[, value]) — создает словарь с ключами из seq и значением value (по умолчанию None).

dict.get(key[, default]) — возвращает значение ключа, но если его нет, не бросает исключение, а возвращает default (по умолчанию None).

dict.items() — возвращает пары (ключ, значение).

dict.keys() — возвращает ключи в словаре.

dict.pop(key[, default]) — удаляет ключ и возвращает значение. Если ключа нет, возвращает default (по умолчанию бросает исключение).

dict.popitem() — удаляет и возвращает пару (ключ, значение). Если словарь пуст, бросает исключение KeyError. Помните, что словари неупорядочены.

dict.setdefault(key[, default]) — возвращает значение ключа, но если его нет, не бросает исключение, а создает ключ со значением default (по умолчанию None).

dict.update([other]) — обновляет словарь, добавляя пары (ключ, значение) из other. Существующие ключи перезаписываются. Возвращает None (не новый словарь!).

dict.values() — возвращает значения в словаре.

Для вставки кода на Python в комментарий заключайте его в теги

Как работать со словарями данных и оптимизировать запросы в ClickHouse

Приветствуем! На связи вновь Глеб Кононенко и Алексей Диков — разработчики из Лиги Цифровой Экономики. Ранее мы уже немного рассказывали про наш опыт работы с распределенными таблицами в ClickHouse в этой статье.

Сегодня хотим поделиться опытом оптимизации запросов и работы со словарями данных. Используемая версия ClickHouse: 23.8.7.24

Напомним характеристики нашего проекта:

  • Данные грузятся каждые 15 минут
  • Постоянно приходит дублирующая информация
  • Необходимо хранить данные в течение 5 лет
  • В среднем в сутки приходит 150 млн строк (пик — до 13 млрд/сут)
  • В базе 1266 млрд строк, в сжатом виде 61 Тб, в несжатом — 585 Тб

Оглавление

  • Создание тестовых таблиц
  • Джойн. Первые шаги, важность порядка
  • Настройки для запросов
  • Пример оптимизации в ClickHouse
  • Работа со словарями

Создание тестовых таблиц

Создадим две таблицы на движке MergeTree. Первая таблица — факты, в которой поля типа String будем сразу по умолчанию наполнять строками разной длины:

create table fct_contract( org_id UUID, contract_num String DEFAULT randomPrintableASCII(randUniform(10, 15)), name String DEFAULT randomPrintableASCII(randUniform(15, 30)), value UInt32, create_date Date ) ENGINE = MergeTree ORDER BY (org_id,contract_num);

Вторая таблица — справочник, поле ИНН генерируется случайным образом в пределах 10 тысяч значений:

create table dim_name( org_id UUID, name String, inn UInt64 DEFAULT randUniform(100000000000, 100000010000) ) ENGINE MergeTree ORDER BY (org_id, name);

Вставим в первую таблицу 15 млн записей:

insert into fct_contract (org_id, value, create_date) SELECT UUIDNumToString(murmurHash3_128(toString(rand()%10000))) as org_id, value, now()::date - randUniform(1, 10000) FROM generateRandom('value UInt32') LIMIT 15000000;

Джойн. Первые шаги, важность порядка

Пока у нас есть одна наполненная и одна пустая таблицы. Даже в этом случае операция джойн может не выполниться из-за нехватки памяти, если мы неправильно выберем порядок соединения таблиц.

Попробуем. Для этого ограничим память для выполнения запросов в ~1 гигабайт:

select org_id, contract_num, name, value, create_date, inn from dim_name join fct_contract on fct_contract.org_id = dim_name.org_id and fct_contract.name = dim_name.name settings max_memory_usage = 1000000000;

Памяти не хватает, и мы получаем ошибку:

Received exception from server (version 23.8.7): Code: 241. DB::Exception: Received from localhost:9000. DB::Exception: Memory limit (for query) exceeded: would use 955.75 MiB (attempt to allocate chunk of 4244058 bytes), maximum: 953.67 MiB.: (while reading column name): (while reading from part /var/lib/clickhouse/store/d30/d30dbd5e-bad0-4bb1-859c-7f26f955c2cc/all_186_186_0/ in table default.fct_contract (d30dbd5e-bad0-4bb1-859c-7f26f955c2cc) located on disk default of type local, from mark 62 with max_rows_to_read = 8192): While executing MergeTreeThread. (MEMORY_LIMIT_EXCEEDED)

А теперь поменяем таблицы в запросе местами и запустим снова:

select org_id, contract_num, name, value, create_date, inn from fct_contract join dim_name on fct_contract.org_id = dim_name.org_id and fct_contract.name = dim_name.name settings max_memory_usage = 1000000000; 0 rows in set. Elapsed: 0.011 sec. Processed 262.14 thousand rows, 19.39 MB (23.49 million rows/s., 1.74 GB/s.) Peak memory usage: 24.86 MiB.

Запрос отрабатывает, ничего не возвращая, потому что справочник в настоящий момент не заполнен. Дело в том, что ClickHouse при операции соединения в первую очередь загружает в оперативную память правую таблицу из запроса.

Наполним наш справочник:

insert into dim_name (org_id, name) select org_id, name from fct_contract;

Попробуем опять выполнить запрос:

select org_id, contract_num, name, value, create_date, inn from fct_contract join dim_name on fct_contract.org_id = dim_name.org_id and fct_contract.name = dim_name.name settings max_memory_usage = 1000000000;
Received exception from server (version 23.8.7): Code: 241. DB::Exception: Received from localhost:9000. DB::Exception: Memory limit (for query) exceeded: would use 1.38 GiB (attempt to allocate chunk of 1342177280 bytes), maximum: 953.67 MiB.: While executing FillingRightJoinSide. (MEMORY_LIMIT_EXCEEDED)

Теперь и ему не хватает памяти… Попробуем это исправить.

Настройки для запросов

У ClickHouse есть разные алгоритмы соединения таблиц. По умолчанию используется hash-алгоритм, который записывает хеш-значения для соединяемых полей правой таблицы в оперативную память. Затем он ищет среди хешей совпадения для данных из левой таблицы. Такой процесс достаточно эффективен по скорости выполнения, но не очень — в плане потребления ресурсов.

Медленный, но менее ресурсозатратный алгоритм — partial_merge. При его использовании сначала сортируются ключи правой таблицы и затем поблочно (partial_merge_join_rows_in_right_blocks) записываются в оперативную память вместе с индексами каждого блока, до тех пор пока корзина (выделенный на запрос объем оперативной памяти) не заполнится. Затем данные сбрасываются на диск. Далее такая же операция происходит с левой таблицей, и уже затем данные сопоставляются с использование min-max индексов.

Подробнее про алгоритмы вы можете прочитать в этой статье.

Включим этот алгоритм:

select org_id, contract_num, name, value, create_date, inn from fct_contract join dim_name on fct_contract.org_id = dim_name.org_id and fct_contract.name = dim_name.name settings max_memory_usage = 1000000000, join_algorithm = 'partial_merge', default_max_bytes_in_join= 500000000;
15000000 rows in set. Elapsed: 22.969 sec. Processed 30.00 million rows, 1.94 GB (1.31 million rows/s., 84.24 MB/s.) Peak memory usage: 927.08 MiB.

Отлично! Наш запрос отработан за 23 секунды.

На какие еще настройки нужно обратить внимание?

Рассмотрим ситуацию, в которой нам надо упорядочить таблицу fct_contract не по заданной при создании таблицы сортировке, а по произвольному полю:

select org_id, contract_num, name, value, create_date from fct_contract order by name settings max_memory_usage = 1000000000;
Received exception from server (version 23.8.7): Code: 241. DB::Exception: Received from localhost:9000. DB::Exception: Memory limit (for query) exceeded: would use 954.38 MiB (attempt to allocate chunk of 4614592 bytes), maximum: 953.67 MiB.: (avg_value_size_hint = 31.0965576171875, avg_chars_size = 27.715869140625, limit = 16384): (while reading column name): (while reading from part /var/lib/clickhouse/store/d30/d30dbd5e-bad0-4bb1-859c-7f26f955c2cc/all_186_186_0/ in table default.fct_contract (d30dbd5e-bad0-4bb1-859c-7f26f955c2cc) located on disk default of type local, from mark 86 with max_rows_to_read = 16384): While executing MergeTreeThread. (MEMORY_LIMIT_EXCEEDED)

Скрипт не отрабатывает. Чтобы это исправить, добавим настройку max_bytes_before_external_sort. Она будет сбрасывать на диск отсортированные данные при достижении указанного значения. Выделим на сортировку 500 Мб. Вот только этого может не хватить, чтобы избежать ошибки с недостатком памяти. Дело в том, что ClickHouse умеет запускать несколько потоков для выполнения запроса, и для каждого будет работать наше ограничение в 500 Мб.

Чтобы этого избежать, требуется ограничить количество потоков. Это можно сделать с помощью настройки max_threads:

select org_id, contract_num, name, value, create_date from fct_contract order by name settings max_memory_usage = 1000000000, max_bytes_before_external_sort = 500000000, max_threads = 1;
15000000 rows in set. Elapsed: 19.259 sec. Processed 15.28 million rows, 1.13 GB (793.45 thousand rows/s., 58.72 MB/s.) Peak memory usage: 539.32 MiB.

А теперь мы захотели сгруппировать таблицу по полю, не входящему в значения «order by». По логике архитектуры в ClickHouse у нас не должно быть таких запросов, они явно сигнализируют о том, что мы неправильно задали ключ сортировки. Но будем исходить из того, что это разовая история.

select name, sum(value) from fct_contract group by name settings max_memory_usage = 1000000000;
Received exception from server (version 23.8.7): Code: 241. DB::Exception: Received from localhost:9000. DB::Exception: Memory limit (for query) exceeded: would use 958.75 MiB (attempt to allocate chunk of 0 bytes), maximum: 953.67 MiB.: While executing AggregatingTransform. (MEMORY_LIMIT_EXCEEDED)

Добавим настройку для группировки, которая будет также сбрасывать данные на диск после достижения выделенного объема. Теперь отсортируем результат и все сделаем в одном потоке.

select name, sum(value) from fct_contract group by name order by sum(value) desc settings max_memory_usage = 1000000000, max_bytes_before_external_sort = 500000000, max_bytes_before_external_group_by = 500000000, max_threads = 1;
15000000 rows in set. Elapsed: 14.964 sec. Processed 35.51 million rows, 1.44 GB (2.37 million rows/s., 96.55 MB/s.) Peak memory usage: 523.50 MiB.

Следующая задача: нам надо соединить таблицы и просуммировать значения.

select inn, sum(value) from fct_contract join dim_name on fct_contract.org_id = dim_name.org_id and fct_contract.name = dim_name.name group by inn settings max_memory_usage = 1000000000, join_algorithm = 'partial_merge', default_max_bytes_in_join= 500000000, max_bytes_before_external_group_by= 500000000, max_threads = 1;
Received exception from server (version 23.8.7): Code: 241. DB::Exception: Received from localhost:9000. DB::Exception: Memory limit (for query) exceeded: would use 955.32 MiB (attempt to allocate chunk of 5077241 bytes), maximum: 953.67 MiB.: While executing SourceFromNativeStream. (MEMORY_LIMIT_EXCEEDED)

Мы снова получаем ошибку из-за ограничений памяти. На текущий момент при соединениях настройки сортировки и группировки не отрабатывают должным образом.

Пример оптимизации в ClickHouse

Мы решили проанализировать наш предыдущий скрипт и пришли к выводу, что нам достаточно суммировать значения по конкретному org_id. Давайте попробуем запустить скрипт без каких-либо дополнительных настроек, предварительно выбрав любой org_id из сгенерированных в нашей таблице:

select inn, sum(value) from fct_contract as contract join dim_name on contract.org_id = dim_name.org_id and contract.name = dim_name.name where org_id = '9f98b98b-1935-2faf-0002-f45721ee9f37' group by inn settings max_memory_usage = 1000000000;
Received exception from server (version 23.8.7): Code: 241. DB::Exception: Received from localhost:9000. DB::Exception: Memory limit (for query) exceeded: would use 1.38 GiB (attempt to allocate chunk of 1342177280 bytes), maximum: 953.67 MiB.: While executing FillingRightJoinSide. (MEMORY_LIMIT_EXCEEDED)

Вполне ожидаемо: наш запрос не помещается в оперативную память. Добавим настройки:

select inn, sum(value) from fct_contract as contract join dim_name on contract.org_id = dim_name.org_id and contract.name = dim_name.name where org_id = '9f98b98b-1935-2faf-0002-f45721ee9f37' group by inn settings max_memory_usage = 1000000000, join_algorithm = 'partial_merge', default_max_bytes_in_join= 500000000;
1376 rows in set. Elapsed: 5.245 sec. Processed 15.05 million rows, 827.51 MB (2.87 million rows/s., 157.77 MB/s.) Peak memory usage: 857.55 MiB.

Запрос выполнился, но насколько оптимально?

При соединении таблиц в ClickHouse крайне важна предварительная выборка данных. Давайте из обеих таблиц отфильтруем данные в подзапросах до джойна и посмотрим, что получится:

select inn, sum(value) from ( select org_id, name, value from fct_contract where org_id = '9f98b98b-1935-2faf-0002-f45721ee9f37' ) as contract join ( select org_id, name, inn from dim_name where org_id = '9f98b98b-1935-2faf-0002-f45721ee9f37' ) as dim on contract.org_id = dim.org_id and contract.name = dim.name group by inn settings max_memory_usage = 1000000000;
1376 rows in set. Elapsed: 0.010 sec. Processed 90.11 thousand rows, 1.55 MB (9.43 million rows/s., 162.36 MB/s.) Peak memory usage: 10.35 MiB.

Мы видим, что по итогам предварительного отбора данных вместо чтения 15 миллионов строк было прочитано 90 тысяч, пик потребления памяти уменьшился более чем в 80 раз. Скорость выполнения выросла в сотни раз.

Работа со словарями

Но если мы не можем отобрать данные из таблиц и нам целиком надо их соединить и агрегировать?

Есть в ClickHouse такая сущность, как словарь. Словари бывают внутренние и внешние (хранят данные из внешних источников). Они могут быть использованы для оптимизации запросов как альтернативы операциям соединения (join-ам). Словари хранят данные в парадигме ключ-значение частично или полностью в оперативной памяти. Ключ для значения указывается в секции Primary Key, перечисление атрибутов в теле запроса обязательно должно начинаться с ключа.

Неверно:

CREATE DICTIONARY dic_name (name String, id UInt64) PRIMARY KEY id..

Верно:

CREATE DICTIONARY dic_name (id UInt64, name String) PRIMARY KEY id. 

Кроме того, при создании словаря нам необходимо указывать следующие секции:

  • Source — источник, которым может быть как таблица внутри самого ClickHouse, так и информация из внешних источников (другая СУБД, внешний файл или http-источник).
  • Lifetime — периодичность автоматического обновления словаря из источника.
  • Layout — тип размещения словаря в памяти.

Создадим на основе нашей таблицы-справочника словарь. В качестве типа размещения выберем complex_key_hashed_array, так как соединение словаря с таблицей будет происходить по нескольким полям. Такой тип более экономичен по потреблению памяти по сравнению с рекомендуемым в документации complex_key_hashed.

Подробнее про типы хранения словарей в памяти можно почитать в официальной документации.

CREATE DICTIONARY dic_dim_name ( org_id UUID, name String, inn UInt64 ) PRIMARY KEY org_id, name SOURCE(CLICKHOUSE(DB 'default' TABLE 'dim_name')) LIFETIME(0) LAYOUT(complex_key_hashed_array());

Запустим запрос словарю, чтобы он прогрузился, и проверим объем занимаемой оперативной памяти:

select * from dic_dim_name; select name, formatReadableSize(bytes_allocated) from system.dictionaries where name = 'dic_dim_name';
┌─name─────────┬─formatReadableSize(bytes_allocated)─┐ │ dic_dim_name │ 910.30 MiB │ └──────────────┴─────────────────────────────────────┘

В нашем случае словарь занял 910.30 MiB. Теперь попробуем сделать нашу агрегацию, но вместо операции соединения мы извлекаем значения из словаря с помощью функции dictGet. В ней указываем наименование словаря, нужные поля и ключ соединения. Если ключ соединения составной, как в нашем случае, его необходимо собрать в кортеж, обернув в функцию tuple().

select dictGet('dic_dim_name', 'inn', tuple(org_id, name)) as inn, sum(value) from fct_contract group by inn;
10000 rows in set. Elapsed: 0.888 sec. Processed 15.00 million rows, 765.01 MB (16.89 million rows/s., 861.45 MB/s.) Peak memory usage: 20.56 MiB.

Мы не только получили верный результат, наш запрос отработал за 888 миллисекунд! В то время как предыдущий запрос без агрегации, но с джойном двух таблиц по алгоритму partial_merge отрабатывал за 23 секунды.

У функции dictGet есть альтернативы. Для добавления значений по умолчанию при отсутствии совпадения можно использовать dictGetOrDefault, это аналог Left Join. С помощью dictHas в секции where мы может отрезать часть данных таблицы, для которых нет соответствующего ключа в словаре. В документации описаны и другие функции.

Словари значительно эффективнее джойнов: с их помощью можно увеличить скорость расчетов, а также извлекать данные из разных источников (другие базы, файлы и даже URL). ClickHouse прекрасно работает со словарями и позволяет размещать в них десятки миллионов значений.

Но есть и минусы:

1) Данные могут устаревать, и словарям надо обновляться. Мы можем настроить период жизни данных, через который словарь сделает это автоматически, но для больших словарей это может занять довольно длительное время.

2) Словарь загружается в память и хранится там на постоянной основе:

  • при обращении к нему командой select * from dict_name;
  • командой system reload dictionary dict_name;
  • при старте сервера, если параметр dictionaries_lazy_load выставлен в false.

Словари могут забирать много оперативной памяти и сами ее не освобождают. При dictionaries_lazy_load=false не поможет даже перезагрузка сервера. Если словарь постоянно используется и есть выгода в скорости обработки данных, то все хорошо. Но если он применяется однократно в каком-то большом расчете, стоит вытолкнуть его из оперативной памяти. Для этого можно открепить словарь (detach), при этом СУБД «забудет» о его существовании, и он будет вытеснен из памяти, но все метаданные будут сохранены. После этого подсоединяем словарь обратно.

detach dictionary dic_dim_name;

Возвращаем на место:

attach dictionary dic_dim_name;

Проверим занимаемое место:

select name, formatReadableSize(bytes_allocated) from system.dictionaries where name = 'dic_dim_name';
┌─name─────────┬─formatReadableSize(bytes_allocated)─┐ │ dic_dim_name │ 0.00 B │ └──────────────┴─────────────────────────────────────┘

Как видим, теперь словарь не занимает оперативной памяти.

Итак, в этом материале мы рассказали про важные настройки в ClickHouse, которые позволяют успешно работать с базой данных:

  • join_algorithm — если мы используем джойны в запросах, мы должны контролировать алгоритм выполнения;
  • max_bytes_before_external_sort — для нетипичной сортировки таблицы;
  • max_bytes_before_external_group_by — для объемных агрегаций;
  • max_memory_usage — позволяет контролировать память, доступную запросу.

Еще мы наглядно продемонстрировали, как важно заниматься предварительным отбором данных перед соединением таблиц, сократив время выполнения скрипта в сотню раз! А также показали, как успешно применять словари в запросах для избегания операций соединения, и научились освобождать их из памяти после использования.

В следующей статье планируем рассказать, как мы запилили бэкап распределенной СУБД.

  • системы хранения данных
  • оптимизация
  • оптимизация поисковых систем
  • оптимизация поиска
  • clickhouse
  • словарь
  • Блог компании Лига Цифровой Экономики
  • IT-инфраструктура
  • Хранение данных
  • Распределённые системы

Работа со словарями Python

При работе с Python все время нужно временно хранить данные для их последующей обработки в соответствующей структуре данных. Язык предоставляет для этого специальную структуру данных - Python словари . В них можно получить доступ к фрагменту данных или значений с помощью ключа ( слова ), который у нас есть.

В этом руководстве мы рассмотрим следующие темы:

  • как создать словарь, используя фигурные скобки и двоеточия;
  • как загрузить данные в словарь с помощью библиотек urllib и random ;
  • как фильтровать словарь с помощью цикла for и специальных итераторов для перебора ключей и значений словаря;
  • как выполнять операции со словарем для получения или удаления значений, и как использовать значения словаря для подмножества значений из него;
  • как сортировать словарь с помощью библиотеки re и как в этом могут помочь функции OrderedDict и лямбда-функции;
  • сравним словари Python со списками, массивами NumPy и Pandas DataFrames .

Как создать словарь в Python

Предположим, что вы проводите « инвентаризацию » фруктов, которые положили в корзину, сохраняя количество каждого фрукта в словаре. Существует несколько способов создания словаря, но в этом руководстве мы используем самые простые. С остальными можно ознакомиться в документации Python по словарям.

Словари можно распознать по фигурным скобкам <> и разделению двоеточием : ключа и значения для каждого элемента.

Переменная fruit в приведенном ниже коде является допустимым словарем. Получить доступ к элементу Python словаря можно, поместив ключ между квадратными скобками [] .Также можно использовать метод .get() , чтобы сделать то же самое:

fruit = # Получаем доступ к словарю `fruit` непосредственно (без использования get) и выводим значение "banana" print(_____["______"]) # Выбираем один из 5 фруктов и показываем, что оба способа извлечения дают аналогичные результаты print(fruit["_____"] == fruit.get("_____"))

Как добавить данные в словарь Python?

Теперь мы поместим данные во вложенный словарь, который содержит в качестве значения другой словарь, а не строку или целое число. Таким образом, в словарь можно сохранять таблицы или матрицы.

Используемые данные - это отзывы на Amazon о книге Донны Тартт " The Goldfinch ". Они были сохранены в простом файле с разделителями. Таблица содержит четыре столбца: оценка, URL-адрес , заголовок отзыва и текст отзыва.

Есть несколько способов представить эти данные в Python словаре, но в нашем случае мы берем URL-адрес в качестве ключа и помещаем другие столбцы в словарь с вложенными значениями:

import urllib import random # Загружаем данные из удаленного места (URL-адреса) file = urllib.request.urlopen("https://gist.githubusercontent.com/twielfaert/a0972bf366d9aaf6cb1206c16bf93731/raw/dde46ad1fa41f442971726f34ad03aaac85f5414/Donna-Tartt-The-Goldfinch.csv") f = file.read() # Преобразуем битовый поток в строки text = f.decode(encoding='utf-8',errors='ignore') # Разбиваем эту одну строку на концах линий lines = text.split("n") # Инициализируем словарь reviews = <> # Заполняем словарь for line in lines: l = line.strip().split("t") # Это просто тестовые данные, чтобы посмотреть, что входит в словарь score = l[0] title = l[2] review = l[3] reviews[id] = # Берем случайный ключ из словаря и выводим его значение

Мы получили набор данных, в котором нет отсутствующих значений. Но можно проверить, присутствуют ли все ключи в словаре, сравнив количество строк из файла с количеством ключей словаря. В нашем случае сортировка словаря Python осуществляется следующим образом:

# Подсчитываем количество строк в файле print("Количество строк: " + str(len(lines))) # Подсчитываем количество ключей в словаре; оно должно равняться количеству строк в файле print("Количество ключей словаря: " + str(len(reviews.keys())))

Как отфильтровать словарь Python

Теперь, когда отзывы хранятся в словаре, пришло время попробовать выполнить некоторые операции. Допустим, вы хотите посмотреть, что на самом деле написали люди, выбрав только отзывы с оценкой 1.0 .

Оценки хранятся в значениях словаря, поэтому придется перебирать словарь. Для этого можно использовать цикл for .

Элементы словаря Python имеют не только ключ и значение, но и специальный итератор для их перебора. Вместо for item in dictionary необходимо использовать for key , value in dictionary.items() . При этом должны использоваться две переменные, ключ и значение, а не одна.

Существуют отдельные итераторы для ключей (.keys()) и значений (.values()) .

Мы сохраняем ключи отзывов с низкой оценкой в списке с именем lowscores . Благодаря этому в дальнейшем можно будет повторно использовать список, чтобы извлечь отзывы из словаря:

# Сохраняем ключи отзывов с низкой оценкой (1.0) в списке lowscores = [] for key, value in reviews.items(): if float(value["score"]) == 1.0: # Convert score to float lowscores.append(key) # Выводим все записи с низкой оценкой for item in __________: print(reviews[____])

Операции над словарями Python

Если словарь, содержащий полный набор данных, большой, то разумнее использовать список lowscores , который мы только что скомпилировали, чтобы создать совершенно новый словарь ( Python список в словарь ). Преимущество этого приема заключается в том, что для дальнейшего анализа не нужно хранить в памяти большой словарь. Можно просто перейти к соответствующему подмножеству исходных данных.

Во-первых, мы используем ключи, хранящиеся в lowscores , для создания нового словаря. Чтобы сделать это, есть два способа: первый - извлекаем только соответствующие элементы из исходного словаря с помощью метода .get() , оставляя исходный словарь без изменений. Второй - использовать метод .pop() , который удаляет извлеченные записи из исходного словаря.

Код для подмножества может выглядеть следующим образом: subset = dict([(k, reviews.get(k)) for k in lowscores]) . Такое написание может показаться незнакомым, потому что цикл задан одной строкой кода. Этот стиль называется « генерацией словаря ». На самом деле это цикл for , который перебирает элементы lowscores , извлекает значения из отзывов и использует их для заполнения нового словаря.

Вы можете сравнить традиционный стиль с использованием цикла и генерацию словаря и убедиться, что они действительно дают идентичный результат:

# Метод с использованием цикла for для создания подмножества словаря forloop = <> for k in lowscores: forloop[k] = reviews[k] # Добавляем специальный метод извлечения релевантных элементов из словаря `reviews` dictcomp = # Удостоверимся, что эти объекты аналогичны print(forloop == ________)

Предположим, что теперь вы хотите изменить словарь Python 3 , чтобы оценки выступали в качестве ключей словаря, а не идентификаторов. Можно использовать для этого цикл for , указав как ключи, так и значения, и создав новый вложенный словарь. Нужно будет извлечь " score " из исходного вложенного словаря, чтобы использовать его в качестве нового ключа.

Чтобы упростить код, мы создаем в отдельной строке новый вложенный словарь как новый объект newvalues . После чего заполняем scoredict идентификаторами в качестве ключей и объектами из словаря newvalues в качестве значений:

from collections import defaultdict scoredict = defaultdict(list) for key, value in reviews.items(): newvalues = # Используем 'score' из значений (!) из исходного словаря в качестве ключей для только что созданного словаря scoredict[_____['_____']].append(newvalues) # Выводим ключи словаря, чтобы удостовериться, что это на самом деле оценки из отзывов print(scoredict.keys())

Как происходит сортировка словаря Python?

Мы попробовали загрузить в словарь реальный набор данных, теперь можно выполнить их простой анализ. Если вы хотите знать, что именно пишут люди, ставящие низкую оценку роману, можно провести сортировку словаря Python , создав список частоты использования слов в отрицательных отзывах ( оценка 1.0 ).

Нам нужно немного обработать текст отзывов, удалив HTML-теги и конвертировав заглавные буквы в словах в нижний регистр. Для первой задачи мы используем регулярное выражение, которое удаляет все теги: re.sub("", "") . Регулярные выражения - это полезный инструмент при работе с текстовыми данными. Они довольно сложны для компиляции и заслуживают отдельного руководства.

Но в нашем примере нужно просто определить наборы символов, которые начинаются с символа . И заменить их на пустые кавычки "" ( ничего ).

Python содержит встроенную функцию для удаления заглавных букв из слов, нужно просто привязать функцию .lowercase() к строке. Таким образом, можно избавиться от слов, начинающихся с заглавной буквы, которые встречаются в начале предложений.

Затем мы создаем частотный словарь, используя defaultdict вместо обычного словаря. Это гарантирует, что каждый « ключ » уже инициализирован, и можно просто увеличивать его частоту, начиная с 1 .

Если вы не используете defaultdict , Python может выдать ошибку при первом увеличении частоты ( с 0 до 1 ), потому что ключ еще не существует. Этого можно избежать, предварительно проверив, существует ли ключ в Python словаре, прежде чем увеличивать значение его частоты. Но это решение не такое элегантное, как defaultdict :

import re # Импортируем defaultdict from collections import ___________ freqdict = defaultdict(int) for item in lowscores: review = reviews[item]["review"] cleantext = re.sub(r'<.*?>', '', review).strip().split() # Remove HTML tags and split the review by word (space separated) for word in cleantext: # Конвертируем все буквы в нижний регистр word = word.lower() # Заполняем следующую строку, чтобы увеличить частоту на один: freqdict[word] += _ print(freqdict)

После того, как наш частотный словарь будет готов, все равно нужно будет осуществить сортировку словаря Python по значению в порядке убывания, чтобы быстро увидеть, какие слова употребляются чаще всего. Поскольку стандартные словари ( в том числе defaultdict ) не могут быть отсортированы определенным образом, нужно использовать другой класс, а именно OrderedDict . Он хранит элементы словаря в порядке их добавления. В этом случае сначала нужно отсортировать элементы, прежде чем сохранять их снова в класс OrderedDict .

Функция sorted принимает три аргумента. Первый - это объект, который нужно отсортировать, наш частотный словарь. При этом необходимо помнить, что доступ к парам ключ-значение в словаре возможен только через функцию .items() . Если вы забудете об этом, Python даже не выдаст предупреждение, а только вернет первый ключ, который встретит. Другими словами: если вы перебираете словарь, и ваш код ведет себя странным образом, проверьте, добавлена ли функция .items() .

Второй аргумент указывает, какую часть первого аргумента следует использовать для сортировки: key=lambda item: item[1] . Но вам придется более углубленно изучить язык Python , чтобы понять, что это такое. Первая часть довольно понятна: вы хотите, чтобы ключи сортировались.

Но что там делает lambda ? Она является анонимной функцией, то есть это функция без имени, которая не может быть вызвана извне. Это альтернативный способ обработки через цикл целого ряда объектов с помощью одной функции. В данном случае используется значение словаря ( item[1] , при item[0] выступающем в качестве ключа ) в качестве аргумента для сортировки.

Третий ( последний ) аргумент, reverse , указывает, должна ли сортировка выполняться по возрастанию ( по умолчанию ) или по убыванию. В данном случае мы хотим увидеть наиболее часто встречающиеся слова вверху и указываем reverse=True .

Прямо сейчас вы были бы разочарованы словами, которые располагаются в самом верху списка отсортированных элементов. Это были бы просто « функциональные слова », такие как « the », « and », « a » и т. д. В английском, как и во многих других языках, эти слова употребляются достаточно часто. Но при этом они совершенно бессмысленны сами по себе.

В текстовой аналитике для удаления этих высокочастотных слов используются так называемые стоп-листы. Мы же применим более примитивный подход, игнорируя верхние 10% слов и рассматривая только слова, которые относятся к остальным 90%. Вы увидите, что в верхней части списка представлены как слова с негативным оттенком, такие как « неинтересно » и « разочаровывающе », так и более позитивные: « увлекательно » и « чудесно ».

Можно поэкспериментировать и посмотреть, в каких частях Python словаря можно найти интересные слова:

from collections import OrderedDict # Создаем словарь Ordered ordict = OrderedDict(sorted(freqdict.items(), key=lambda item: item[1], reverse=True)) # Игнорируем верхние 10% top10 = int(len(ordict.keys())/10) # Выводим 100 слов из верхних 90% print(list(ordict.items())[top10:top10+100])

Сравнение словарей со списками Python, массивами NumPy и Pandas DataFrames

Словари - важная структура данных Python , позволяющая поместить данные в объекты для дальнейшей обработки. Они, наряду со списками и кортежами, являются одной из основных, наиболее мощных и гибких структур данных Python . Но в последнее время большая часть функциональных возможностей словаря может быть заменена и заменяется Pandas , библиотекой анализа данных Python . Она позволяет лучше обрабатывать и анализировать данные на Python , и при этом не нужно использовать сторонние специализированные языки статистического программирования ( в частности, R ).

Такие библиотеки, как Pandas , позволяют обработчикам данных работать быстрее и эффективнее. Им больше не нужно беспокоиться о деталях более низкого уровня, касающихся того, как хранятся данные. Но Pandas также использует словари ( наряду с другими расширенными структурами данных, такими как массив NumPy ) для хранения данных.

Даже при применении Pandas иногда рекомендуется использовать словари. Например, когда значения необходимо просто сопоставить, и вам не нужны функции Pandas ни для чего другого. Использование объекта Pandas в таких случаях просто неэффективно и излишне.

Pandas включает в себя функции для преобразования словаря в Pandas DataFrame , и наоборот, а DataFrame может осуществлять Python сортировку словаря. Оба они действительно являются полезными частями современного инструментария.

Словари в Python: что нужно знать и как пользоваться

Продолжаем осваивать «змеиный» язык. Сегодня поговорим о словарях — важном инструменте для хранения данных в Python.

Иллюстрация: Оля Ежак для Skillbox Media

Дмитрий Зверев

Дмитрий Зверев

Любитель научной фантастики и технологического прогресса. Хорошо сочетает в себе заумного технаря и утончённого гуманитария. Пишет про IT и радуется этому.

Нет, словари нужны не для того, чтобы переводить запутанный код программистов на человеческий. Их используют для того, чтобы удобно хранить данные и быстро получать к ним доступ по запросу. В этой статье подробно разберём, что такое словари и как с ними работать в Python.

А если вы уже знакомы со словарями и хотите освежить в памяти отдельные моменты, используйте навигацию или скачайте шпаргалку в конце статьи.

  • Что такое словари
  • Как их создавать
  • Основные операции
  • Достаём значение
  • Добавляем новый элемент
  • Удаляем ключ
  • Изменяем значение

Что такое словарь

Словарь в языках программирования — это что-то вроде телефонной книги, где под каждым номером скрывается какой-то человек.

Только на языке разработчиков номера называют ключами, а людей, которым они принадлежат, — значениями. Вот как это можно представить в виде таблицы:

Ключ Значение
8 (984) 123-53-11 Дизайнер Валера
8 (934) 256-32-54 Менеджер Егор (не отвечать)

При этом ключи в словарях уникальны, а значения могут повторяться. Условно говоря, дизайнеру Валере может принадлежать сколько угодно номеров — но у каждого номера может быть только один владелец.

Мы привели пример с телефонной книгой, но вы можете хранить в словаре всё что угодно: названия песен, имена покупателей, товары в интернет-магазине и так далее. При этом стоит помнить важное правило:

Ключами могут быть строки, числа (целые и дробные) и кортежи. Нельзя использовать списки, словари и другие изменяемые типы данных. В значения можно «положить» любые типы данных — и даже новые словари.

Например, давайте в качестве ключа укажем название книги, а значением сделаем её автора.

Ключ Значение
«Гарри Поттер и философский камень» Джоан Роулинг
«Убить пересмешника» Харпер Ли
«Грокаем алгоритмы» Адитья Бхаргава

Теперь, если у нас есть название книги, мы можем быстро найти её автора. Этим словари и удобны: знаем ключ — моментально получаем значение.

�� Словарь — это неупорядоченная структура данных. Это значит, что все пары «ключ — значение» хранятся в произвольном порядке. Идея в том, что нам неважно, где находится элемент: в начале, в серединие или в конце. Важно то, что он лежит где-то внутри и мы можем при случае его достать.

Пример упорядоченной структуры — это список. Там у всех элементов есть свои индексы, благодаря которым их можно отсортировать как душе угодно. Если хотите больше знать о списках, читайте другую нашу статью. А мы пойдём дальше — создадим наш первый словарь.

Как создать словарь в Python

Словари в Python оформляются фигурными скобками. Внутри них находятся пары «ключ — значение». Первым пишется ключ, а затем, через двоеточие, — значение. Сами пары отделяются друг от друга запятыми.

Метод pop()

Что делает: удаляет элемент из словаря по ключу.

harryPotterAuthor = dict.pop('Гарри Поттер и философский камень') print(harryPotterAuthor) > Джоан Роулинг print(dict) > 'Убить пересмешника': 'Харпер Ли'>

Метод keys()

Что делает: возвращает все ключи из словаря — но без значений.

dictKeys = dict.keys() print(dictKeys) > ['Гарри Поттер и философский камень', 'Убить пересмешника']

Метод values()

Что делает: возвращает все значения из словаря — но без ключей.

dictValues = dict.values() print(dictValues) > ['Джоан Роулинг', 'Харпер Ли']

Метод items()

Что делает: возвращает все пары «ключ — значение».

dictItems = dict.items() print(dictItems) > [('Гарри Поттер и философский камень', 'Джоан Роулинг'), ('Убить пересмешника', 'Харпер Ли')]

Что запомнить

Лучше не заучивать голую теорию, а сразу отрабатывать знания на практике. Начать можно, например, с бесплатных упражнений на w3schools.com. А чтобы быстро находить нужный метод и не ходить каждый раз в гугл, можете сохранить себе шпаргалку по ссылке или таблицу ниже.

Метод Что делает
clear() Удаляет все элементы из словаря
copy() Создаёт копию словаря
update() Добавляет в словарь одну или несколько пар «ключ — значение»
pop() Удаляет элемент из словаря по ключу
keys() Возвращает список ключей из словаря
values() Возвращает список значений из словаря
items() Возвращает все пары «ключ — значение»
popitem() Удаляет последнюю добавленную пару «ключ — значение»
get() Возвращает значение из словаря по ключу — или None, если такого ключа не существует. Можно указать своё значение в качестве второго аргумента
setdefault() Возвращает значение указанного ключа. Если ключа не существует, создаёт его и добавляет в словарь со значением None или тем, что вы добавите в качестве второго аргумента

Читайте также:

  • Списки в Python: что это такое и как с ними работать
  • «Прошёл модуль курса и начал рассылать резюме»: музыкант, который стал питонистом
  • Python для новичков: сферы применения и возможности

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *