.. _Pythonalia:
Pythone to i owo
-----------------
Wykład zacząłem od stwierdzenia, że zmienne pojawiają się w każdym języku programowania. W kolejnych rozdziałach opisałem pętle, instrukcje warunkowe i inne pojęcia, które również są obecne w różnych językach programowania. Python ma jednak swoje *specyficzne* konstrukcje, które nadają specyficzny charakter programom w Pythonie.
.. _args:
``*args`` - zmienna liczba argumentów
""""""""""""""""""""""""""""""""""""""
Mam nadzieję, że wykorzystujesz już funkcje w swoich programach. To bardzo ważne - funkcje pozwalają na lepsze zorganizowanie kodu. Z czasem coraz większa część Twojego programu będzie "schowana" w funkcjach; sekcja ``__main__`` bedzie je tylko wywoływać. Główną wadą funkcji jest to, że trzeba ją wywołać dokładnie tak, jak została zadeklarowana. Jako przykład niech posłuży funkcja obliczająca długość wektora ``norm()``:
.. raw:: html
Powyższa funkcja oblicza długość wektora (x,y) z przestrzeni dwuwymiarowej, korzystając ze wzoru: :math:`n = \sqrt(x^2 + y^2)`. Zapewne chcielibyśmy mieć też odpowiednią funkcję dla trzech wymiarów obliczającą :math:`n = \sqrt(x^2 + y^2 + z^2)`, czyli ``norm(x,y,z)``. Napiszmy zatem:
.. raw:: html
Okazuje się jednak, że druga funkcja nadpisała tą pierwszą! Funkcja dwuargumentowa zniknęła! Po uruchomieniu tego programu zobaczysz komunikat:
.. code-block:: bash
Traceback (most recent call last):
module script_28 line 9
print(norm(2.3,-1.2))
TypeError: norm() missing 1 positional argument: z
.. admonition:: Uwaga
:class: def
W skrypcie w języku Python może istnieć tylko jedna funkcja o danej nazwie.
Jak obejść to ograniczenie?
- można mieć dwa oddzielne programy, jeden do obliczeń w dwóch a drugi w trzech wymiarach; w każdym z nich funkcje będą niezależne i będą mogły mieć takie same nazwy
- stworzyć dwie funkcje o róznych nazwach, np. ``norm2()`` oraz ``norm3()``
- stworzyć :ref:`własne klasy `: oddzielnie dla dwóch i trzech wymiarów; dwie różne klasy mogą mieć bowiem metody o identycznych nazwach
- stworzyć :ref:`własne moduły `, które w tym kontekście działają jak klasy
- zastosować funkcje o *zmiennej liczbie argumentów*
Poniższy przykład ilustruje to ostatnie podejście:
.. raw:: html
Zauważ, że tym razem lista argumentów funkcji zawiera ``*args``. Tak, ta gwiazdka jest bardzo ważna, bez niej nie będzie to działać. Oznacza ona *'rozpakowaną listę'*: w definicji funkcji pojawia się ``*args`` a w środku funkcji zaś dostajesz listę o nazwie ``args``. Dzięki temu funkcja może policzyć długość wektora ze wzoru:
.. math::
n = \sqrt(\sum_{i=1}^{k} x_i^2)
gdzie :math:`k` to wymiar przestrzeni (liczba współrzędnych wektora). W wywołaniu funkcji jednak nie korzystamy z listy, tylko zwyczajnie wymieniamy argumenty funkcji po przecinku. Listę argumentów *zwyczajowo* nazywamy ``args``, choć to akurat nie jest obowiązkowe; mozna użyć dowolnej nazwy.
Jak już wspomniałem, w Pythonie nie możemy zadeklarować dwóch różnych funkcji o takiej samej nazwie. Czasem ``*args`` jest jedynym możliwym rozwiązaniem. Na poprzednim wykładzie
:ref:`deklarowaliśmy własne klasy `. Klasa ta miała **konstruktor** czyli specjalną metodę o nazwie ``__init__()``. Konstruktor *musi* nazywać się dokładnie tak; nie możemy stworzyć dwóch różnych konstruktorów, deklarując metody ``__init1__()`` oraz ``__init2__()``. Powróćmy zatem do przykładu `klasy Vec3 <#vec3>`__ i rozbudujmy konstruktor tej klasy o nowe możliwości. Chcielibyśmy, aby przyjmował on:
- nic - czyli pustą listę parametrów; w takim przypadku wszystkie współrzędne wektora zostaną zainicjowane zerami
- jeden argument, np ``c`` - w takim przypadku ``x, y, z = c, c, c``
- trzy argumenty - wartości składowych wektora
.. raw:: html
Powyższy przykład pokazuje też, że można łączyć argumenty "*wymagane*", takie jak ``self`` oraz argumenty dodatkowe. Funkcja ``__init__()`` wymaga podania co najmniej jednego argumentu, czyli ``self``, wszystkie inne podane wartości zostaną wstawione do listy ``*args``. A co by było w przypadku, gdybyśmy chcieli dodać jeszcze jeden wymagany parametr? Przypuśćmy że każdy wektor musi mieć swój identyfikator (``id``) - liczbę nadawaną przez użytkownika w trakcie tworzenia wektora. Wtedy wymieniamy dodatkowy argument na liście argumentów funkcji *przed* ``*args``. Definicja funkcji wygląda teraz nastepująco: ``def __init__(self, id, *params)``. Zauważ też, że zamiast ``*args`` pojawiło się ``*params``, co jednak nie wpływa na działanie programu - lista przechowująca argumenty wolne może się nazywać dowolnie, *liczy się tylko gwiazdka*
.. raw:: html
Owa *gwiazdka* zwana jest czasem *operatorem rozpakowania*. Może ona bowiem rozpakować dowolną listę i zamienic je na wartości, na przykład:
.. raw:: html
Widać, że o ile pierwsza instrukcja ``print()`` drukuje *listę wartości*, to druga już tylko te wartości. Druga instrukcja ``print()`` jest więc równoważna trzeciej, czyli ``print(1,2,3,4,5)``
.. admonition:: Uwaga
:class: def
Argumenty dodatkowe ``*args`` muszą być wymieniowne w definicji funkcji **za** argumentami wymaganymi
``**kwargs`` - argumenty nazwane
""""""""""""""""""""""""""""""""""""
Język Python oferuje jeszcze jedno udogodnienie przy tworzeniu funkcji: *argumenty nazwane*. Aby przekazać dodatkowe parametry, piszemy w wywołaniu funkcji ``nazwa=wartosc``. Konstrukcja ta pojawiła się już na pierwszym wykładzie, przy okazji `drukowania końca linii <#print_end>`__ instrukcją ``print()``. Właśnie dzięki ``**kwargs`` mogliśmy napisać ``print(zmienna,end="")`` albo ``print(zmienna,end=",")``. Argumenty nazwane bardzo często stosujemy, aby dostarczyć funkcji dodatkowe parametry, które zmieniają jej działanie. Jeżeli użytkownik nie podał ``**kwargs``, funkcja wykorzystuje domyślne wartości parametrów.
Jak wprowadzić ``**kwargs`` do swojej funkcji? Jak stworzyć funkcję, która wykorzystuje ``**kwargs``? Należy zadeklarować je jako ostatni element w definicji funkcji:
.. raw:: html
Na przykładzie tym widzimy, że:
- wewnątrz funkcji używamy ``kwargs`` choć w deklaracji funkcji było ``**kwargs``
- ``kwargs`` jest po prostu słownikiem, który :ref:`omówiliśmy wcześniej `
- ``kwargs`` przypominają nieco :ref:`wartości domyślne argumentów funkcji `: jeżeli użytkownik je podał, do korzystamy z dodatkowych wartości; jeżeli nie, to nic się nie stało
- niestety użytkownik, który wywołuje funkcję korzystającą z ``**kwargs`` musi znać nazwy parametrów; w tym konkretnym przypadku wywołania funkcji: ``scal_napisy("zupa", "drugie", separator=",")`` albo ``scal_napisy("zupa", "drugie", separ=",")`` nie zadziałają.
Funkcja, która scala tylko dwa napisy jest mało użyteczna. Możemy ją jednak łatwo usprawnić, korzystając z ``*args``:
.. raw:: html
.. admonition:: Uwaga
:class: def
Argumenty nazwane ``*kwargs`` muszą być wymieniowne **jako ostatnie** w definicji funkcji, a więc **za** argumentami wymaganymi oraz **za** argumentami dowolnymi, o ile takie są.
.. admonition:: Podsumowanie
Najbardziej ogólna postać definicji funkcji to ``def superfunkcja(arg1, arg2, *args, **kwargs)``. Użytkownik *musi* podać co najmniej dwa argumenty, inaczej będzie błąd. Kolejne argumenty trafią do listy ``args``, argumenty nazwane zaś do słownika ``kwargs``.
Dynamiczne sprawdzanie typu : ``isinstance``
"""""""""""""""""""""""""""""""""""""""""""""
Funkcja w Pythonie nie wie, jakiego typu argumenty dostała. Okazuje się jednak, że Python umożliwia sprawdzenie, jakiego typu jest zmienna. Sprawdzenie to nazwiemy *dynamicznym*, jego wynik zależy od aktualnej wartości zmiennych. Służy do tego funkcja ``isinstance(co, typ)``. Pierwszym jej argumentem jest badana zmienna a drugim nazwa typu, na przykład:
.. raw:: html
Z przykładu tego jasno wynika, że ``9.0`` nie jest typu ``int`` (linia 5). Instrukcja ``isinstance()`` szczególnie przydaje się do odróżniania czy wartość jest naprawdę wartością czy też może listą wartości.
Tak dla przykładu: funkcja scalająca napisy będzie znacznie wygodniejsza w użyciu, gdy można podać jej listę napisów jako argument:
.. raw:: html
Powyższa funkcja dla każdego argumentu sprawdza, czy jest listą. Jeżeli tak, to ją rozpakowuje i rekurencyjnie wywołuje samą siebie dla rozpakowanych argumentów. Aktualnie funkcja może przyjąć dowolnie wiele napisów a także list zawierających napisy. Najważniejsze jest jednak to, że wciąż działa dla dwóch argumentów, jak działała na początku tego rozdziału.
Wyjątki
"""""""""""""""""""""""""""""""""""""""""""""
Czasami w życiu programu zdarzy się sytuacja wyjątkowa, np. dzielenie przez 0 lub wykroczenie poza zakres tablicy.
Sytuacje te skutkują wypisaniem komunikatu na ekran i natychmiatowym zakończeniem programu. Oto prosty przykład:
.. raw:: html
Czy można taki wyjątek jakoś "*przeżyć*" i iść dalej? Owszem można, o ile ów potencjalnie kłopotliwy fragment programu
zamknięty zostanie w bloku ``try`` ... ``except``:
.. raw:: html
Dzięki przejęciu wyjątku program biegnie do końca. Straciliśmy jednak informację o błędzie, która poprzednio pojawiała się na ekranie!
Przejmując wyjątek musimy sami ją wydrukować, np tak:
.. raw:: html
W powyższym przykładzie tworzymy zmienną ``e`` która jest obiektem klasy ``Exception``, która jest klasą bazową
dla wyjątków w Pytonie. Tak więc ``IOError`` czy ``ZeroDivisionError`` również jest typu ``Exception``
i zostałyby przejete w jednym i tym samym bloku ``except``. Niekiedy chcemy rozdzielić obsługę wyjątków
dla poszczególnych przypadków. Możemy to zrobić jak poniżej:
.. raw:: html
.. admonition:: Uwaga
:class: def
Celem wyjątków jest poradzenie sobie z nieprzewidzianym problemem, który spowoduje `błąd wykonania programu` w czasie jego działania.
Wyjątek nie pomoże w przypadku błędu składniowego, np ``for k range(10)`` (gdzie brakuje słowa kluczowego ``in``).
Wyjątek nie wykryje też błędu w algorytmie, np ``średnia_z_5 = (1.2 + 1.3 + 0.1 + 2.1 +0.8) / 4.0`` (błędnie policzona średnia)