Czy można zaimplementować Python dla pętli zakresu bez zmiennej iteratora?


187

Czy można wykonać następujące czynności bez i?

for i in range(some_number):
    # do something

Jeśli chcesz po prostu zrobić coś N razy i nie potrzebujesz iteratora.


21
To dobre pytanie! PyDev nawet oznacza „i” jako ostrzeżenie dla „nieużywanej zmiennej”. Poniższe rozwiązanie usuwa to ostrzeżenie.
Ashwin Nanjappa

@Ashwin Możesz użyć \ @UnusedVariable, aby usunąć to ostrzeżenie. Zauważ, że musiałem uciec od symbolu „at”, aby komentarz mógł przejść.
Raffi Khatchadourian

Zadaję ci to samo pytanie. Jest to denerwujące ostrzeżeniami pylint. Oczywiście możesz wyłączyć ostrzeżenia poprzez dodatkowe tłumienie, takie jak proponowany @Raffi Khatchadourian. Byłoby miło unikać ostrzeżeń pylint i komentarzy dotyczących tłumienia.
tangoal

Odpowiedzi:


110

Z czubka mojej głowy, nie.

Myślę, że najlepsze, co możesz zrobić, to coś takiego:

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

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

Ale myślę, że możesz po prostu żyć z dodatkową izmienną.

Oto opcja użycia _zmiennej, która w rzeczywistości jest tylko inną zmienną.

for _ in range(n):
    do_something()

Zauważ, że _przypisany jest ostatni wynik, który powrócił w interaktywnej sesji Pythona:

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

Z tego powodu nie użyłbym tego w ten sposób. Nie znam żadnego idiomu wspomnianego przez Ryana. Może to zepsuć twojego tłumacza.

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

Zgodnie z gramatyką Pythona jest to dopuszczalna nazwa zmiennej:

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

4
„Ale myślę, że możesz po prostu żyć z dodatkowym„ i ”„ Tak, to tylko akademicki punkt.
James McMahon

1
@nemo, możesz spróbować zrobić dla _ w zakresie (n): jeśli nie chcesz używać nazw alfanumerycznych.
Nieznany

Czy w takim przypadku _ jest zmienną? Czy jest to coś innego w Pythonie?
James McMahon

1
@nemo Tak, to tylko akceptowalna nazwa zmiennej. W tłumaczu automatycznie przypisuje się ostatnie utworzone wyrażenie.
Nieznany

3
@kurczak Jest punkt. Używanie _wyjaśnia, że ​​należy go zignorować. Powiedzenie, że nie ma sensu tego robić, jest jak powiedzenie, że nie ma sensu komentować kodu - ponieważ i tak zrobiłby dokładnie to samo.
Lambda Fairy

69

Być może szukasz

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

jest to najszybszy sposób na iterację timesczasów w Pythonie.


2
Nie zajmowałem się wydajnością, byłem tylko ciekawy, czy istnieje szybszy sposób na napisanie oświadczenia. Chociaż sporadycznie używam Pythona od około 2 lat, nadal czuję, że brakuje mi wielu rzeczy. Itertools to jedna z tych rzeczy, dziękuję za informację.
James McMahon

5
To ciekawe, nie wiedziałem o tym. Właśnie spojrzałem na dokumenty itertools; ale zastanawiam się, dlaczego jest to szybsze niż użycie zasięgu lub xrange?
si28719e

5
@blackkettle: jest szybszy, ponieważ nie musi zwracać bieżącego indeksu iteracji, który jest mierzalną częścią kosztu xrange (i zakresu Pythona 3, który daje iterator, a nie listę). @nemo, zakres jest tak zoptymalizowany, jak to tylko możliwe, ale potrzeba zbudowania i zwrócenia listy jest nieuchronnie cięższą pracą niż iterator (w Py3 zakres zwraca iterator, taki jak xrange Py2; zgodność wsteczna nie pozwala na taką zmianę w Py2), szczególnie takiego, który nie musi zwracać zmiennej wartości.
Alex Martelli,

4
@Cristian, tak, wyraźnie przygotowuje i zwraca int Python za każdym razem, inc. gc działa, ma wymierny koszt - wewnętrzne korzystanie z licznika nie ma znaczenia.
Alex Martelli,

4
Teraz rozumiem. Różnica wynika z narzutu GC, a nie z „algorytmu”. Nawiasem mówiąc, przeprowadzam szybki test porównawczy, a przyspieszenie wyniosło ~ 1,42x.
Cristian Ciupitu

59

Ogólnym idiomem przypisywania wartości, która nie jest używana, jest jej nazwa _.

for _ in range(times):
    do_stuff()

18

To, co wszyscy sugerują używać _ nie mówi, że _ jest często używany jako skrót do jednej z funkcji gettext , więc jeśli chcesz, aby twoje oprogramowanie było dostępne w więcej niż jednym języku, najlepiej jest unikać jego używania. do innych celów.

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

Dla mnie takie użycie _wydaje się okropnym pomysłem, nie miałbym nic przeciwko temu.
KeithWM

9

Oto przypadkowy pomysł, który wykorzystuje (nadużywa?) Model danych ( 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

Zastanawiam się, czy jest coś takiego w standardowych bibliotekach?


10
Myślę, że posiadanie metody takiej jak __nonzero__efekty uboczne to okropny pomysł.
ThiefMaster

2
Użyłbym __call__zamiast tego. while x():nie jest trudniejsze do napisania.
Jasmijn

1
Istnieje również argument za unikaniem nazwy Counter; pewnie, to nie jest zarezerwowane ani wbudowane, ale collections.Counterjest rzeczą , a zrobienie klasy o tej samej nazwie grozi zamieszaniem opiekuna (nie to, że to już nie ryzykuje).
ShadowRanger

7

Możesz użyć _11 (lub dowolnej liczby lub innego nieprawidłowego identyfikatora), aby zapobiec kolizji nazw z gettext. Za każdym razem, gdy użyjesz podkreślenia + nieprawidłowego identyfikatora, otrzymujesz fałszywą nazwę, której można użyć w pętli.


Miły! PyDev się z tobą zgadza: pozbywa się żółtego ostrzeżenia „Nieużywana zmienna”.
Mike Gryzonie

2

Być może odpowiedź zależy od tego, jaki masz problem z używaniem iteratora? może być użyty

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

lub

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

loop(100, lambda a:a)

ale szczerze mówiąc, nie widzę sensu w stosowaniu takich podejść


1
Uwaga: Python (zdecydowanie nie interpreter referencyjny CPython przynajmniej, prawdopodobnie nie większość innych) nie optymalizuje rekurencji ogona, więc N będzie ograniczony do czegoś w sąsiedztwie wartości sys.getrecursionlimit()(domyślnie gdzieś w dolnej czwórce zakres cyfr na CPython); użycie sys.setrecursionlimitzwiększy limit, ale w końcu osiągniesz limit stosu C, a tłumacz umrze z przepełnieniem stosu (nie tylko podniesienie ładnego RuntimeError/ RecursionError).
ShadowRanger


1

Zamiast niepotrzebnego licznika masz teraz niepotrzebną listę. Najlepszym rozwiązaniem jest użycie zmiennej zaczynającej się od „_”, która mówi kontrolerom składni, że masz świadomość, że nie używasz zmiennej.

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

0

Ogólnie zgadzam się z powyższymi rozwiązaniami. Mianowicie z:

  1. Używanie podkreślenia w forpętli (2 i więcej linii)
  2. Definiowanie normalnego whilelicznika (3 i więcej linii)
  3. Deklaracja niestandardowej klasy z __nonzero__implementacją (wiele innych wierszy)

Jeśli ktoś jest określenie przedmiotu jak w # 3 Polecam dla wdrażania protokołu z hasła lub zastosować contextlib .

Dalej proponuję jeszcze inne rozwiązanie. Jest 3-liniowy i nie ma najwyższej elegancji, ale wykorzystuje pakiet itertools , a zatem może być interesujący.

from itertools import (chain, repeat)

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

W tym przykładzie 2 oznacza liczbę powtórzeń pętli. łańcuch owija dwa powtarzające się iteratory, pierwszy jest ograniczony, ale drugi jest nieskończony. Pamiętaj, że są to prawdziwe obiekty iteratora, dlatego nie wymagają nieskończonej pamięci. Oczywiście jest to znacznie wolniejsze niż rozwiązanie nr 1 . Chyba napisany jako część funkcji może wymagać oczyszczenia do krotnie zmiennej.


2
chainjest niepotrzebne, times = repeat(True, 2); while next(times, False):robi to samo.
AChampion

0

Mieliśmy trochę zabawy z następującymi, którymi warto się podzielić:

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

Wyniki:

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

0

Jeśli do_somethingjest to prosta funkcja lub może być w nią zapakowana, proste czasy map()mogą 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)

Jeśli chcesz przekazać argumenty do_something, może się okazać, że przepis itertoolsrepeatfunc brzmi dobrze:

Aby przekazać te same argumenty:

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)

Aby przekazać różne argumenty:

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

deque(starmap(do_something, argses), 0)

-1

Jeśli naprawdę chcesz uniknąć umieszczenia czegoś o nazwie (zmienna iteracyjna jak w OP, niechciana lista lub niechciany generator zwracający prawdziwą pożądaną ilość czasu), możesz to zrobić, jeśli naprawdę chcesz:

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

Stosowana sztuczka polega na utworzeniu anonimowej klasy, type('', (), {})której wynikiem jest klasa o pustej nazwie, ale należy pamiętać, że nie jest ona wstawiana do lokalnej lub globalnej przestrzeni nazw (nawet jeśli podano niepustą nazwę). Następnie używasz członka tej klasy jako zmiennej iteracyjnej, która jest nieosiągalna, ponieważ klasa, do której należy, jest nieosiągalna.


Oczywiście jest to celowo patologiczne, więc krytykowanie go nie ma sensu, ale zauważę tutaj dodatkową pułapkę. W CPython, interpretatorze referencyjnym, definicje klas są naturalnie cykliczne (tworzenie klasy nieuchronnie tworzy cykl referencyjny, który zapobiega deterministycznemu czyszczeniu klasy na podstawie zliczania referencji). Oznacza to, że czekasz na cyklicznym GC, aby rozpocząć i posprzątać klasę. Zwykle będzie gromadzony jako część młodszego pokolenia, które domyślnie jest gromadzone często, ale mimo to każda pętla oznacza ~ 1,5 KB śmieci w / nieokreślistyczny czas życia.
ShadowRanger

Zasadniczo, aby uniknąć nazwanej zmiennej, która byłaby (zwykle) deterministycznie czyszczona w każdej pętli (gdy jest ona odbijana, a stara wartość czyszczona), tworzysz ogromną nienazwaną zmienną, która jest czyszczona w sposób niedeterministyczny i może łatwo trwa dłuzej.
ShadowRanger


-7

Co powiesz na:

while range(some_number):
    #do something

3
To nieskończona pętla, ponieważ warunek range(some_number)jest zawsze spełniony!
śmiertelny

@deadly: Cóż, jeśli some_numberjest mniejsza lub równa 0, nie jest nieskończona, po prostu nigdy nie działa. :-) Jest to raczej nieefektywne w przypadku nieskończonej pętli (szczególnie w Py2), ponieważ tworzy nowy list(Py2) lub rangeobiekt (Py3) dla każdego testu (nie jest to stały z punktu widzenia interpretera, musi się załadować rangei some_numberkażdą pętlę, wywołaj range, a następnie przetestuj wynik).
ShadowRanger
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.