4. Więcej narzędzi kontroli przepływu
*************************************

Oprócz właśnie przedstawionego wyrażenia "while", Python używa kilku
innych, które napotkamy w tym rozdziale.


4.1. Instrukcje "if"
====================

Prawdopodobnie najbardziej znanym typem instrukcji jest instrukcja
"if". Na przykład:

   >>> x = int(input("Wprowadź liczbę całkowitą: "))
   Wprowadź liczbę całkowitą: 42
   >>> if x < 0:
   ...     x = 0
   ...     print('Wartość ujemna zmieniona na zero')
   ... elif x == 0:
   ...     print('Zero')
   ... elif x == 1:
   ...     print('Jeden')
   ... else:
   ...     print('Więcej niż jeden')
   ...
   Więcej niż jeden

Części "elif" może być zero lub więcej i część "else" jest opcjonalna.
Keyword „"elif"” jest skrótem od „else if” i jest przydatny by uniknąć
nadmiarowych wcięć. Sekwencja "if" … "elif" … "elif" … jest
zamiennikiem instrukcji "switch" lub "case" z innych języków.

Jeśli porównujesz tę samą wartość z wieloma stałymi lub sprawdzasz
poszczególne typy lub atrybuty, może ci się przydać instrukcja
"match". Więcej szczegółów znajdziesz w Instrukcje match.


4.2. Instrukcje "for"
=====================

Instrukcja "for" różni się troszeczkę w Pythonie od tego, co używasz w
C lub Pascalu. Nie prowadzi się iteracji od liczby do liczby (jak w
Pascalu) lub daje się użytkownikowi możliwość definiowania kroku
iteracji i warunki zakończenia iteracji (jak w C). Instrukcja "for" w
Pythonie powoduje iterację po elementach jakiejkolwiek sekwencji
(listy lub łańcucha znaków), w takim porządku, w jakim są one
umieszczone w danej sekwencji. Na przykład (gra słów niezamierzona):

   >>> # Zmierz kilka napisów:
   >>> words = ['kot', 'okienko', 'defenestracja']
   >>> for w in words:
   ...     print(w, len(w))
   ...
   kot 3
   okienko 7
   defenestracja 13

Kod, który zmienia kolekcję podczas iterowania po niej, może być
trudny. Zamiast tego, zazwyczaj prostsze jest przejść pętlą po kopii
kolekcji lub stworzyć nową kolekcję:

   # Utwórz próbną kolekcję
   users = {'Hans': 'aktywny', 'Éléonore': 'nieaktywny', '景太郎': 'aktywny'}

   # Strategia:  Iteracja po kopii
   for user, status in users.copy().items():
       if status == 'nieaktywny':
           del users[user]

   # Strategia:  Utwórz nową kolekcję
   active_users = {}
   for user, status in users.items():
       if status == 'aktywny':
           active_users[user] = status


4.3. Funkcja "range()"
======================

Jeśli potrzebujesz iterować po sekwencji liczb, przydatna jest
wbudowana funkcja "range()". Generuje ciągi arytmetyczne:

   >>> for i in range(5):
   ...     print(i)
   ...
   0
   1
   2
   3
   4

Podany punkt końcowy nigdy nie jest częścią generowanej sekwencji;
"range(10)" generuje 10 wartości, poprawne indeksy dla elementów
sekwencji o długości 10. Możliwe jest zacząć zakres od innej liczby
lub podać inne zwiększenie (nawet ujemne; czasem jest to nazywane
„krokiem”):

   >>> list(range(5, 10))
   [5, 6, 7, 8, 9]

   >>> list(range(0, 10, 3))
   [0, 3, 6, 9]

   >>> list(range(-10, -100, -30))
   [-10, -40, -70]

By przeiterować po indeksach sekwencji możesz połączyć "range()" i
"len()" w następujący sposób:

   >>> a = ['Wyszły', 'w', 'pole', 'kurki', 'trzy']
   >>> for i in range(len(a)):
   ...     print(i, a[i])
   ...
   0 Wyszły
   1 w
   2 pole
   3 kurki
   4 trzy

Jednak w większości takich przypadków wygodnie jest użyć funkcji
"enumerate()", patrz Techniki pętli.

Dzieje się dziwna rzecz jeśli po prostu wydrukujesz zakres:

   >>> range(10)
   range(0, 10)

Pod wieloma względami obiekt zwracany przez "range()" zachowuje się,
jakby był listą, ale w rzeczywistości nią nie jest. Jest obiektem,
który zwraca kolejne elementy żądanej sekwencji w trakcie twojego
iterowania po nim, lecz naprawdę nie tworzy listy, tak więc oszczędza
miejsce w pamięci komputera.

Mówimy, że taki obiekt to *iterable*, to znaczy odpowiedni jako cel
dla funkcji i konstrukcji, które spodziewają się czegoś, z czego można
pobierać kolejne elementy aż do wyczerpania zapasu. Widzieliśmy, że
instrukcja "for" jest takim konstruktem, podczas gdy przykładem
funkcji, która spodziewa się obiektu iterable jest "sum()":

   >>> sum(range(4))  # 0 + 1 + 2 + 3
   6

Później napotkamy więcej funkcji, które zwracają iterable i biorą
iterable jako argumenty. W rozdziale Struktury danych, omówimy
bardziej szczegółowo "list()".


4.4. Instrukcje "break" oraz "continue"
=======================================

Instrukcja "break" wyłamuje się z najbardziej wewnętrznej pętli "for"
lub "while":

   >>> for n in range(2, 10):
   ...     for x in range(2, n):
   ...         if n % x == 0:
   ...             print(f"{n} równa się {x} * {n//x}")
   ...             break
   ...
   4 równa się 2 * 2
   6 równa się 2 * 3
   8 równa się 2 * 4
   9 równa się 3 * 3

Instrukcja "continue" kontynuuje następną iterację pętli:

   >>> for num in range(2, 10):
   ...     if num % 2 == 0:
   ...         print(f"Znaleziono liczbę parzystą {num}")
   ...         continue
   ...     print(f"Znaleziono liczbę nieparzystą {num}")
   ...
   Znaleziono liczbę parzystą 2
   Znaleziono liczbę nieparzystą 3
   Znaleziono liczbę parzystą 4
   Znaleziono liczbę nieparzystą 5
   Znaleziono liczbę parzystą 6
   Znaleziono liczbę nieparzystą 7
   Znaleziono liczbę parzystą 8
   Znaleziono liczbę nieparzystą 9


4.5. Klauzule "else" na pętlach
===============================

W pętli "for" lub "while" instrukcja "break" może być sparowana z
klauzulą "else". Jeśli pętla zakończy się bez wykonania "break",
wykonana zostanie klauzula "else".

W pętli "for", klauzula "else" jest wykonywana po tym, jak pętla
zakończy swoją ostatnią iterację, czyli jeśli nie nastąpiło
przerwanie.

W pętli "while", klauzula wykonuje się ostatni raz po tym, jak warunek
pętli staje się fałszywy.

In either kind of loop, the "else" clause is **not** executed if the
loop was terminated by a "break".  Of course, other ways of ending the
loop early, such as a "return" or a raised exception, will also skip
execution of the "else" clause.

Jest to zilustrowane w poniższej pętli "for", która szuka liczb
pierwszych:

   >>> for n in range(2, 10):
   ...     for x in range(2, n):
   ...         if n % x == 0:
   ...             print(n, 'równa się', x, '*', n//x)
   ...             break
   ...     else:
   ...         # pętla przeszła całość nie znajdując dzielnika
   ...         print(n, 'jest liczbą pierwszą')
   ...
   2 jest liczbą pierwszą
   3 jest liczbą pierwszą
   4 równa się 2 * 2
   5 jest liczbą pierwszą
   6 równa się 2 * 3
   7 jest liczbą pierwszą
   8 równa się 2 * 4
   9 równa się 3 * 3

(Tak, to jest poprawny kod. Przyjrzyjcie się uważnie: klauzula "else"
należy do pętli "for", **nie** do instrukcji "if".)

Jednym ze sposobów myślenia o klauzuli else jest wyobrażenie sobie jej
w połączeniu z "if" wewnątrz pętli. Podczas wykonywania pętli będzie
ona wykonywać sekwencję if/if/if/else. Warunek "if" znajduje się
wewnątrz pętli, napotykany wiele razy. Jeśli warunek zostanie
kiedykolwiek spełniony, nastąpi "break". Jeśli warunek nigdy się nie
spełni, zostanie wykonana klauzula "else" spoza pętli.

W przypadku użycia z pętlą, klauzula "else" ma więcej wspólnego z
klauzulą "else" instrukcji "try" niż z instrukcją "if": klauzula
"else" instrukcji "try" działa, gdy nie wystąpi wyjątek, a klauzula
"else" pętli działa, gdy nie wystąpi "break". Więcej informacji na
temat instrukcji "try" i wyjątków można znaleźć w rozdziale Obsługa
wyjątków.


4.6. Instrukcje "pass"
======================

Instrukcja "pass" nie robi nic. Można jej użyć, gdy składnia wymaga
instrukcji a program nie wymaga działania. Na przykład:

   >>> while True:
   ...     pass  # Aktywne oczekiwanie na przerwanie z klawiatury (Ctrl+C)
   ...

Często jej się używa do tworzenia minimalnych klas:

   >>> class MyEmptyClass:
   ...     pass
   ...

Innym miejscem, w którym można użyć "pass" to placeholder dla funkcji
lub ciała warunku, kiedy pracujesz nad nowym kodem. Pozwoli ci to
myśleć na bardziej abstrakcyjnym poziomie. "pass" jest „po cichu”
ignorowane:

   >>> def initlog(*args):
   ...     pass   # Pamiętaj, aby to zaimplementować!
   ...


4.7. Instrukcje "match"
=======================

Instrukcja "match" bierze wyrażenie i porównuje jego wartość do
kolejnych wzorców podanych jako jeden lub więcej blok case. Jest to
powierzchownie podobne do instrukcji switch w C, Javie lub
JavaScripcie (i wielu innych językach), ale bardziej jest podobne do
pattern matchingu w takich językach jak Rust lub Haskell. Jedynie
pierwszy pasujący wzorzec jest zostaje wykonany i może on również
wydobywać komponenty (elementy sekwencji lub atrybuty obiektu) z
wartości do zmiennych.

Najprostsza forma porównuje wartość podmiotu z jednym lub wieloma
literałami:

   def http_error(status):
       match status:
           case 400:
               return "Niewłaściwe żądanie"
           case 404:
               return "Nie znaleziono"
           case 418:
               return "Jestem czajniczkiem"
           case _:
               return "Coś jest nie tak z internetem"

Zwróć uwagę na ostatni blok: „nazwa zmiennej” "_" zachowuje się jak
*dzika karta* i zawsze znajduje dopasowanie. Jeśli żaden z case'ów nie
będzie dopasowany, żadne z rozgałęzień nie będzie wykonane.

Możesz łączyć kilka literałów w jeden wzorzec używając "|" („or”):

   case 401 | 403 | 404:
       return "Niedozwolone"

Wzorce mogą wyglądać jak przypisania rozpakowujące i można ich używać
do powiązywania zmiennych:

   # punkt to dwukrotka (x, y)
   match point:
       case (0, 0):
           print("Początek")
       case (0, y):
           print(f"Y={y}")
       case (x, 0):
           print(f"X={x}")
       case (x, y):
           print(f"X={x}, Y={y}")
       case _:
           raise ValueError("Nie punkt")

Przyjrzyj się temu uważnie! Pierwszy wzorzec ma dwa literały i można o
nim myśleć jako o rozszerzeniu dosłownego wzorca pokazanego wyżej. Ale
następne dwa wzorce składają się z literału i zmiennej. Ta zmienna
*wiąże* wartość z podmiotu ("point"). Czwarty wzorzec wyłapuje dwie
wartości, co czyni go konceptualnie bliższym do przypisania z
„rozpakowaniem” "(x, y) = point".

Jeśli używasz klas, aby nadać strukturę swoim danym, możesz użyć nazwę
klasy oraz listę argumentów przypominającą konstruktor, ale z
możliwością wyłapania atrybutów w zmienne:

   class Point:
       def __init__(self, x, y):
           self.x = x
           self.y = y

   def where_is(point):
       match point:
           case Point(x=0, y=0):
               print("Początek")
           case Point(x=0, y=y):
               print(f"Y={y}")
           case Point(x=x, y=0):
               print(f"X={x}")
           case Point():
               print("Gdzieś indziej")
           case _:
               print("Nie punkt")

Możesz użyć parametrów pozycyjnych z jakimiś wbudowanymi klasami,
które posiadają kolejność dla atrybutów (na przykład dataclasses).
Możesz też określić wybrane pozycje dla atrybutów we wzorcach
ustawiając specjalny atrybut "__match_args__" w twoich klasach. Jeśli
jest on ustawiony na („x”, „y”), wszystkie następujące wzorce są
równoważne (i wszystkie powiązują atrybut "y" ze zmienną "var"):

   Point(1, var)
   Point(1, y=var)
   Point(x=1, y=var)
   Point(y=var, x=1)

Rekomendowanym sposobem na czytanie wzorców jest patrzenie na nie jako
na rozszerzoną formę tego, co wstawił(a)byś po lewej stronie
przypisania, aby zrozumieć które zmienne będą ustawione na co. Tylko
samodzielne nazwy (jak "var" powyżej) zyskują przypisanie w instrukcji
match. Nazwy z kropkami (jak "foo.bar"), nazwy atrybutów ("x=" i "y="
powyżej) lub nazwy klas (rozpoznawane przez „(…)” przy nich jak
"Point" powyżej) nie mają nigdy przypisań.

Wzory mogą być zagnieżdżane w dowolny sposób. Na przykład, jeśli mamy
krótką listę punktów, z dodanym "__match_args__", możemy dopasować ją
w następujący sposób:

   class Point:
       __match_args__ = ('x', 'y')
       def __init__(self, x, y):
           self.x = x
           self.y = y

   match points:
       case []:
           print("Brak punktów")
       case [Point(0, 0)]:
           print("Początek")
       case [Point(x, y)]:
           print(f"Pojedynczy punkt {x}, {y}")
       case [Point(0, y1), Point(0, y2)]:
           print(f"Dwa na osi Y na {y1}, {y2}")
       case _:
           print("Coś innego")

Możemy dodać klauzulę "if" do wzorca, nazywaną „guardem”. Jeśli guard
jest fałszywy, "match" próbuje dopasować następny blok case. Zwróć
uwagę, że przechwycenie wartości dzieje się zanim wyliczona jest
wartość guarda:

   match point:
       case Point(x, y) if x == y:
           print(f"Y=X at {x}")
       case Point(x, y):
           print(f"Nie na przekątnej")

Kilka innych ważnych funkcjonalności tej instrukcji:

* Podobnie do przypisań z „rozpakowywaniem”, wzorce z krotkami i
  listami mają to samo znaczenie i dopasowują się do dowolnych
  sekwencji. Ważnym wyjątkiem jest to, że nie dopasowują się do
  iteratorów i ciągów znaków.

* Wzorce sekwencji wspierają rozszerzone rozpakowywanie: "[x, y,
  *rest]" i "(x, y, *rest)" działają podobnie do przypisań z
  rozpakowywaniem. Nazwa po "*" może być również "_", aby "(x, y, *_)"
  dopasowywała się do sekwencji o conajmniej dwóch elementach bez
  powiązywania ze zmienną pozostałych elementów.

* Wzorce mapowania: "{"bandwidth": b, "latency": l}" przechwytuje
  wartości ""bandwidth"" i ""latency"" ze słownika. W przeciwieństwie
  do wzorców sekwencji, nadmiarowe klucze są ignorowane.
  Rozpakowywanie typu "**rest" jest również wspierane. (Ale "**_"
  byłoby nadmierne, więc jest niedozwolone.)

* Można przechwytywać podwzorce używając słowa kluczowego "as":

     case (Point(x1, y1), Point(x2, y2) as p2): ...

  przechwyci drugi element wejścia jako "p2" (jeśli wejście jest
  sekwencją dwóch punktów)

* Większość literałów jest porównywanych przez równość, lecz
  singletony "True", "False" i "None" są porównywane przez
  identyczność.

* Wzorce mogą używać nazwanych stałych. Trzeba je podawać po kropce,
  aby uniknąć interpretacji przechwycenia jako zwykłej zmiennej:

     from enum import Enum
     class Color(Enum):
         RED = 'red'
         GREEN = 'green'
         BLUE = 'blue'

     color = Color(input("Wprowadź swój wybór spośród 'red', 'blue' lub 'green': "))

     match color:
         case Color.RED:
             print("Widzę czerwone!")
         case Color.GREEN:
             print("Zielono mi")
         case Color.BLUE:
             print("Niebo jest niebieskie")

Bardziej szczegółowe wyjaśnienie i dodatkowe przykłady możesz znaleźć
w **PEP 636**, który jest napisany w formie tutoriala.


4.8. Definiowanie funkcji
=========================

Możemy stworzyć funkcję, która wypisuje ciąg Fibonacciego do wskazanej
granicy:

   >>> def fib(n):    # write Fibonacci series less than n
   ...     """Print a Fibonacci series less than n."""
   ...     a, b = 0, 1
   ...     while a < n:
   ...         print(a, end=' ')
   ...         a, b = b, a+b
   ...     print()
   ...
   >>> # Now call the function we just defined:
   >>> fib(2000)
   0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597

Słowo kluczowe "def" oznacza *definicję* funkcji. Po nim musi
następować nazwa funkcji oraz lista formalnych parametrów otoczona
nawiasami. Instrukcje, które stanowią ciało funkcji zaczynają się w
następnej linii i muszą być wcięte.

Opcjonalnie, pierwszy wiersz ciała funkcji może być gołym napisem
(literałem): jest to tzw. napis dokumentujący lub inaczej *docstring*.
(Więcej o docstringach znajdziesz w sekcji Napisy dokumentujące
(docstringi).) Istnieją pewne narzędzia, które używają docstringów do
automatycznego tworzenia drukowanej lub dostępnej online dokumentacji
albo pozwalają użytkownikowi na interaktywne przeglądanie kodu. Dobrym
zwyczajem jest pisane napisów dokumentacyjnych w czasie pisania
programu: spróbuj się do tego przyzwyczaić.

*Wykonanie* funkcji powoduje stworzenie nowej tablicy symboli
lokalnych używanych w tej funkcji. Mówiąc precyzyjniej: wszystkie
przypisania do zmiennych lokalnych funkcji powodują umieszczenie tych
wartości w lokalnej tablicy symboli. Odniesienia do zmiennych najpierw
szukają swych wartości w lokalnej tablicy symboli, potem w lokalnych
tablicach symboli funkcji otaczających, potem w globalnej, a dopiero
na końcu w tablicy nazw wbudowanych w interpreter. Tak więc, zmiennym
globalnym ani zmiennym w otaczających funkcjach nie można wprost
przypisać wartości w ciele funkcji (chyba, że zostaną wymienione w
niej za pomocą instrukcji "global" lub dla zmiennych w otaczających
funkcjach, wymienionych w instrukcji "nonlocal"), aczkolwiek mogą w
niej być używane (czytane).

Parametry (argumenty) wywołania funkcji wprowadzane są do lokalnej
tablicy symboli w momencie wywołania funkcji. Tak więc, argumenty
przekazywane są jej przez wartość (gdzie *wartość* jest zawsze
*odniesieniem* do obiektu, a nie samym obiektem). [1] Nowa tablica
symboli tworzona jest również w przypadku, gdy funkcja wywołuje inną
funkcję lub wywołuje się przez rekursję.

Definicja funkcji powiązuje nazwę funkcji z obiektem funkcji w
aktualnej tablicy symboli. Interpreter rozpoznaje obiekt wskazany tą
nazwą jako funkcję zdefiniowaną przez użytkownika. Inne nazwy też mogą
wskazywać na ten sam obiekt funkcji i mogą być używane, aby dostać się
do funkcji:

   >>> fib
   <function fib at 10042ed0>
   >>> f = fib
   >>> f(100)
   0 1 1 2 3 5 8 13 21 34 55 89

Przychodząc z innych języków, mógłbyś oponować, że "fib" nie jest
funkcją, ale procedurą, jako że nie zwraca wartości. Tak naprawdę
nawet funkcje bez instrukcji "return" zwracają wartość, chociaż dość
nudną. Tę wartość nazywamy "None" (to wbudowana nazwa). Wypisywanie
wartości "None" jest normalnie pomijane przez interpreter, jeśli
miałaby to jedyna wypisywana wartość. Możesz ją zobaczyć, jeśli bardzo
chcesz, używając "print()":

   >>> fib(0)
   >>> print(fib(0))
   None

Prosto można napisać funkcję, która zwraca listę numerów ciągu
Fibonnaciego zamiast go wyświetlać:

   >>> def fib2(n):  # Zwróć ciąg Fibonacciego do n
   ...     """Zwróć listę zawierającą ciąg Fibonacciego do n."""
   ...     result = []
   ...     a, b = 0, 1
   ...     while a < n:
   ...         result.append(a)    # Zobacz poniżej
   ...         a, b = b, a+b
   ...     return result
   ...
   >>> f100 = fib2(100)    # Wywołaj
   >>> f100                # Wypisz wynik
   [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89]

Ten przykład, jak zazwyczaj, prezentuje nowe cechy Pythona:

* Instrukcja "return" zwraca wartość funkcji. "return" bez wyrażenia
  podanego jako argument zwraca "None". Dojście do końca funkcji
  również zwraca "None".

* Instrukcja "result.append(a)" wywołuje *metodę* listy obiektów
  "result". Metoda to funkcja, która „należy” do obiektu i jest
  nazwana "obj.methodname", gdzie "obj" jest jakimś obiektem (może też
  być wyrażeniem) a "methodname" jest nazwą metody, które jest
  zdefiniowana przez typ obiektu. Różne typy definiują różne metody.
  Metody różnych typów mogą mieć te same nazwy bez powodowania
  dwuznaczności. (Da się definiować własne typy obiektów i metody,
  używając *klas*, patrz Klasy.) Metoda "append()" pokazana w
  przykładzie jest zdefiniowana dla listy obiektów; dodaje nowy
  element na końcu listy. W tym przykładzie jest równoważna "result =
  result + [a]", ale bardziej wydajna.


4.9. Więcej o definiowaniu funkcji
==================================

Można też definiować funkcje ze zmienną liczbą argumentów. Są trzy
sposoby, które można łączyć.


4.9.1. Domyślne wartości argumentów
-----------------------------------

Najbardziej przydatnym sposobem jest podanie domyślnej wartości dla
jednego lub więcej argumentów. Tworzy to funkcję, która może zostać
wywołana z mniejszą liczbą argumentów, niż jest podane w jej
definicji. Na przykład:

   def ask_ok(prompt, retries=4, reminder='Spróbuj ponownie!'):
       while True:
           reply = input(prompt)
           if reply in {'t', 'ta', 'tak'}:
               return True
           if reply in {'n', 'ni', 'nie'}:
               return False
           retries = retries - 1
           if retries < 0:
               raise ValueError('Niepoprawna odpowiedź użytkownika')
           print(reminder)

Tę funkcję można wywołać na kilka sposobów:

* podając tylko wymagany argument: "ask_ok('Czy na pewno chcesz
  wyjść?')"

* podając jeden z opcjonalnych argumentów: "ask_ok('OK nadpisać
  plik?', 2)"

* lub podając wszystkie argumenty: "ask_ok('OK nadpisać plik?', 2, 'No
  coś ty, tylko tak lub nie!')"

Ten przykład wprowadza słowo kluczowe "in". Sprawdza ono, czy
sekwencja zawiera szczególną wartość.

Wartości domyślne są ewaluowane w momencie definiowania funkcji w
scopie *defining*, więc

   i = 5

   def f(arg=i):
       print(arg)

   i = 6
   f()

wyświetli "5".

**Ważna uwaga**: Wartość domyślna jest wyliczana tylko raz. Ma to
znaczenie, gdy domyślna wartość jest obiektem mutowalnym takim jak
lista, słownik lub instancje większości klas. Na przykład następująca
funkcja akumuluje argumenty przekazane do niej w kolejnych
wywołaniach:

   def f(a, L=[]):
       L.append(a)
       return L

   print(f(1))
   print(f(2))
   print(f(3))

To wyświetli

   [1]
   [1, 2]
   [1, 2, 3]

Jeśli nie chcesz, żeby domyślna wartość była współdzielona pomiędzy
kolejnymi wywołaniami, możesz napisać funkcję w ten sposób:

   def f(a, L=None):
       if L is None:
           L = []
       L.append(a)
       return L


4.9.2. Argumenty nazwane
------------------------

Funkcje mogą być również wywoływane przy użyciu *argumentów nazwanych*
w formie "kwarg=value". Na przykład poniższa funkcja:

   def parrot(voltage, state='jest sztywna', action='fru', type='norweska błękitna'):
       print("-- Ta papuga nie zrobi", action, end=' ')
       print("nawet jeśli podłączę ją do", voltage, "woltów.")
       print("-- Śliczne upierzenie,", type)
       print("-- Ona", state, "!")

akceptuje jeden wymagany argument ("voltage") i trzy opcjonalne
argumenty ("state", "action" i "type"). Funkcja może być wywołana w
dowolny z poniższych sposobów:

   parrot(1000)                                          # 1 argument pozycyjny
   parrot(voltage=1000)                                  # 1 argument nazwany
   parrot(voltage=1000000, action='FRUUUUU')             # 2 argumenty nazwane
   parrot(action='FRUUUUU', voltage=1000000)             # 2 argumenty nazwane
   parrot('miliona', 'wyzionęła ducha', 'hop')         # 3 argumenty pozycyjne
   parrot('tysiąca', state='wącha kwiatki')  # 1 pozycyjny, 1 nazwany

ale wszystkie poniższe wywołania byłyby niepoprawne:

   parrot()                     # brakuje wymaganego argumentu
   parrot(voltage=5.0, 'zdechła')  # argument nie-nazwany za argumentem nazwanym
   parrot(110, voltage=220)     # zduplikowana wartość dla tego samego argumentu
   parrot(actor='John Cleese')  # nieznany argument nazwany

W wywołaniu funkcji argumenty nazwane muszą znajdować się za
argumentami pozycyjnymi. Wszystkie przekazane argumenty nazwane muszą
pasować do jednego argumentu akceptowanego przez funkcję (na przykład
"actor" nie jest poprawnym argumentem dla funkcji "parrot") a ich
kolejność nie ma znaczenia. Dotyczy to również nie-opcjonalnych
argumentów (na przykład "parrot(voltage=1000)" też jest poprawne).
Żaden argument nie może otrzymać wartości więcej niż raz. Tutaj jest
przykład, który się nie powiedzie z powodu tego ograniczenia:

   >>> def function(a):
   ...     pass
   ...
   >>> function(0, a=0)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: function() got multiple values for argument 'a'

Kiedy ostatni formalny parametr ma postać "**nazwa", to otrzymuje on
słownik (zobacz Mapping Types --- dict) zawierający wszystkie
argumenty kluczowe oprócz tych które odpowiadają formalnym parametrom.
Może to być połączone z formalnym parametrem o postaci "*nazwa"
(opisanym w następnej subsekcji) który otrzymuje  krotkę zawierającą
argumenty pozycyjne z poza listy formalnych parametrów.  ("*nazwa"
musi występować przed "**nazwa".) Dla przykładu możemy zdefiniować
funkcję tak:

   def cheeseshop(kind, *arguments, **keywords):
       print("-- Czy jest może", kind, "?")
       print("-- Przykro mi, nie mamy już sera", kind)
       for arg in arguments:
           print(arg)
       print("-" * 40)
       for kw in keywords:
           print(kw, ":", keywords[kw])

Można ją wywołać w ten sposób:

   cheeseshop("limburger", "Jest bardzo płynny, proszę pana.",
              "Naprawdę jest bardzo, BARDZO płynny, proszę pana.",
              shopkeeper="Michael Palin",
              client="John Cleese",
              sketch="Sklep z serami")

i oczywiście wyświetli się nam:

   -- Czy jest może limburger ?
   -- Przykro mi, nie mamy już sera limburger
   Jest bardzo płynny, proszę pana.
   Naprawdę jest bardzo, BARDZO płynny, proszę pana.
   ----------------------------------------
   shopkeeper : Michael Palin
   client : John Cleese
   sketch : Sklep z serami

Zwróć uwagę, że kolejność w jakim argumenty kluczowe są wyświetlane
dokładnie odpowiada kolejności w jakim zostały one podane w wywołaniu
funkcji.


4.9.3. Parametry specjalne
--------------------------

Domyślnie, argumenty mogą być przekazywane do funkcji Pythona albo
poprzez swoje umiejscowienie (pozycję)  albo wprost poprzez słowo
kluczowe. Dla czytelności oraz wydajności, ma sens aby ograniczyć
sposób w jaki argumenty mogą być przekazywane tak aby deweloper musiał
tylko spojrzeć na definicję funkcji aby zrozumieć czy argumenty są
przekazywane poprzez pozycję, pozycję i słowa kluczowe czy same słowa
kluczowe.

Definicja funkcji może wyglądać w ten sposób:

   def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):
         -----------    ----------     ----------
           |             |                  |
           |        pozycyjny lub nazwany   |
           |                                - tylko nazwany
            -- tylko pozycyjne

gdzie "/" i "*" są opcjonalne. Jeśli są używane, te symbole wskazują
rodzaj parametru poprzez sposób przekazywania argumentów do funkcji:
tylko pozycyjne, pozycyjne lub słowo kluczowe i tylko słowo kluczowe.
Parametry słów kluczowych są również nazywane parametrami nazwanymi.


4.9.3.1. Argumenty pozycyjne-lub-nazwane
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Jeśli "/" czy "*" nie są obecne w definicji funkcji, argumenty mogą
być przekazywane i poprzez pozycje i przez słowa kluczowe.


4.9.3.2. Parametry tylko-pozycyjne (positional-only)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Wchodząc w szegóły, można oznaczyć pewne parametry jako *tylko-
pozycyjne*. Jeśli argument jest *tylko pozycyjny*, ma znaczenie
kolejność parametrów a parametry te nie mogą być przekazywane przez
słowa kluczowe. Argumenty tylko-pozycyjne są umieszczane przed "/"
(forward-slash).  "/" rozdziela parametry tylko-pozycyjne od reszty
parametrów. Jeśli w definicji funkcji nie ma "/" , nie ma parametrów
tylko pozycyjnych.

Argumenty po "/" mogą mieć charakter *pozycja-lub-słowo-kluczowe* lub
*tylko-kluczowe*.


4.9.3.3. Argumenty tylko-nazwane (keyword-only)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Aby oznaczyć parametry jako *tylko słowa kluczowe* (*keyword-only*),
to znaczy wskazać, że parametry muszą być przekazywane przez argument
słowa kluczowego, umieść "*"  na liście argumentów tuż przed pierwszym
parametrem, który ma być przekazywany tylko przez słowo kluczowe.


4.9.3.4. Przykłady funkcji
~~~~~~~~~~~~~~~~~~~~~~~~~~

Rozważ następujące przykłady definicji funkcji, przykładając
szczególną uwagę do oznaczeń  "/" oraz "*":

   >>> def standard_arg(arg):
   ...     print(arg)
   ...
   >>> def pos_only_arg(arg, /):
   ...     print(arg)
   ...
   >>> def kwd_only_arg(*, arg):
   ...     print(arg)
   ...
   >>> def combined_example(pos_only, /, standard, *, kwd_only):
   ...     print(pos_only, standard, kwd_only)

Pierwsza z definicji, "standard_arg", mająca najbardziej znaną formę,
nie wprowadza ograniczeń na sposób wywoływania, argumenty mogą być
przekazywane poprzez pozycję lub słowo kluczowe:

   >>> standard_arg(2)
   2

   >>> standard_arg(arg=2)
   2

Druga z funkcji "pos_only_arg" jest ograniczona do wykorzystywania
tylko argumentów pozycyjnych jako, że posiada "/" w swojej definicji:

   >>> pos_only_arg(1)
   1

   >>> pos_only_arg(arg=1)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: pos_only_arg() got some positional-only arguments passed as keyword arguments: 'arg'

The third function "kwd_only_arg" only allows keyword arguments as
indicated by a "*" in the function definition:

   >>> kwd_only_arg(3)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: kwd_only_arg() takes 0 positional arguments but 1 was given

   >>> kwd_only_arg(arg=3)
   3

Ostatnia z funkcji wykorzystuje wszystkie trzy konwencje w swojej
definicji:

   >>> combined_example(1, 2, 3)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: combined_example() takes 2 positional arguments but 3 were given

   >>> combined_example(1, 2, kwd_only=3)
   1 2 3

   >>> combined_example(1, standard=2, kwd_only=3)
   1 2 3

   >>> combined_example(pos_only=1, standard=2, kwd_only=3)
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: combined_example() got some positional-only arguments passed as keyword arguments: 'pos_only'

Na koniec, przyjrzyj się definicji funkcji, która ma potencjalną
kolizję pomiędzy pozycyjnym argumentem "name"  a "**kwds" gdzie "name"
jest kluczem:

   def foo(name, **kwds):
       return 'name' in kwds

Nie jesteśmy w stanie sprawić, aby ta funkcja zwróciła "True", jako że
słowo kluczowe "'name'" zawsze powiązane będzie z pierwszym
parametrem. Dla przykładu:

   >>> foo(1, **{'name': 2})
   Traceback (most recent call last):
     File "<stdin>", line 1, in <module>
   TypeError: foo() got multiple values for argument 'name'
   >>>

Korzystając z "/" (argumentów tylko-pozycyjnych), da się to zrobić
ponieważ pozwala ono na "name" jako argument pozycyjny i  "'name'"
jako klucz w argumentach kluczowych:

   >>> def foo(name, /, **kwds):
   ...     return 'name' in kwds
   ...
   >>> foo(1, **{'name': 2})
   True

Innymi słowy, nazwy w argumentach pozycyjnych mogą być bez
dwuznaczności wykorzystywane jako klucze w "**kwds".


4.9.3.5. Podsumowanie
~~~~~~~~~~~~~~~~~~~~~

Przypadki użycia determinują, jakich typów parametrów kiedy w
definicji funkcji:

   def f(pos1, pos2, /, pos_or_kwd, *, kwd1, kwd2):

Kieruj się następującym:

* Wykorzystuj argumenty tylko-pozycyjne gdy chcesz aby nazwa
  parametrów nie była dostepna dla użytkownika. To może być pomocne
  gdy nazwy parametów nie mają specjalnego znaczenia a ty chcesz
  wymusić określoną kolejność argumentów gdy funkcja jest wywoływana.
  Ewentualnie gdy chcesz aby część argumentów była pozycyjna a część
  była jakimiś słowami kluczowymi.

* Wykorzystuj argumenty tylko-kluczowe gdy nazwy mają znaczenie i
  definicja funkcji jest łatwiejsza do zrozumienia poprzez wprost
  podawanie nazw, lub jeśli nie chcesz aby użytkownicy polegali na
  kolejności argumentów.

* W przypadku budowania API, wykorzystanie argumentów tylko-
  pozycyjnych pozwoli w przyszłości uniknąć problemów, gdy nazwa
  parametru jest zmieniona.


4.9.4. Arbitralne listy argumentów
----------------------------------

Najmniej często wykorzystywaną opcją jest specyfikowanie, że funkcja
może być wywoływana z arbitralną liczbą argumentów. Takie argumenty
zostaną opakowane w krotkę (zobacz krotki). Przed zmienną liczbą
argumentów, można wymusić jeden lub więcej argumentów.

   def write_multiple_items(file, separator, *args):
       file.write(separator.join(args))

Zazwyczaj takie argumenty będą ostatnie na liście formalnych
parametrów, ponieważ zbierają one wszystkie pozostałe argumenty
przekazane funkcji. Każdy formalny argument po parametrze "*args" może
być  'tylko-kluczowy', to znaczy da się go wprowadzić tylko poprzez
słowo kluczowe a nie przez pozycję.

   >>> def concat(*args, sep="/"):
   ...     return sep.join(args)
   ...
   >>> concat("ziemia", "mars", "wenus")
   'ziemia/mars/wenus'
   >>> concat("ziemia", "mars", "wenus", sep=".")
   'ziemia.mars.wenus'


4.9.5. Rozpakowywanie listy argumentów
--------------------------------------

Odwrotna sytuacja wystąpi, gdy argumenty już są listą albo krotką a
muszą być rozpakowane gdyż funkcja wymaga argumentów przekazanych
pozycyjnie, jeden po drugim .  Dla przykładu, wbudowana funkcja
"range()" oczekuje oddzielnych argumentów *start* oraz *stop*. Jeśli
nie są dostępne oddzielnie, wywołaj funkcją z operatorem  "*" aby
wypakować argumenty z listy lub krotki:

   >>> list(range(3, 6))            # zwykłe wywołanie z osobnymi argumentami
   [3, 4, 5]
   >>> args = [3, 6]
   >>> list(range(*args))            # wywołanie z argumentami rozpakowanymi z listy
   [3, 4, 5]

W podobny sposób, słowniki moga dostarczać argumentów kluczowych
poprzez operator "**":

   >>> def parrot(voltage, state='sztywna', action='fru'):
   ...     print("-- Ta papuga nie zrobi", action, end=' ')
   ...     print("nawet jeśli podłączę ją do", voltage, "woltów.", end=' ')
   ...     print("Jest", state, "!")
   ...
   >>> d = {"voltage": "czterech milionów", "state": "sztywna jak kłoda", "action": "FRUU"}
   >>> parrot(**d)
   -- Ta papuga nie zrobi FRUU nawet jeśli podłączę ją do czterech milionów woltów. Jest sztywna jak kłoda !


4.9.6. Wyrażenia Lambda
-----------------------

Niewielkie anonimowe funkcje mogą byś tworzone z wykorzystaniem słowa
kluczowego  "lambda". Wykorzystując tą notację: "lambda a, b: a+b"
powstanie funkcja sumująca podane argumenty. Funkcje lambda mogą być
wykorzystywane zawsze wtedy gdy potrzebne są objekty funkcji. Są
synaktycznie ograniczone do jednego wyrażenia. Semantycznie, jest to
tylko lukier składniowy normalnej definicji funkcji. Podobnie jak
funkcje zagnieżdżone funkcje lambda mogą odwoływać się do zmiennych z
otaczającego zakresu

   >>> def make_incrementor(n):
   ...     return lambda x: x + n
   ...
   >>> f = make_incrementor(42)
   >>> f(0)
   42
   >>> f(1)
   43

Powyższy przykład wykorzystuje ekspresję lambda aby zwrócić funkcję.
Inne wykorzystanie to przekazanie małej funkcji jako argumentu:

   >>> pairs = [(1, 'jeden'), (2, 'dwa'), (3, 'trzy'), (4, 'cztery')]
   >>> pairs.sort(key=lambda pair: pair[1])
   >>> pairs
   [(4, 'cztery'), (2, 'dwa'), (1, 'jeden'), (3, 'trzy')]


4.9.7. Napisy dokumentujące (docstringi)
----------------------------------------

Oto kilka konwencji dotyczących zawartości i formatowania docstringów.

Pierwsza linijka powinna zawierać zwięzłe streszczenie sensu jaki stoi
za obiektem. Nie powinna wprost zawierać nazwy obiektu ani typu, gdyż
te są dostępne w inny sposób (chyba, że nazwa jest czasownikiem
opisujęcym działanie funkcji). Ta linijka powinna zaczynać się z dużej
litery i kończyć kropką.

Jeśli w docstringu jest więcej niż jedna linijka, druga linijka
powinna być pusta, aby rozdzielić ją od reszty opisu. Następne linijki
powinny zawierać konwencje nazewnicze, efekty uboczne itd.

Parser Pythona nie usuwa wcięć z literału stringu wielolinijkowego,
więc jeśli to jest pożądane, to narzędzia obrabiające dokumentację
powinny usuwać wcięcia. Robi się to, wykorzystując następujące.
Pierwsza nie-pusta linijka *po* pierwszej określa jak dużo wcięcia
jest w całym docstringu. (Nie można do tego wykorzystać pierwszej
linijki, ponieważ ta zazwyczaj przylega do cudzysłowów, więc sposób
wcięcia jest nieoczywisty.) "Ilość wcięcia" z tej linijki jest
następnie usuwana z tej i wszystkich następnych linijek. W kolejnych
linijkach nie powinno być mniej wcięć ale jeśli tak będzie to całość
wcięcia powinna być usunięta. Ilość wcięcia powinna być usuwana po
zamianie tabulatorów na spacje (zazwyczaj na 8 spacji).

Poniżej przykład wielolinijkowego docstringu:

   >>> def my_function():
   ...     """Nie robi nic, ale dokumentuje to.
   ...
   ...     Nie, naprawdę, ona nic nie robi.
   ...     """
   ... pass
   ...
   >>> print(my_function.__doc__)
   Nie robi nic, ale dokumentuje to.

       Nie, naprawdę, ona nic nie robi.


4.9.8. Adnotacje funkcji
------------------------

Adnotacje funkcji to całkowicie opcjonalne metadane dające informacje
o funkcjach zdefiniowanych przez użytkowników (zobacz **PEP 3107**
oraz **PEP 484** aby uzyskać więcej informacji).

*Adnotacje* przechowywane są w atrybucie "__annotations__" funkcji
jako słownik i nie mają oprócz żadnego innego wpływu na nią.
Adnotacje parametrów są definiowane po nazwie parametru i dwukropku,
jako wyrażenie sprawdzające wartość argumentu.  Adonotacje do
zwracanego wyniku  definiuje się pomiędzy nawiasem z listą parametrów
a drukropkiem kończącym instrukcję "def" , poprzez literał "->", z
następującym po nim wyrażeniem.  Poniższy przykład ma adnotację
dotyczącą argumentu wymaganego, argumentu opcjonalnego oraz adnotację
zwracanego wyniku:

   >>> def f(ham: str, eggs: str = 'jajka') -> str:
   ...     print("adnotacje:", f.__annotations__)
   ...     print("argumenty:", ham, eggs)
   ...     return ham + ' i ' + eggs
   ...
   >>> f('szynka')
   adnotacje: {'ham': <class 'str'>, 'return': <class 'str'>, 'eggs': <class 'str'>}
   argumenty: szynka jajka
   'szynka i jajka'


4.10. Intermezzo: Styl kodowania
================================

Teraz, kiedy już jesteś gotowa(-wy) aby pisać dłuższe, bardziej
złożone pythonowe dzieła, dobrze żebyśmy porozmawiali o *stylu
kodowania*. Większość języków może być pisana (a mówiąc precyzyjniej,
*formatowana*) w różnym stylu; bardziej lub mniej czytelnym. Zawsze
dobrze jest dążyć, aby twój kod był łatwy do czytania przez innych a w
tym bardzo pomaga stosowanie fajnego stylu kodowania.

Dla Pythona **PEP 8** stał się wzorcem stylu, którego trzyma się
większość projektów; szerzy czytelny i miły dla oka styl kodowania. W
którymś momencie, powinien go przeczytać każdy developer Pythona,
poniżej przedstawiliśmy jego najistotniejsze elementy:

* Jako wcięcie, wykorzystuj cztery spacje, nie tabulator.

  Cztery spacje są dobrym kompromisem pomiędzy płytkim wcięciem
  (pozwala na więcej kroków zagnieżdżania ) a głębokim wcięciem (jest
  łatwiejsze do przeczytania). Tabulatorów najlepiej nie używać,
  wprowadzają zamiesznie.

* Zawijaj linie tak, aby nie ich długość nie przekraczała 79 znaków.

  To pomoże użytkownikom z małymi wyświetlaczami a na większych
  ekranach pozwoli mieć kilka plików obok siebie na ekranie.

* Wstawiaj puste linie aby oddzielić od siebie funkcje, klasy lub
  większe bloki kodu wewnątrz funkcji.

* Jeśli jest to możliwe, wstawiaj komentarze w oddzielnej linii.

* Wykorzystuj docstringi

* Korzystaj ze spacji naokoło operatorów oraz za przecinkami, ale nie
  przy nawiasach:   "a = f(1, 2) + g(3, 4)".

* Nazywaj klasy i funkcje w konsekwentny sposób. Preferowaną konwencją
  jest "UpperCamelCase" dla nazw klas i "lowercase_with_underscores"
  dla funkcji i metod. Zawsze wykorzystuj "self" jako nazwę dla
  pierwszego argumentu metody (zobacz Pierwsze spojrzenie na klasy aby
  dowiedzieć się więcej o klasach i metodach).

* Nie wykorzystuj wymyślnych zestawów znaków jeśli twój kod będzie
  wykorzystywany międzynarodowo. Najlepiej trzymać się domyślnych w
  Pythonie: UTF-8 lub nawet ASCII.

* Podobnie, nie korzystaj ze znaków innych niż ASCII jako
  identyfikatorów, jeśli jest chociaż szansa, że osoby mówiące innym
  językiem będą czytać lub rozwijać Twój kod.

-[ Przypisy ]-

[1] Tak właściwie, *wywołanie przez referencję do obiektu* byłoby
    lepszym opisem, jako że jeśli mutowalny obiekt jest przekazany,
    wszystkie zmiany które wywoływany robi (elementy wstawiane na
    listę) będą widziane przez wywołującego.
