Klasy
Definicję klasy zaczynamy od słowa kluczowego class
, a następnie podajemy nazwę klasy, zazwyczaj zaczynając od wielkiej litery. Jeżeli klasa po czymś dziedziczy, to podajemy to wewnątrz nawiasów okrągłych po nazwie klasy.
Klasa abstrakcyjna
Aby zdefiniować klasę abstrakcyjną, której obiektu nie możemy utworzyć, ale możemy ją wykorzystywać w dziedziczeniu, skorzystamy z klasy bazowej ABC
z modułu abc
. Z tego samego modułu przydatny będzie także dekorator abstractmethod
za pomocą którego możemy oznaczyć wybraną metodę klasy abstrakcyjnej jako abstrakcyjną, która musi zostać zaimplementowana w klasie pochodnej.
| from abc import ABC, abstractmethod
class Figure(ABC):
"""Klasa abstrakcyjna reprezentująca figurę.
Aby utworzyć klasę abstrakcyjną, dziedziczymy po specjalnej klasie ABC, którą należy zaimportować z pakietu abc.
"""
@abstractmethod
def area(self) -> float:
"""Metoda obliczająca pole figury.
Metoda abstrakcyjna.
Metody abstrakcyjne oznaczamy za pomocą dekoratora @abstractmethod z pakietu abc.
Muszą zostać przeciążone w klasie potomnej.
"""
pass
@abstractmethod
def perimeter(self) -> float:
"""Metoda obliczająca obwód figury.
Metoda abstrakcyjna.
"""
pass
if __name__ = "__main__":
figure = Figure() # BŁĄD! Nie możemy tworzyć obiektu klasy abstrakcyjnej
|
Dziedziczenie
Standardowe klasy powinny mieć zdefiniowany własny konstruktor/inicjalizator: metodę __init__
.
Do każdej metody w klasie przekazujemy jako pierwszy parametr self
, który jest odniesieniem do obiektu klasy, na której pracujemy (podobnie jak this
w niektórych językach).
| from random import randint
from math import pi, sin, radians
class Quad(Figure):
"""Klasa reprezentująca czworokąt.
Dziedziczy po klasie Figure.
"""
def __init__(self, side1: float, side2: float, side3: float, side4: float, diagonal1: float, diagonal2: float, angle: float):
"""Konstruktor klasy.
Argumenty:
side1, side2, side3, side4 - długości boków
diagonal1, diagonal2 - długości przekątnych
alpha - kąt pomiędzy przekątnymi
"""
# Zmienne zaczynające się od pojedyńczego znaku podłogi traktowane są umownie jako protected.
self._sides = [side1, side2, side3, side4]
# Zmienne zaczynające się od podwójnego znaku podłogi traktowane są umownie jako prywatne.
self.__d1 = diagonal1
self.__d2 = diagonal2
self.__angle = angle
def area(self) -> float:
"""Metoda obliczająca pole czworokąta na podstawie długości przekątnych i kąta alpha pomiędzy nimi.
Metoda publiczna, dziedziczona przez potomków
"""
return (self.__d1 * self.__d2 * sin(radians(self.__angle))) / 2
def perimeter(self) -> float:
"""Metoda obliczająca obwód figury jako sumę długości boków.
Metoda publiczna, dziedziczona przez potomków.
"""
return sum(self._sides)
|
Atrybuty klasy
Zmienne wewnątrz klasy, których nazwa zaczyna się od podwójnego znaku podłogi, uznajemy za prywatne. Zmienne te nie mogą być użyte na zewnątrz klasy.
Pozostałe zmienne są generalnie publiczne, chociaż te, których nazwa zaczyna się od pojedyńczego znaku podłogi uznajemy za protected, tzn. dostępne tylko w klasach pochodnych.
Aby mieć kontrolę nad dostępem do zmiennych w obiekcie, możemy skorzystać z dekoratorów property
oraz setter
odpowiednio do tworzenia atrybutów do odczytu i zapisu.
| class Rectangle(Quad):
"""Klasa reprezentująca prostokąt.
Dziedziczy po klasie Quad reprezentującej czworokąt, jako że prostokąt jest czworokątem.
"""
def __init__(self, height: float, width: float):
"""Konstruktor klasy.
Jako że klasa Rectangle dziedziczy po klasie Quad wymagane jest wywołanie konstruktora klasy rodzica.
Wykonujemy to pisząc super().__init__(argumenty).
Argumenty:
height - wysokość prostokąta
width - szerokość prostokąta
"""
super().__init__(height, width, height, width, 0, 0, 0)
@property
def height(self) -> float:
"""Wysokość prostokąta. Getter.
Atrybut klasy do odczytu. Działa podobnie jak getter z Javy.
"""
return self._sides[0]
@height.setter
def height(self, value: float):
"""Wysokość prostokąta. Setter.
Atrybut klasy do zapisu. Działa podobnie jak setter z Javy.
"""
self._sides[0] = value
self._sides[2] = value
@property
def width(self) -> float:
"""Szerokość prostokąta. Getter."""
return self._sides[1]
@width.setter
def width(self, value: float):
"""Szerokość prostokąta. Setter."""
self._sides[1] = value
self._sides[3] = value
def area(self) -> float:
"""Metoda obliczająca pole prostokąta przemnażając wysokość i szerokość."""
return self.width * self.height
|
| class Square(Rectangle):
"""Klasa reprezentująca kwadrat.
Dziedziczy po klasie Rectangle reprezentującej prostokąt, jako że kwadrat jest prostokątem.
"""
def __init__(self, size: float):
"""Konstruktor klasy.
Jako że klasa Square dziedziczy po klasie Rectangle wymagane jest wywołanie konstruktora klasy rodzica.
Wykonujemy to pisząc super().__init__(argumenty).
Argumenty:
size - długość boku
"""
super().__init__(size, size)
class Ellipse(Figure):
"""Klasa reprezentująca elipsę.
Dziedziczy po klasie Figure.
"""
def __init__(self, radius1: float, radius2: float):
"""
Konstruktor klasy.
Jako że klasa Ellipse dziedziczy po klasie abstrakcyjnej Figure, nie wywołujemy konstruktora klasy rodzica.
Argumenty:
radius1 - pierwszy promień elipsy
radius2 - drugi promień elipsy
"""
self._radius1 = radius1
self._radius2 = radius2
def area(self):
"""Metoda obliczająca pole elipsy. Nie została zaimplementowana."""
raise NotImplemented
def perimeter(self):
"""Metoda obliczająca obwód elipsy. Nie została zaimplementowana."""
raise NotImplemented
class Circle(Ellipse):
"""Klasa reprezentująca koło.
Dziedziczy po klasie Ellipse reprezentującej elipsę, jako że koło jest też elipsą.
"""
def __init__(self, radius: float):
"""Konstruktor klasy.
Jako że klasa Circle dziedziczy po klasie Ellipse wymagane jest wywołanie konstruktora klasy rodzica.
Wykonujemy to pisząc super().__init__(argumenty).
Argumenty:
radius - promień koła
"""
super().__init__(radius, radius)
@classmethod
def one(cls):
"""Metoda klasowa tworząca koło o promieniu 1.
Taki sposób pozwala na utworzenie dodatkowych "konstruktorów".
"""
return cls(1)
@property
def radius(self):
"""Promień koła. Getter.
Wartość tylko do odczytu, nie ma metody setter.
"""
return self._radius1
def area(self) -> float:
"""Metoda obliczająca pole koła."""
return pi * (self.radius ** 2)
def perimeter(self) -> float:
"""Metoda obliczająca obwód koła."""
return 2 * pi * self.radius
if __name__ == "__main__":
# figure = Figure() - Błąd, nie możemy utworzyć obiektu klasy z abstrakcyjnymi metodami
quad = Quad(1, 2, 3, 4, 5, 6, 30)
rectangle = Rectangle(1, 2)
square = Square(1)
ellipse = Ellipse(1, 2)
circle = Circle(5)
quad.__d1 = 10 # Działa, ale nie jest poprawne, __d1 jest prywatne
print(f"quad.__d1 = {quad.__d1}") # Działa, ale nie jest poprawne, __d1 jest prywatne
quad._sides[0] = 10 # Działa, ale nie jest poprawne, _sides jest protected
print(f"quad._sides[0] = {quad._sides[0]}") # Działa, ale nie jest poprawne, _sides jest protected
rectangle.width = 10 # Działa
print(f"rectangle.width = {rectangle.width}") # Działa
# circle.radius = 10 # Nie działa, nie ma settera
print(f"circle.radius = {circle.radius}") # Działa
circle_one = Circle.one() # Wywołanie metody klasowej
print(f"kwadrat jest figurą: {isinstance(square, Figure)}")
print(f"kwadrat jest czworokątem: {isinstance(square, Quad)}")
print(f"kwadrat jest prostokątem: {isinstance(square, Rectangle)}")
print(f"kwadrat jest kwadratem: {isinstance(square, Square)}")
print(f"kwadrat jest elipsą: {isinstance(square, Ellipse)}")
print(f"kwadrat jest kołem: {isinstance(square, Circle)}")
print(f"kwadrat jest typu: {type(square)}")
selection = randint(1, 5)
figure = None
if selection == 1:
figure = Quad(1, 2, 3, 4, 6, 7, 30)
elif selection == 2:
figure = Rectangle(1, 2)
elif selection == 3:
figure = Square(1)
elif selection == 4:
figure = Ellipse(1, 2)
elif selection == 5:
figure = Circle(1)
print(f"Wylosowana figura jest typu: {type(figure)}")
if isinstance(figure, Rectangle):
# Sprawdzamy, czy figure jest prostokątem, a jeśli tak, to wypisujemy jego wysokość.
# Przy takim podejściu środowisko będzie podpowiadać metody dostępne w klasie Rectangle i traktować figure jako Rectangle.
print(f"Wysokość: {figure.height}")
|