Можно ли реализовать Python для цикла диапазона без переменной итератора?


187

Можно ли делать следующие без i?

for i in range(some_number):
    # do something

Если вы просто хотите сделать что-то N раз и вам не нужен итератор.


21
Это хороший вопрос! PyDev даже помечает 'i' как предупреждение для 'неиспользуемой переменной'. Решение ниже удаляет это предупреждение.
Эшвин Нанджаппа

@Ashwin Вы можете использовать \ @UnusedVariable, чтобы удалить это предупреждение. Обратите внимание, что мне нужно экранировать символ «at», чтобы этот комментарий прошел.
Раффи Хачадурян

Я тот же вопрос задаю вам. Это раздражает с предупреждениями Pylint. Конечно, вы можете отключить предупреждения путем дополнительного подавления, как предложил @Raffi Khatchadourian. Было бы неплохо избегать предупреждений Pylint и комментариев подавления.
тангоал

Ответы:


110

С макушки головы, нет.

Я думаю, что лучшее, что вы могли бы сделать, это что-то вроде этого:

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

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

Но я думаю, что вы можете просто жить с дополнительной iпеременной.

Вот вариант использования _переменной, которая на самом деле является просто другой переменной.

for _ in range(n):
    do_something()

Обратите внимание, что _присваивается последний результат, который вернулся в интерактивном сеансе Python:

>>> 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; но мне интересно, почему это быстрее, чем просто использование range или xrange?
si28719e

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

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

4
Теперь я понимаю. Разница заключается в издержках GC, а не в «алгоритме». Кстати, я запустил тест по быстрому времени, и ускорение составило ~ 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. Каждый раз, когда вы используете подчеркивание + неверный идентификатор, вы получаете фиктивное имя, которое можно использовать в цикле for.


Ницца! 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, и интерпретатор умрет с переполнением стека (не просто подняв nice RuntimeError/ RecursionError).
ShadowRanger


1

Вместо ненужного счетчика теперь у вас есть ненужный список. Лучшее решение - использовать переменную, начинающуюся с «_», которая сообщает средствам проверки синтаксиса, что вы знаете, что не используете переменную.

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

0

Я в целом согласен с решениями, приведенными выше. А именно с:

  1. Использование подчеркивания в for-loop (2 и более строк)
  2. Определение нормального whileсчетчика (3 и более строк)
  3. Объявление пользовательского класса с __nonzero__реализацией (еще много строк)

Если нужно определить объект как в # 3, я бы порекомендовал реализовать протокол с ключевым словом или применить contextlib .

Далее я предлагаю еще одно решение. Это 3 лайнера, он не обладает исключительной элегантностью, но использует пакет itertools и поэтому может представлять интерес.

from itertools import (chain, repeat)

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

В этих примерах 2 - количество повторений цикла. Цепочка объединяет два повторяющихся итератора, первый ограничен, а второй бесконечен. Помните, что это настоящие итераторы, поэтому они не требуют бесконечной памяти. Очевидно, что это намного медленнее, чем решение № 1 . Если она не написана как часть функции, она может потребовать очистки переменной times .


2
chainне нужно, times = repeat(True, 2); while next(times, False):делает то же самое.
Чемпион

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, вы можете также найти рецепт itertoolsrepeatfunc :

Чтобы передать те же аргументы:

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

Если вы действительно хотите избежать ввода чего-либо с именем (либо итерационной переменной, как в OP, либо нежелательным списком, либо нежелательным генератором, возвращающим true требуемое количество времени), вы можете сделать это, если действительно хотите:

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

Уловка, которая используется, состоит в том, чтобы создать анонимный класс, type('', (), {})который приводит к классу с пустым именем, но обратите внимание, что он не вставлен в локальное или глобальное пространство имен (даже если было предоставлено непустое имя). Затем вы используете член этого класса в качестве переменной итерации, которая недоступна, поскольку класс, членом которого он является, недоступен.


Очевидно, что это преднамеренно патологически, поэтому критиковать его не имеет смысла, но я отмечу здесь еще одну ловушку. На CPython, ссылочном интерпретаторе, определения классов являются естественно циклическими (создание класса неизбежно создает ссылочный цикл, который предотвращает детерминированную очистку класса на основе подсчета ссылок). Это означает, что вы ожидаете циклический сборщик мусора, чтобы очистить класс. Обычно он собирается как часть молодого поколения, которое по умолчанию собирается часто, но даже в этом случае каждый цикл означает ~ 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.