É possível implementar um loop Python para intervalo sem uma variável iteradora?

187

É possível fazer o seguinte sem o i?

for i in range(some_number):
    # do something

Se você quiser fazer algo N várias vezes e não precisar do iterador.

James McMahon
fonte
21
Essa é uma boa pergunta! O PyDev até sinaliza o 'i' como um aviso para 'variável não utilizada'. A solução abaixo remove esse aviso.
Ashwin Nanjappa
@ Ashwin Você pode usar \ @UnusedVariable para remover esse aviso. Observe que eu precisava escapar do símbolo 'at' para que esse comentário fosse executado.
Raffi Khatchadourian
Eu encabeço a mesma pergunta que você. É irritante com avisos de pilão. É claro que você pode desativar os avisos por supressão adicional, como o @Raffi Khatchadourian proposto. Seria bom evitar avisos de pilão e comentários de supressão.
tangoal

Respostas:

110

Do alto da minha cabeça, não.

Eu acho que o melhor que você poderia fazer é algo assim:

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

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

Mas acho que você pode simplesmente viver com a ivariável extra .

Aqui está a opção de usar a _variável, que na realidade é apenas outra variável.

for _ in range(n):
    do_something()

Observe que _é atribuído o último resultado retornado em uma sessão python interativa:

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

Por esse motivo, eu não o usaria dessa maneira. Não conheço nenhum idioma mencionado por Ryan. Isso pode atrapalhar o seu intérprete.

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

E, de acordo com a gramática Python , é um nome de variável aceitável:

identifier ::= (letter|"_") (letter | digit | "_")*
Desconhecido
fonte
4
"Mas acho que você pode simplesmente viver com o" i "extra". Sim, é apenas um ponto acadêmico.
613 James McMahon
1
@ nemo, você pode tentar fazer _ no intervalo (n): se você não quiser usar nomes alfanuméricos.
Desconhecido
_ É uma variável nesse caso? Ou isso é algo mais em Python?
James McMahon
1
@nemo Sim, é apenas um nome de variável aceitável. No intérprete, é atribuída automaticamente a última expressão que você criou.
Desconhecido
3
@kurczak Há um ponto. O uso _deixa claro que deve ser ignorado. Dizer que não há sentido em fazer isso é como dizer que não há sentido em comentar seu código - porque faria exatamente o mesmo.
Fada Lambda
69

Você pode estar procurando

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

esta é a maneira mais rápida de repetir os timestempos no Python.

Alex Martelli
fonte
2
Eu não estava preocupado com o desempenho, apenas estava curioso se havia uma maneira mais tensa de escrever a declaração. Embora eu esteja usando esporadicamente o Python há cerca de 2 anos, ainda sinto muita falta. Itertools é uma dessas coisas, obrigado pela informação.
James McMahon
5
Isso é interessante, eu não estava ciente disso. Acabei de dar uma olhada nos documentos da ferramenta; mas eu me pergunto por que isso é mais rápido do que apenas usar range ou xrange?
Si28719e 04/04/09
5
@blackkettle: é mais rápido porque não precisa retornar o índice de iteração atual, que é uma parte mensurável do custo do xrange (e o intervalo do Python 3, que fornece um iterador, não uma lista). @nemo, o range é o mais otimizado possível, mas a necessidade de criar e retornar uma lista é inevitavelmente mais trabalhosa do que um iterador (no Py3, o range retorna um iterador, como o xrange do Py2; a compatibilidade com versões anteriores não permite essa alteração no Py2), especialmente aquele que não precisa retornar um valor variável.
Alex Martelli
4
@ Cristian, sim, claramente preparando e retornando um Python int toda vez, inc. gc, tem um custo mensurável - não é necessário usar um contador internamente .
2828 Alex Martelli
4
Eu entendo agora. A diferença vem da sobrecarga do GC, não do "algoritmo". A propósito, eu corro uma referência de tempo rápida e a aceleração foi de ~ 1,42x.
Cristian Ciupitu 27/09/09
59

O idioma geral para atribuir a um valor que não é usado é nomeá-lo _.

for _ in range(times):
    do_stuff()
Ryan
fonte
18

O que todo mundo sugerindo que você use _ não está dizendo é que _ é freqüentemente usado como um atalho para uma das funções gettext ; portanto, se você deseja que seu software esteja disponível em mais de um idioma, é melhor evitar usá-lo. para outros fins.

import gettext
gettext.bindtextdomain('myapplication', '/path/to/my/language/directory')
gettext.textdomain('myapplication')
_ = gettext.gettext
# ...
print _('This is a translatable string.')
Ignacio Vazquez-Abrams
fonte
Para mim, esse uso _parece uma péssima idéia, não me importaria de entrar em conflito com ela.
KeithWM 12/01/19
9

Aqui está uma ideia aleatória que utiliza (abusos?) O modelo de dados ( link 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

Gostaria de saber se existe algo assim nas bibliotecas padrão?

saffsd
fonte
10
Eu acho que ter um método como os __nonzero__efeitos colaterais é uma ideia horrível.
ThiefMaster
2
Eu usaria em seu __call__lugar. while x():não é muito mais difícil de escrever.
Jasmijn
1
Há também um argumento para evitar o nome Counter; com certeza, não está reservado ou no escopo interno, mas collections.Counteré uma coisa , e criar uma classe com o mesmo nome arrisca a confusão do mantenedor (não que isso já não esteja arriscando isso).
ShadowRanger
7

Você pode usar _11 (ou qualquer número ou outro identificador inválido) para impedir a colisão de nomes com o gettext. Sempre que você usa sublinhado + identificador inválido, você obtém um nome falso que pode ser usado no loop for.

JirkaV
fonte
Agradável! O PyDev concorda com você: isso elimina o aviso amarelo "Variável não utilizada".
mike roedor
2

Pode ser que a resposta dependa de qual problema você tem ao usar o iterador? pode ser usado

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

ou

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

loop(100, lambda a:a)

mas, francamente, não vejo sentido em usar essas abordagens

Anurag Uniyal
fonte
1
Nota: Python (definitivamente não é o intérprete de referência do CPython, pelo menos, provavelmente não a maioria dos outros) não otimiza a recursão da cauda; portanto, N será limitado a algo próximo do valor de sys.getrecursionlimit()(que é o padrão em algum lugar entre os quatro baixos). intervalo de dígitos no CPython); O uso sys.setrecursionlimitaumentaria o limite, mas, eventualmente, você atingiria o limite da pilha C e o intérprete morreria com um estouro de pilha (não apenas aumentando um bom RuntimeError/ RecursionError).
precisa
1
t=0    
for _ in range(10):
    print t
    t = t+1

RESULTADO:

0
1 
2 
3 
4 
5 
6 
7
8
9
Imran Farid
fonte
1

Em vez de um contador desnecessário, agora você tem uma lista desnecessária. A melhor solução é usar uma variável que comece com "_", que informa aos verificadores de sintaxe que você sabe que não está usando a variável.

x = range(5)
while x:
  x.pop()
  print "Work!"
Robert Jacobs
fonte
0

Eu geralmente concordo com as soluções dadas acima. Ou seja, com:

  1. Usando sublinhado em for-loop (2 e mais linhas)
  2. Definindo um whilecontador normal (3 e mais linhas)
  3. Declarando uma classe personalizada com __nonzero__implementação (muito mais linhas)

Se alguém definir um objeto como em # 3, eu recomendaria implementar o protocolo with with keyword ou aplicar contextlib .

Além disso, proponho outra solução. É um forro de 3 linhas e não é de suprema elegância, mas usa o pacote itertools e, portanto, pode ser de seu interesse.

from itertools import (chain, repeat)

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

Neste exemplo, 2 é o número de vezes para iterar o loop. A cadeia está agrupando dois iteradores repetidos , o primeiro sendo limitado, mas o segundo é infinito. Lembre-se de que esses são objetos iteradores verdadeiros, portanto, eles não requerem memória infinita. Obviamente, isso é muito mais lento que a solução 1 . A menos que seja escrito como parte de uma função, pode ser necessária uma limpeza para a variável times .

Yauhen Yakimovich
fonte
2
chainé desnecessário, times = repeat(True, 2); while next(times, False):faz a mesma coisa.
AChampion
0

Nós nos divertimos com o seguinte, interessante para compartilhar:

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

Resultados:

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

Se do_somethingé uma função simples ou pode ser agrupada em uma, uma simples map()lata pode 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)

Se você deseja passar argumentos do_something, também pode achar que a receita do itertoolsrepeatfunc lê bem:

Para passar os mesmos argumentos:

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)

Para passar argumentos diferentes:

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

deque(starmap(do_something, argses), 0)
mtd
fonte
-1

Se você realmente deseja evitar colocar algo com um nome (seja uma variável de iteração como no OP, ou uma lista indesejada ou um gerador indesejado retornando verdadeiro a quantidade de tempo desejada), você pode fazer isso se realmente quiser:

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

O truque usado é criar uma classe anônima type('', (), {})que resulta em uma classe com nome vazio, mas NB: ela não é inserida no espaço para nome local ou global (mesmo que um nome não vazio tenha sido fornecido). Em seguida, você usa um membro dessa classe como variável de iteração inacessível, pois a classe da qual ele é membro é inacessível.

skyking
fonte
Obviamente, isso é intencionalmente patológico, portanto, criticar é irrelevante, mas observarei uma armadilha adicional aqui. No CPython, o intérprete de referência, as definições de classe são naturalmente cíclicas (a criação inevitável de uma classe cria um ciclo de referência que impede a limpeza determinística da classe com base na contagem de referências). Isso significa que você está esperando no GC cíclico para iniciar e limpar a classe. Geralmente, ele é coletado como parte da geração mais jovem, que por padrão é coletada com frequência, mas, mesmo assim, cada loop significa ~ 1,5 KB de lixo com vida útil não determinística.
ShadowRanger
Basicamente, para evitar uma variável nomeada que seria (normalmente) limpa de forma determinística em cada loop (quando é recuperada e o valor antigo limpo), você está criando uma enorme variável sem nome que é limpa de forma não determinística e pode facilmente durar mais tempo.
ShadowRanger
-7

A respeito:

while range(some_number):
    #do something
Adam Scott
fonte
3
Esse é um loop infinito, pois a condição range(some_number)é sempre verdadeira!
mortal
@eadly: Bem, se some_numberé menor ou igual a 0, não é infinito, ele simplesmente nunca é executado. :-) E é bastante ineficiente para um loop infinito (especialmente no Py2), uma vez que cria um novo list(Py2) ou rangeobjeto (Py3) para cada teste (não é uma constante do ponto de vista do intérprete, ele precisa carregar rangee some_numbercada loop, chame rangee teste o resultado).
ShadowRanger