{
"cells": [
{
"cell_type": "markdown",
"id": "383f58a8",
"metadata": {
"collapsed": false
},
"source": [
"<div class=\"alert alert-block\" style=\"text-align: center; background-color: lightgrey; font-weight: bold;\">\n",
"<h1> Algorytmy i programowanie</h1>\n",
"<h2> <i>Klasy w Pythonie</i></h2> \n",
"<h3></h3>\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "a7812fed",
"metadata": {
"collapsed": false
},
"source": [
"## Elementy OOP"
]
},
{
"cell_type": "markdown",
"id": "405d9e39",
"metadata": {
"collapsed": false
},
"source": [
"**Programowanie obiektowe** (z ang. *object oriented progmamming*, OOP) jest jednym z podstawowych paradygmatów programowania. Technika ta w uproszczeniu polega na tym, aby dane powiązać z obiektami i ich **atrybutami**, których zachowanie modelowane jest za pomocą **metod**. Python jest językiem obiektowym, widzieliśmy to przy okazji pracy z klasą `string` (np. metoda `split` lub `isnumeric`) . W Pythonie możemy również łatwo tworzyć własne obiekty i związane z nimi metody. Kluczowym w tym procesie pojęciem jest pojęcie **klasy**. </br>\n",
"\n",
"**Klasy**, używając obrazowego języka, są jakby szablonami czy wzorcami, według których tworzone są obiekty. Konkretne obiekty to tzw. **instancje** danych klas. Aby utworzyć klasę używamy słowa kluczowego `class`. **Atrybuty** to pewne cechy związane z konkretną instancją, a **metody** to funkcje, które pozwalają wykonywać operacje na instancjach. </br>\n",
"\n",
"Aby utworzyć klasę należy użyć słowa kluczowego `class`."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "93a67983",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Car:\n",
" model = 'Ford'\n",
" color = 'red'\n",
" year_of_production = 2010 "
]
},
{
"cell_type": "markdown",
"id": "1820e25e",
"metadata": {
"collapsed": false
},
"source": [
"Utwórzmy instancję klasy `Car`. Zmienne `model`, `color`, `year_of_production` są atrybutami tej klasy. "
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "1712790f",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"car1 = Car()"
]
},
{
"cell_type": "markdown",
"id": "8b93132b",
"metadata": {
"collapsed": false
},
"source": [
"Jeśli chcemy sprawdzić wartość atrybutu konkretnej instancji używamy polecenia `nazwa_instancji.nazwa_atrybutu`."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "72fff4dd",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Ford red 2010\n"
]
}
],
"source": [
"print(car1.model, car1.color, car1.year_of_production)"
]
},
{
"cell_type": "markdown",
"id": "4982fb48",
"metadata": {
"collapsed": false
},
"source": [
"Możemy też sami utworzyć nowy atrybut:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "25713649",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"car1.power = 160"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "93347448",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"160\n"
]
}
],
"source": [
"print(car1.power)"
]
},
{
"cell_type": "markdown",
"id": "6bf8f4d4",
"metadata": {
"collapsed": false
},
"source": [
"Oczywiście jeśli utworzymy nową instancję klasy `Car`, to zostanie ona utworzona według pierwotnego schematu, a więc nie będzie do niej dołączony atrybut `power`."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "c0920886",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"car2 = Car()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "cca0a06f",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Ford red 2010\n"
]
}
],
"source": [
"print(car2.model, car2.color, car2.year_of_production)"
]
},
{
"cell_type": "markdown",
"id": "0d325122",
"metadata": {
"collapsed": false
},
"source": [
"Zgodnie z przewidywaniami, powyższe atrybuty są znane, ale powołanie się na atrybut `power` spowoduje błąd."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "b9900e55",
"metadata": {
"collapsed": false
},
"outputs": [
{
"ename": "AttributeError",
"evalue": "'Car' object has no attribute 'power'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[10], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mcar2\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpower\u001b[49m\n",
"\u001b[0;31mAttributeError\u001b[0m: 'Car' object has no attribute 'power'"
]
}
],
"source": [
"car2.power"
]
},
{
"cell_type": "markdown",
"id": "c08125a6",
"metadata": {
"collapsed": false
},
"source": [
"W każdym momencie możemy również sprawdzić jaki jest domyślny atrybut klasy poprzez `nazwa_klasy.nazwa_atrybutu`."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "acb1b40f",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"'red'"
]
},
"execution_count": 11,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"Car.color"
]
},
{
"cell_type": "markdown",
"id": "0a80cd44",
"metadata": {
"collapsed": false
},
"source": [
"Zatem jeśli zmienimy atrybuty instancji `car2`, to powyższa instrukcja pozwala nam łatwo odwołać się do atrybuty klasy."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "fc496196",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Volvo\n",
"Ford\n"
]
}
],
"source": [
"car2.model = 'Volvo'\n",
"print(car2.model)\n",
"print(Car.model)"
]
},
{
"cell_type": "markdown",
"id": "cc389d5f",
"metadata": {
"collapsed": false
},
"source": [
"Utwórzmy teraz klasę, która oprócz atrybutów będzie posiadała także metodę."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "a38f3e3b",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class LinearFunction:\n",
" \n",
" a_coeff = 2\n",
" b_coeff = 3\n",
" \n",
" def count_zeros(self):\n",
" if self.a_coeff == 0:\n",
" if self.b_coeff != 0:\n",
" return 'Brak miejsc zerowych'\n",
" else:\n",
" return 'Każda liczba rzeczywista jest miejscem zerowym'\n",
" else:\n",
" x = (-1) * self.b_coeff / self.a_coeff\n",
" return x"
]
},
{
"cell_type": "markdown",
"id": "d09eaa9c",
"metadata": {
"collapsed": false
},
"source": [
"Użyty powyżej parametr `self` pozwala metodzie odwołać się do utworzonej instancji. Dlatego w poniższym przykładzie instrukcja `l1.count_zeros()` spowoduje wykonanie metody `count_zeros(self)`, gdzie w miejsce parametru `self` zostanie wstawiona obiekt `l1`, czyli instancja, która sama wywołała tę metodę."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "aff48267",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2\n",
"3\n",
"-1.5\n"
]
}
],
"source": [
"l1 = LinearFunction()\n",
"print(l1.a_coeff)\n",
"print(l1.b_coeff)\n",
"zero = l1.count_zeros()\n",
"print(zero)"
]
},
{
"cell_type": "markdown",
"id": "b2e39f78",
"metadata": {
"collapsed": false
},
"source": [
"Jak poprzednio możemy łatwo zmienić atrybuty instancji prze wykonaniem metody."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "244109e2",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"l2 = LinearFunction()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "46dd0e0c",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"l2.a_coeff = 0\n",
"l2.b_coeff = 1"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "6362696e",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Brak miejsc zerowych\n"
]
}
],
"source": [
"print(l2.count_zeros())"
]
},
{
"cell_type": "markdown",
"id": "a6dcad49",
"metadata": {
"collapsed": false
},
"source": [
"### Konstruktor\n",
"\n",
"W powyższych przykładach każdorazowe modyfikowanie atrybutów będących współczynnikami funkcji liniowej mogło wydawać się dziwaczne. Najdogodniejszym sposobem tworzenia instancji jest użycie **konstruktora**, czyli specjalnej metody `__init__`, która służy do tworzenia instancji i jest wywoływana automatycznie po utworzeniu przez nas instancji danej klasy. W poprzednim przykładzie właśnie za pomocą konstruktora moglibyśmy nadać wartości atrybutów naszej instancji (konkretnych dla wszystkich obiektów lub przekazanych za pomocą zmiennych).\n",
"\n",
"Można tego dokonać w następujący sposób."
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "829bd358",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class LinearFunction:\n",
" \n",
" def __init__(self, a, b) :\n",
" self.a_coeff = a\n",
" self.b_coeff = b\n",
" \n",
" def count_zeros(self):\n",
" if self.a_coeff == 0:\n",
" if self.b_coeff != 0:\n",
" return 'Brak miejsc zerowych'\n",
" else:\n",
" return 'Każda liczba rzeczywista jest miejscem zerowym'\n",
" else:\n",
" x = (-1) * self.b_coeff / self.a_coeff\n",
" return x"
]
},
{
"cell_type": "markdown",
"id": "3dec71ca",
"metadata": {
"collapsed": false
},
"source": [
"Zaznaczmy, że argumentami powyższego konstruktora jest `self`, czyli tworzony obiekt, oraz dwie dodatkowe zmienne, które zostaną przypisane wartościom artybutów `a_coeff` i `b_coeff`, odpowiednio. Dlatego utworzenie obiektu tej klasy będzie wymagało podania dokładnie tych dwóch parametrów."
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "1c68eee2",
"metadata": {
"collapsed": false
},
"outputs": [
{
"ename": "TypeError",
"evalue": "LinearFunction.__init__() missing 2 required positional arguments: 'a' and 'b'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[20], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m l3 \u001b[38;5;241m=\u001b[39m \u001b[43mLinearFunction\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
"\u001b[0;31mTypeError\u001b[0m: LinearFunction.__init__() missing 2 required positional arguments: 'a' and 'b'"
]
}
],
"source": [
"l3 = LinearFunction()"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "7cc45cd1",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"-1.0\n"
]
}
],
"source": [
"l3 = LinearFunction(1, 1)\n",
"print(l3.count_zeros())"
]
},
{
"cell_type": "markdown",
"id": "9b54f1e3",
"metadata": {
"collapsed": false
},
"source": [
"W przypadku bardziej rozbudowanych klas, jak w poniższym przykładzie, warto użyć tzw. doc-stringów służących do tworzenia dokumentacji. Są to komentarze rozpoczynające się i kończące trzema cudzysłowami. Są one pomocna dla osoby, która będzie czytała nasz kod."
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "af165f2a",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Rectangle:\n",
" \n",
" \"\"\"\n",
" Klasa prostokąt służy do wykonywania operacji na prostokątach.\n",
" \"\"\"\n",
" \n",
" PI = 3.141 # atrybut klasy\n",
" \n",
" def __init__(self, a, b): #konstruktor\n",
" \n",
" \"\"\"\n",
" Aby utworzyć instancję klasy Rectangle musimy podać parametry a i b odpowiadające długości boków, \n",
" które zostaną przypisane atrybutom a i b instancji.\n",
" \"\"\"\n",
" \n",
" self.a = a\n",
" self.b = b\n",
" \n",
" \n",
" def is_square(self): # tej metody nie opiszemy\n",
" return self.a == self.b\n",
" \n",
" def area(self): # tu zostawmy opis\n",
" \"\"\"\n",
" Metoda ta pozwala obliczyć pole prostokąta\n",
" \"\"\"\n",
" return self.a * self.b\n",
" \n",
" def perimeter(self):\n",
" \"\"\"\n",
" Metoda ta pozwala obliczyć obwód prostokąta\n",
" \"\"\"\n",
" return 2*self.a + 2*self.b\n",
" \n",
" def rad_of_circumcircle(self):\n",
" \"\"\"\n",
" Metoda ta pozwala obliczyć promień okręgu opisanego na prostokącie\n",
" \"\"\"\n",
" return 0.5 * (self.a ** 2 + self.b ** 2) ** 0.5\n",
" \n",
" def area_of_circumcircle(self):\n",
" \"\"\"\n",
" Metoda ta pozwala obliczyć pole koła, którego brzeg stanowi okrą opisany na prostokącie\n",
" \"\"\"\n",
" r = self.rad_of_circumcircle()\n",
" return self.PI * r ** 2\n",
" \n",
" "
]
},
{
"cell_type": "markdown",
"id": "3b232bab",
"metadata": {
"collapsed": false
},
"source": [
"Wówczas możemy użyć polecenia `help(nazwa_klasy)` w celu wyświetlenia wszystkich metod tej klasy wraz z ich opisami."
]
},
{
"cell_type": "code",
"execution_count": 34,
"id": "c9a5aa88",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Help on class Rectangle in module __main__:\n",
"\n",
"class Rectangle(builtins.object)\n",
" | Rectangle(a, b)\n",
" | \n",
" | Klasa prostokąt służy do wykonywania operacji na prostokątach.\n",
" | \n",
" | Methods defined here:\n",
" | \n",
" | __init__(self, a, b)\n",
" | Aby utworzyć instancję klasy Rectangle musimy podać parametry a i b odpowiadające długości boków, \n",
" | które zostaną przypisane atrybutom a i b instancji.\n",
" | \n",
" | area(self)\n",
" | Metoda ta pozwala obliczyć pole prostokąta\n",
" | \n",
" | area_of_circumcircle(self)\n",
" | Metoda ta pozwala obliczyć pole koła, którego brzeg stanowi okrą opisany na prostokącie\n",
" | \n",
" | is_square(self)\n",
" | \n",
" | perimeter(self)\n",
" | Metoda ta pozwala obliczyć obwód prostokąta\n",
" | \n",
" | rad_of_circumcircle(self)\n",
" | Metoda ta pozwala obliczyć promień okręgu opisanego na prostokącie\n",
" | \n",
" | ----------------------------------------------------------------------\n",
" | Data descriptors defined here:\n",
" | \n",
" | __dict__\n",
" | dictionary for instance variables (if defined)\n",
" | \n",
" | __weakref__\n",
" | list of weak references to the object (if defined)\n",
" | \n",
" | ----------------------------------------------------------------------\n",
" | Data and other attributes defined here:\n",
" | \n",
" | PI = 3.141\n",
"\n"
]
}
],
"source": [
"help(Rectangle)"
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "7f739491",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Help on class LinearFunction in module __main__:\n",
"\n",
"class LinearFunction(builtins.object)\n",
" | LinearFunction(a, b)\n",
" | \n",
" | Methods defined here:\n",
" | \n",
" | __init__(self, a, b)\n",
" | Initialize self. See help(type(self)) for accurate signature.\n",
" | \n",
" | count_zeros(self)\n",
" | \n",
" | ----------------------------------------------------------------------\n",
" | Data descriptors defined here:\n",
" | \n",
" | __dict__\n",
" | dictionary for instance variables (if defined)\n",
" | \n",
" | __weakref__\n",
" | list of weak references to the object (if defined)\n",
"\n"
]
}
],
"source": [
"help(LinearFunction)"
]
},
{
"cell_type": "markdown",
"id": "43a573d8",
"metadata": {
"collapsed": false
},
"source": [
"Widzimy różnicę w opisie klas LinearFunction i Rectangle oraz jak pomocna może być prosta dokumentacja.\n",
"\n",
"Poniżej zobaczmy przykładowe wywołanie metod klasy `Rectangle`."
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "c29fcd05",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"prost1 = Rectangle(3,4)"
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "836f183b",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"3"
]
},
"execution_count": 37,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"prost1.a"
]
},
{
"cell_type": "code",
"execution_count": 38,
"id": "eda665e8",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 38,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"prost1.b"
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "31cd698d",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"12"
]
},
"execution_count": 39,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"prost1.area()"
]
},
{
"cell_type": "code",
"execution_count": 40,
"id": "ec08f622",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"14"
]
},
"execution_count": 40,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"prost1.perimeter()"
]
},
{
"cell_type": "code",
"execution_count": 41,
"id": "6cecdca8",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"19.63125"
]
},
"execution_count": 41,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"prost1.area_of_circumcircle()"
]
},
{
"cell_type": "code",
"execution_count": 42,
"id": "71082d77",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"3.141"
]
},
"execution_count": 42,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"Rectangle.PI"
]
},
{
"cell_type": "markdown",
"id": "2c4b2c3d",
"metadata": {
"collapsed": false
},
"source": [
"Kolejny przykład pokazuje, że atrybuty klasy (i konkretnej instancji) możemy kontrolować metodami (w tym przypadku konstruktorem). Zauważmy, że każdorazowe utworzenie elementu wiąże się z wywołaniem konstruktora. Możemy to wykorzystać do zliczania liczby utworzonych elementów. Zauważmy, że mimo tej samej nazwy czym innym jest **atrybut klasy** o nazwie `najtansze`, a czym innym **atrybut instancji** o tej samej nazwie. Atrybut klasy pozwala z poziomu klasy znaleźć cenę najtańszych butów (spośród wszystkich do tej pory utworzonych), atrybut instancji jest parametrem logicznym pozwalającym stwierdzić, czy ta konkretna instancja reprezentuje buty o najniższej cenie (do której mamy dostęp dzięki atrybutowi cena)."
]
},
{
"cell_type": "code",
"execution_count": 43,
"id": "864645ee",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Buty:\n",
" \n",
" ilosc = 0 \n",
" najtansze = None\n",
" najdrozsze = None # tego nie będziemy używać\n",
" \n",
" def __init__(self, cena, rodzaj, kolor):\n",
" self.cena = cena\n",
" self.kolor = kolor\n",
" self.rodzaj = rodzaj\n",
" Buty.ilosc += 1 # ta instrukcja aktualizuje atrybut klasy przy każdym utworzeniu instancji tej klasy\n",
" # poniższa instrukcja if określa czy tworzona instancja jest najtańszą ze wszystkich utworzonych\n",
" if Buty.najtansze == None: \n",
" self.najtansze = True\n",
" Buty.najtansze = self.cena\n",
" else:\n",
" if Buty.najtansze >= self.cena:\n",
" self.najtansze = True\n",
" Buty.najtansze = self.cena\n",
" else:\n",
" self.najtansze = False "
]
},
{
"cell_type": "code",
"execution_count": 44,
"id": "4245c988",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"buty1 = Buty(99.99, 'Trampki', 'Czarne')"
]
},
{
"cell_type": "code",
"execution_count": 45,
"id": "aa241d5a",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"buty2 = Buty(199.99, 'Glany', 'Różowe')"
]
},
{
"cell_type": "code",
"execution_count": 46,
"id": "e7d0c84d",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 46,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"Buty.ilosc"
]
},
{
"cell_type": "code",
"execution_count": 47,
"id": "fba1b0d9",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"99.99\n"
]
}
],
"source": [
"print(Buty.najtansze)"
]
},
{
"cell_type": "code",
"execution_count": 48,
"id": "742809e7",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 48,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"buty1.najtansze"
]
},
{
"cell_type": "code",
"execution_count": 49,
"id": "da268e34",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 49,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"buty2.najtansze"
]
},
{
"cell_type": "markdown",
"id": "c1eb5a1a",
"metadata": {
"collapsed": false
},
"source": [
"## Dziedziczenie\n",
"\n",
"Dziedziczenie polega na definiowaniu podklas danej klasy, które domyślnie posiadają te same (co klasa macierzysta) atrybuty i metody, do których możemy dodać nowe (atrybuty i metody) lub je zmodyfikować.\n",
"\n",
"W Pythonie definiowanie podklasy polega na umieszczeniu po słowie `class` nazwy definiowanej podklasy, a następnie w nawiasie nazwy klasy macierzystej. \n",
"\n",
"Dla przykładu, jeśli mamy zdefiniowaną klasę `Vehicle` o atrybutach `brand`, `color`, `year` (tworzone są one przez konstruktor), to podklasę `Car` definiujemy następująco.\n"
]
},
{
"cell_type": "code",
"execution_count": 50,
"id": "bf28e181",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Atrybuty obiektu vehicle to: {'brand': 'Tesla', 'color': 'red', 'year': 2020}\n",
"Atrybuty obiektu car to: {'brand': 'Tesla', 'color': 'red', 'year': 2020}\n",
"move\n",
"I can move\n"
]
}
],
"source": [
"class Vehicle:\n",
" action = \"move\"\n",
" def __init__(self, brand, color, year):\n",
" self.brand = brand\n",
" self.color = color\n",
" self.year = year\n",
" def intro(self):\n",
" print(\"I can\", self.action)\n",
" \n",
" \n",
"class Car(Vehicle):\n",
" pass # po dwukropku konieczna jest co najmniej jedna instrukcja\n",
" \n",
" \n",
"vehicle = Vehicle('Tesla', 'red', 2020)\n",
"print(\"Atrybuty obiektu vehicle to:\", vehicle.__dict__) #atrybut __dict__ jest zawsze tworzony z instancją, umożliwia podgląd atrybutów instancji tworzonych przez konstruktor\n",
"\n",
"car = Car('Tesla', 'red', 2020)\n",
"\n",
"print(\"Atrybuty obiektu car to:\", car.__dict__)\n",
"print(car.action)\n",
"car.intro()"
]
},
{
"cell_type": "markdown",
"id": "be638bd4",
"metadata": {
"collapsed": false
},
"source": [
"Jak widzimy podklasa `car` dziedziczy wszystkie atrybuty klasy `Vehicle` oraz jej metody wraz z konstruktorem.\n",
"\n",
"Oczywiście podczas tworzenia podklasy możemy modyfikować atrybuty."
]
},
{
"cell_type": "code",
"execution_count": 51,
"id": "fc9232e1",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Atrybuty obiektu vehicle to: {'brand': 'Tesla', 'color': 'red', 'year': 2020}\n",
"Atrybuty obiektu car to: {'brand': 'Tesla', 'color': 'red', 'year': 2020}\n",
"drive\n",
"I can drive\n"
]
}
],
"source": [
"class Vehicle:\n",
" action = \"move\"\n",
" def __init__(self, brand, color, year):\n",
" self.brand = brand\n",
" self.color = color\n",
" self.year = year\n",
" def intro(self):\n",
" print(\"I can\", self.action)\n",
" \n",
" \n",
"class Car(Vehicle):\n",
" action = \"drive\" \n",
" \n",
" \n",
"vehicle = Vehicle('Tesla', 'red', 2020)\n",
"print(\"Atrybuty obiektu vehicle to:\", vehicle.__dict__) #atrybut __dict__ jest zawsze tworzony z instancją, umożliwia podgląd atrybutów instancji tworzonych przez konstruktor\n",
"\n",
"car = Car('Tesla', 'red', 2020)\n",
"\n",
"print(\"Atrybuty obiektu car to:\", car.__dict__)\n",
"print(car.action)\n",
"car.intro()"
]
},
{
"cell_type": "markdown",
"id": "22221ca5",
"metadata": {
"collapsed": false
},
"source": [
"Jeśli zdecydujemy się użyć innego konstruktora podklasy `(__init__(self, parametry_konstruktora_podklasy))` oraz chcemy wykorzystać konstruktor nadklasy, to możemy odwołać się do niej poprzez `super().__init__(parametry_konstruktora_nadklasy)`."
]
},
{
"cell_type": "code",
"execution_count": 52,
"id": "cc5142f4",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Atrybuty obiektu vehicle to: {'brand': 'Tesla', 'color': 'red', 'year': 2020}\n",
"Atrybuty obiektu car to: {'brand': 'Tesla', 'color': 'red', 'year': 2020, 'horsepower': 300}\n"
]
}
],
"source": [
"class Vehicle:\n",
" action = \"move\"\n",
" def __init__(self, brand, color, year):\n",
" self.brand = brand\n",
" self.color = color\n",
" self.year = year\n",
" def intro(self):\n",
" print(\"I can\", self.action)\n",
" \n",
" \n",
"class Car(Vehicle):\n",
" action = \"drive\" \n",
" def __init__(self, brand, color, year, horsepower): # nowy atrybut - horsepower - w podklasie\n",
" super().__init__(brand, color, year) # atrybuty dziedziczone z nadklasy zostaną ustawione dzięki konstruktorowi z nadklasy\n",
" self.horsepower = horsepower # osobno definiujemy wartość nowego atrybutu\n",
" \n",
" \n",
"vehicle = Vehicle('Tesla', 'red', 2020)\n",
"print(\"Atrybuty obiektu vehicle to:\", vehicle.__dict__) #atrybut __dict__ jest zawsze tworzony z instancją, umożliwia podgląd atrybutów instancji tworzonych przez konstruktor\n",
"\n",
"car = Car('Tesla', 'red', 2020, 300)\n",
"\n",
"print(\"Atrybuty obiektu car to:\", car.__dict__)"
]
},
{
"cell_type": "markdown",
"id": "99ca9ba0",
"metadata": {
"collapsed": false
},
"source": [
"Podobnie możemy tworzyć nowe metody korzystając z metod z nadklasy."
]
},
{
"cell_type": "code",
"execution_count": 53,
"id": "1aa8b1d1",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"I can move\n",
"I am a car and I can drive\n"
]
}
],
"source": [
"class Vehicle:\n",
" action = \"move\"\n",
" def __init__(self, brand, color, year):\n",
" self.brand = brand\n",
" self.color = color\n",
" self.year = year\n",
" def intro(self):\n",
" print(\"I can\", self.action)\n",
" \n",
" \n",
"class Car(Vehicle):\n",
" action = \"drive\" \n",
" def __init__(self, brand, color, year, horsepower): # nowy atrybut - horsepower - w podklasie\n",
" super().__init__(brand, color, year) # atrybuty dziedziczone z nadklasy zostaną ustawione dzięki konstruktorowi z nadklasy\n",
" self.horsepower = horsepower # osobno definiujemy wartość nowego atrybutu\n",
" def intro(self):\n",
" print(\"I am a car and\", end=\" \")\n",
" super().intro()\n",
" \n",
" \n",
"vehicle = Vehicle('Tesla', 'red', 2020)\n",
"car = Car('Tesla', 'red', 2020, 300)\n",
"\n",
"vehicle.intro()\n",
"car.intro()"
]
},
{
"cell_type": "markdown",
"id": "667f0984",
"metadata": {
"collapsed": false
},
"source": [
"## Metody specjalne\n",
"\n",
"Python pozwala nam również na definiowanie w jaki sposób znane funkcje lub operatory powinny być wykonywane na nowo tworzonych obiektach danej klasy. Służą do tego tzw. metody specjalne. Dokładną listę wszystkich metod specjalnych można znaleźć np. tutaj https://docs.python.org/pl/3/reference/datamodel.html#special-method-names. My jednak skupimy się tylko na wybranych metodach specjalnych."
]
},
{
"cell_type": "markdown",
"id": "243cac96",
"metadata": {
"collapsed": false
},
"source": [
"### Metoda `__str__(self)`\n",
"\n",
"Metoda ta pozwala nam określić w jaki sposób ma zostać wykonana funckja `print`, której argumentem będzie obiekt utworzonej przez nas klasy. Należy pamiętać, że metoda ta powinna zwracać zmienną typu `str`, którą wyświetli funkcja `print`. Przykładowo, można w następujący sposób zdefiniować funckję `print` od instancji klasy `Rectangle`."
]
},
{
"cell_type": "code",
"execution_count": 54,
"id": "dc2cdc95",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Rectangle:\n",
" \n",
" \"\"\"\n",
" Klasa prostokąt służy do wykonywania operacji na prostokątach.\n",
" \"\"\"\n",
" \n",
" PI = 3.141 # atrybut klasy\n",
" \n",
" def __init__(self, a, b): #konstruktor\n",
" \n",
" \"\"\"\n",
" Aby utworzyć instancję klasy Rectangle musimy podać parametry a i b odpowiadające długości boków, \n",
" które zostaną przypisane atrybutom a i b instancji.\n",
" \"\"\"\n",
" \n",
" self.a = a\n",
" self.b = b\n",
" \n",
" def __str__(self):\n",
" \n",
" \"\"\"\n",
" Pozwala nam określić jak ma zachować się funkcja print\n",
" \"\"\"\n",
" \n",
" return \"Prostokąt o bokach \" + str(self.a) + \" i \" + str(self.b) # znak + oznacza tutaj konkatenację łańcuchów znaków\n",
" \n"
]
},
{
"cell_type": "code",
"execution_count": 55,
"id": "2f92d3c1",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prostokąt o bokach 3 i 4\n"
]
}
],
"source": [
"rect1 = Rectangle(3, 4)\n",
"print(rect1)"
]
},
{
"cell_type": "markdown",
"id": "23a491ca",
"metadata": {
"collapsed": false
},
"source": [
"### Operatory binarne\n",
"\n",
"Załóżmy teraz, że chcemy zinterpretować w jaki sposób operator `+` ma zachowywać się dla obiektów typy `Rectangle`. Do tego celu służy metoda specjalna `__mul__(self,other)`. Mianowicie w momencie napotkania przez interpreter Pythona instrukcji `x + y`, gdzie `x` jest obiektem posiadającym zdefiniowaną metodę `__mul__` zostanie wykonana metoda `__mul__(x, y)`."
]
},
{
"cell_type": "code",
"execution_count": 56,
"id": "5c61f95a",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Rectangle:\n",
" \n",
" \"\"\"\n",
" Klasa prostokąt służy do wykonywania operacji na prostokątach.\n",
" \"\"\"\n",
" \n",
" PI = 3.141 # atrybut klasy\n",
" \n",
" def __init__(self, a, b): #konstruktor\n",
" \n",
" \"\"\"\n",
" Aby utworzyć instancję klasy Rectangle musimy podać parametry a i b odpowiadające długości boków, \n",
" które zostaną przypisane atrybutom a i b instancji.\n",
" \"\"\"\n",
" \n",
" self.a = a\n",
" self.b = b\n",
" \n",
" def __str__(self):\n",
" \n",
" \"\"\"\n",
" Pozwala nam określić jak ma zachować się funkcja print\n",
" \"\"\"\n",
" \n",
" return \"Prostokąt o bokach \" + str(self.a) + \" i \" + str(self.b) # znak + oznacza tutaj konkatenację łańcuchów znaków\n",
" \n",
" def __mul__(self, other):\n",
" if isinstance(other, int) :\n",
" return Rectangle(self.a*other, self.b*other)\n",
" else:\n",
" print(\"Działanie nie jest zdefiniowane\")\n",
" return self"
]
},
{
"cell_type": "code",
"execution_count": 57,
"id": "238dfccd",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prostokąt o bokach 6 i 8\n",
"Działanie nie jest zdefiniowane\n"
]
}
],
"source": [
"rect1 = Rectangle(3, 4)\n",
"rect2 = rect1*2\n",
"print(rect2)\n",
"rect3 = rect1*0.5"
]
},
{
"cell_type": "markdown",
"id": "b0c1bf83",
"metadata": {
"collapsed": false
},
"source": [
"**Uwaga:** W powyższym kodzie zastosowaliśmy funkcję `isinstance`, która sprawdza, czy obiekt będący pierwszym argumentem jest instancją klasy stojącej jako drugi argument."
]
},
{
"cell_type": "markdown",
"id": "be812534",
"metadata": {
"collapsed": false
},
"source": [
"Podobnie można wykorzystać również metodę specjalną `__rmul__(self, other)`, która jest tzw. prawostronnym mnożeniem. Mianowicie w momencie napotkania przez interpreter Pythona instrukcji `x + y`, gdzie `y` jest obiektem posiadającym zdefiniowaną metodę `__rmul__` zostanie wykonana metoda `__rmul__(x, y)`."
]
},
{
"cell_type": "code",
"execution_count": 58,
"id": "0d02a8b9",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Rectangle:\n",
" \n",
" \"\"\"\n",
" Klasa prostokąt służy do wykonywania operacji na prostokątach.\n",
" \"\"\"\n",
" \n",
" PI = 3.141 # atrybut klasy\n",
" \n",
" def __init__(self, a, b): #konstruktor\n",
" \n",
" \"\"\"\n",
" Aby utworzyć instancję klasy Rectangle musimy podać parametry a i b odpowiadające długości boków, \n",
" które zostaną przypisane atrybutom a i b instancji.\n",
" \"\"\"\n",
" \n",
" self.a = a\n",
" self.b = b\n",
" \n",
" def __str__(self):\n",
" \n",
" \"\"\"\n",
" Pozwala nam określić jak ma zachować się funkcja print\n",
" \"\"\"\n",
" \n",
" return \"Prostokąt o bokach \" + str(self.a) + \" i \"+str(self.b) # znak + oznacza tutaj konkatenację łańcuchów znaków\n",
" \n",
" def __rmul__(self, other):\n",
" if isinstance(other, int) :\n",
" return Rectangle(self.a*other, self.b*other)\n",
" else:\n",
" print(\"Działanie nie jest zdefiniowane\")\n",
" return self"
]
},
{
"cell_type": "code",
"execution_count": 59,
"id": "63d59bad",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prostokąt o bokach 6 i 8\n",
"Działanie nie jest zdefiniowane\n"
]
}
],
"source": [
"rect1 = Rectangle(3, 4)\n",
"rect2 = 2*rect1\n",
"print(rect2)\n",
"\n",
"rect3 = 0.5*rect1\n",
"\n",
"#rect4 = rect1 * 2 # ta instrukcja zwróci błąd"
]
},
{
"cell_type": "markdown",
"id": "3947fdaf",
"metadata": {
"collapsed": false
},
"source": [
"Innymi często definiowanymi operatorami binarnymi są:\n",
" - `__add__(self,other)` - pozwala zdefiniować operator dodawania\n",
" - `__radd__(self,other)` - pozwala zdefiniować operator dodawania prawostronnego\n",
" - `__sub__(self,other)` - pozwala zdefiniować operator odejmowania\n",
" - `__rsub__(self,other)` - pozwala zdefiniować operator odejmowania prawostronnego\n",
" - `__truediv__(self,other)` - pozwala zdefiniować operator `/`\n",
" - `__floordiv__(self,other)` - pozwala zdefiniować operator `//`\n",
" - `__mod__(self,other)` - pozwala zdefiniować operator `%`\n",
" - `__eq__(self,other)` - pozwala zdefiniować operator `==`\n",
" - `__ne__(self,other)` - pozwala zdefiniować operator `!=`\n",
" - `__le__(self,other)` - pozwala zdefiniować operator `<=`\n",
" - `__ge__(self,other)` - pozwala zdefiniować operator `>=`\n",
" - `__lt__(self,other)` - pozwala zdefiniować operator `<`\n",
" - `__gt__(self,other)` - pozwala zdefiniować operator `>`\n",
" "
]
},
{
"cell_type": "markdown",
"id": "662cbee0",
"metadata": {
"collapsed": false
},
"source": [
"**Zadanie 4.** Uzupełnij powyższą definicję klasy `Rectangle` tak, aby można było wykonywać następujące operatory:\n",
" - `x==y`, gdzie `x` i `y` są instancjami klasy `Rectangle` oraz operator powinien zwrócić `True` lub `False` w zależności, czy prostokąty są identyczne, czy nie.\n",
" - `x!=y`, gdzie `x` i `y` są instancjami klasy `Rectangle` oraz operator powinien zadziałać odwrotnie do `x==y`\n",
" - `x+y`, gdzie `x` jest instancją klasy `Rectangle` oraz `y` jest zmienną typu `int` lub `float`. Wynik powinien zwrócić obiekt klasy `Rectangle`, którego bok `b` został zwiększony o `y`\n",
" - `x+y`, gdzie `y` jest instancją klasy `Rectangle` oraz `x` jest zmienną typu `int` lub `float`. Wynik powinien zwrócić obiekt klasy `Rectangle`, którego bok `a` został zwiększony o `x`\n",
" - `x<y`, gdzie `x` i `y` są instancjami klasy `Rectangle` oraz operator powinien zwrócić `True`, gdy pole powierzchni prostokąta `x` jest mniejsze niż prostokąta `y`\n",
" - `x/y`, gdzie `x` jest instancją klasy `Rectangle` oraz `y` jest zmienną typu `int` lub `float`. Wynik powinien zwrócić obiekt klasy `Rectangle`, którego boki zostały podzielone przez `y`"
]
},
{
"cell_type": "markdown",
"id": "e13b3618",
"metadata": {
"collapsed": false
},
"source": [
]
},
{
"cell_type": "markdown",
"id": "aefe8ea5",
"metadata": {
"collapsed": false
},
"source": [
]
},
{
"cell_type": "markdown",
"id": "cd22e657",
"metadata": {
"collapsed": false
},
"source": [
]
}
],
"metadata": {
"kernelspec": {
"argv": [
"/usr/bin/python3",
"-m",
"ipykernel",
"--HistoryManager.enabled=False",
"--matplotlib=inline",
"-c",
"%config InlineBackend.figure_formats = set(['retina'])\nimport matplotlib; matplotlib.rcParams['figure.figsize'] = (12, 7)",
"-f",
"{connection_file}"
],
"display_name": "Python 3 (system-wide)",
"env": {
},
"language": "python",
"metadata": {
"cocalc": {
"description": "Python 3 programming language",
"priority": 100,
"url": "https://www.python.org/"
}
},
"name": "python3",
"resource_dir": "/ext/jupyter/kernels/python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 4
}
"cells": [
{
"cell_type": "markdown",
"id": "383f58a8",
"metadata": {
"collapsed": false
},
"source": [
"<div class=\"alert alert-block\" style=\"text-align: center; background-color: lightgrey; font-weight: bold;\">\n",
"<h1> Algorytmy i programowanie</h1>\n",
"<h2> <i>Klasy w Pythonie</i></h2> \n",
"<h3></h3>\n",
"</div>"
]
},
{
"cell_type": "markdown",
"id": "a7812fed",
"metadata": {
"collapsed": false
},
"source": [
"## Elementy OOP"
]
},
{
"cell_type": "markdown",
"id": "405d9e39",
"metadata": {
"collapsed": false
},
"source": [
"**Programowanie obiektowe** (z ang. *object oriented progmamming*, OOP) jest jednym z podstawowych paradygmatów programowania. Technika ta w uproszczeniu polega na tym, aby dane powiązać z obiektami i ich **atrybutami**, których zachowanie modelowane jest za pomocą **metod**. Python jest językiem obiektowym, widzieliśmy to przy okazji pracy z klasą `string` (np. metoda `split` lub `isnumeric`) . W Pythonie możemy również łatwo tworzyć własne obiekty i związane z nimi metody. Kluczowym w tym procesie pojęciem jest pojęcie **klasy**. </br>\n",
"\n",
"**Klasy**, używając obrazowego języka, są jakby szablonami czy wzorcami, według których tworzone są obiekty. Konkretne obiekty to tzw. **instancje** danych klas. Aby utworzyć klasę używamy słowa kluczowego `class`. **Atrybuty** to pewne cechy związane z konkretną instancją, a **metody** to funkcje, które pozwalają wykonywać operacje na instancjach. </br>\n",
"\n",
"Aby utworzyć klasę należy użyć słowa kluczowego `class`."
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "93a67983",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Car:\n",
" model = 'Ford'\n",
" color = 'red'\n",
" year_of_production = 2010 "
]
},
{
"cell_type": "markdown",
"id": "1820e25e",
"metadata": {
"collapsed": false
},
"source": [
"Utwórzmy instancję klasy `Car`. Zmienne `model`, `color`, `year_of_production` są atrybutami tej klasy. "
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "1712790f",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"car1 = Car()"
]
},
{
"cell_type": "markdown",
"id": "8b93132b",
"metadata": {
"collapsed": false
},
"source": [
"Jeśli chcemy sprawdzić wartość atrybutu konkretnej instancji używamy polecenia `nazwa_instancji.nazwa_atrybutu`."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "72fff4dd",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Ford red 2010\n"
]
}
],
"source": [
"print(car1.model, car1.color, car1.year_of_production)"
]
},
{
"cell_type": "markdown",
"id": "4982fb48",
"metadata": {
"collapsed": false
},
"source": [
"Możemy też sami utworzyć nowy atrybut:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "25713649",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"car1.power = 160"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "93347448",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"160\n"
]
}
],
"source": [
"print(car1.power)"
]
},
{
"cell_type": "markdown",
"id": "6bf8f4d4",
"metadata": {
"collapsed": false
},
"source": [
"Oczywiście jeśli utworzymy nową instancję klasy `Car`, to zostanie ona utworzona według pierwotnego schematu, a więc nie będzie do niej dołączony atrybut `power`."
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "c0920886",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"car2 = Car()"
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "cca0a06f",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Ford red 2010\n"
]
}
],
"source": [
"print(car2.model, car2.color, car2.year_of_production)"
]
},
{
"cell_type": "markdown",
"id": "0d325122",
"metadata": {
"collapsed": false
},
"source": [
"Zgodnie z przewidywaniami, powyższe atrybuty są znane, ale powołanie się na atrybut `power` spowoduje błąd."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "b9900e55",
"metadata": {
"collapsed": false
},
"outputs": [
{
"ename": "AttributeError",
"evalue": "'Car' object has no attribute 'power'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mAttributeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[10], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m \u001b[43mcar2\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mpower\u001b[49m\n",
"\u001b[0;31mAttributeError\u001b[0m: 'Car' object has no attribute 'power'"
]
}
],
"source": [
"car2.power"
]
},
{
"cell_type": "markdown",
"id": "c08125a6",
"metadata": {
"collapsed": false
},
"source": [
"W każdym momencie możemy również sprawdzić jaki jest domyślny atrybut klasy poprzez `nazwa_klasy.nazwa_atrybutu`."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "acb1b40f",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"'red'"
]
},
"execution_count": 11,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"Car.color"
]
},
{
"cell_type": "markdown",
"id": "0a80cd44",
"metadata": {
"collapsed": false
},
"source": [
"Zatem jeśli zmienimy atrybuty instancji `car2`, to powyższa instrukcja pozwala nam łatwo odwołać się do atrybuty klasy."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "fc496196",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Volvo\n",
"Ford\n"
]
}
],
"source": [
"car2.model = 'Volvo'\n",
"print(car2.model)\n",
"print(Car.model)"
]
},
{
"cell_type": "markdown",
"id": "cc389d5f",
"metadata": {
"collapsed": false
},
"source": [
"Utwórzmy teraz klasę, która oprócz atrybutów będzie posiadała także metodę."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "a38f3e3b",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class LinearFunction:\n",
" \n",
" a_coeff = 2\n",
" b_coeff = 3\n",
" \n",
" def count_zeros(self):\n",
" if self.a_coeff == 0:\n",
" if self.b_coeff != 0:\n",
" return 'Brak miejsc zerowych'\n",
" else:\n",
" return 'Każda liczba rzeczywista jest miejscem zerowym'\n",
" else:\n",
" x = (-1) * self.b_coeff / self.a_coeff\n",
" return x"
]
},
{
"cell_type": "markdown",
"id": "d09eaa9c",
"metadata": {
"collapsed": false
},
"source": [
"Użyty powyżej parametr `self` pozwala metodzie odwołać się do utworzonej instancji. Dlatego w poniższym przykładzie instrukcja `l1.count_zeros()` spowoduje wykonanie metody `count_zeros(self)`, gdzie w miejsce parametru `self` zostanie wstawiona obiekt `l1`, czyli instancja, która sama wywołała tę metodę."
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "aff48267",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2\n",
"3\n",
"-1.5\n"
]
}
],
"source": [
"l1 = LinearFunction()\n",
"print(l1.a_coeff)\n",
"print(l1.b_coeff)\n",
"zero = l1.count_zeros()\n",
"print(zero)"
]
},
{
"cell_type": "markdown",
"id": "b2e39f78",
"metadata": {
"collapsed": false
},
"source": [
"Jak poprzednio możemy łatwo zmienić atrybuty instancji prze wykonaniem metody."
]
},
{
"cell_type": "code",
"execution_count": 15,
"id": "244109e2",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"l2 = LinearFunction()"
]
},
{
"cell_type": "code",
"execution_count": 16,
"id": "46dd0e0c",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"l2.a_coeff = 0\n",
"l2.b_coeff = 1"
]
},
{
"cell_type": "code",
"execution_count": 18,
"id": "6362696e",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Brak miejsc zerowych\n"
]
}
],
"source": [
"print(l2.count_zeros())"
]
},
{
"cell_type": "markdown",
"id": "a6dcad49",
"metadata": {
"collapsed": false
},
"source": [
"### Konstruktor\n",
"\n",
"W powyższych przykładach każdorazowe modyfikowanie atrybutów będących współczynnikami funkcji liniowej mogło wydawać się dziwaczne. Najdogodniejszym sposobem tworzenia instancji jest użycie **konstruktora**, czyli specjalnej metody `__init__`, która służy do tworzenia instancji i jest wywoływana automatycznie po utworzeniu przez nas instancji danej klasy. W poprzednim przykładzie właśnie za pomocą konstruktora moglibyśmy nadać wartości atrybutów naszej instancji (konkretnych dla wszystkich obiektów lub przekazanych za pomocą zmiennych).\n",
"\n",
"Można tego dokonać w następujący sposób."
]
},
{
"cell_type": "code",
"execution_count": 19,
"id": "829bd358",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class LinearFunction:\n",
" \n",
" def __init__(self, a, b) :\n",
" self.a_coeff = a\n",
" self.b_coeff = b\n",
" \n",
" def count_zeros(self):\n",
" if self.a_coeff == 0:\n",
" if self.b_coeff != 0:\n",
" return 'Brak miejsc zerowych'\n",
" else:\n",
" return 'Każda liczba rzeczywista jest miejscem zerowym'\n",
" else:\n",
" x = (-1) * self.b_coeff / self.a_coeff\n",
" return x"
]
},
{
"cell_type": "markdown",
"id": "3dec71ca",
"metadata": {
"collapsed": false
},
"source": [
"Zaznaczmy, że argumentami powyższego konstruktora jest `self`, czyli tworzony obiekt, oraz dwie dodatkowe zmienne, które zostaną przypisane wartościom artybutów `a_coeff` i `b_coeff`, odpowiednio. Dlatego utworzenie obiektu tej klasy będzie wymagało podania dokładnie tych dwóch parametrów."
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "1c68eee2",
"metadata": {
"collapsed": false
},
"outputs": [
{
"ename": "TypeError",
"evalue": "LinearFunction.__init__() missing 2 required positional arguments: 'a' and 'b'",
"output_type": "error",
"traceback": [
"\u001b[0;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[0;32mIn[20], line 1\u001b[0m\n\u001b[0;32m----> 1\u001b[0m l3 \u001b[38;5;241m=\u001b[39m \u001b[43mLinearFunction\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
"\u001b[0;31mTypeError\u001b[0m: LinearFunction.__init__() missing 2 required positional arguments: 'a' and 'b'"
]
}
],
"source": [
"l3 = LinearFunction()"
]
},
{
"cell_type": "code",
"execution_count": 21,
"id": "7cc45cd1",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"-1.0\n"
]
}
],
"source": [
"l3 = LinearFunction(1, 1)\n",
"print(l3.count_zeros())"
]
},
{
"cell_type": "markdown",
"id": "9b54f1e3",
"metadata": {
"collapsed": false
},
"source": [
"W przypadku bardziej rozbudowanych klas, jak w poniższym przykładzie, warto użyć tzw. doc-stringów służących do tworzenia dokumentacji. Są to komentarze rozpoczynające się i kończące trzema cudzysłowami. Są one pomocna dla osoby, która będzie czytała nasz kod."
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "af165f2a",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Rectangle:\n",
" \n",
" \"\"\"\n",
" Klasa prostokąt służy do wykonywania operacji na prostokątach.\n",
" \"\"\"\n",
" \n",
" PI = 3.141 # atrybut klasy\n",
" \n",
" def __init__(self, a, b): #konstruktor\n",
" \n",
" \"\"\"\n",
" Aby utworzyć instancję klasy Rectangle musimy podać parametry a i b odpowiadające długości boków, \n",
" które zostaną przypisane atrybutom a i b instancji.\n",
" \"\"\"\n",
" \n",
" self.a = a\n",
" self.b = b\n",
" \n",
" \n",
" def is_square(self): # tej metody nie opiszemy\n",
" return self.a == self.b\n",
" \n",
" def area(self): # tu zostawmy opis\n",
" \"\"\"\n",
" Metoda ta pozwala obliczyć pole prostokąta\n",
" \"\"\"\n",
" return self.a * self.b\n",
" \n",
" def perimeter(self):\n",
" \"\"\"\n",
" Metoda ta pozwala obliczyć obwód prostokąta\n",
" \"\"\"\n",
" return 2*self.a + 2*self.b\n",
" \n",
" def rad_of_circumcircle(self):\n",
" \"\"\"\n",
" Metoda ta pozwala obliczyć promień okręgu opisanego na prostokącie\n",
" \"\"\"\n",
" return 0.5 * (self.a ** 2 + self.b ** 2) ** 0.5\n",
" \n",
" def area_of_circumcircle(self):\n",
" \"\"\"\n",
" Metoda ta pozwala obliczyć pole koła, którego brzeg stanowi okrą opisany na prostokącie\n",
" \"\"\"\n",
" r = self.rad_of_circumcircle()\n",
" return self.PI * r ** 2\n",
" \n",
" "
]
},
{
"cell_type": "markdown",
"id": "3b232bab",
"metadata": {
"collapsed": false
},
"source": [
"Wówczas możemy użyć polecenia `help(nazwa_klasy)` w celu wyświetlenia wszystkich metod tej klasy wraz z ich opisami."
]
},
{
"cell_type": "code",
"execution_count": 34,
"id": "c9a5aa88",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Help on class Rectangle in module __main__:\n",
"\n",
"class Rectangle(builtins.object)\n",
" | Rectangle(a, b)\n",
" | \n",
" | Klasa prostokąt służy do wykonywania operacji na prostokątach.\n",
" | \n",
" | Methods defined here:\n",
" | \n",
" | __init__(self, a, b)\n",
" | Aby utworzyć instancję klasy Rectangle musimy podać parametry a i b odpowiadające długości boków, \n",
" | które zostaną przypisane atrybutom a i b instancji.\n",
" | \n",
" | area(self)\n",
" | Metoda ta pozwala obliczyć pole prostokąta\n",
" | \n",
" | area_of_circumcircle(self)\n",
" | Metoda ta pozwala obliczyć pole koła, którego brzeg stanowi okrą opisany na prostokącie\n",
" | \n",
" | is_square(self)\n",
" | \n",
" | perimeter(self)\n",
" | Metoda ta pozwala obliczyć obwód prostokąta\n",
" | \n",
" | rad_of_circumcircle(self)\n",
" | Metoda ta pozwala obliczyć promień okręgu opisanego na prostokącie\n",
" | \n",
" | ----------------------------------------------------------------------\n",
" | Data descriptors defined here:\n",
" | \n",
" | __dict__\n",
" | dictionary for instance variables (if defined)\n",
" | \n",
" | __weakref__\n",
" | list of weak references to the object (if defined)\n",
" | \n",
" | ----------------------------------------------------------------------\n",
" | Data and other attributes defined here:\n",
" | \n",
" | PI = 3.141\n",
"\n"
]
}
],
"source": [
"help(Rectangle)"
]
},
{
"cell_type": "code",
"execution_count": 35,
"id": "7f739491",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Help on class LinearFunction in module __main__:\n",
"\n",
"class LinearFunction(builtins.object)\n",
" | LinearFunction(a, b)\n",
" | \n",
" | Methods defined here:\n",
" | \n",
" | __init__(self, a, b)\n",
" | Initialize self. See help(type(self)) for accurate signature.\n",
" | \n",
" | count_zeros(self)\n",
" | \n",
" | ----------------------------------------------------------------------\n",
" | Data descriptors defined here:\n",
" | \n",
" | __dict__\n",
" | dictionary for instance variables (if defined)\n",
" | \n",
" | __weakref__\n",
" | list of weak references to the object (if defined)\n",
"\n"
]
}
],
"source": [
"help(LinearFunction)"
]
},
{
"cell_type": "markdown",
"id": "43a573d8",
"metadata": {
"collapsed": false
},
"source": [
"Widzimy różnicę w opisie klas LinearFunction i Rectangle oraz jak pomocna może być prosta dokumentacja.\n",
"\n",
"Poniżej zobaczmy przykładowe wywołanie metod klasy `Rectangle`."
]
},
{
"cell_type": "code",
"execution_count": 36,
"id": "c29fcd05",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"prost1 = Rectangle(3,4)"
]
},
{
"cell_type": "code",
"execution_count": 37,
"id": "836f183b",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"3"
]
},
"execution_count": 37,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"prost1.a"
]
},
{
"cell_type": "code",
"execution_count": 38,
"id": "eda665e8",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"4"
]
},
"execution_count": 38,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"prost1.b"
]
},
{
"cell_type": "code",
"execution_count": 39,
"id": "31cd698d",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"12"
]
},
"execution_count": 39,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"prost1.area()"
]
},
{
"cell_type": "code",
"execution_count": 40,
"id": "ec08f622",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"14"
]
},
"execution_count": 40,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"prost1.perimeter()"
]
},
{
"cell_type": "code",
"execution_count": 41,
"id": "6cecdca8",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"19.63125"
]
},
"execution_count": 41,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"prost1.area_of_circumcircle()"
]
},
{
"cell_type": "code",
"execution_count": 42,
"id": "71082d77",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"3.141"
]
},
"execution_count": 42,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"Rectangle.PI"
]
},
{
"cell_type": "markdown",
"id": "2c4b2c3d",
"metadata": {
"collapsed": false
},
"source": [
"Kolejny przykład pokazuje, że atrybuty klasy (i konkretnej instancji) możemy kontrolować metodami (w tym przypadku konstruktorem). Zauważmy, że każdorazowe utworzenie elementu wiąże się z wywołaniem konstruktora. Możemy to wykorzystać do zliczania liczby utworzonych elementów. Zauważmy, że mimo tej samej nazwy czym innym jest **atrybut klasy** o nazwie `najtansze`, a czym innym **atrybut instancji** o tej samej nazwie. Atrybut klasy pozwala z poziomu klasy znaleźć cenę najtańszych butów (spośród wszystkich do tej pory utworzonych), atrybut instancji jest parametrem logicznym pozwalającym stwierdzić, czy ta konkretna instancja reprezentuje buty o najniższej cenie (do której mamy dostęp dzięki atrybutowi cena)."
]
},
{
"cell_type": "code",
"execution_count": 43,
"id": "864645ee",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Buty:\n",
" \n",
" ilosc = 0 \n",
" najtansze = None\n",
" najdrozsze = None # tego nie będziemy używać\n",
" \n",
" def __init__(self, cena, rodzaj, kolor):\n",
" self.cena = cena\n",
" self.kolor = kolor\n",
" self.rodzaj = rodzaj\n",
" Buty.ilosc += 1 # ta instrukcja aktualizuje atrybut klasy przy każdym utworzeniu instancji tej klasy\n",
" # poniższa instrukcja if określa czy tworzona instancja jest najtańszą ze wszystkich utworzonych\n",
" if Buty.najtansze == None: \n",
" self.najtansze = True\n",
" Buty.najtansze = self.cena\n",
" else:\n",
" if Buty.najtansze >= self.cena:\n",
" self.najtansze = True\n",
" Buty.najtansze = self.cena\n",
" else:\n",
" self.najtansze = False "
]
},
{
"cell_type": "code",
"execution_count": 44,
"id": "4245c988",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"buty1 = Buty(99.99, 'Trampki', 'Czarne')"
]
},
{
"cell_type": "code",
"execution_count": 45,
"id": "aa241d5a",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"buty2 = Buty(199.99, 'Glany', 'Różowe')"
]
},
{
"cell_type": "code",
"execution_count": 46,
"id": "e7d0c84d",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"2"
]
},
"execution_count": 46,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"Buty.ilosc"
]
},
{
"cell_type": "code",
"execution_count": 47,
"id": "fba1b0d9",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"99.99\n"
]
}
],
"source": [
"print(Buty.najtansze)"
]
},
{
"cell_type": "code",
"execution_count": 48,
"id": "742809e7",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"True"
]
},
"execution_count": 48,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"buty1.najtansze"
]
},
{
"cell_type": "code",
"execution_count": 49,
"id": "da268e34",
"metadata": {
"collapsed": false
},
"outputs": [
{
"data": {
"text/plain": [
"False"
]
},
"execution_count": 49,
"metadata": {
},
"output_type": "execute_result"
}
],
"source": [
"buty2.najtansze"
]
},
{
"cell_type": "markdown",
"id": "c1eb5a1a",
"metadata": {
"collapsed": false
},
"source": [
"## Dziedziczenie\n",
"\n",
"Dziedziczenie polega na definiowaniu podklas danej klasy, które domyślnie posiadają te same (co klasa macierzysta) atrybuty i metody, do których możemy dodać nowe (atrybuty i metody) lub je zmodyfikować.\n",
"\n",
"W Pythonie definiowanie podklasy polega na umieszczeniu po słowie `class` nazwy definiowanej podklasy, a następnie w nawiasie nazwy klasy macierzystej. \n",
"\n",
"Dla przykładu, jeśli mamy zdefiniowaną klasę `Vehicle` o atrybutach `brand`, `color`, `year` (tworzone są one przez konstruktor), to podklasę `Car` definiujemy następująco.\n"
]
},
{
"cell_type": "code",
"execution_count": 50,
"id": "bf28e181",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Atrybuty obiektu vehicle to: {'brand': 'Tesla', 'color': 'red', 'year': 2020}\n",
"Atrybuty obiektu car to: {'brand': 'Tesla', 'color': 'red', 'year': 2020}\n",
"move\n",
"I can move\n"
]
}
],
"source": [
"class Vehicle:\n",
" action = \"move\"\n",
" def __init__(self, brand, color, year):\n",
" self.brand = brand\n",
" self.color = color\n",
" self.year = year\n",
" def intro(self):\n",
" print(\"I can\", self.action)\n",
" \n",
" \n",
"class Car(Vehicle):\n",
" pass # po dwukropku konieczna jest co najmniej jedna instrukcja\n",
" \n",
" \n",
"vehicle = Vehicle('Tesla', 'red', 2020)\n",
"print(\"Atrybuty obiektu vehicle to:\", vehicle.__dict__) #atrybut __dict__ jest zawsze tworzony z instancją, umożliwia podgląd atrybutów instancji tworzonych przez konstruktor\n",
"\n",
"car = Car('Tesla', 'red', 2020)\n",
"\n",
"print(\"Atrybuty obiektu car to:\", car.__dict__)\n",
"print(car.action)\n",
"car.intro()"
]
},
{
"cell_type": "markdown",
"id": "be638bd4",
"metadata": {
"collapsed": false
},
"source": [
"Jak widzimy podklasa `car` dziedziczy wszystkie atrybuty klasy `Vehicle` oraz jej metody wraz z konstruktorem.\n",
"\n",
"Oczywiście podczas tworzenia podklasy możemy modyfikować atrybuty."
]
},
{
"cell_type": "code",
"execution_count": 51,
"id": "fc9232e1",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Atrybuty obiektu vehicle to: {'brand': 'Tesla', 'color': 'red', 'year': 2020}\n",
"Atrybuty obiektu car to: {'brand': 'Tesla', 'color': 'red', 'year': 2020}\n",
"drive\n",
"I can drive\n"
]
}
],
"source": [
"class Vehicle:\n",
" action = \"move\"\n",
" def __init__(self, brand, color, year):\n",
" self.brand = brand\n",
" self.color = color\n",
" self.year = year\n",
" def intro(self):\n",
" print(\"I can\", self.action)\n",
" \n",
" \n",
"class Car(Vehicle):\n",
" action = \"drive\" \n",
" \n",
" \n",
"vehicle = Vehicle('Tesla', 'red', 2020)\n",
"print(\"Atrybuty obiektu vehicle to:\", vehicle.__dict__) #atrybut __dict__ jest zawsze tworzony z instancją, umożliwia podgląd atrybutów instancji tworzonych przez konstruktor\n",
"\n",
"car = Car('Tesla', 'red', 2020)\n",
"\n",
"print(\"Atrybuty obiektu car to:\", car.__dict__)\n",
"print(car.action)\n",
"car.intro()"
]
},
{
"cell_type": "markdown",
"id": "22221ca5",
"metadata": {
"collapsed": false
},
"source": [
"Jeśli zdecydujemy się użyć innego konstruktora podklasy `(__init__(self, parametry_konstruktora_podklasy))` oraz chcemy wykorzystać konstruktor nadklasy, to możemy odwołać się do niej poprzez `super().__init__(parametry_konstruktora_nadklasy)`."
]
},
{
"cell_type": "code",
"execution_count": 52,
"id": "cc5142f4",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Atrybuty obiektu vehicle to: {'brand': 'Tesla', 'color': 'red', 'year': 2020}\n",
"Atrybuty obiektu car to: {'brand': 'Tesla', 'color': 'red', 'year': 2020, 'horsepower': 300}\n"
]
}
],
"source": [
"class Vehicle:\n",
" action = \"move\"\n",
" def __init__(self, brand, color, year):\n",
" self.brand = brand\n",
" self.color = color\n",
" self.year = year\n",
" def intro(self):\n",
" print(\"I can\", self.action)\n",
" \n",
" \n",
"class Car(Vehicle):\n",
" action = \"drive\" \n",
" def __init__(self, brand, color, year, horsepower): # nowy atrybut - horsepower - w podklasie\n",
" super().__init__(brand, color, year) # atrybuty dziedziczone z nadklasy zostaną ustawione dzięki konstruktorowi z nadklasy\n",
" self.horsepower = horsepower # osobno definiujemy wartość nowego atrybutu\n",
" \n",
" \n",
"vehicle = Vehicle('Tesla', 'red', 2020)\n",
"print(\"Atrybuty obiektu vehicle to:\", vehicle.__dict__) #atrybut __dict__ jest zawsze tworzony z instancją, umożliwia podgląd atrybutów instancji tworzonych przez konstruktor\n",
"\n",
"car = Car('Tesla', 'red', 2020, 300)\n",
"\n",
"print(\"Atrybuty obiektu car to:\", car.__dict__)"
]
},
{
"cell_type": "markdown",
"id": "99ca9ba0",
"metadata": {
"collapsed": false
},
"source": [
"Podobnie możemy tworzyć nowe metody korzystając z metod z nadklasy."
]
},
{
"cell_type": "code",
"execution_count": 53,
"id": "1aa8b1d1",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"I can move\n",
"I am a car and I can drive\n"
]
}
],
"source": [
"class Vehicle:\n",
" action = \"move\"\n",
" def __init__(self, brand, color, year):\n",
" self.brand = brand\n",
" self.color = color\n",
" self.year = year\n",
" def intro(self):\n",
" print(\"I can\", self.action)\n",
" \n",
" \n",
"class Car(Vehicle):\n",
" action = \"drive\" \n",
" def __init__(self, brand, color, year, horsepower): # nowy atrybut - horsepower - w podklasie\n",
" super().__init__(brand, color, year) # atrybuty dziedziczone z nadklasy zostaną ustawione dzięki konstruktorowi z nadklasy\n",
" self.horsepower = horsepower # osobno definiujemy wartość nowego atrybutu\n",
" def intro(self):\n",
" print(\"I am a car and\", end=\" \")\n",
" super().intro()\n",
" \n",
" \n",
"vehicle = Vehicle('Tesla', 'red', 2020)\n",
"car = Car('Tesla', 'red', 2020, 300)\n",
"\n",
"vehicle.intro()\n",
"car.intro()"
]
},
{
"cell_type": "markdown",
"id": "667f0984",
"metadata": {
"collapsed": false
},
"source": [
"## Metody specjalne\n",
"\n",
"Python pozwala nam również na definiowanie w jaki sposób znane funkcje lub operatory powinny być wykonywane na nowo tworzonych obiektach danej klasy. Służą do tego tzw. metody specjalne. Dokładną listę wszystkich metod specjalnych można znaleźć np. tutaj https://docs.python.org/pl/3/reference/datamodel.html#special-method-names. My jednak skupimy się tylko na wybranych metodach specjalnych."
]
},
{
"cell_type": "markdown",
"id": "243cac96",
"metadata": {
"collapsed": false
},
"source": [
"### Metoda `__str__(self)`\n",
"\n",
"Metoda ta pozwala nam określić w jaki sposób ma zostać wykonana funckja `print`, której argumentem będzie obiekt utworzonej przez nas klasy. Należy pamiętać, że metoda ta powinna zwracać zmienną typu `str`, którą wyświetli funkcja `print`. Przykładowo, można w następujący sposób zdefiniować funckję `print` od instancji klasy `Rectangle`."
]
},
{
"cell_type": "code",
"execution_count": 54,
"id": "dc2cdc95",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Rectangle:\n",
" \n",
" \"\"\"\n",
" Klasa prostokąt służy do wykonywania operacji na prostokątach.\n",
" \"\"\"\n",
" \n",
" PI = 3.141 # atrybut klasy\n",
" \n",
" def __init__(self, a, b): #konstruktor\n",
" \n",
" \"\"\"\n",
" Aby utworzyć instancję klasy Rectangle musimy podać parametry a i b odpowiadające długości boków, \n",
" które zostaną przypisane atrybutom a i b instancji.\n",
" \"\"\"\n",
" \n",
" self.a = a\n",
" self.b = b\n",
" \n",
" def __str__(self):\n",
" \n",
" \"\"\"\n",
" Pozwala nam określić jak ma zachować się funkcja print\n",
" \"\"\"\n",
" \n",
" return \"Prostokąt o bokach \" + str(self.a) + \" i \" + str(self.b) # znak + oznacza tutaj konkatenację łańcuchów znaków\n",
" \n"
]
},
{
"cell_type": "code",
"execution_count": 55,
"id": "2f92d3c1",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prostokąt o bokach 3 i 4\n"
]
}
],
"source": [
"rect1 = Rectangle(3, 4)\n",
"print(rect1)"
]
},
{
"cell_type": "markdown",
"id": "23a491ca",
"metadata": {
"collapsed": false
},
"source": [
"### Operatory binarne\n",
"\n",
"Załóżmy teraz, że chcemy zinterpretować w jaki sposób operator `+` ma zachowywać się dla obiektów typy `Rectangle`. Do tego celu służy metoda specjalna `__mul__(self,other)`. Mianowicie w momencie napotkania przez interpreter Pythona instrukcji `x + y`, gdzie `x` jest obiektem posiadającym zdefiniowaną metodę `__mul__` zostanie wykonana metoda `__mul__(x, y)`."
]
},
{
"cell_type": "code",
"execution_count": 56,
"id": "5c61f95a",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Rectangle:\n",
" \n",
" \"\"\"\n",
" Klasa prostokąt służy do wykonywania operacji na prostokątach.\n",
" \"\"\"\n",
" \n",
" PI = 3.141 # atrybut klasy\n",
" \n",
" def __init__(self, a, b): #konstruktor\n",
" \n",
" \"\"\"\n",
" Aby utworzyć instancję klasy Rectangle musimy podać parametry a i b odpowiadające długości boków, \n",
" które zostaną przypisane atrybutom a i b instancji.\n",
" \"\"\"\n",
" \n",
" self.a = a\n",
" self.b = b\n",
" \n",
" def __str__(self):\n",
" \n",
" \"\"\"\n",
" Pozwala nam określić jak ma zachować się funkcja print\n",
" \"\"\"\n",
" \n",
" return \"Prostokąt o bokach \" + str(self.a) + \" i \" + str(self.b) # znak + oznacza tutaj konkatenację łańcuchów znaków\n",
" \n",
" def __mul__(self, other):\n",
" if isinstance(other, int) :\n",
" return Rectangle(self.a*other, self.b*other)\n",
" else:\n",
" print(\"Działanie nie jest zdefiniowane\")\n",
" return self"
]
},
{
"cell_type": "code",
"execution_count": 57,
"id": "238dfccd",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prostokąt o bokach 6 i 8\n",
"Działanie nie jest zdefiniowane\n"
]
}
],
"source": [
"rect1 = Rectangle(3, 4)\n",
"rect2 = rect1*2\n",
"print(rect2)\n",
"rect3 = rect1*0.5"
]
},
{
"cell_type": "markdown",
"id": "b0c1bf83",
"metadata": {
"collapsed": false
},
"source": [
"**Uwaga:** W powyższym kodzie zastosowaliśmy funkcję `isinstance`, która sprawdza, czy obiekt będący pierwszym argumentem jest instancją klasy stojącej jako drugi argument."
]
},
{
"cell_type": "markdown",
"id": "be812534",
"metadata": {
"collapsed": false
},
"source": [
"Podobnie można wykorzystać również metodę specjalną `__rmul__(self, other)`, która jest tzw. prawostronnym mnożeniem. Mianowicie w momencie napotkania przez interpreter Pythona instrukcji `x + y`, gdzie `y` jest obiektem posiadającym zdefiniowaną metodę `__rmul__` zostanie wykonana metoda `__rmul__(x, y)`."
]
},
{
"cell_type": "code",
"execution_count": 58,
"id": "0d02a8b9",
"metadata": {
"collapsed": false
},
"outputs": [
],
"source": [
"class Rectangle:\n",
" \n",
" \"\"\"\n",
" Klasa prostokąt służy do wykonywania operacji na prostokątach.\n",
" \"\"\"\n",
" \n",
" PI = 3.141 # atrybut klasy\n",
" \n",
" def __init__(self, a, b): #konstruktor\n",
" \n",
" \"\"\"\n",
" Aby utworzyć instancję klasy Rectangle musimy podać parametry a i b odpowiadające długości boków, \n",
" które zostaną przypisane atrybutom a i b instancji.\n",
" \"\"\"\n",
" \n",
" self.a = a\n",
" self.b = b\n",
" \n",
" def __str__(self):\n",
" \n",
" \"\"\"\n",
" Pozwala nam określić jak ma zachować się funkcja print\n",
" \"\"\"\n",
" \n",
" return \"Prostokąt o bokach \" + str(self.a) + \" i \"+str(self.b) # znak + oznacza tutaj konkatenację łańcuchów znaków\n",
" \n",
" def __rmul__(self, other):\n",
" if isinstance(other, int) :\n",
" return Rectangle(self.a*other, self.b*other)\n",
" else:\n",
" print(\"Działanie nie jest zdefiniowane\")\n",
" return self"
]
},
{
"cell_type": "code",
"execution_count": 59,
"id": "63d59bad",
"metadata": {
"collapsed": false
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Prostokąt o bokach 6 i 8\n",
"Działanie nie jest zdefiniowane\n"
]
}
],
"source": [
"rect1 = Rectangle(3, 4)\n",
"rect2 = 2*rect1\n",
"print(rect2)\n",
"\n",
"rect3 = 0.5*rect1\n",
"\n",
"#rect4 = rect1 * 2 # ta instrukcja zwróci błąd"
]
},
{
"cell_type": "markdown",
"id": "3947fdaf",
"metadata": {
"collapsed": false
},
"source": [
"Innymi często definiowanymi operatorami binarnymi są:\n",
" - `__add__(self,other)` - pozwala zdefiniować operator dodawania\n",
" - `__radd__(self,other)` - pozwala zdefiniować operator dodawania prawostronnego\n",
" - `__sub__(self,other)` - pozwala zdefiniować operator odejmowania\n",
" - `__rsub__(self,other)` - pozwala zdefiniować operator odejmowania prawostronnego\n",
" - `__truediv__(self,other)` - pozwala zdefiniować operator `/`\n",
" - `__floordiv__(self,other)` - pozwala zdefiniować operator `//`\n",
" - `__mod__(self,other)` - pozwala zdefiniować operator `%`\n",
" - `__eq__(self,other)` - pozwala zdefiniować operator `==`\n",
" - `__ne__(self,other)` - pozwala zdefiniować operator `!=`\n",
" - `__le__(self,other)` - pozwala zdefiniować operator `<=`\n",
" - `__ge__(self,other)` - pozwala zdefiniować operator `>=`\n",
" - `__lt__(self,other)` - pozwala zdefiniować operator `<`\n",
" - `__gt__(self,other)` - pozwala zdefiniować operator `>`\n",
" "
]
},
{
"cell_type": "markdown",
"id": "662cbee0",
"metadata": {
"collapsed": false
},
"source": [
"**Zadanie 4.** Uzupełnij powyższą definicję klasy `Rectangle` tak, aby można było wykonywać następujące operatory:\n",
" - `x==y`, gdzie `x` i `y` są instancjami klasy `Rectangle` oraz operator powinien zwrócić `True` lub `False` w zależności, czy prostokąty są identyczne, czy nie.\n",
" - `x!=y`, gdzie `x` i `y` są instancjami klasy `Rectangle` oraz operator powinien zadziałać odwrotnie do `x==y`\n",
" - `x+y`, gdzie `x` jest instancją klasy `Rectangle` oraz `y` jest zmienną typu `int` lub `float`. Wynik powinien zwrócić obiekt klasy `Rectangle`, którego bok `b` został zwiększony o `y`\n",
" - `x+y`, gdzie `y` jest instancją klasy `Rectangle` oraz `x` jest zmienną typu `int` lub `float`. Wynik powinien zwrócić obiekt klasy `Rectangle`, którego bok `a` został zwiększony o `x`\n",
" - `x<y`, gdzie `x` i `y` są instancjami klasy `Rectangle` oraz operator powinien zwrócić `True`, gdy pole powierzchni prostokąta `x` jest mniejsze niż prostokąta `y`\n",
" - `x/y`, gdzie `x` jest instancją klasy `Rectangle` oraz `y` jest zmienną typu `int` lub `float`. Wynik powinien zwrócić obiekt klasy `Rectangle`, którego boki zostały podzielone przez `y`"
]
},
{
"cell_type": "markdown",
"id": "e13b3618",
"metadata": {
"collapsed": false
},
"source": [
]
},
{
"cell_type": "markdown",
"id": "aefe8ea5",
"metadata": {
"collapsed": false
},
"source": [
]
},
{
"cell_type": "markdown",
"id": "cd22e657",
"metadata": {
"collapsed": false
},
"source": [
]
}
],
"metadata": {
"kernelspec": {
"argv": [
"/usr/bin/python3",
"-m",
"ipykernel",
"--HistoryManager.enabled=False",
"--matplotlib=inline",
"-c",
"%config InlineBackend.figure_formats = set(['retina'])\nimport matplotlib; matplotlib.rcParams['figure.figsize'] = (12, 7)",
"-f",
"{connection_file}"
],
"display_name": "Python 3 (system-wide)",
"env": {
},
"language": "python",
"metadata": {
"cocalc": {
"description": "Python 3 programming language",
"priority": 100,
"url": "https://www.python.org/"
}
},
"name": "python3",
"resource_dir": "/ext/jupyter/kernels/python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.12"
}
},
"nbformat": 4,
"nbformat_minor": 4
}