Jeśli piszesz w Python od jakiegoś czasu lub jeśli przerobiłeś dobre kursy czy książki to na pewno spotkałeś się z instrukcją range. W dużym uproszczeniu range generuję listę liczb, po których możemy iterować. Instrukcja ta występuje zarówno w Python2 jak i Python3. W obu idea jest ta sama, ale różnią się jednym bardzo ważnym detalem, który omówimy później. Poznaj kompleksowe wyjaśnienie range w Python.
Podstawowe użycie range
Jak wspomniałem range w python służy do generowania listy liczb. Wystarczy, że jako parametr podana zostanie wartość maksymalna. Podstawowe użycie może wyglądać zatem następująco:
range(10)
Tylko, że takie użycie nic nam nie da. W ten sposób zostanie utworzony iterowalny obiekt ( podobnie jak generagtor ), po którym możemy przechodzić. Spróbujmy zatem wypisać 10 liczb. Musimy do tego skorzystać z list comprehension, które swoją drogą zasługują na oddzielny wpis.
print([a for a in range(10)])
Wynik kodu będzie identyczny jak ten niżej. Który jest czytelniejszy?
print([0, 2, 3, 4, 5, 6, 7, 8, 9])
Jako, że jest to już lista, nie musimy już iterować po tych elementach. Wyobraźcie sobie jak wyglądałby kod, gdybyśmy chcieli wypisać 100, 1000 lub kilkaset tysięcy liczb.
Range z start i stop
Nie zawsze oczekujemy, że lista będzie iterowana od 0. Na szczęście implementacja range przewiduje i to. Wystarczy podać dwa parametry, gdzie pierwszy będzie oznaczał początek a drugi koniec. Zatem jeśli chcemy wypisać liczby od 5 do 9 wystarczy fragment kodu
print([a for a in range(5, 10)])
Range z start, stop i step
Innym, częstym problemem korzystania z listy jest zwiększenie kolejnych wartości o inną wartość niż jeden. Na to również jest sposób. Implementacja range przewiduje również trzeci, opcjonalny parametr, który mówi o ile przeskoczyć z każdym kolejnym krokiem. Tak więc jeśli chcemy wypisać nieparzyste liczby od 1 do 10 wystarczy dodać
print([a for a in range(1, 10, 2)])
Lista malejąca
Jako, że każdy z parametrów może być dodatni lub ujemny można zrobić również odliczanie w dół
for a in range(5, 0, -1):
if a > 1:
print('Wpis o range na blogu ddeby pojawi się za {} dni'.format(a))
elif a == 1:
print('Wpis o range na blogu ddeby pojawi się za {} dzień'.format(a))
else:
print('Wpis o range na blogu ddeby pojawi się dziś')
Implementacja range
Jak widać implementacja range wygląda więc następująco
range([start], stop, [step])
Gdzie:
- start - to początek, czyli pierwszy indeks
- Stop - wartość końcowa, która nie jest zawarta w wyniku
- Step - różnica pomiędzy kolejnymi iteracjami
Dzięki takiemu rozwiązaniu oferuje zdecydowanie większość przypadków, które są potrzebne.
Ważne:
Wszystkie parametry muszą być liczbami całkowitymi
Wszystkie parametry mogą być dodatnie lub ujemne
Range w python2 i python3
Wspomniałem wcześniej, że range występował w obu wersjach Pythona. Żeby zrozumieć różnicę sprawdźmy co zwraca range w Python2.
range(10) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Jak widzimy jest to lista. W Python w celu zaoszczędzenia pamięci na dużą skalę używane są generatory. Generatory mają tę zaletę, że są “lazy load”, dzięki czemu ładowane do pamięci są dopiero w momencie odwołania się. Żeby range również był “lazy load” wystarczy dodać x. A więc odpowiednik range z Python3 w Python2 wygląda następująco
xrange(10) # xrange(10)
Proste, ale jednak range jest nieco bardziej intuicyjne. Podejrzewam, że właśnie dlatego range w Python3 zastąpił xrange z Python2 i wycofano się z samego xrange. Dlatego własnie na początku wpisu musielismy skorzystać z list comprehension. Oczywiście jest to duże uproszczenie i przedstawiona wiedza jest wiedzą w pigułce.
Przykład range do tabliczki mnożenia w Python
def table(n):
for i in range(n):
print([j for j in range(n)])
Range z liczbami rzeczywistymi
Range jest bardzo ciekawą konstrukcją w Python niestety jest przygotowany jedynie pod liczby całkowite. Spróbujmy zatem napisać odpowiednik funkcję frange, czyli range dla liczb typu float
def frange(start, stop, step):
while start < stop:
yield start
start += step
print([a for a in frange(1, 5, 0.5)]) # [1, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0, 4.5]
Jest to jedna z wielu możliwych implementacji, ale zależało mi głownie na tym, żeby implementacja była jako generator. W tej implementacji wymagane są wszystkie parametry, gdyż obsługa zwłaszcza opcjonalnego atrybutu start niepotrzebnie utrudniłaby zrozumienie kodu.
A Wy do czego wykorzystujecie range?
Range to nie generator!
Edit. Muszę bardzo podziękować dla użytkownika mitcom , bo faktycznie zauważył błąd we wpisie. Edytuję go więc, żeby nie wprowadzać ludzi w błąd. Faktycznie range nie jest generatorem ale również jest " lazy load" . Żeby to sprawdzić wystarczy, że spróbujemy wykonać metodę next na obiekcie typu range
numbers = range(20)
next(numbers) # TypeError: 'range' object is not an iterator
Spróbujmy wykonać tę samą operację na generatorze
gen_numbers = (a for a in range(20))
next(gen_numbers) # 0
next(gen_numbers) # 1
Dodatkowo range ma zdefiniowaną długość, czego nie można powiedzieć o generatorze
gen_numbers = (a for a in range(20))
len(gen_numbers) # TypeError: object of type 'generator' has no len()
numbers = range(20)
len(numbers) # 20
Mam nadzieję, że nie wprowadziłem Was w błąd. Range faktycznie nie jest generatorem ale nie jest od razu ładowany do pamięci.