пітонічний спосіб зробити щось N разів без змінної індексу?


161

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

Сьогодні я писав такий код, як:

for i in xrange(N):
    do_something()

Мені довелося щось робити N разів. Але кожен раз не залежав від значення i(змінної індексу). Я зрозумів, що створюю змінну, яку ніколи не використовував (i ), і подумав, що "Напевно існує більш пітонічний спосіб зробити це без необхідності цієї марної змінної індексу".

Отже ... питання: чи знаєте ви, як зробити це просте завдання більш (пітонічним) красивим способом?


7
Я щойно дізнався про змінну _, але в іншому випадку я би розглядав спосіб, як ти це робиш Pythonic. Я не думаю, що я ніколи не бачив простий цикл, зроблений будь-яким іншим способом, принаймні в python. Хоча я впевнений, що є конкретні випадки використання, коли ви дивитесь на це і говорите "Зачекайте, це виглядає жахливо" - але загалом, xrange - це кращий спосіб (наскільки я бачив).
Уейн Вернер


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). Але це справді час справді. Я б за замовчуванням використовував код ОП, тому що він швидше читається (для мене).
Майк Буер

3
Це добре знати про швидкість. Я, безумовно, перегукуюся з почуттями Майка про те, що код OP краще читати.
Уейн Вернер

@Wayne, я думаю, звичка дійсно дуже потужна - за винятком того, що ви звикли до неї, чому б інакше "рахувати від 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

6
Не переможець, але це може бути тому, що ви посилаєтесь на інший пост замість того, щоб додати більше деталей у відповіді
Пуховик

5
@Downgoat: Дякую за відгук. Тим не менш, про цю ідіому сказати не так вже й багато. Мій погляд на іншу посаду полягав у тому, щоб зазначити, що пошук, можливо, дав відповідь. Я вважаю іронічним те, що це запитання має кілька разів більш високу оцінку, як і інше.
GreenMatt

39

Я просто використовую for _ in range(n), це прямо до суті. Це створить весь список для величезних чисел у Python 2, але якщо ви використовуєте Python 3, це не проблема.


10

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

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

тоді ви можете передавати функцію як аргумент.


@Hamish: Майже нічого. (17,8 нас за цикл за тих же умов, що і терміни для відповіді Алекса, за різницю в 0,2 ми).
Майк Бурз

9

_ Те саме, що і x. Однак це ідіома пітона, яка використовується для вказівки ідентифікатора, який ви не збираєтесь використовувати. У python ці ідентифікатори не займають запам'ятовування та не виділяють простір, як це робиться в інших мовах. Це легко забути. Вони просто імена, які вказують на об'єкти, в даному випадку ціле число на кожній ітерації.


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))

Я також придумав альтернативне рішення, яке базується на принципі Martelli і використовує його 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 малий, наслідки для продуктивності / пам'яті не мають значного значення.
davidscolgan

Я завжди здивований тим, наскільки продуктивними є розробники Python :) Хоча я згоден, що це не ідіоматично, і хтось новий, хто читає Python, може не зрозуміти, що відбувається так просто, як просто за допомогою ітератора
Asfand Qazi

1

Що про простий цикл?

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

Ви вже маєте змінну; чому б не використати його?


1
Моє єдине думка - це 3 рядки коду проти одного (?)
AJP

2
@AJP - Більше, як 4 рядки проти 2 рядків
ArtOfWarfare

додає порівняння (раз> 0) та декремент (раз - = 1) до накладних витрат ... так повільніше, ніж для циклу ...
F1Rumors

@ F1Rumors Це не вимірювали, але я був би здивований, якщо компілятори JIT, такі як PyPy, повинні генерувати повільніший код для такого простого циклу.
Філіп Класен
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.