Чи можливо реалізувати Python для циклу діапазону без змінної ітератора?


187

Чи можна зробити наступне без i?

for i in range(some_number):
    # do something

Якщо ви просто хочете зробити щось N кількість разів і не потрібен ітератор.


21
Це гарне запитання! PyDev навіть позначає "я" як попередження про "невикористовувану змінну". Наведене нижче рішення видаляє це попередження.
Ешвін Нанджаппа

@Ashwin Ви можете використовувати \ @UnusedVariable, щоб видалити це попередження. Зауважте, що мені потрібно було уникнути символу "at", щоб пройти цей коментар.
Раффі Хатчадурян

Я очолюю те саме питання. Це дратує попередження про пілінт. Звичайно, ви можете відключити попередження шляхом додаткового придушення, як, наприклад, запропонував @Raffi Khatchadourian. Було б непогано уникати попереджень і коментарів щодо придушення.
танговий

Відповіді:


110

Зверху моєї голови, ні.

Я думаю, що найкраще ви могли зробити щось подібне:

def loop(f,n):
    for i in xrange(n): f()

loop(lambda: <insert expression here>, 5)

Але я думаю, що ви можете просто жити з додатковою iзмінною.

Тут є можливість використовувати _змінну, яка насправді є лише іншою змінною.

for _ in range(n):
    do_something()

Зауважте, що _присвоюється останній результат, який повернувся в інтерактивному сеансі пітона:

>>> 1+2
3
>>> _
3

З цієї причини я б не використовував це таким чином. Я не знаю жодної ідіоми, про яку згадував Райан. Це може зіпсувати ваш перекладач.

>>> for _ in xrange(10): pass
...
>>> _
9
>>> 1+2
3
>>> _
9

Відповідно до граматики Python , це прийнятна назва змінної:

identifier ::= (letter|"_") (letter | digit | "_")*

4
"Але я думаю, що ви можете просто жити з додатковим" я "" Так, це просто академічний момент.
Джеймс Макмахон

1
@nemo, ви можете спробувати робити _ в діапазоні (n): якщо ви не хочете використовувати буквено-цифрові імена.
Невідомо

Чи є змінною у такому випадку? Або це щось інше в Python?
Джеймс Макмахон

1
@nemo Так, це просто прийнятна назва змінної. У перекладачі автоматично присвоюється останній вираз, який ви зробили.
Невідомо

3
@kurczak Є пункт. Використання _дає зрозуміти, що його слід ігнорувати. Сказати, що робити це не має сенсу, як сказати, що немає сенсу коментувати свій код - адже це все одно зробить саме те саме.
Фея Лямбда

69

Ви можете шукати

for _ in itertools.repeat(None, times): ...

це найшвидший спосіб повторити timesчас на Python.


2
Я не переймався продуктивністю, мені просто цікаво, чи існував більш стислий спосіб написання заяви. Хоча я використовую Python спорадично вже близько 2 років, я все ще відчуваю, що мені багато чого не вистачає. Itertools - одна з таких речей, дякую за інформацію.
Джеймс Макмахон

5
Це цікаво, я цього не знав. Я щойно подивився на документи itertools; але мені цікаво, чому це швидше, ніж просто використовувати діапазон чи xrange?
si28719e

5
@blackkettle: це швидше, оскільки йому не потрібно повертати поточний індекс ітерації, який є вимірюваною частиною вартості xrange (і діапазон Python 3, який дає ітератор, а не список). @nemo, діапазон настільки ж оптимізований, як це може бути, але необхідність складання та повернення списку неминуче важча робота, ніж ітератор (у Py3, діапазон повертає ітератор, як xrange Py2; зворотна сумісність не дозволяє такої зміни в Py2), особливо той, якому не потрібно повертати змінне значення.
Алекс Мартеллі

4
@ Крістіан, так, чітко готуючи і повертаючи Python int кожен раз, в т.ч. gc робота, чи має вимірні витрати - використання лічильника всередині не має значення.
Алекс Мартеллі

4
Я тепер розумію. Різниця походить від накладних витрат GC, а не від "алгоритму". До речі, я біжу швидко timeit тест і прискорення був ~ 1.42x.
Крістіан Цюпіту

59

Загальна ідіома для присвоєння значенню, яке не використовується, - називати його _.

for _ in range(times):
    do_stuff()

18

Що всі, хто пропонує вам використовувати _, не говорять про те, що _ часто використовується як ярлик до однієї з функцій gettext , тому, якщо ви хочете, щоб ваше програмне забезпечення було доступне більш ніж однією мовою, тоді вам краще уникати його використання. для інших цілей.

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print _('This is a translatable string.')

Мені таке використання _здається жахливою ідеєю, я б не проти конфліктувати з нею.
KeithWM

9

Ось випадкова ідея, яка використовує (зловживає?) Модель даних ( посилання Py3 ).

class Counter(object):
    def __init__(self, val):
        self.val = val

    def __nonzero__(self):
        self.val -= 1
        return self.val >= 0
    __bool__ = __nonzero__  # Alias to Py3 name to make code work unchanged on Py2 and Py3

x = Counter(5)
while x:
    # Do something
    pass

Цікаво, чи є щось подібне в стандартних бібліотеках?


10
Я думаю, що такий метод, як __nonzero__побічні ефекти, - жахлива ідея.
ThiefMaster

2
Я б використав __call__замість цього. while x():не так важче написати.
Ясмійн

1
Існує також аргумент щодо уникнення імені Counter; звичайно, це не зарезервовано або у вбудованій області, але collections.Counterце річ , а створення однойменного класу ризикує заплутатися у підтримці (не те, що це вже не ризикує).
ShadowRanger

7

Ви можете використовувати _11 (або будь-яке число або інший недійсний ідентифікатор), щоб запобігти зіткненню імен з gettext. Щоразу, коли ви використовуєте підкреслювач + недійсний ідентифікатор, ви отримуєте фіктивне ім'я, яке можна використовувати для циклу.


Приємно! PyDev погоджується з вами: це позбавляється від жодного попередження "Невикористана змінна".
мійський гризун

2

Можливо, відповідь залежатиме від того, яка проблема у вас із використанням ітератора? може бути корисним

i = 100
while i:
    print i
    i-=1

або

def loop(N, doSomething):
    if not N:
        return
    print doSomething(N)
    loop(N-1, doSomething)

loop(100, lambda a:a)

але відверто кажучи, я не бачу сенсу використовувати такі підходи


1
Примітка: Python (безумовно, не інтерпретатор посилання CPython, принаймні, мабуть, не більшість інших) не оптимізує рекурсію хвоста, тому N буде обмежений чимось у сусідньому значенні sys.getrecursionlimit()(що за замовчуванням десь у низькій четвірку) діапазон цифр на CPython); використання sys.setrecursionlimitможе підняти ліміт, але в кінцевому підсумку ви досягнете ліміту стека C, і інтерпретатор загине при переповненні стека (не просто піднявши приємне RuntimeError/ RecursionError).
ShadowRanger


1

Замість зайвого лічильника тепер у вас є непотрібний список. Найкращим рішенням є використання змінної, яка починається з "_", яка повідомляє перевіряючим синтаксисам, що вам відомо, що ви не використовуєте змінну.

x = range(5)
while x:
  x.pop()
  print "Work!"

0

Я загалом згоден з рішеннями, наведеними вище. А саме з:

  1. Використання підкреслення в for-loop (2 та більше рядків)
  2. Визначення нормального whileлічильника (3 і більше рядків)
  3. Оголошення користувацького класу з __nonzero__реалізацією (ще багато рядків)

Якщо потрібно визначити об'єкт як у №3, я б рекомендував реалізувати протокол для за допомогою ключового слова або застосувати контекстний зв'язок .

Далі пропоную ще одне рішення. Це 3 вкладиші і не має вищої елегантності, але він використовує пакет itertools і, таким чином, може представляти інтерес.

from itertools import (chain, repeat)

times = chain(repeat(True, 2), repeat(False))
while next(times):
    print 'do stuff!'

У цьому прикладі 2 - кількість разів повторити цикл. ланцюг обгортає два повторювані ітератори, перший обмежений, але другий нескінченний. Пам'ятайте, що це справжні ітераторські об'єкти, отже, їм не потрібна нескінченна пам'ять. Очевидно, це набагато повільніше, ніж рішення №1 . Якщо записано як частину функції, це може потребувати очищення змінної часу .


2
chainзайвий, times = repeat(True, 2); while next(times, False):робить те ж саме.
чемпіон AC

0

Ми повеселилися наступним, цікавим поділитися таким чином:

class RepeatFunction:
    def __init__(self,n=1): self.n = n
    def __call__(self,Func):
        for i in xrange(self.n):
            Func()
        return Func


#----usage
k = 0

@RepeatFunction(7)                       #decorator for repeating function
def Job():
    global k
    print k
    k += 1

print '---------'
Job()

Результати:

0
1
2
3
4
5
6
---------
7

0

Якщо do_somethingце проста функція або її можна загорнути в одну, простий раз map()може do_something range(some_number):

# Py2 version - map is eager, so it can be used alone
map(do_something, xrange(some_number))

# Py3 version - map is lazy, so it must be consumed to do the work at all;
# wrapping in list() would be equivalent to Py2, but if you don't use the return
# value, it's wastefully creating a temporary, possibly huge, list of junk.
# collections.deque with maxlen 0 can efficiently run a generator to exhaustion without
# storing any of the results; the itertools consume recipe uses it for that purpose.
from collections import deque

deque(map(do_something, range(some_number)), 0)

Якщо ви хочете передати аргументи do_something, ви можете також знайти рецепт itertools, якийrepeatfunc добре читає:

Щоб передати ті ж самі аргументи:

from collections import deque
from itertools import repeat, starmap

args = (..., my args here, ...)

# Same as Py3 map above, you must consume starmap (it's a lazy generator, even on Py2)
deque(starmap(do_something, repeat(args, some_number)), 0)

Щоб передавати різні аргументи:

argses = [(1, 2), (3, 4), ...]

deque(starmap(do_something, argses), 0)

-1

Якщо ви дійсно хочете уникати введення чого-небудь з іменем (або змінної ітерації, як в ОП, або небажаного списку, або небажаного генератора, що повертає справжню потрібну кількість часу), ви можете зробити це, якщо ви дійсно хотіли:

for type('', (), {}).x in range(somenumber):
    dosomething()

Використовуваний трюк полягає у створенні анонімного класу, type('', (), {})який приводить до класу з порожнім іменем, але зауважте, що він не вставляється у локальну чи глобальну область імен (навіть якщо введено непусте ім'я). Потім ви використовуєте член цього класу як змінну ітерації, яка недосяжна, оскільки клас, до якого він входить, недоступний.


Очевидно, що це навмисно патологічно, тому критикувати це поруч, але я зазначу тут додаткову проблему. У CPython, посилальному інтерпретаторі, визначення класів є природно циклічними (створення класу неминуче створює цикл відліку, який запобігає детермінованому очищенню класу на основі підрахунку посилань). Це означає, що ви чекаєте на циклічний GC, щоб почати та прибирати клас. Зазвичай його збирають у складі молодого покоління, яке за замовчуванням збирається часто, але навіть так, кожен цикл означає ~ 1,5 Кб сміття з недетермінованим терміном експлуатації.
ShadowRanger

В основному, щоб уникнути іменної змінної, яка буде (як правило) детерміновано очищена для кожного циклу (коли вона відновлюється, а старе значення очищається), ви створюєте величезну безіменну змінну, яка очищається недетерміновано і може легко тривати довше.
ShadowRanger


-7

А як на рахунок:

while range(some_number):
    #do something

3
Це нескінченна петля, оскільки умова range(some_number)завжди справжня!
смертельний

@deadly: Ну, якщо some_numberменше або дорівнює 0, це не нескінченно, воно просто ніколи не працює. :-) І це неефективно для нескінченного циклу (особливо на Py2), оскільки він створює свіжий list(Py2) або rangeоб'єкт (Py3) для кожного тесту (це не константа з точки зору перекладача, він повинен завантажувати rangeі some_numberкожен цикл, зателефонуйте range, а потім протестуйте результат).
ShadowRanger
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.