# Wektory w Pythonie (klasy i obiekty) ## Klasy i Wektory ### Struktura programu w Pythonie Program w Pythonie w przeciwieństwie do C++ (i generalnie języków kompilowanych) nie składa się ze zbioru definicji (klas, funkcji, zmiennych) oraz jednego punktu startowego (funkcja main). Cały program jest serią instrukcji wykonywanych w kolejności takiej, jak w pliku. Czy w takim razie nie ma funkcji? Popatrzmy jak wygląda instrukcja `if`: ```py if moja_zmienna <= 10: print("Za mało") else: print("Wystarczy") ``` Wykonanie konstrukcji `if ... else ...` wcale nie powoduje wykonania całego kodu, który tam jest napisany - wykonane zostanie tylko jedno `print` zależenie od wartości zmiennej `moja_zmienna`. Podobnie definicja funkcji: ```py def nazwa_funkcji(argument1, argument2): print("Wykonuję nazwa_funkcji") print(f" argument1={argument1}") print(f" argument1={argument2}") print(" wykonano!") ``` Nie powoduje wykonania wszystkich wymienionych instrukcji, ale przypisania ich do podanej nazwy, czyli właściwie utworzenia zmiennej `nazwa_funkcji`, której wartością są instrukcje do wykonania. :::{important} W Pythonie (prawie) wszystko jest zmienną. ::: Nie wierzysz? Sprawdź: ```py old_print = print def lol_print(*args, *kwargs): old_print(*args, **args) old_print("LOL!") print = lol_print print("Hello, world!") ``` Jest tylko niewielki zbiór słów, których nie można użyć do nazywania zmiennych, które zawsze będą miały swoje specjalne znaczenie, np.: `if`, `elif` (odpowiednik `else if`), `else`, `def`, `class`, `while`, `for`, `import`. Mimo wszystko programy w Pythonie wyglądają podobnie do programów w innych językach: 1. Dołączając do programu biblioteki (polecenie `import`). 2. Definiując własne klasy... 3. ...i własne funkcje. 4. Pisząc kod używający tych bibliotek, klas i funkcji. ```python import lib1 class Klasa1: def metoda1(self, ): ... def metoda2(self, arg1, arg2): ... print("Hello!") class Klasa2: def metoda1(self, arg1): ... def funkcja1(arg1, arg2): ... print("I'm here") funkcja1() print("Bye") ``` :::{important} Do grupowania instrukcji w Pythonie służą wcięcia (spacje na początku linijek). Nie ma on zatem wąsatych nawiasów (`{}`; to odpowiednik z C++), ani `begin ... end` (to odpowiednik z Pascala). Instrukcje wchodzące w skład funkcji `funkcja1` są "wypisane pod tą funkcją`; podobnie funkcje wewnątrz klas i instrukjce w ich skład wchodzące. ![Ilustracja wcięć do definiowania struktury programu](static/Gemini_generated/python_indents.png) ::: ### C++ a Python Z grubsza równoważne programy w C++ i Pythonie: ![Python vs CPP próbka kodu](static/py_vs_cc.png) |Co?|C++|Python| |-|-|-| |Grupowanie instrukcji|klamry - `{ ... }`|`:` i wcięcia| |Oddzielanie instrukcji|`;` średnik|koniec linijki| |Tworzenie zmiennych|`Typ zmienna = wartość`|`zmienna = wartość`| |Wypisywanie|`std::cout << "Napis1 " << zmmienna1 << " napis2 " << zmienna2 << ... << std::endl`|`print(f"Napis1 {zmienna1} napis2 {zmienna2} ...")`| |Dołączenie biblioteki|`#include `|`import biblioteka`| |**Rozszerzalne tablice:**||| |...tworzenie|`std::vector wektor`|`lista = []`| |...rozszerzanie|`wektor.push_back(rzecz)`|`lista.append(rzecz)`| |...czytanie wartości|`wektor[indeks]`|`lista[indeks]`| |**Własne Typy**||| |Tworzenie|`class Nazwa {...};` albo `struct Nazwa {...}`|`class Nazwa: ...`| |Dostęp do zmiennych innego obiektu|`obiekt.zmienna`|`obiekt.zmienna`| |Dostęp do zmiennych wewnątrz obiektu|`zmienna` albo `this->zmienna`|`self.zmienna` (`self` to pierwszy argument funkcji w obiekcie)| |**Warunki**|| ||`if (warunek) {`|`if warunek:`| ||` ...`|` ...`| ||`} else if (warunek) {`|`elif warunek:`| ||` ...`|` ...`| ||`} else {`|`else:`| ||` ...`|` ...`| ||`}`|| |**Pętla while**||| ||`while (warunek) {`|`while warunek:`| ||` ...`|` ...`| ||`}`|| |**Funkcje**||| |...tworzenie|`TypWyniku nazwa(TypArg1 arg1, TypArg2 arg2, ...) { ... }`|`def nazwa(arg1, arg2)`| |...zwracanie wyniku|`return wynik`|`return wynik`| ## Pierwszy program o wektorach Wyszukaj i otwórz program Kate. Utwórz nowy plik i wklej do niego następującą zawartość: ```py class Vector2D: def __init__(self, name): # To jest komentarz. Możesz w dowolym miejscu wstawić linijkę zaczynającą się od # # i napisać tak cokolwiek. # To dobry sposób na robienie notatek! self.x = 0.0 self.y = 0.0 self.name = name def add(self, other_vector): self.x = self.x + other_vector.x self.y = self.y + other_vector.x def multiply(self, scale): ... def show(self): print(f"Wektor {self.name}=({self.x}, {self.y})") def XY(x, y): new_vec = Vector2D("v") new_vec.x = x new_vec.y = y return new_vec print("Nowy wektor:") v = Vector2D("v") v.show() print("Ustawmy kilka współrzędnych:") v.x = 1.0 v.y = 1.2 v.show() print("Przeskalujemy?") v.multiply(10) v.show() v.multiply(1/10) v.show() ``` Następnie zapisz plik jako `vectors.py`, **zwróć uwagę na to, w jakim folderze go zapisujesz**. Teraz otwórz eksplorator plików i znajdź, coś zapisał. Następnie otwórz terminal. W terminalu, podobnie jak w eskploratorze plików chodzimy po folderach. Nie tyle w celu otwierania plików, co dla uruchamiania programów. Służą do tego *polecenia*: - `cd "nazwa folderu"`: w ten sposób wchodzimy do folderu (odpowiednik podwójnego kliknięcia). - `ls`: w ten sposób wypisujemy listę plików w folderze, w którym jesteśmy (to, co w eksploratorze normalnie po prostu widać). - `python "nazwa pliku.py"`: w ten sposób uruchamiamy program w Pythonie. :::{important} Po każdym poleceniu należy wcisnąć klawisz Enter, żeby je wykonać. Nie musisz pisać całej nazwy pliku/folderu ręcznie, możesz wpisać początek i nacisnąć Tab — terminal uzupełni resztę. ::: :::{topic} Zadanie 1. Znajdź błąd w funkcji `Vector2d.add`. 2. Czym różnią się wynuiki tych dwóch instrukcje `print`? ```py zmienna1 = "XDDDDD" zmienna2 = [1, 2, 3] print(f"Bla bla bla {zmienna1}, bla coś={zmienna2}") print("Bla bla bla {zmienna1}, bla coś={zmienna2}") ``` 2. Uzupełnij kod funkcji `Vector2d.multiply`, żeby mnożyła wektor przez skalar. 3. Skąd wiadomo, gdzie się zaczyna, a gdzie kończy funkcja `Vector2d.add`? 3. Jak zostanie wypisany wektor utworzony jako `u = Vector2D("v")`? 4. Jaki będzie wynik dopisania do programu poniższego kodu? ```py v1 = Vector2D("v1") v1.x = v1.y = 1 v2 = v1 v2.multiply(10) v1.show() ``` Dlaczego tak? 5. Dopisz do klasy `Vector2D` funkcję `Vector2D.copy_from`, która po wywołaniu `v2.copy_from(v1)` skopiuje współrzędne z wektora 1 do wektora 2. ::: ## Interaktywny Python i moduły Pythona można używać nie tylko do wykonania instrukcji zapisanych w pliku. Można też uruchomić go tak, żeby po każdej instrukcji od razu ją wykonywał i podawał efekt wykonania. :::{topic} Zadanie **Upewnij się, że jesteś w folderze z plikiem `vectors.py`!** W terminalu wydaj po prostu polecenie `python`. Kiedy zobaczysz napis `>>> ` wpisz polecenie `2+2` (tak, jak zawsze w terminalu). Co się stało? To teraz użyj Pythona, żeby obliczyć: - $\frac{17 \cdot 85}{5}$, - $(274762 + 457263) * (1254 + 7689)$, - kwadrat powyższego wyrażenia, - $\frac{2357}{123}$ - sumę wszystkich czterech dodychczasowych wyników. ::: ### Moduły Python potrafi też wykonywać bardziej skomplikowane działania matematyczne. Moduł (biblioteka/nagłówek) służący do tego celu to `math`. Żeby móc skorzystać z nich, musimy kazać Pythonowi wczytać go do naszego programu: `import math`. Następnie możemy używać różnych funkcji z niego — pełna lista funkcji [jest tutaj](https://docs.python.org/3/library/math.html). :::{topic} Zadanie Korzystając z [podlinkowanej listy funkcji](https://docs.python.org/3/library/math.html) oblicz: - $\sin(23^\circ)$, - $\log_3 7$, - $\sqrt{2}^{\sqrt{2}}$. ::: Możemy korzystać nie tylko z własnych Pythonowych modułów, ale także z naszych własnych. Na przykład wydaj kolejno te polecenia: - `import vectors` - `vv = vectors.Vector2D("vv")` - `vv.show()` :::{topic} Zadanie Zrób tak, żeby dla wektora `v` zarówno `perp(v)`, jak i `v.perp()` zwracały w wyniku dowolny wektor prostopadły do `v`. ::: ### Wynik funkcji vs `print()` Jaka jest różnica pomiędzy tymi dwiema funkcjami? ```py import math def num_digits_1(n): return math.ceil(math.log(n)) def num_digits_2(n): print(math.ceil(math.log(n))) ``` ## Iloczyn skalarny Z wykładu wiemy, że iloczyn skalarny spełnia $v_1 \dot v_2 = 0 \;\Leftrightarrow\; v_1 \perp v_2$. Ta własność to szczególny przypadek bardziej ogólnej, mówiącej że $v_1 \bullet v_2 = |v_1| \cdot |v_2| \cdot \cos \alpha$, gdzie $\alpha$ to kąt między wektorami $v_1, v_2$, zapis $|v|$ oznacza długość wektora $v$. Ta własność jest całkiem przydatna w programowaniu gier! :::{topic} Zadanie Gracz stoi w punkcie $P$ i patrzy wzdłuż wektora $v$, jego kąt widzenia to $30^\circ$. Czy widzi potwora stojącego w punkcie $M$? Zakładamy, że potwór ma rozmiar jednego punktu i nie ma przeszkód, które zasłaniałyby widzenie. Napisz funkcję, która zwracając `True` albo `False` odpowie na to pytanie, wszystkie jej argumenty są podane jako zmienne klasy `Vector2D`: ```py def can_see(P, v, M): ... ``` ::: ## Podsumowanie i zadania domowe ![Ściąga z while/class/def/list wygenerowana przez Gemini](static/Gemini_generated/cheatsheet.png) Interpretacja: - Funkcje to maszynki, do których wrzucamy argumenty, a one wypluwają jakiś wynik (`return`). - Klasy to takie większe maszynki, które trzymają w środku własne rzeczy (dane) i mogą robić z nimi różne rzeczy (funkcje w tych klasach). - Pętle while robią w kółko te same instrukcje, przy czym przed każdym wykonaniem od nowa sprawdzają, czy warunek jest spełniony. - Listy to ponumerowane od zera pudełka na dane. ### Zadania domowe Wszystkie zadania powinny być kompatybilne z klasą Vector2D napisaną na ćwiczeniach. :::{topic} Zadanie `v.dot` **[1 punkt]** Do klasy Vector2D z ćwiczeń dodaj funkcję `Vector2D.dot(v2)`, która zwróci iloczyn skalarny tego wektora z podanym jako argument. ::: :::{topic} Zadanie domowe `v.base` **[1 punkt]** Napisz funkcję `lid2d(v1, v2)`, która dla danych wektorów zwróci `True` albo `False` zależnie od tego, czy podane dwa wektory są liniowo niezależne. ::: :::{topic} Zadanie domowe `v.coord` **[2 punkt]** Napisz funkcję `compute_coordinates(bv1, bv2, v)`, która zwróci współrzędne wektora `v` w bazie `(bv1, bv2)`. Nie musisz sprawdzać, czy `(bv1, bv2)` to faktycznie baza — załóż, że tak jest. Wynik możesz zwrócić jako `Vector2D`. ::: :::{topic} Zadanie domowe `v.right` **[2 punkty]** Korzystając z ogólniejszej własności iloczynu skalarnego poznanej na dzisiejszych zajęciach napisz funkcję `is_at_right(v, p1, p2)`, która sprawdzi, czy wektor `v` jest po prawej stronie prostej z `p1` do `p2` (tzn. prostej przez te dwa punkty, ale prawo/lewo bierzemy tak, jakby ktoś na niej stał patrząc z `p1` w kierunku `p2`). :::