W tym epizodzie w sposób obszerny i wyczerpujący poznajemy dwie pętle występujące w Pythonie – for i while. Po tym odcinku pętle nie będą mieć dla nas żadnych tajemnic.

Klasyczna pętla for

Oto najbardziej klasyczna pętla for. Idziemy w pętli po każdym elemencie listy i wypisujemy go na ekranie:

lst = [1,2,3,4,5]
for num in lst:
    print(num)
# 1
# 2
# 3
# 4
# 5

Oczywiście możemy wypisać go, podniesionego do potęgi na przykład:

lst = [1,2,3,4,5]
for num in lst:
    print(num**2)
# 1
# 4
# 9
# 16
# 25

Albo możemy przejść po każdej literze danego napisu i wypisać ją, wielkimi literami:

msg = "hello"
for letter in msg:
    print(letter.upper())
# H
# E
# L
# L
# O

Albo możemy przejść po każdym elemencie z listy, wypisać go jego hash oraz długość:

passwords = ["admin", "boss", "master"]
for pswd in passwords:
    print(pswd, len(pswd), hash(pswd))
# admin 5 7323597272211714652
# boss 4 -1604753090111711131
# master 6 4641334375596423939

Albo możemy przejść po każdej literze z danego napisu i sprawdzić, jaka liczba ASCII odpowiada tej literze (komputery tak naprawdę nie przechowują liter, bo nie znają czegoś takiego, tylko pewne liczby):

for letter in "hello":
    print(letter, ord(letter))
# h 104
# e 101
# l 108
# l 108
# o 111

Funkcja ord zamienia literę na odpowiadającą jej liczbę. Jak widzimy, dla komputera nie ma czegoś takiego, jak mała litera h. Jest liczba 104. W dodatku zapisana w systemie dwójkowym, bo komputery znają tylko dwie cyfry (0 i 1, nie ma prądu albo jest prąd). No chyba, że mówimy o komputerze kwantowym, ale ten, u siebie w domu, raczej nieprędko zobaczymy.

Co nam tam szkodzi, liczbę w systemie dwójkowym też sobie wypisać możemy:

for letter in "hello":
    print(letter, ord(letter), bin(ord(letter)))
# h 104 0b1101000
# e 101 0b1100101
# l 108 0b1101100
# l 108 0b1101100
# o 111 0b1101111

Funkcja ord zamienia literę na odpowiadającą jej liczbę zaś funkcja bin zamienia liczbę na zapis dwójkowy. 0b to przedrostek, oznacza, że jest to liczba binarna. A zatem dla naszego komputera nie ma czegoś takiego jak mała litera h, jest liczba „1101000” która po naszemu oznacza 104.

Tak czy inaczej, możliwości pętli są nieograniczone. Skupmy się jednak na tym, czego o pętlach jeszcze nie wiemy oraz na wypełnianiu luk w wiedzy i pamięci.

For…else

for letter in "hello":
    print(letter, ord(letter), bin(ord(letter)))
else:
    print("FINISH")
# h 104 0b1101000
# e 101 0b1100101
# l 108 0b1101100
# l 108 0b1101100
# o 111 0b1101111
#FINISH

Dziwny zapis, prawda? Może on sugerować, że mamy do czynienia z jakąś alternatywą, albo pętla, albo blok else. Nic bardziej mylnego. Blok else ma się wykonać po zakończeniu pętli, zakładając, że nie padło w niej słówko kluczowe break, kończące pętlę, którego jeszcze nie znamy.

W takiej postaci moglibyśmy zapisać to tak bez żadnej różnicy:

for letter in "hello":
    print(letter, ord(letter), bin(ord(letter)))
print("FINISH")
# h 104 0b1101000
# e 101 0b1100101
# l 108 0b1101100
# l 108 0b1101100
# o 111 0b1101111
#FINISH

Break oraz continue, słówka kluczowe dla pętli, niedługo poznamy, natomiast tę konstrukcję for…else warto znać, nie po to, aby z niej korzystać, ale w razie potrzeby zrozumieć dość dziwny w zasadzie zapis.

Break, continue – słówka kluczowe

Popatrzmy na tę pętlę, która wypisuje liczby z listy:

lst = [1,2,3,4,-1,5,6,-1,7]
for num in lst:
    print(num)
# 1
# 2
# 3
# 4
# -1
# 5
# 6
# -1
# 7

Cóż, a co jeśli nie chcemy, aby nam wypisywało -1? Możemy użyć słówka kluczowego continue, które oznacza „pomiń ten obrót pętli”:

lst = [1,2,3,4,-1,5,6,-1,7]
for num in lst:
    if num == -1:
        continue
    print(num)
# 1
# 2
# 3
# 4
# 5
# 6
# 7

Tutaj mamy instrukcję – dla każdego num w liście, jeżeli num jest równe -1 nie rób nic, idź do następnego elementu, a jak nie jest równe -1 to jeszcze ten element wypisz.

A do czego służy słówko kluczowe break? Sprawdźmy:

lst = [1,2,3,4,-1,5,6,-1,7]
for num in lst:
    if num == -1:
        break
    print(num)
# 1
# 2
# 3
# 4

Break oznacza wyjdź z pętli. A zatem, mieliśmy wypisane liczby od 1 do 4. Potem doszło do tego, że num wynosi -1 i to był ten warunek, gdzie pętla po prostu się zakończyła.

Tutaj możemy przypomnieć sobie już wspomniany blok for…else i spróbować wykonać ten kod:

lst = [1,2,3,4,-1,5,6,-1,7]
for num in lst:
    if num == -1:
        break
    print(num)
else:
    print("No -1 detected")
# 1
# 2
# 3
# 4

Dostajemy liczby od 1 do 4, wyjście z pętli i blok else się nie wykonuje, ponieważ ta pętla uciekła się do break. I nie chodzi o obecność tego słówka kluczowego w pętli, ale o fakt, że jeden z elementów wynosił -1, spełnił warunek, który spowodował break, czyli „wyskoczenie” z pętli, do którego ewidentnie doszło.

Zobaczmy na ten kod:

lst = [1,2,3,4,5]
for num in lst:
    if num == -1:
        break
    print(num)
else:
    print("No -1 detected")
# 1
# 2
# 3
# 4
# 5
# No -1 detected

Tutaj blok else się wykonał, bo do wyskoczenia przy pomocy break nigdy nie doszło, jako że żaden z elementów nie spełnił warunku o byciu równym -1.

Nie wiem, czy się to nam jakkolwiek przyda,

While True, break

Myślę, że warto się pochylić nad taką pętlą jak while, zwłaszcza while z zawsze prawdziwym warunkiem. Oto przykład:

while True:
    print("Hello world")
    break
#Hello world

While True to pętla zawsze prawdziwa, a zatem wykona się i jeżeli nie zapiszemy break to będzie nam w nieskończoność ten hello world wypisywać. Dodam, że możemy tam dać coś innego, co jest „zawsze prawdziwe”, nie musi być prawda bezpośrednia, może być coś, co do prawdy zostanie skonwertowane, ot choćby to:

while 2 == 2:
    print("Hello world")
    break
#Hello world

2 zawsze się będzie równać 2. I ta pętla mogłaby być nieskończona, gdyby nie nasz break. Dla przykładu – pętla z warunkiem, który w pewnym momencie przestanie być prawdziwy:

lst = ["abc", 123, "hello"]
print(lst)
#['abc', 123, 'hello']
print(len(lst))
#3
while len(lst) > 0:
    print(lst.pop())
#hello
# 123
# abc
print(lst, len(lst))
#[] 0

Tutaj mamy listę z trzema elementami. Jej długość to 3. Dajemy warunek dla pętli while – „chodź tak długo, aż długość listy przestanie być większa od 0”. W pętli zrzucamy ostatni element z listy i go wypisujemy. Po zakończeniu pętli lista jest pusta a jej długość wynosi 0.

Wracając do pętli zawsze prawdziwych, z których wychodzimy przez break – po co nam one?

Sprawdzają się wtedy, gdy nie wiemy, jak długo chcemy w tej pętli przebywać. Zróbmy najpierw taki może abstrakcyjny przykład. Losujemy liczbę od 1 do 6 i wypisujemy i nie przestajemy, aż nie wylosujemy 6:

import random
while True:
    number = random.randint(1,6)
    print(number)
    if number == 6:
        print("Finally, 6")
        print("Goodbye!")
        break
# 2
# 1
# (...)
# 3
# 6
# Finally, 6
# Goodbye!

Importujemy random, pętla nieskończona, losujemy liczbę, wypisujemy. Jeżeli liczba to 6 to kończymy, a jeżeli nie – pętla trwa. Aż to 6 wylosujemy. Oczywiście, warunek musi być warunkiem możliwym do spełnienia. Jeżeli nasz warunek to number równy 7 (a losuje od 1 do 6) to ta pętla chodzić w nieskończoność będzie. Tak samo, gdybyśmy zapomnieli ten break napisać, co z niedbalstwa nawet mi się zdarzyło pisząc ten przykład.

Dlatego pamiętajmy – uwaga na pętle while True, one, jeżeli się pomylimy, mogą stać się pętlami chodzącymi w nieskończoność. Dobra logika, która sprawia, że kiedyś ta pętla się zakończy to podstawa.

Teraz może bardziej realistyczny przykład: program nas prosi o wybranie opcji od a do e. I pętla będzie chodzić tak długo, aż napiszemy coś, co jest literą od a do e.

while True:
    option = input("Pick an option between a and e\n")
    if option in "abcde":
        print(f"Im doing option {option}")
        break

Tutaj nas prosi o podanie opcji od a do e. Jeżeli podamy coś innego, będzie prosić dalej. Aż do skutku. Potem „wykona” podaną opcję i wyjdzie z pętli. Jeżeli podamy coś nieprawidłowego – a nieprawidłowe jest wszystko poza literami abcde – będzie powtarzać pętlę.

Wszystko, też dobry jestem – zawsze jest jakieś „ale”. Po wciśnięciu samego „enter” dostaniemy wiadomość „Im doing option” bez opcji podanej i pętla też się skończy. Jest tak, ponieważ po wciśnięciu samego entera tam idzie pusty string „”. I jakoś tak ten pusty string „” jest częścią napisu „abcde”.

Zobaczmy, co możemy zaradzić w tej sprawie. Może zamiana na listę rozwiąże problem?

while True:
    option = input("Pick an option between a and e\n")
    if option in list("abcde"):
        print(f"Im doing option {option}")
        break

Tak, rozwiązało. Właśnie dlatego programy się testuje. I to zazwyczaj porządnie a nie jak my „manualnie”. Bo zawsze może wkraść się jakiś błąd.

Oczywiście istnieją inne sposoby rozwiązania tego problemu. Choćby sprawdzenie, czy tekst wprowadzony jest dłuższy od 0:

while True:
    option = input("Pick an option between a and e\n")
    if len(option) > 0 and option in "abcde":
        print(f"Im doing option {option}")
        break

Tutaj może tego tak nie widać, ale poznaliśmy nowy operator logiczny – and. Warunek jest: jeżeli tekst jest dłuższy od 0 i znajduje się w abcde. Ten warunek można uprościć korzystając z „prawdziwości” – 0 zawsze jest nieprawdziwe, zaś „niezero” prawdziwe:

while True:
    option = input("Pick an option between a and e\n")
    if len(option) and option in "abcde":
        print(f"Im doing option {option}")
        break

Nie musimy kochać tego zapisu, też uważam, że dla początkujących może być mylący. Ale zgodnie z „prawdziwością” wszystko co 0 to fałsz zaś niezero – prawda. Więc jeżeli wprowadzimy tekst o długości co najmniej 1 i będzie on w zbiorze „abcde” to warunek mamy spełniony.

Jeżeli nas to interesuje, to nie tylko liczby można poddawać testowi na „prawdziwość” (czyli if cośtam, bez znaków mniejszości, większości, równości). Testowi na „prawdziwość” można poddawać również napisy. I napis pusty „” to fałsz, zaś każdy inny, nawet spacja – prawda.

A zatem możemy tę logikę jeszcze uprościć:

while True:
    option = input("Pick an option between a and e\n")
    if option and option in "abcde":
        print(f"Im doing option {option}")
        break

„if option” oznacza „jeśli option poddane testowi na prawdziwość wyniesie prawda” czyli, w przypadku napisu, „jeżeli option nie jest pustym napisem”. Dalej mamy operator and (i) oraz sprawdzenie, czy option znajduje się w „abcde”.

No dobra, posiedźmy nad tym jeszcze. Co zrobić, aby program wykonał opcję „a” nawet jeśli wpiszemy „A” wielką literą?

To można rozwiązać bardzo prosto:

while True:
    option = input("Pick an option between a and e\n")
    if option and option.lower() in "abcde":
        print(f"Im doing option {option}")
        break

Mamy „zawsze prawdziwą” pętlę while, pytamy o opcję. Jeżeli opcja nie jest pustym napisem i po zmniejszeniu do małej litery znajduje się w „abcde” to wykonujemy ją i kończymy, jeżeli nie trwamy w pętli, aż użytkownik łaskawie wpisze poprawną opcję, którą jest litera od a do e, nawet wielka.

Oczywiście jesteśmy zawsze po to, aby użytkownikowi życie ułatwiać i w tym hipotetycznym przykładzie możemy chcieć dodać funkcjonalność, która sprawdza że „aaaaa” bądź cokolwiek innego, co się zaczyna na poprawną literę też będzie prowadziło do spełnienia warunku. Że tylko pierwsza litera będzie sprawdzana, reszta nie (bo użytkownikowi może omsknąć się ręka).

Jak to zrobić? Bardzo prosto, odnosząc się do naszego napisu a raczej jego elementu o indeksie zerowym:

while True:
    option = input("Pick an option between a and e\n")
    if option and option[0].lower() in "abcde":
        print(f"Im doing option {option[0]}")
        break

Indeks 0 czyli pierwsza litera, resztę ignorujemy. Teraz możemy sobie wpisać „aaaaaa” albo „Aaaaaaa”, cokolwiek, co zaczyna się na prawidłową literę. Program „wykona” zadaną opcję i wyjdzie z pętli.

Myślę, że jest to jakiś przykład pętli while zawsze prawdziwej. Kontynuujmy naukę zatem.

„Bardzo klasyczne” while

„Bardzo klasyczne” while (określenie moje) to takie, w którym najpierw tworzymy zmienną, typu liczbowego zazwyczaj, potem stawiamy while i warunek z tą zmienną, następnie wewnątrz pętli coś z tą zmienną robimy i ją modyfikujemy. Po pewnym czasie warunek przestanie być prawdziwy:

number = 0
while number <= 5:
    print(number)
    number +=1
# 0
# 1
# 2
# 3
# 4
# 5

Tutaj tworzymy zmienną number i nadajemy jej wartość 0. Tworzymy pętlę while z warunkiem, że ma działać tak długo, jak number jest mniejszy lub równy pięć. Następnie wypisujemy liczbę i zwiększamy ją o 1. W pewnym momencie liczba ta wyniesie 6 i warunek przestanie być prawdziwy. Możemy popatrzeć na tę pętlę i wiedzieć ile razy się wykona.

Dodam, że kolejność ma znaczenie. Jeżeli chcemy pozbyć się tego zera możemy dać number na 1:

number = 1
while number <= 5:
    print(number)
    number +=1
# 1
# 2
# 3
# 4
# 5

Ale możemy też najpierw zwiększać liczbę a potem ją wypisywać:

number = 0
while number <= 5:
    number += 1
    print(number)

# 1
# 2
# 3
# 4
# 5
# 6

Warto zwrócić uwagę, że tutaj nam 6 wypisało. Dzieje się tak, ponieważ warunek jest sprawdzany przy każdym obrocie pętli, nie zaś po każdej instrukcji. Cały blok (wcięty spacjami/tabem) się wykonuje i dopiero potem warunek jest sprawdzany.

A zatem liczba 4 była mniejsza lub równa pięć w pewnym momencie, pętla się wykonała, podnosząc 4 do 5 i wypisując. Warunek został ponownie sprawdzony, 5 jest może nie mniejsze, ale z pewnością równe 5. Mamy teraz cały blok kodu do wykonania. Podnosimy 5 o 1, mamy 6, drukujemy i dopiero teraz sprawdzamy czy 6 jest mniejsze lub równe 5. Nie jest – do widzenia.

Jak widać pętle są podstępne, zwłaszcza pętle while i trzeba być bardzo pedantycznym, aby wszystko działało tak, jak tego chcemy. Dużo ich w programowaniu mieć nie będziemy (a w każdym razie nie takich), są łatwiejsze sposoby. Natomiast było to swego rodzaju zobrazowanie jak działa pętla i dobrze, że się nad tym „klasycznym while” pochyliliśmy. Dodam tylko, że wszystko zależy od warunku, taki warunek wyeliminuje nam te 6 z tak określonej pętli:

number = 0
while number < 5:
    number += 1
    print(number)

# 1
# 2
# 3
# 4
# 5

Jeżeli głowa nam się gotuje – niepotrzebnie. Ten klasyczny while musiałem pokazać, ale korzystać z niego zbyt często nie będziemy, o ile w ogóle. Bardziej w celach edukacyjnych jest tu prezentowany.

Ale tak – tworzymy zmienną. Tworzymy warunek dla zmiennej, który w pewnym momencie musi przestać być prawdziwy. Tworzymy blok kodu, gdzie A) – coś ze zmienną robimy, B) zmieniamy zmienną tak, aby po iluś obrotach pętli w końcu prawdziwy być przestał (jak widać A i B kolejnością zamienić możemy). Jeżeli postawimy zły warunek albo np. zapomnimy zmodyfikować naszą zmienną, to wpadniemy w pętlę nieskończoną.

Tym niemniej, wracamy do bardziej przyjemnych ćwiczeń.

Obiekt range – pętla for

W pętli for przechodzić możemy nie tylko po elementach listy albo literach napisu, ale też po obiekcie range. Oto przykład:

for num in range(6):
    print(num, bin(num), hex(num))

# 0 0b0 0x0
# 1 0b1 0x1
# 2 0b10 0x2
# 3 0b11 0x3
# 4 0b100 0x4
# 5 0b101 0x5

Tutaj przechodzimy po liczbach od 0 do 5 i, tak akurat zechciałem, wypisujemy je w systemie dziesiętnym (naszym) oraz dwójkowym (binarnym) i szesnastkowym (heksadecymalnym). Służą (do konwersji na te systemy) odpowiednio funkcje bin i hex.

A jeżeli chcemy liczby od 1 do 6 (włącznie z 6)?

for num in range(1,7):
    print(num, bin(num), hex(num))

# 1 0b1 0x1
# 2 0b10 0x2
# 3 0b11 0x3
# 4 0b100 0x4
# 5 0b101 0x5
# 6 0b110 0x6

Obiekt range jest o tyle dziwny, że jeżeli chcemy od 1 do 6 to musimy zapisać odpowiednio 1 i 7. Oznacza to „od 1 włącznie do 7 z wyłączeniem 7”. Dziwne, ale tak to wygląda.

Możemy jeszcze podać coś takiego jak step, czyli krok, trzeci argument. Idźmy od 0 do 10 (włącznie) ale skaczmy o 2:

for num in range(0,11,2):
    print(num, bin(num), hex(num))

# 0 0b0 0x0
# 2 0b10 0x2
# 4 0b100 0x4
# 6 0b110 0x6
# 8 0b1000 0x8
# 10 0b1010 0xa

Tutaj mamy zapis 0,11,2 czyli od 0 (włącznie) do 11 z wyłączeniem 11, skacz po 2.

Enumerate – indeks i wartość

Bawiąc się listami bądź napisami możemy iterować po ich elementach. Oto wypisywanie z tekstu znaków jeden po drugim:

msg = "hello"
for char in msg:
    print(char)
# h
# e
# l
# l
# o

Jak pamiętamy, tekst jest indeksowany od 0 i np. pod indeksem 0 znajduje się tutaj litera 'h’. A co jeśli – z jakichś powodów – chcemy przejść zarówno po indeksie jak i wartości? Służy do tego enumerate:

msg = "hello"
for idxchar in enumerate(msg):
    print(idxchar)
# (0, 'h')
# (1, 'e')
# (2, 'l')
# (3, 'l')
# (4, 'o')

Jak widać zwraca nam krotkę z indeksem i wartością. Krotki też są indeksowane więc do indeksu możemy się odnieść po indeksie 0 zaś do wartości po indeksie 1:

msg = "hello"
for idxchar in enumerate(msg):
    print(f"Index {idxchar[0]} = {idxchar[1]}")

# Index 0 = h
# Index 1 = e
# Index 2 = l
# Index 3 = l
# Index 4 = o

Ale to jest przesadnie skomplikowane, zaś krotkę możemy sobie „rozpakować” do dwóch zmiennych, co jest daleko bardziej czytelne:

msg = "hello"
for idx, char in enumerate(msg):
    print(f"Index {idx} = {char}")

# Index 0 = h
# Index 1 = e
# Index 2 = l
# Index 3 = l
# Index 4 = o

Enumerate możemy też użyć na liście, aby móc przejść w pętli zarówno po indeksie jak i jej wartości:

lst = [1,2,3,4,5]
for idx, num in enumerate(lst):
    print(f"{idx} = {num}")
# 0 = 1
# 1 = 2
# 2 = 3
# 3 = 4
# 4 = 5

Czy to się może nam do czegoś przydać? Cóż, są takie sytuacje. Tym niemniej, ja prezentuję, jak to działa.

Zip – złóż dwie listy w krotkę

Trochę już o tych krotkach było (są jak listy, ale niemutowalne, nie można zmieniać ich wartości, natomiast podobnie jak listy podlegają indeksowaniu), więc teraz zrobimy sobie takie coś, jak złożenie 2 list w krotkę, używając funkcji zip. Musimy tylko pamiętać, aby te listy były jednakowej długości:

colors = ["red", "green", "blue"]
numbers = [100, 200, 255]
for colnum in zip(colors, numbers):
    print(colnum)
    
# ('red', 100)
# ('green', 200)
# ('blue', 255)

Tutaj mamy nazwy kolorów w jednej liście i ich liczbę, oznaczającą nasycenie, w drugiej. Następnie przechodzimy sobie w pętli po krotkach zawierających kolor i jego nasycenie. Do „produkcji” tych krotek użyliśmy funkcji zip.

Do krotek możemy odwoływać się po indeksie, tutaj indeks 0 to jest nazwa koloru zaś indeks 1 jego nasycenie, oto dowód:

colors = ["red", "green", "blue"]
numbers = [100, 200, 255]
for colnum in zip(colors, numbers):
    print(colnum[0], colnum[1])

# red 100
# green 200
# blue 255

I oczywiście krotki nie są od tego, aby się do nich po nic nie mówiących indeksach odnosić (przynajmniej moim zdaniem), tylko po to, aby je „rozpakowywać” do dwóch, więcej mówiących zmiennych:

colors = ["red", "green", "blue"]
numbers = [100, 200, 255]
for color, number in zip(colors, numbers):
    print(color, number)

# red 100
# green 200
# blue 255

Natomiast gdyby kogoś zastanowiło to, co mamy po prawej stronie naszej pętli, ten „in zip(colors, numbers)”, i zadał sobie całkiem dobre pytanie: po czym my tak naprawdę przechodzimy w pętli? Na takie pytanie jest tylko jedna odpowiedź: sprawdźmy to:

colors = ["red", "green", "blue"]
numbers = [100, 200, 255]
print(zip(colors, numbers))
#<zip object at 0x000001E00410B5C0>

Obiekt zip niewiele nam mówi. Konwersja do listy:

colors = ["red", "green", "blue"]
numbers = [100, 200, 255]
print(list(zip(colors, numbers)))
#[('red', 100), ('green', 200), ('blue', 255)]

I już widzimy, że to, co zwraca nam zip to w zasadzie lista krotek. Każda krotka pod indeksiem 0 ma element z listy colors zaś pod indeksem 1 element z listy numbers. Po tym iterujemy, biorąc tę krotki w całości, albo rozpakowując je do 2 zmiennych. Jeszcze raz, jak to wygląda:

colors = ["red", "green", "blue"]
numbers = [100, 200, 255]
for color, number in zip(colors, numbers):
    print(color, number)

# red 100
# green 200
# blue 255

Pętle po słownikach – klucze, wartości, pary klucz-wartość

Po słowniku też można przechodzić w pętli. Można to robić po elementach .keys, .values oraz .items. Zobaczmy, co one kryją:

color = {
    "red" : 100,
    "green" : 200,
    "blue" : 255
}
print(color.keys())
#dict_keys(['red', 'green', 'blue'])
print(color.values())
#dict_values([100, 200, 255])
print(color.items())
#dict_items([('red', 100), ('green', 200), ('blue', 255)])

Keys to lista kluczy, values to lista wartości, items to lista zawierająca krotki z kluczem i wartością. Na dobrą sprawę zamiast używać items mogli je byśmy sami stworzyć wykorzystując już nam znaną funkcję zip:

color = {
    "red" : 100,
    "green" : 200,
    "blue" : 255
}
print(color.keys())
#dict_keys(['red', 'green', 'blue'])
print(color.values())
#dict_values([100, 200, 255])
print(list(zip(color.keys(), color.values())))
#[('red', 100), ('green', 200), ('blue', 255)]

Ale nieważne. Iteracja po kluczach:

color = {
    "red" : 100,
    "green" : 200,
    "blue" : 255
}
for key in color.keys():
    print(key)
# red
# green
# blue

Iteracja po wartościach:

color = {
    "red" : 100,
    "green" : 200,
    "blue" : 255
}
for val in color.values():
    print(val)
# 100
# 200
# 255

Iteracja po spakowanych w krotki parach klucz-wartość:

color = {
    "red" : 100,
    "green" : 200,
    "blue" : 255
}
for keyval in color.items():
    print(keyval)

# ('red', 100)
# ('green', 200)
# ('blue', 255)

Iteracja po parach klucz-wartość, ale do 2 zmiennych rozpakowanych:

color = {
    "red" : 100,
    "green" : 200,
    "blue" : 255
}
for key,val in color.items():
    print(key, val)

# red 100
# green 200
# blue 255

A teraz, jeszcze na koniec, iteracja po parach klucz-wartość, ale z użyciem zip, sami sobie tę listę krotek z parą klucz-wartość pakujemy:

color = {
    "red" : 100,
    "green" : 200,
    "blue" : 255
}
for key,val in zip(color.keys(), color.values()):
    print(key, val)

# red 100
# green 200
# blue 255

Myślę, że tyle pętli na razie wystarczy. Poznaliśmy naprawdę dużo.