Как перебирать словарь в цикле python
Перейти к содержимому

Как перебирать словарь в цикле python

Эффективный обход словарей в цикле в Python

Словарь (dictionary, dict) — это ассоциативный массив, который позволяет сохранять значения по ключам.

Это очень важная, даже можно сказать основополагающая структура данных, которая используется в Python под капотом буквально повсюду: модули, классы, объекты, locals() , globals() — все это так или иначе работает лишь благодаря словарям.

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

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

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

Что будет если просто попытаться обойти словарь в цикле?

Объявим словарь с отношением различных валют к российскому рублю, который нам по какой-то причине нужно обойти:

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

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

А выведет он следующее:

То есть обход словаря в цикле будет возвращать только ключи этого словаря.

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

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

Как обойти в цикле ключи словаря?

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

У словаря есть метод .keys() , который возвращает представление словаря (dict view), возвращающее ключи.

Что такое представление словаря? Это некий объект, который предоставляет доступ к данным в словаре, либо к части этих данных, и работает по следующим принципам:

  • не копирует содержимое словаря, а обращается к нему динамически, на больших словарях это здорово экономит память и улучшает скорость работы программы;
  • если словарь изменяется, то эти изменения автоматически становятся доступными и через представление словаря;
  • не является списком, не поддерживает извлечение элементов по индексам;
  • является итерируемым объектом, можно использовать в циклах сколько угодно раз.

Создадим такое представление словаря по ключам:

Давайте добавим новый ключ в словарь:

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

Обратите внимание, что представление словаря — это не список, а совершенно другой объект. Представление словаря не поддерживает извлечение значений по индексам:

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

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

Обратите внимание, что если в цикле вам нужны не только ключи словаря, но и значения, то обходить словарь таким образом — не самое эффективное решение. Смотрите дальше, как можно обойти словарь, чтобы получать и ключи, и значения.

Как обойти в цикле значения словаря?

По аналогии с ключами, из словаря можно извлечь только значения, без ключей. Это делается через метод словарей .values() , который возвращает представление словаря, содержащее только значения.

Это представление работает по тем же правилам, что и возвращаемое методом .keys() .

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

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

Как обойти в цикле и ключи, и значения словаря?

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

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

Это представление работает по точно таким же правилам, как .keys() и .values() . Единственное отличие этого представления от предыдущих состоит в том, что оно возвращает не единичные значения, а кортежи из двух значений.

В Python есть возможность распаковывать итерируемые объекты, такие как кортежи, в различные переменные. Давайте на примере посмотрим как это работает:

Таким образом можно распаковывать последовательности любого размера. Это намного проще, чем извлекать значения по индексам и присваивать в отдельные переменные. Этот приём можно использовать практически в любом месте программы, в том числе и в заголовке цикла.

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

Заключение

При обходе словаря стоит руководствоваться следующей логикой:

  • если в цикле используются и ключи, и значения словаря, то нужно использовать метод .items() ;
  • если в цикле используются только значения словаря, а ключи не важны, то нужно использовать метод .values() ;
  • если в цикле нужны ключи словаря и ничего больше, то нужно использовать метод .keys() .

Идеоматичный код проще читается и, как правило, работает быстрее.

Посмотрите запись классического выступления Реймонда Хеттингера, где он рассказывает про написание идеоматичного код. Много внимания уделяется циклам и словарям.

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

Если понравилась статья, то подпишитесь на уведомления о новых постах в блоге, чтобы ничего не пропустить!

Обход словарей в цикле

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

Получить доступ к элементав цикле можно следующим образом:

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

С другой стороны, лучше воспользоваться методом .items() , который возвращает итерируемый объект dict_items с кортежами из пар «ключ-значение»:

Используя данный объект мы можем обойти словарь в цикле двумя другими способами. Первый способ основан на том что переменной i присваиваются кортежи из a.items() :

Второй способ, основан на том что кортежи из a.items() распаковываются в переменные key и value:

Метод .items() возвращает представление словаря — итерируемый и доступный только для чтения. Помимо него существуют еще два метода, которые возвращают представления: .keys() — возвращает представление ключей и .values() — возвращает представление значений. Так что появляется еще два способа обойти ключи и значения в цикле, которые, кстати, являются более «читабельными»:

Представления словарей это весьма любопытные объекты. Они доступны только для чтения, но при этом поддерживают некоторые операции, специфичные для словарей. Что бы продемонстрировать это, сначала создадим подходящие словари:

А затем создадим их представления и посмотрим на их содержимое:

Теперь мы можем выполнять над a_view и b_view операции так, словно они являются множествами:

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

Удивительно, но тоже самое касается не только представлений ключей, но и представлений пар «ключ-значение»:

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

Пишем красивый идиоматический Python


Иногда сложно найти в Сети правильные, а главное актуальные «best practices» для языка. Документация, конечно же, содержит всю необходимую информацию, но отсеять нужную вещь в абзацах подробного (на то она и документация) описания довольно сложно. Но недавно мне улыбнулся поиск Google, и я наткнулся на очень полезные «паттерны» языка Python от одного из core разработчиков — Raymond Hettinger.

Примечание: Все рекомендации даны в нескольких вариантах: сначала идут самые «плохие» варианты, а дальше предлагается лучшая альтернатива. Актуально для версии языка 2.7, отличия для версии 3.3 читайте в примечаниях к конкретному «паттерну».

Цикл по массиву из чисел

Плохо: иногда пишут так.

Хорошо: лучший, с генератором. Но в 32 битной системе список из миллиона чисел будет занимать

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

Примечание: В версии Python 3.3 xrange уже в ядре и называеться просто range .

Цикл по списку

Плохо: часто бывшие С программисты пишут так.

Хорошо: лучший вариант.

Но если нужно пройти по списку задом на перед?

Плохо: опять, прошло из C дает о себе знать:

Хорошо: но в Python пишут вот так:

Цикл по списку с индексами

Плохо тоже что и выше.

Хорошо: более элегантный вариант:

Цикл по двум спискам

Плохо тоже что и выше.

Хорошо: с двух списков делаем один список кортежей. Проблема в том что zip использует больше памяти чем первый вариант.

Отлично: в отличии от zip , izip использует кэширование, что помогает существенно сэкономить память.

Примечание: В версии Python 3.3 izip вписан в ядро и называется просто zip .

Сортировка списка по алгоритму

Плохо: используя функцию для сравнения.

Хорошо: используя сортировку по ключу. Использует намного меньше памяти.

Примечание: Метод cmp убран с ядра Python 3.x.

Цикл по ключам словаря

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

Для изменения словаря в цикле используйте цикл по ключам (Пример: удаление всех ключей начинающихся с R ):

В этом случае d.keys() делает копию ключей словаря, что позволяет нам свободно работать с оригинальной структурой.

Цикл по ключам и значением словаря

Плохо: цикл по ключам и возвращение значение по последним. Медленный способ:

Хорошо: быстрее делать цикл по значениях:

Отлично: Но самый лучший и быстрый способ это использовать итератор:

Соединение двух списков в один словарь

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

Подсчет элементов в словаре

Плохо: обычный способ:

Хорошо: использует функцию get() :

Отлично: самый продвинутый способ это использовать defaultdict() . Но вы должны знать как он работает.

Группирование элементов списка

Плохо: если нужно сгруппировать элементы списка по некоторому признаку (в примере — длина строки) часто используют такой метод:

Хорошо: но есть способ гораздо элегантней и быстрее:

На сегодня все. Надеюсь эти тривиальные, но полезные примеры помогут кому-то улучшить свой код, как они помогли это сделать мне. Их автором является Raymond Hettinger (@raymondh), Python Core Developer.

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

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