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.
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.
fromabcimportABC,abstractmethodclassFigure(ABC):"""Klasa abstrakcyjna reprezentująca figurę. Aby utworzyć klasę abstrakcyjną, dziedziczymy po specjalnej klasie ABC, którą należy zaimportować z pakietu abc. """@abstractmethoddefarea(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@abstractmethoddefperimeter(self)->float:"""Metoda obliczająca obwód figury. Metoda abstrakcyjna. """passif__name__="__main__":figure=Figure()# BŁĄD! Nie możemy tworzyć obiektu klasy abstrakcyjnej
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).
fromrandomimportrandintfrommathimportpi,sin,radiansclassQuad(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=diagonal1self.__d2=diagonal2self.__angle=angledefarea(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)))/2defperimeter(self)->float:"""Metoda obliczająca obwód figury jako sumę długości boków. Metoda publiczna, dziedziczona przez potomków. """returnsum(self._sides)
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.
classRectangle(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)@propertydefheight(self)->float:"""Wysokość prostokąta. Getter. Atrybut klasy do odczytu. Działa podobnie jak getter z Javy. """returnself._sides[0]@height.setterdefheight(self,value:float):"""Wysokość prostokąta. Setter. Atrybut klasy do zapisu. Działa podobnie jak setter z Javy. """self._sides[0]=valueself._sides[2]=value@propertydefwidth(self)->float:"""Szerokość prostokąta. Getter."""returnself._sides[1]@width.setterdefwidth(self,value:float):"""Szerokość prostokąta. Setter."""self._sides[1]=valueself._sides[3]=valuedefarea(self)->float:"""Metoda obliczająca pole prostokąta przemnażając wysokość i szerokość."""returnself.width*self.height
classSquare(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)classEllipse(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=radius1self._radius2=radius2defarea(self):"""Metoda obliczająca pole elipsy. Nie została zaimplementowana."""raiseNotImplementeddefperimeter(self):"""Metoda obliczająca obwód elipsy. Nie została zaimplementowana."""raiseNotImplementedclassCircle(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)@classmethoddefone(cls):"""Metoda klasowa tworząca koło o promieniu 1. Taki sposób pozwala na utworzenie dodatkowych "konstruktorów". """returncls(1)@propertydefradius(self):"""Promień koła. Getter. Wartość tylko do odczytu, nie ma metody setter. """returnself._radius1defarea(self)->float:"""Metoda obliczająca pole koła."""returnpi*(self.radius**2)defperimeter(self)->float:"""Metoda obliczająca obwód koła."""return2*pi*self.radiusif__name__=="__main__":# figure = Figure() - Błąd, nie możemy utworzyć obiektu klasy z abstrakcyjnymi metodamiquad=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 prywatneprint(f"quad.__d1 = {quad.__d1}")# Działa, ale nie jest poprawne, __d1 jest prywatnequad._sides[0]=10# Działa, ale nie jest poprawne, _sides jest protectedprint(f"quad._sides[0] = {quad._sides[0]}")# Działa, ale nie jest poprawne, _sides jest protectedrectangle.width=10# Działaprint(f"rectangle.width = {rectangle.width}")# Działa# circle.radius = 10 # Nie działa, nie ma setteraprint(f"circle.radius = {circle.radius}")# Działacircle_one=Circle.one()# Wywołanie metody klasowejprint(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=Noneifselection==1:figure=Quad(1,2,3,4,6,7,30)elifselection==2:figure=Rectangle(1,2)elifselection==3:figure=Square(1)elifselection==4:figure=Ellipse(1,2)elifselection==5:figure=Circle(1)print(f"Wylosowana figura jest typu: {type(figure)}")ifisinstance(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}")