Подтвердить что ты не робот

В чем преимущество iteritems?

Я использую Python 2.7.5 @Mac OS X 10.9.3 с памятью 8 ГБ и Core i5 1,7 ГГц. Я тестировал потребление времени, как показано ниже.

d = {i:i*2 for i in xrange(10**7*3)} #WARNING: it takes time and consumes a lot of RAM

%time for k in d: k,d[k]
CPU times: user 6.22 s, sys: 10.1 ms, total: 6.23 s
Wall time: 6.23 s

%time for k,v in d.iteritems(): k, v
CPU times: user 7.67 s, sys: 27.1 ms, total: 7.7 s
Wall time: 7.69 s

Кажется, что итераторы медленнее. Мне интересно, в чем преимущество iteritems по прямому доступу к dict.

Обновление: для получения более точного профиля времени

In [23]: %timeit -n 5 for k in d: v=d[k]
5 loops, best of 3: 2.32 s per loop

In [24]: %timeit -n 5 for k,v in d.iteritems(): v
5 loops, best of 3: 2.33 s per loop
4b9b3361

Ответ 1

Чтобы ответить на ваш вопрос, сначала нужно прочесть некоторую информацию о том, как и когда iteritems() был добавлен в API.

iteritems() метод был добавлен в Python2.2 после введения и generators на этом языке (см. также: В чем разница между dict.items() и dict.iteritems()?). Фактически метод явно упоминается в PEP 234. Таким образом, он был представлен как ленивая альтернатива уже существующему items().

Это повторялось так же, как file.xreadlines() по сравнению с file.readlines(), который был введен в Python 2.1 (и кстати уже устарел в python2.3).

В python 2.3 был добавлен модуль itertools, который вводил ленивых копий map, filter и т.д.

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

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

Например, в коде:

function(dictionary.iteritems())

вы не можете просто использовать цикл for для замены iteritems(), как в вашем примере. Вам нужно будет написать функцию (или использовать ген xp, даже если они были недоступны, когда был введен iteritems(), и они не будут DRY...).

Извлечение элементов из dict выполняется довольно часто, поэтому имеет смысл предоставить встроенный метод и, по сути, есть один: items(). Проблема с items() заключается в следующем:

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

Таким образом, при представлении итераторов и генераторов было очевидно просто добавить ленивого аналога. Если вам нужен список элементов, потому что вы хотите его индексировать или повторять несколько раз, используйте items(), иначе вы можете просто использовать iteritems() и избежать проблем, указанных выше.

Преимущества использования iteritems() такие же, как при использовании items() по сравнению с ручным получением значения:

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

Плюс преимущества ленивости.


Как я уже сказал, я не могу воспроизвести результаты вашей работы. На моей машине iteritems() всегда быстрее, чем итерация + поиск ключом. В любом случае, разница все равно незначительна, и, вероятно, это связано с тем, как ОС обрабатывает кеширование и память в целом. В других словах ваш аргумент об эффективности не является сильным аргументом против (или pro) использования одной или другой альтернативы.

Учитывая равные показатели в среднем, используйте наиболее читаемую и сжатую альтернативу: iteritems(). Это обсуждение было бы похоже на запрос "зачем использовать foreach, когда вы можете просто зацикливать по индексу с той же производительностью?". Важность foreach заключается не в том, что вы повторяете быстрее, а в том, что вы избегаете писать код котельной и улучшать читаемость.


Я хотел бы указать, что iteritems() фактически удален в python3. Это было частью "очистки" этой версии. Python3 items() method id (в основном), эквивалентный Python2 viewitems() (на самом деле это бэкпорт, если я не ошибаюсь...).

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

Ответ 2

Использование for k,v in d.iteritems() с более описательными именами может облегчить чтение кода в цикле.

Ответ 3

в отличие от использования команды time, работающей в ipython с timeit дает:

d = {i:i*2 for i in xrange(10**7*3)} #WARNING: it takes time and consumes a lot of RAM

timeit for k in d: k, d[k]
1 loops, best of 3: 2.46 s per loop

timeit for k, v in d.iteritems(): k, v
1 loops, best of 3: 1.92 s per loop

Я запускал это на windows, python 2.7.6. вы запускаете его несколько раз, чтобы подтвердить, что это не что-то происходит с самой системой?

Ответ 4

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

Для тщательности я приурочил множество разных конфигураций. Все они рассчитаны с использованием timeit с коэффициентом повторения 10. Это использует CPython версии 2.7.6 на Mac OS X 10.9.3 с 16 ГБ оперативной памятью и 2.3 ГГц Core i7.

Исходная конфигурация

python -m timeit -n 10 -s 'd={i:i*2 for i in xrange(10**7*3)}' 'for k in d: k, d[k]'
>> 10 loops, best of 3: 2.05 sec per loop

python -m timeit -n 10 -s 'd={i:i*2 for i in xrange(10**7*3)}' 'for k, v in d.iteritems(): k, v'
>> 10 loops, best of 3: 1.74 sec per loop

Предложение Бакуриу

Это предложение включает передачу в цикле iteritems и присвоение значения переменной v в первом цикле путем доступа к словарю в k.

python -m timeit -n 10 -s 'd={i:i*2 for i in xrange(10**7*3)}' 'for k in d: v = d[k]'
>> 10 loops, best of 3: 1.29 sec per loop

python -m timeit -n 10 -s 'd={i:i*2 for i in xrange(10**7*3)}' 'for k, v in d.iteritems(): pass'
>> 10 loops, best of 3: 934 msec per loop

Нет назначения в первом

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

python -m timeit -n 10 -s 'd={i:i*2 for i in xrange(10**7*3)}' 'for k in d: d[k]'
>> 10 loops, best of 3: 1.27 sec per loop

Интересно, что назначение тривиально самому доступу - разница составляет всего 20 мсек. В каждом сравнении (даже в финальном, несправедливом) выигрывает iteritems.

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

Ответ 5

dict.iter() сильно выигрывает в python 3.5.

Вот небольшой показатель производительности:

d = {i:i*2 for i in range(10**3)}
timeit.timeit('for k in d: k,d[k]', globals=globals())
75.92739052970501
timeit.timeit('for k, v in d.items(): k,v', globals=globals())
57.31370617801076