способ сделать что-то N раз без индексной переменной?

161

С каждым днем ​​я люблю питона все больше и больше.

Сегодня я писал такой код:

for i in xrange(N):
    do_something()

Я должен был сделать что-то N раз. Но каждый раз не зависел от значения i(индексная переменная). Я понял, что создаю переменную, которую никогда не использовал ( i), и подумал: «Безусловно, существует более питонический способ сделать это без необходимости этой бесполезной индексной переменной».

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

Мануэль Араос
источник
7
Я только что узнал о переменной _, но в противном случае я бы рассмотрел способ, которым вы делаете это Pythonic. Я не думаю, что когда-либо видел простой цикл for, выполненный каким-либо другим способом, по крайней мере, в python. Хотя я уверен, что есть конкретные случаи использования, когда вы смотрите на это и говорите: «Подождите, это выглядит ужасно» - но в целом, xrange является предпочтительным способом (насколько я видел).
Уэйн Вернер
Возможные дубликаты Возможно ли реализовать цикл Python для диапазона без переменной итератора?
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
5
ПРИМЕЧАНИЕ: xrange не существует в Python3. Используйте rangeвместо этого.
Джон Хенкель

Ответы:

110

Немного более быстрый подход, чем цикл xrange(N):

import itertools

for _ in itertools.repeat(None, N):
    do_something()
Алекс Мартелли
источник
3
Насколько быстрее? Есть ли разница в Python 3.1?
Хэмиш Грубиджан
15
@Hamish: Мой тест с 2,6 говорит, что на 32% быстрее (23,2 против 17,6 для N = 1000). Но в любом случае это действительно время. Я бы по умолчанию использовал код OP, потому что он более читабелен (для меня).
Майк Бурс
3
Это приятно знать о скорости. Я, конечно, разделяю мнение Майка о том, что код ОП более читабелен.
Уэйн Вернер
@ Уэйн, я думаю, что привычка действительно очень сильная - за исключением того факта, что вы к ней привыкли, почему еще «нужно считать от 0 до N-1 [[и полностью игнорировать счет]] каждый раз, выполняя этот подсчет -независимая операция "по своей сути более понятна, чем" повторить N раз следующую операцию "...?
Алекс Мартелли
4
Вы уверены, что скорость действительно актуальна? Не правда ли, что если вы сделаете что-то существенное в этом цикле, то, скорее всего, это займет сотни или тысячи раз больше, чем выбранный вами стиль итерации?
Хеннинг
55

Используйте переменную _, как я узнал, когда задавал этот вопрос , например:

# A long way to do integer exponentiation
num = 2
power = 3
product = 1
for _ in xrange(power):
    product *= num
print product
GreenMatt
источник
6
Не downvoter, но это может быть потому, что вы ссылаетесь на другой пост, а не добавляете больше подробностей в ответе
Downgoat
5
@ Downgoat: Спасибо за отзыв. Тем не менее, не так много, чтобы сказать об этой идиоме. При обращении к другому посту я хотел указать, что поиск мог бы дать ответ. Я нахожу ироничным, что этот вопрос имеет в несколько раз больше голосов, чем другой.
GreenMatt
40

Я просто использую for _ in range(n), это прямо к делу. В Python 2 будет сгенерирован весь список огромных чисел, но если вы используете Python 3, это не проблема.

L̳o̳̳n̳̳g̳̳p̳o̳̳k̳̳e̳̳
источник
10

Так как функция является первоклассным гражданином, вы можете написать небольшую оболочку (из ответов Алекса)

def repeat(f, N):
    for _ in itertools.repeat(None, N): f()

тогда вы можете передать функцию в качестве аргумента.

Anycorn
источник
@ Хэмиш: Почти ничего. (17,8 доллара за цикл при тех же условиях, что и для ответа Алекса, для разницы в 0,2 доллара).
Майк Бурс
9

_ - это то же самое, что и x. Однако это идиома Python, которая используется для обозначения идентификатора, который вы не собираетесь использовать. В python эти идентификаторы не запоминаются и не выделяют место, как переменные в других языках. Это легко забыть. Это просто имена, которые указывают на объекты, в данном случае целое число на каждой итерации.

Khorkrak
источник
8

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

from itertools import repeat
N = 10000000

def payload(a):
    pass

def standard(N):
    for x in range(N):
        payload(None)

def underscore(N):
    for _ in range(N):
        payload(None)

def loopiter(N):
    for _ in repeat(None, N):
        payload(None)

def loopiter2(N):
    for _ in map(payload, repeat(None, N)):
        pass

if __name__ == '__main__':
    import timeit
    print("standard: ",timeit.timeit("standard({})".format(N),
        setup="from __main__ import standard", number=1))
    print("underscore: ",timeit.timeit("underscore({})".format(N),
        setup="from __main__ import underscore", number=1))
    print("loopiter: ",timeit.timeit("loopiter({})".format(N),
        setup="from __main__ import loopiter", number=1))
    print("loopiter2: ",timeit.timeit("loopiter2({})".format(N),
        setup="from __main__ import loopiter2", number=1))

Я также придумал альтернативное решение, которое основано на Мартелли и использует map()для вызова функции полезной нагрузки. Хорошо, я немного обманул, потому что я взял на себя свободу заставить полезную нагрузку принимать параметр, который отбрасывается: я не знаю, есть ли способ обойти это. Тем не менее, вот результаты:

standard:  0.8398549720004667
underscore:  0.8413165839992871
loopiter:  0.7110594899968419
loopiter2:  0.5891903560004721

таким образом, использование карты дает улучшение примерно на 30% по сравнению со стандартным циклом и на 19% больше, чем у Мартелли.

япошки
источник
4

Предположим, что вы определили do_something как функцию, и вы хотите выполнить это N раз. Может быть, вы можете попробовать следующее:

todos = [do_something] * N  
for doit in todos:  
    doit()
Кокс Чен
источник
45
Конечно. Давайте не просто вызывать функцию миллион раз, давайте выделим список из миллиона элементов. Если процессор работает, разве память не должна немного напрягаться? Ответ не может быть охарактеризован как определенно «бесполезный» (он показывает другой, функционирующий подход), поэтому я не могу понизить голос, но я не согласен и я полностью против этого.
tzot
1
Разве это не просто список из N ссылок на одно и то же значение функции?
Ник МакКарди
лучше сделать это fn() for fn in itertools.repeat(do_something, N)и сохранить предварительную генерацию массива ... это моя любимая идиома.
F1Rumors
1
@tzot Почему снисходительный тон? Этот человек приложил усилия, чтобы написать ответ, и теперь может отговорить от участия в будущем. Даже если это влияет на производительность, это рабочий вариант, особенно если N невелико, влияние на производительность / память незначительно.
Давидсколган
Я всегда удивляюсь, насколько одержимы разработчики Python :) Хотя я согласен, что это не идиоматично, и кто-то новичок в чтении Python может не понимать, что происходит так же ясно, как при использовании итератора
Asfand Qazi
1

Как насчет простого цикла while?

while times > 0:
    do_something()
    times -= 1

У вас уже есть переменная; почему бы не использовать его?

Карлос Рамирес
источник
1
Я только думаю, что это 3 строки кода против одной (?)
AJP
2
@AJP - Больше похоже на 4 строки против 2 строк
ArtOfWarfare
добавляет сравнение (времена> 0) и декремент (времена - = 1) к накладным расходам ... так медленнее, чем цикл for ...
F1Rumors
@ F1Rumors Я не измерял это, но я был бы удивлен, если бы JIT-компиляторы, такие как PyPy, генерировали более медленный код для такого простого цикла while.
Филипп Классен