maneira pitônica de fazer algo N vezes sem uma variável de índice?


161

Todos os dias eu amo python cada vez mais.

Hoje, eu estava escrevendo um código como:

for i in xrange(N):
    do_something()

Eu tive que fazer algo N vezes. Mas cada vez que não dependia do valor dei (variável de índice). Percebi que estava criando uma variável que nunca usei ( i) e pensei: "Certamente há uma maneira mais pitônica de fazer isso sem a necessidade dessa variável de índice inútil".

Então ... a pergunta é: você sabe como fazer essa tarefa simples de uma maneira mais bonita (pitônica)?


7
Acabei de aprender sobre a variável _, mas, caso contrário, consideraria o modo como você o faz Pythonic. Acho que nunca vi um loop for simples feito de outra maneira, pelo menos em python. Embora eu tenha certeza de que há casos de uso específicos nos quais você olha e diz "Espere, isso parece terrível" - mas, em geral, o xrange é a maneira preferida (até onde eu vi).
Wayne Werner


5
NOTA: O xrange não existe no Python3. Use em rangevez disso.
John Henckel

Respostas:


110

Uma abordagem um pouco mais rápida do que a repetição xrange(N)é:

import itertools

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

3
Quão rápido? Ainda existe uma diferença no Python 3.1?
Hamish Grubijan

15
@ Hamish: Meu teste com o 2.6 diz 32% mais rápido (23.2 nos vs 17.6 nos para N = 1000). Mas isso é realmente um tempo de qualquer maneira. Eu adotaria o código do OP como padrão, porque é mais legível imediatamente (para mim).
quer

3
É bom saber sobre a velocidade. Eu certamente ecoo o sentimento de Mike sobre o código do OP ser mais legível.
Wayne Werner

@Wayne, eu acho que o hábito é realmente muito poderoso - exceto pelo fato de você estar acostumado a isso, por que mais "contaria de 0 a N-1 [[e ignoraria completamente a contagem]] toda vez que realizasse essa contagem? operação independente "seja intrinsecamente mais clara que" repita N vezes a seguinte operação "...?
precisa

4
você tem certeza de que a velocidade é realmente relevante? Não é verdade que, se você fizer algo significativo nesse loop, provavelmente levará centenas ou milhares de tempo para o estilo de iteração escolhido?
Henning

55

Use a variável _, como aprendi quando fiz essa pergunta , por exemplo:

# A long way to do integer exponentiation
num = 2
power = 3
product = 1
for _ in xrange(power):
    product *= num
print product

6
Não é o downvoter mas pode ser porque você está se referindo a outro post em vez de adicionar mais detalhes na resposta
Downgoat

5
@ Downgoat: Obrigado pelo feedback. Dito isto, não há muito a dizer sobre esse idioma. Meu objetivo ao me referir a outro post foi apontar que uma pesquisa pode ter produzido a resposta. Acho irônico que essa questão tenha várias vezes mais votos do que a outra.
GreenMatt


10

como a função é um cidadão de primeira classe, você pode escrever um pequeno invólucro (das respostas de Alex)

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

então você pode passar a função como argumento.


@ Hamish: Quase nada. (17,8 us por loop, nas mesmas condições dos tempos da resposta de Alex, para uma diferença de 0,2 us).
quer

9

_ É a mesma coisa que x. No entanto, é um idioma python usado para indicar um identificador que você não pretende usar. Em python, esses identificadores não ocupam memória ou alocam espaço, como as variáveis ​​em outros idiomas. É fácil esquecer isso. Eles são apenas nomes que apontam para objetos, neste caso, um número inteiro em cada iteração.


8

Achei as várias respostas realmente elegantes (principalmente as de Alex Martelli), mas queria quantificar o desempenho em primeira mão, então preparei o seguinte script:

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

Eu também vim com uma solução alternativa que se baseia na de Martelli e usa map()para chamar a função de carga útil. OK, enganei-me um pouco porque tive a liberdade de fazer a carga aceitar um parâmetro que é descartado: não sei se há uma maneira de contornar isso. No entanto, aqui estão os resultados:

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

portanto, o uso do mapa gera uma melhoria de aproximadamente 30% sobre o padrão para loop e de 19% extra sobre o Martelli.


4

Suponha que você definiu do_something como uma função e gostaria de executá-lo N vezes. Talvez você possa tentar o seguinte:

todos = [do_something] * N  
for doit in todos:  
    doit()

45
Certo. Não vamos apenas chamar a função um milhão de vezes, vamos alocar uma lista de um milhão de itens também. Se a CPU estiver funcionando, a memória também não deve ficar um pouco estressada? A resposta não pode ser caracterizada como definitivamente "não útil" (está mostrando uma abordagem diferente e funcional), por isso não posso votar em branco, mas discordo e sou totalmente contra.
tzot

1
Não é apenas uma lista de N referências ao mesmo valor de função?
Nick McCurdy

melhor fazer fn() for fn in itertools.repeat(do_something, N)e salvar pré-gerar a matriz ... esse é o meu idioma preferido.
F1Rumors

1
@tzot Por que o tom condescendente? Essa pessoa se esforça para escrever uma resposta e agora pode ser desencorajada a contribuir no futuro. Mesmo que tenha implicações no desempenho, é uma opção de trabalho e, especialmente se N for pequeno, as implicações no desempenho / memória não são significativas.
Davidscolgan #

Sempre fico surpreso com o quão obsoletos são os desenvolvedores de Python :) Embora eu concorde que não é idiomático, e alguém novo no Python que lê pode não entender o que está acontecendo tão claramente quanto quando simplesmente usa um iterador
Asfand Qazi

1

Que tal um loop while simples?

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

Você já tem a variável; por que não usá-lo?


1
Meu único pensamento é que ele é de 3 linhas de código contra uma (?)
AJP

2
@AJP - Mais como 4 linhas vs 2 linhas
ArtOfWarfare

acrescenta a comparação (tempos> 0) e do decréscimo (vezes - = 1) para as despesas gerais ... de modo mais lento do que o loop for ...
F1Rumors

@ F1Rumors Não medi-lo, mas eu ficaria surpreso se os compiladores JIT como o PyPy gerassem código mais lento para um loop while tão simples.
Philipp Claßen 13/04/19
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.