pythonowy sposób na zrobienie czegoś N razy bez zmiennej indeksu?


161

Każdego dnia coraz bardziej kocham Pythona.

Dzisiaj pisałem kod taki jak:

for i in xrange(N):
    do_something()

Musiałem coś zrobić N razy. Ale za każdym razem nie zależało od wartości i(zmienna indeksu). Zdałem sobie sprawę, że tworzę zmienną, której nigdy nie używałem ( i), i pomyślałem: „Z pewnością istnieje bardziej pythonowy sposób zrobienia tego bez potrzeby stosowania tej bezużytecznej zmiennej indeksu”.

A więc ... pytanie brzmi: czy wiesz, jak wykonać to proste zadanie w bardziej (pytonicznie) piękny sposób?


7
Właśnie dowiedziałem się o zmiennej _, ale poza tym rozważyłbym sposób, w jaki to robisz Pythonic. Nie sądzę, żebym kiedykolwiek widział prostą pętlę for wykonaną w inny sposób, przynajmniej w Pythonie. Chociaż jestem pewien, że są określone przypadki użycia, w których patrzysz na to i mówisz „Czekaj, to wygląda okropnie” - ale ogólnie xrange jest preferowanym sposobem (o ile widziałem).
Wayne Werner


5
UWAGA: xrange nie istnieje w Python3. Użyj rangezamiast tego.
John Henckel,

Odpowiedzi:


110

Nieco szybszym podejściem niż zapętlenie xrange(N)jest:

import itertools

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

3
O ile szybciej? Czy nadal istnieje różnica w Pythonie 3.1?
Hamish Grubijan

15
@ Hamish: Mój test z 2,6 mówi, że 32% szybciej (23,2 nas w porównaniu z 17,6 nas dla N = 1000). Ale to i tak naprawdę czas. Domyślnie bym użył kodu OP, ponieważ jest on bardziej czytelny (dla mnie).
Mike Boers

3
Dobrze wiedzieć o szybkości. Z pewnością zgadzam się z opinią Mike'a, że ​​kod OP jest bardziej czytelny.
Wayne Werner

@Wayne, myślę, że nawyk jest naprawdę bardzo potężny - poza faktem, że jesteś do tego przyzwyczajony, po co w innym przypadku „liczyć od 0 do N-1 [[i całkowicie ignorować liczenie]] za każdym razem, gdy wykonujesz to liczenie -niezależna operacja "jest wewnętrznie bardziej przejrzysta niż" powtórz N razy następującą operację "...?
Alex Martelli

4
czy na pewno prędkość jest naprawdę istotna? Czy nie jest tak, że jeśli zrobisz coś znaczącego w tej pętli, najprawdopodobniej zajmie to setki lub tysiące czasu tyle samo, co wybrany styl iteracji?
Henning

55

Użyj zmiennej _, jak się nauczyłem , zadając to pytanie , na przykład:

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

6
Nie przeciwnik, ale może to być spowodowane tym, że odnosisz się do innego postu, zamiast dodawać więcej szczegółów w odpowiedzi
Downgoat

5
@Downgoat: Dzięki za opinię. To powiedziawszy, nie ma wiele do powiedzenia na temat tego idiomu. Odnosząc się do innego postu, chciałem wskazać, że odpowiedź mogła przynieść wyszukiwanie. Uważam za ironiczne, że to pytanie ma kilka razy więcej głosów niż inne.
GreenMatt


10

ponieważ funkcja jest obywatelem pierwszej klasy, możesz napisać małe opakowanie (od odpowiedzi Alexa)

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

wtedy możesz przekazać funkcję jako argument.


@ Hamish: Prawie nic. (17,8 us na pętlę w tych samych warunkach, co czasy odpowiedzi Alexa, przy różnicy 0,2 us).
Mike Boers

9

_ Jest tym samym, co x. Jednak jest to idiom Pythona, który służy do wskazania identyfikatora, którego nie zamierzasz używać. W Pythonie te identyfikatory nie zajmują pamięci ani nie przydzielają miejsca, jak zmienne w innych językach. Łatwo o tym zapomnieć. To tylko nazwy wskazujące na obiekty, w tym przypadku liczba całkowita w każdej iteracji.


8

Uważam, że różne odpowiedzi są naprawdę eleganckie (szczególnie Alex Martelli), ale chciałem oszacować wydajność z pierwszej ręki, więc przygotowałem następujący scenariusz:

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

Wymyśliłem również alternatywne rozwiązanie, które opiera się na rozwiązaniu Martelliego i używa map()do wywołania funkcji ładunku. OK, trochę oszukałem, ponieważ pozwoliłem sobie na to, by ładunek akceptował parametr, który zostanie odrzucony: nie wiem, czy istnieje sposób na obejście tego. Niemniej jednak oto wyniki:

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

więc użycie mapy daje poprawę o około 30% w stosunku do standardowej pętli i dodatkowe 19% w porównaniu ze standardem Martelli.


4

Załóżmy, że zdefiniowałeś funkcję do_something jako funkcję i chcesz wykonać ją N razy. Może możesz spróbować następujących rzeczy:

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

45
Pewnie. Nie tylko wywołujmy tę funkcję milion razy, przydzielmy też listę miliona elementów. Jeśli procesor działa, czy pamięć nie powinna być trochę obciążona? Odpowiedź nie może być określona jako zdecydowanie „nieprzydatna” (pokazuje inne, funkcjonujące podejście), więc nie mogę przegłosować, ale się nie zgadzam i jestem temu całkowicie przeciwny.
tzot

1
Czy nie jest to po prostu lista N odniesień do tej samej wartości funkcji?
Nick McCurdy,

raczej lepiej zrobić fn() for fn in itertools.repeat(do_something, N)i zapisać wstępne generowanie tablicy ... to jest mój ulubiony idiom.
F1Rumors

1
@tzot Dlaczego protekcjonalny ton? Ta osoba włożyła wysiłek w napisanie odpowiedzi i teraz może być zniechęcona do udziału w przyszłości. Nawet jeśli ma wpływ na wydajność, jest opcją działającą, a zwłaszcza jeśli N jest małe, wpływ na wydajność / pamięć nie jest znaczący.
davidscolgan

Zawsze jestem zaskoczony obsesją programistów Pythona z obsesją na punkcie wydajności :) Chociaż zgadzam się, że nie jest to idiomatyczne, a ktoś nowy czytając Pythona może nie rozumieć, co się dzieje, tak wyraźnie, jak przy użyciu iteratora
Asfand Qazi

1

A co z prostą pętlą while?

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

Masz już zmienną; dlaczego tego nie użyć?


1
Myślę tylko o tym, że są to 3 linie kodu w porównaniu z jednym (?)
AJP

2
@AJP - Bardziej jak 4 linie kontra 2 linie
ArtOfWarfare

dodaje porównanie (razy> 0) i ubytek (razy - = 1) do narzutów ... tak wolniej niż pętla for ...
F1Rumors

@ F1Rumors Nie mierzyłem tego, ale byłbym zaskoczony, gdyby kompilatory JIT, takie jak PyPy, generowały wolniejszy kod dla tak prostej pętli while.
Philipp Claßen
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.