Python range() - zakresy liczb
Tworzenie zakresów liczbowych
W Pythonie funkcja range()
jest wbudowana. Oznacza to, że zawsze możesz wywołać range()
bez konieczności wcześniejszych przygotowań. Wywołanie range()
tworzy obiekt range
, który możesz wykorzystać w praktyce. Później zobaczysz praktyczne przykłady wykorzystania obiektów range
.
Możesz przekazać funkcji range()
jeden, dwa lub trzy argumenty całkowitoliczbowe. Odpowiada to trzem różnym przypadkom użycia:
- Zakresy liczb zaczynające się od zera
- Zakresy kolejnych liczb
- Zakresy liczb ze stałym krokiem
Zaraz Ci pokażę jak tworzyć i wykorzystywać każdy z nich.
Tworzenie zakresu liczb zaczynającego się od zera
Kiedy wywołujesz range()
z jednym argumentem, tworzysz zakres liczb zaczynający się od zera i kończący się przed podaną liczbą:
>>> range(5)
range(0, 5)
Tutaj stworzyłeś zakres od zera do pięciu. Aby zobaczyć poszczególne elementy w zakresie, możesz użyć list()
do konwersji zakresu na listę:
>>> list(range(5))
[0, 1, 2, 3, 4]
Sprawdzając range(5)
widzimy, że zawiera liczby zero, jeden, dwa, trzy i cztery. Pięć samo w sobie nie jest częścią zakresu. Jedną z przyjemnych właściwości tych zakresów jest to, że argument, w tym przypadku 5
, jest równy liczbie elementów w zakresie.
Tworzenie zakresu liczb od początku do końca
Możesz wywołać range()
z dwoma argumentami. Pierwsza wartość będzie początkiem zakresu. Podobnie jak wcześniej, zakres będzie liczył do, ale nie włącznie, drugiej wartości:
>>> range(1, 7)
range(1, 7)
Reprezentacja obiektu zakresu pokazuje tylko argumenty, które podałeś, więc w tym przypadku nie jest zbyt pomocna. Możesz użyć list()
do sprawdzenia poszczególnych elementów:
>>> list(range(1, 7))
[1, 2, 3, 4, 5, 6]
Zauważ, że range(1, 7)
zaczyna się od jednego i obejmuje kolejne liczby do sześciu. Siedem jest granicą zakresu i nie jest włączone. Możesz obliczyć liczbę elementów w zakresie, odejmując wartość początkową od wartości końcowej. W tym przykładzie jest 7 - 1 = 6
elementów.
Tworzenie zakresu liczb od początku do końca z określonym krokiem
Ostatnim sposobem na skonstruowanie zakresu jest podanie trzeciego argumentu, który określa krok między elementami w zakresie. Domyślnie krok wynosi jeden, ale możesz przekazać dowolną niezerową liczbę całkowitą. Na przykład, możesz przedstawić wszystkie nieparzyste liczby poniżej dwudziestu w następujący sposób:
>>> range(1, 20, 2)
range(1, 20, 2)
Tutaj określasz, że chcesz zakresu liczb od jednego do dwudziestu, co dwa. Spójrz na liczby, które są częścią tego zakresu:
>>> list(range(1, 20, 2))
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
Możesz potwierdzić, że zakres zawiera wszystkie nieparzyste liczby poniżej dwudziestu. Różnica między kolejnymi elementami w zakresie wynosi dwa, co jest równe wartości kroku, którą podałeś jako trzeci argument.
W tych przykładach zacząłeś korzystać z zakresów w Pythonie. W dalszej części samouczka dowiesz się więcej o tym, jak działa range()
pod spodem, a także zobaczysz kilka przykładów, kiedy warto używać obiektów range
we własnym kodzie.
Używanie funkcji range()
Pythona do tworzenia określonych zakresów
Widziałeś już składnię range()
i jak używać jednego, dwóch lub trzech argumentów do określenia różnych rodzajów zakresów. W tej sekcji zagłębisz się w funkcję range()
Pythona i zobaczysz, jak reprezentować konkretne zakresy.
Uwaga: Technicznie rzecz biorąc, range()
nie jest funkcją. Zamiast tego, range
jest typem lub klasą. Kiedy wywołujesz range()
, wywołujesz konstruktor klasy range
w celu utworzenia nowego obiektu zakresu.
Mimo to, do wszystkich praktycznych celów możesz traktować range()
jako funkcję, która zwraca obiekt range
.
Po pierwsze, zauważ, że zakres jest sekwencją leniwą. Oznacza to, że Python nie tworzy poszczególnych elementów zakresu podczas tworzenia zakresu. Zamiast tego tworzy każdy element, gdy o niego poprosisz, na przykład podczas iteracji po zakresie.
To różni się od na przykład listy. Listy są konstruowane natychmiast, więc wszystkie elementy listy są natychmiast obecne w pamięci po utworzeniu listy. Dlatego wcześniej przekonwertowałeś zakresy na listy, aby sprawdzić poszczególne elementy:
>>> range(1, 7)
range(1, 7)
>>> list(range(1, 7))
[1, 2, 3, 4, 5, 6]
Obiekt range
jest leniwy. W tym przykładzie poszczególne liczby w zakresie są po raz pierwszy referencjonowane, gdy konwertujesz go na listę. Ponadto nie wyczerpujesz zakresów podczas iteracji po nich. Możesz ich używać ponownie tyle razy, ile chcesz.
Jedną z właściwości zakresów, która może wydawać się nieintuicyjna, jest to, że wartość początkowa zakresu jest włączona, ale wartość końcowa nie. Zakresy Pythona reprezentują przedziały półotwarte, używając terminu technicznego.
Przedziały zamknięte, które obejmują zarówno wartość początkową, jak i końcową, są bardziej powszechne w codziennym użyciu. Na przykład zwykła kostka do gry ma wartości od jednego do sześciu. Jednak używanie przedziałów półotwartych w programowaniu ma kilka zalet, które Edsger W. Dijkstra opisuje w następujący sposób:
- Jeśli wartość początkowa nie jest uwzględniona w zakresie, musisz użyć
-1
, aby określić zakres od zera. - Jeśli wartość końcowa jest uwzględniona w zakresie, to kłopotliwe jest zdefiniowanie pustego zakresu.
- Jeśli zakres jest zdefiniowany jako przedział półotwarty, to łatwo obliczyć liczbę elementów w zakresie.
Widziałeś już kilka przykładów ostatniego punktu. Przypomnijmy, że range(1, 7)
zawiera sześć elementów, które obliczamy jako 7 - 1 = 6
. Następnie zbadasz, jak tworzyć specjalne rodzaje zakresów.
Iteruj po zakresach za pomocą pętli
W Pythonie możesz tworzyć pętle przy użyciu dwóch różnych konstrukcji:
- Pętla
while
lub pętla nieoznaczona powtarza operację, dopóki nie zostanie spełniony określony warunek. - Pętla
for
lub pętla oznaczona powtarza operację dla każdego elementu istniejącej sekwencji.
Ponieważ zakres jest sekwencją, zazwyczaj używa się pętli for
do iteracji po zakresach.
W wielu językach, w tym C++, Java i JavaScript, pętle for
są głównie oparte na indeksach. Pętle Pythona są inne. W Pythonie pętla for
jest oparta na elementach sekwencji, a nie indeksach. Jeśli jesteś bardziej zaznajomiony z innym językiem, może być konieczne dostosowanie podejścia do pętli.
W szczególności, jeśli chcesz ponownie utworzyć pętlę opartą na indeksach, możesz być skłonny użyć zakresu:
>>> word = "Python"
>>> for index in range(len(word)):
... print(index, word[index])
...
0 P
1 y
2 t
3 h
4 o
5 n
Iterujesz po indeksach i używasz każdego indeksu do wybrania odpowiedniej litery ze słowa "Python".
To podejście działa, ale zwykle nie jest najlepszym sposobem pracy z pętlami w Pythonie. W rzeczywistości, jeśli traktujesz zakres jako indeksy w Pythonie, powinieneś poszukać alternatywy. Istnieją lepsze sposoby, które zbadasz w nadchodzących sekcjach. Ale najpierw zbadasz sytuację, w której range()
jest odpowiednim narzędziem do pracy.
Powtarzanie operacji
Jeśli potrzebujesz powtórzyć operację ustaloną liczbę razy, użycie zakresu jest często dobrym rozwiązaniem. Wiesz, że range(n)
to sekwencja zawierająca n
elementów, więc pętla po takim zakresie powtórzy operację n
razy.
Na przykład, następujący blok kodu powtarza wywołania print()
trzy razy:
>>> for _ in range(3):
... print("Ding dong")
... print("Sheldon!")
...
Ding dong
Sheldon!
Ding dong
Sheldon!
Ding dong
Sheldon!
W tym kodzie powtarzasz blok wcięty dla każdego elementu w zakresie. Ponieważ same elementy zakresu są nieistotne, używasz podkreślenia (_) jako zmiennej tymczasowej.
Są też sytuacje, gdy pracujesz z prawdziwymi zakresami liczb i chcesz tworzyć pętle nad tymi zakresami. W następnym przykładzie utworzysz tabelę mnożenia, która pokazuje iloczyny wszystkich kombinacji liczb całkowitych do dziesięciu:
>>> for number in range(2, 12):
... for product in range(number, number * 12, number):
... print(f"{product:>4d}", end="")
... print()
...
2 4 6 8 10 12 14 16 18 20 22 24
3 6 9 12 15 18 21 24 27 30 33 36
4 8 12 16 20 24 28 32 36 40 44 48
5 10 15 20 25 30 35 40 45 50 55 60
6 12 18 24 30 36 42 48 54 60 66 72
7 14 21 28 35 42 49 56 63 70 77 84
8 16 24 32 40 48 56 64 72 80 88 96
9 18 27 36 45 54 63 72 81 90 99 108
10 20 30 40 50 60 70 80 90 100 110 120
11 22 33 44 55 66 77 88 99 110 121 132
Tworzysz dwie pętle, które razem utworzą dwuwymiarową tabelę mnożenia. Po pierwsze, iterujesz po liczbach od jednego do dziesięciu włącznie. Będą one reprezentować wiersze w tabeli, a te liczby możesz zobaczyć na początku każdego wiersza.
Drugą pętlę używasz do wypełnienia każdej kolumny danego wiersza. W tym zakresie używasz argumentów do obliczenia number
razy każdej liczby całkowitej od jednego do dziesięciu. W szczególności ostatni argument kroku zapewnia, że liczby w każdym wierszu są prawidłowo rozmieszczone.
Aby sformatować tabelę, używasz f-stringa i parametru end
funkcji print()
, który utrzymuje każdą liczbę na tej samej linii. W tym przykładzie pracujesz bezpośrednio z zakresem liczb i ma sens użycie zakresu w pęcie.
Iteracja bezpośrednio po iteratorze
Wcześniej użyłeś zakresu do skonstruowania indeksów ciągu znaków. Niewielka zmiana tego przykładu daje indywidualne znaki ciągu:
>>> word = "Loop"
>>> for index in range(len(word)):
... print(word[index])
...
L
o
o
p
W większości pętli indeks w ogóle nie jest potrzebny. Jeśli jesteś zainteresowany znakami ciągu, możesz skorzystać z tego, że ciąg jest już iterowalny. Oznacza to, że możesz iterować bezpośrednio po samym ciągu:
>>> word = "Loop"
>>> for char in word:
... print(char)
...
L
o
o
p
Iteracja bezpośrednio po sekwencji, jak tutaj, jest prostsza i bardziej czytelna niż używanie indeksów. Jeśli masz pętlę, w której używasz indeksów do znajdowania poszczególnych elementów, powinieneś zamiast tego iterować bezpośrednio po elementach.
Badanie innych funkcji i zastosowań zakresów
Do tej pory wiesz, jak konstruować zakresy w Pythonie i jak możesz iterować po nich. Widziałeś nawet kilka alternatyw dla używania range()
w konkretnych przypadkach użycia.
W tej sekcji przyjrzysz się bliżej obiektowi range
i dowiesz się, jakie operacje wspiera. Dowiesz się, że zakres ma wiele atrybutów i metod wspólnych z krotkami i listami, nawet jeśli zakres jest leniwy.
Dostęp do Indywidualnych Liczb w Zbiorze
Możesz użyć notacji nawiasów kwadratowych w Pythonie, aby wybrać pojedynczy element ze zbioru liczb:
>>> numbers = range(1, 20, 2)
>>> numbers[3]
7
>>> numbers[-2]
17
Najpierw tworzysz zbiór liczb zawierający nieparzyste liczby mniejsze od dwudziestu. Następnie wybierasz liczbę o indeksie trzy. Ponieważ sekwencje w Pythonie są indeksowane od zera, jest to czwarta liczba nieparzysta, czyli siedem. Na koniec wybierasz drugą liczbę od końca, która jest siedemnaście.
### Tworzenie Podzbiorów za pomocą Sliców
Oprócz wybierania pojedynczych elementów ze zbioru liczb, możesz używać **sliców** do tworzenia nowych zbiorów. Slice może pobrać jeden lub kilka elementów z sekwencji, a operacja używa podobnych parametrów jak `range()`.
W składni sliców używa się dwukropka (:) do oddzielania argumentów. Dodatkowo, liczby określają początek, koniec i opcjonalnie krok dla slicu. Slice taki jak `[1:5]` zaczyna się od indeksu `1` i przebiega do, ale nie włącznie, indeksu `5`. Możesz dodać krok na końcu, więc `[1:5:2]` będzie również przebiegać od indeksu `1` do `5`, ale uwzględni tylko co drugi indeks.
Jeśli zastosujesz slice do zbioru liczb, otrzymasz nowy zbiór, który będzie zawierał niektóre lub wszystkie elementy oryginalnego zbioru:
```python
>>> numbers = range(1, 20, 2)
>>> numbers[1:5]
range(3, 11, 2)
>>> numbers[1:5:2]
range(3, 11, 4)
Ponownie, zaczynasz od nieparzystych liczb mniejszych od dwudziestu. Pobieranie slicu [1:5]
daje nowy zbiór zawierający nieparzyste liczby od trzech do dziewięciu włącznie. Jeśli dodasz krok do swojego slicu, krok w wynikowym zbiorze zmieni się odpowiednio.
Sprawdzenie, czy liczba jest członkiem zbioru
Testy przynależności w zbiorach liczb są szybkie. Czasami możesz sprawdzić, czy wartość jest członkiem zbioru, zamiast wykonywać inne rodzaje walidacji.
Na przykład, możesz sprawdzić, czy year
jest rokiem przestępnym w XXI wieku w następujący sposób:
>>> year = 2023
>>> year in range(2000, 2100, 4)
False
>>> year = 2024
>>> year in range(2000, 2100, 4)
True
Lata przestępne występują co cztery lata, więc sprawdzasz, czy year
znajduje się w zakresie od 2000 roku, włączając co czwarty rok do 2100 roku.
Uwaga: Ogólnie rzecz biorąc, obliczenia dotyczące lat przestępnych są nieco bardziej skomplikowane. Rok przestępny to rok, który jest wielokrotnością czterech, z wyjątkiem lat podzielnych przez 100, ale nie przez 400. W praktyce te wyjątki oznaczają, że 1900 i 2100 nie są latami przestępnymi, ale 2000 jest rokiem przestępnym.
Zawsze możesz zastąpić test przynależności do zbioru równoważnym warunkiem logicznym:
>>> year = 2023
>>> 2000 <= year < 2100 and year % 4 == 0
False
>>> year = 2024
>>> 2000 <= year < 2100 and year % 4 == 0
True
Używasz operatora modulo (%) do sprawdzenia, czy rok jest podzielny przez cztery. W niektórych przypadkach użycie range()
jest bardziej czytelne niż szczegółowe wypisywanie odpowiedniego równania.
Jednym ze subtelnych szczegółów w testach przynależności do zbioru jest to, że wszyscy członkowie zbiorów są liczbami całkowitymi. Oznacza to, że liczby z częścią dziesiętną nigdy nie są członkami zbioru:
>>> number = 4.2
>>> number in range(1, 10)
False
Tutaj sprawdzasz, czy 4,2 jest częścią zakresu liczb całkowitych zaczynającego się od jednego i liczącego do dziesięciu. Chociaż cztery jest w zakresie, liczba dziesiętna nie jest. Aby uzyskać ten sam wynik za pomocą testu logicznego, musisz pamiętać o sprawdzeniu, czy liczba jest również podzielna przez jeden:
>>> 1 <= number < 10
True
>>> 1 <= number < 10 and number % 1 == 0
False
Możesz użyć operatora modulo z wartością kroku zbioru, aby upewnić się, że dana liczba jest zgodna z krokiem. Ogólnie rzecz biorąc, jeśli wartość początkowa nie jest podzielna przez wartość kroku, należy odjąć wartość początkową od number
przed zastosowaniem operatora modulo.
Tworzenie zbioru używającego parametrów podobnych do liczb całkowitych
Do tej pory używałeś liczb całkowitych do tworzenia zbiorów liczb. Możesz także użyć liczb podobnych do liczb całkowitych, takich jak liczby binarne lub szesnastkowe:
>>> range(0b110)
range(0, 6)
>>> range(0xeb)
range(0, 235)
Tutaj 0b110
to reprezentacja binarna liczby 6, natomiast 0xeb
to reprezentacja szesnastkowa liczby 235.
Okazuje się, że możesz również tworzyć zbiory z samodzielnie zdefiniowanych liczb podobnych do liczb całkowitych. Aby być liczbą podobną do liczby całkowitej, Twoja klasa musi definiować specjalną metodę .__index__()
, aby przekonwertować swoją liczbę na zwykłą liczbę całkowitą.
Na przykład rozważmy specjalne liczby nazywane cyframi π. Wszystkie składają się z cyfr stałej π, która wynosi około 3,1415926. Pierwsza cyfra π to 3, następna to 31, trzecia to 314 i tak dalej. Utworzysz klasę PiDigits
aby reprezentować takie cyfry π:
pi_digits.py
from dataclasses import dataclass
@dataclass
class PiDigits:
num_digits: int
def __index__(self):
return int("3141592653589793238462643383279"[:self.num_digits])
Zapisz tę klasę w pliku o nazwie pi_digits.py
. Następnie możesz go importować i bawić się nim:
>>> from pi_digits import PiDigits
>>> PiDigits(3)
PiDigits(num_digits=3)
>>> int(PiDigits(3))
314
>>> range(PiDigits(3))
range(0, 314)
Najpierw tworzysz trzecią cyfrę π, 314. Ponieważ zaimplementowałeś .__index__()
, możesz przekonwertować PiDigits(3)
na zwykłą liczbę całkowitą za pomocą int()
i użyć jej bezpośrednio jako argumentu do range()
.
Cyfr π możesz używać do wszystkich argumentów w range()
:
>>> range(PiDigits(1), PiDigits(6))
range(3, 314159)
>>> range(PiDigits(2), PiDigits(8), PiDigits(1))
range(31, 31415926, 3)
>>> len(range(PiDigits(2), PiDigits(8), PiDigits(1)))
10471965
Ostatnie obliczenie długości potwierdza, że wynikowy zbiór zawiera miliony liczb, zgodnie z oczekiwaniami. Mimo że masz pewną elastyczność w podawaniu argumentów do range()
, wszystkie muszą być podobne do liczb całkowitych.
Jeśli potrzebujesz większej elastyczności, na przykład tworzenia zakresów liczb zmiennoprzecinkowych, masz kilka opcji. Możesz utworzyć własną klasę FloatRange
. Przykład tego znajduje się w materiałach do pobrania. Jeśli w swoim projekcie używasz biblioteki NumPy, powinieneś zamiast tego użyć jej funkcji arange()
.
Podsumowanie
Zagłębiłeś się w range()
i poznałeś wiele jego funkcji. Chociaż nauczyłeś się, że zbiory liczb są najczęściej używane w pętlach, widziałeś również niektóre właściwości obiektów range
, które mogą być przydatne również poza pętlami.