Python 3 - zorientowany obiektowo

Python był językiem zorientowanym obiektowo od samego początku. Dzięki temu tworzenie i używanie klas i obiektów jest wręcz łatwe. Ten rozdział pomoże ci stać się ekspertem w korzystaniu z obsługi programowania obiektowego w języku Python.

Jeśli nie masz wcześniejszego doświadczenia z programowaniem obiektowym (OO), możesz skorzystać z kursu wprowadzającego lub przynajmniej jakiegoś samouczka, aby zrozumieć podstawowe pojęcia.

Jednak tutaj jest małe wprowadzenie do programowania obiektowego (OOP), aby pomóc -

Przegląd terminologii OOP

  • Class- Prototyp zdefiniowany przez użytkownika dla obiektu, który definiuje zestaw atrybutów charakteryzujących dowolny obiekt klasy. Atrybuty to elementy składowe danych (zmienne klas i zmienne instancji) oraz metody, do których dostęp uzyskuje się za pomocą notacji kropkowej.

  • Class variable- Zmienna wspólna dla wszystkich instancji klasy. Zmienne klasy są zdefiniowane w klasie, ale poza żadną z metod tej klasy. Zmienne klas nie są używane tak często, jak zmienne instancji.

  • Data member - Zmienna klasy lub zmienna instancji, która przechowuje dane powiązane z klasą i jej obiektami.

  • Function overloading- Przypisanie więcej niż jednego zachowania do określonej funkcji. Wykonywana operacja różni się w zależności od typów obiektów lub argumentów.

  • Instance variable - Zmienna zdefiniowana wewnątrz metody i należąca tylko do aktualnego wystąpienia klasy.

  • Inheritance - Przeniesienie cech klasy do innych klas, które z niej pochodzą.

  • Instance- indywidualny przedmiot określonej klasy. Na przykład obiekt obj należący do klasy Circle jest instancją klasy Circle.

  • Instantiation - Tworzenie instancji klasy.

  • Method - Specjalny rodzaj funkcji zdefiniowany w definicji klasy.

  • Object- Unikalna instancja struktury danych zdefiniowana przez jej klasę. Obiekt zawiera zarówno elementy członkowskie danych (zmienne klas i zmienne instancji), jak i metody.

  • Operator overloading - Przypisanie więcej niż jednej funkcji do konkretnego operatora.

Tworzenie klas

Instrukcja class tworzy nową definicję klasy. Nazwa klasy następuje bezpośrednio po słowie kluczowym class, po którym następuje dwukropek w następujący sposób -

class ClassName:
   'Optional class documentation string'
   class_suite
  • Klasa zawiera ciąg dokumentacji, do którego można uzyskać dostęp za pośrednictwem ClassName.__doc__.

  • Plik class_suite składa się ze wszystkich instrukcji komponentów definiujących elementy klasy, atrybuty danych i funkcje.

Przykład

Poniżej znajduje się przykład prostej klasy Pythona -

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)
  • Zmienna empCount jest zmienną klasową, której wartość jest wspólna dla wszystkich instancji zmiennej a w tej klasie. Dostęp do tego można uzyskać jako Employee.empCount z poziomu klasy lub spoza niej.

  • Pierwsza metoda __init __ () to specjalna metoda, nazywana konstruktorem klasy lub metodą inicjalizacji, którą Python wywołuje podczas tworzenia nowej instancji tej klasy.

  • Deklarujesz inne metody klas, takie jak zwykłe funkcje, z wyjątkiem tego, że pierwszym argumentem każdej metody jest self . Python dodaje argument self do listy za Ciebie; nie musisz go dołączać, gdy wywołujesz metody.

Tworzenie obiektów instancji

Aby utworzyć instancje klasy, wywołujesz klasę używając nazwy klasy i przekazujesz wszystkie argumenty , które akceptuje jej metoda __init__ .

This would create first object of Employee class
emp1 = Employee("Zara", 2000)
This would create second object of Employee class
emp2 = Employee("Manni", 5000)

Dostęp do atrybutów

Dostęp do atrybutów obiektu uzyskuje się za pomocą operatora kropki z obiektem. Dostęp do zmiennej klasy można uzyskać za pomocą nazwy klasy w następujący sposób -

emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)

Teraz, łącząc wszystkie koncepcje razem -

#!/usr/bin/python3

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)


#This would create first object of Employee class"
emp1 = Employee("Zara", 2000)
#This would create second object of Employee class"
emp2 = Employee("Manni", 5000)
emp1.displayEmployee()
emp2.displayEmployee()
print ("Total Employee %d" % Employee.empCount)

Wykonanie powyższego kodu daje następujący wynik -

Name :  Zara ,Salary:  2000
Name :  Manni ,Salary:  5000
Total Employee 2

Możesz dodawać, usuwać lub modyfikować atrybuty klas i obiektów w dowolnym momencie -

emp1.salary = 7000  # Add an 'salary' attribute.
emp1.name = 'xyz'  # Modify 'age' attribute.
del emp1.salary  # Delete 'age' attribute.

Zamiast używać zwykłych instrukcji do uzyskiwania dostępu do atrybutów, możesz użyć następujących funkcji -

  • Plik getattr(obj, name[, default]) - aby uzyskać dostęp do atrybutu obiektu.

  • Plik hasattr(obj,name) - aby sprawdzić, czy atrybut istnieje, czy nie.

  • Plik setattr(obj,name,value)- ustawić atrybut. Jeśli atrybut nie istnieje, zostanie utworzony.

  • Plik delattr(obj, name) - aby usunąć atrybut.

hasattr(emp1, 'salary')    # Returns true if 'salary' attribute exists
getattr(emp1, 'salary')    # Returns value of 'salary' attribute
setattr(emp1, 'salary', 7000) # Set attribute 'salary' at 7000
delattr(emp1, 'salary')    # Delete attribute 'salary'

Wbudowane atrybuty klas

Każda klasa Pythona śledzi wbudowane atrybuty i można uzyskać do nich dostęp za pomocą operatora kropki, jak każdy inny atrybut -

  • __dict__ - Słownik zawierający przestrzeń nazw klasy.

  • __doc__ - Ciąg dokumentacji klasy lub brak, jeśli jest niezdefiniowany.

  • __name__ - Nazwa klasy.

  • __module__- Nazwa modułu, w którym zdefiniowana jest klasa. W trybie interaktywnym ten atrybut to „__main__”.

  • __bases__ - Prawdopodobnie pusta krotka zawierająca klasy bazowe, w kolejności ich występowania na liście klas bazowych.

W przypadku powyższej klasy spróbujmy uzyskać dostęp do wszystkich tych atrybutów -

#!/usr/bin/python3

class Employee:
   'Common base class for all employees'
   empCount = 0

   def __init__(self, name, salary):
      self.name = name
      self.salary = salary
      Employee.empCount += 1
   
   def displayCount(self):
     print ("Total Employee %d" % Employee.empCount)

   def displayEmployee(self):
      print ("Name : ", self.name,  ", Salary: ", self.salary)

emp1 = Employee("Zara", 2000)
emp2 = Employee("Manni", 5000)
print ("Employee.__doc__:", Employee.__doc__)
print ("Employee.__name__:", Employee.__name__)
print ("Employee.__module__:", Employee.__module__)
print ("Employee.__bases__:", Employee.__bases__)
print ("Employee.__dict__:", Employee.__dict__ )

Wykonanie powyższego kodu daje następujący wynik -

Employee.__doc__: Common base class for all employees
Employee.__name__: Employee
Employee.__module__: __main__
Employee.__bases__: (<class 'object'>,)
Employee.__dict__: {
   'displayCount': <function Employee.displayCount at 0x0160D2B8>, 
   '__module__': '__main__', '__doc__': 'Common base class for all employees', 
   'empCount': 2, '__init__': 
   <function Employee.__init__ at 0x0124F810>, 'displayEmployee': 
   <function Employee.displayEmployee at 0x0160D300>,
   '__weakref__': 
   <attribute '__weakref__' of 'Employee' objects>, '__dict__': 
   <attribute '__dict__' of 'Employee' objects>
}

Niszczenie obiektów (zbieranie śmieci)

Python automatycznie usuwa niepotrzebne obiekty (typy wbudowane lub instancje klas), aby zwolnić miejsce w pamięci. Proces, w którym Python okresowo odzyskuje bloki pamięci, które nie są już używane, jest określany jako wyrzucanie elementów bezużytecznych.

Moduł odśmiecania pamięci Pythona działa podczas wykonywania programu i jest wyzwalany, gdy liczba odwołań do obiektu osiągnie zero. Liczba odwołań do obiektu zmienia się wraz ze zmianą liczby aliasów wskazujących na obiekt.

Liczba odwołań do obiektu zwiększa się, gdy zostanie mu przypisana nowa nazwa lub zostanie umieszczony w kontenerze (liście, krotce lub słowniku). Liczba odwołań do obiektu zmniejsza się, gdy jest usuwany za pomocą polecenia del , jego odwołanie jest ponownie przypisywane lub jego odwołanie wykracza poza zakres. Gdy liczba odwołań do obiektu osiągnie zero, Python zbiera je automatycznie.

a = 40      # Create object <40>
b = a       # Increase ref. count  of <40> 
c = [b]     # Increase ref. count  of <40> 

del a       # Decrease ref. count  of <40>
b = 100     # Decrease ref. count  of <40> 
c[0] = -1   # Decrease ref. count  of <40>

Zwykle nie zauważysz, kiedy garbage collector niszczy osieroconą instancję i odzyskuje jej miejsce. Jednak klasa może implementować specjalną metodę __del __ () , nazywaną destruktorem, która jest wywoływana, gdy instancja ma zostać zniszczona. Ta metoda może służyć do czyszczenia wszelkich zasobów innych niż pamięć używanych przez wystąpienie.

Przykład

Destruktor __del __ () wyświetla nazwę klasy instancji, która ma zostać zniszczona -

#!/usr/bin/python3

class Point:
   def __init__( self, x=0, y=0):
      self.x = x
      self.y = y
   def __del__(self):
      class_name = self.__class__.__name__
      print (class_name, "destroyed")

pt1 = Point()
pt2 = pt1
pt3 = pt1
print (id(pt1), id(pt2), id(pt3))   # prints the ids of the obejcts
del pt1
del pt2
del pt3

Wykonanie powyższego kodu daje następujący wynik -

140338326963984 140338326963984 140338326963984
Point destroyed

Note- Najlepiej byłoby zdefiniować swoje klasy w oddzielnym pliku, a następnie zaimportować je do głównego pliku programu za pomocą instrukcji import .

W powyższym przykładzie przy założeniu, że definicja klasy Point jest zawarta w pliku point.py i nie ma w nim żadnego innego kodu wykonywalnego.

#!/usr/bin/python3
import point

p1 = point.Point()

Dziedziczenie klas

Zamiast zaczynać od zera, możesz utworzyć klasę, wyprowadzając ją z wcześniej istniejącej klasy, wymieniając klasę nadrzędną w nawiasach po nowej nazwie klasy.

Klasa potomna dziedziczy atrybuty swojej klasy nadrzędnej i możesz używać tych atrybutów tak, jakby były zdefiniowane w klasie potomnej. Klasa potomna może również przesłonić składowe danych i metody z rodzica.

Składnia

Klasy pochodne są deklarowane podobnie jak ich klasa nadrzędna; jednakże lista klas bazowych do dziedziczenia jest podana po nazwie klasy -

class SubClassName (ParentClass1[, ParentClass2, ...]):
   'Optional class documentation string'
   class_suite

Przykład

#!/usr/bin/python3

class Parent:        # define parent class
   parentAttr = 100
   def __init__(self):
      print ("Calling parent constructor")

   def parentMethod(self):
      print ('Calling parent method')

   def setAttr(self, attr):
      Parent.parentAttr = attr

   def getAttr(self):
      print ("Parent attribute :", Parent.parentAttr)

class Child(Parent): # define child class
   def __init__(self):
      print ("Calling child constructor")

   def childMethod(self):
      print ('Calling child method')

c = Child()          # instance of child
c.childMethod()      # child calls its method
c.parentMethod()     # calls parent's method
c.setAttr(200)       # again call parent's method
c.getAttr()          # again call parent's method

Wykonanie powyższego kodu daje następujący wynik -

Calling child constructor
Calling child method
Calling parent method
Parent attribute : 200

W podobny sposób możesz kierować klasą z wielu klas nadrzędnych w następujący sposób -

class A:        # define your class A
.....

class B:         # define your calss B
.....

class C(A, B):   # subclass of A and B
.....

Możesz użyć funkcji issubclass () lub isinstance (), aby sprawdzić relacje dwóch klas i instancji.

  • Plik issubclass(sub, sup) funkcja boolowska zwraca True, jeśli dana podklasa sub jest rzeczywiście podklasą nadklasy sup.

  • Plik isinstance(obj, Class)funkcja boolowska zwraca True, jeśli obj jest instancją klasy Class lub instancją podklasy Class

Metody zastępujące

Zawsze możesz zastąpić metody klasy nadrzędnej. Jednym z powodów nadpisywania metod rodzica jest to, że możesz chcieć specjalnej lub innej funkcjonalności w swojej podklasie.

Przykład

#!/usr/bin/python3

class Parent:        # define parent class
   def myMethod(self):
      print ('Calling parent method')

class Child(Parent): # define child class
   def myMethod(self):
      print ('Calling child method')

c = Child()          # instance of child
c.myMethod()         # child calls overridden method

Wykonanie powyższego kodu daje następujący wynik -

Calling child method

Podstawowe metody przeciążania

W poniższej tabeli wymieniono niektóre ogólne funkcje, które można zastąpić we własnych klasach -

Sr.No. Metoda, opis i przykładowa rozmowa
1

__init__ ( self [,args...] )

Konstruktor (z dowolnymi opcjonalnymi argumentami)

Przykładowe wywołanie: obj = className (args)

2

__del__( self )

Destructor, usuwa obiekt

Przykładowe wywołanie: del obj

3

__repr__( self )

Szacunkowa reprezentacja ciągu

Przykładowe wywołanie: repr (obj)

4

__str__( self )

Reprezentacja ciągu do druku

Przykładowe wywołanie: str (obj)

5

__cmp__ ( self, x )

Porównanie obiektów

Przykładowe wywołanie: cmp (obj, x)

Operatory przeciążenia

Załóżmy, że utworzyłeś klasę Vector do reprezentowania wektorów dwuwymiarowych. Co się dzieje, gdy dodajesz je za pomocą operatora plus? Najprawdopodobniej Python będzie na ciebie krzyczał.

Możesz jednak zdefiniować metodę __add__ w swojej klasie, aby wykonywać dodawanie wektorów, a wtedy operator plus zachowywałby się zgodnie z oczekiwaniami -

Przykład

#!/usr/bin/python3

class Vector:
   def __init__(self, a, b):
      self.a = a
      self.b = b

   def __str__(self):
      return 'Vector (%d, %d)' % (self.a, self.b)
   
   def __add__(self,other):
      return Vector(self.a + other.a, self.b + other.b)

v1 = Vector(2,10)
v2 = Vector(5,-2)
print (v1 + v2)

Wykonanie powyższego kodu daje następujący wynik -

Vector(7,8)

Ukrywanie danych

Atrybuty obiektu mogą, ale nie muszą być widoczne poza definicją klasy. Musisz nazwać atrybuty przedrostkiem z podwójnym podkreśleniem, a wtedy te atrybuty nie będą bezpośrednio widoczne dla osób z zewnątrz.

Przykład

#!/usr/bin/python3

class JustCounter:
   __secretCount = 0
  
   def count(self):
      self.__secretCount += 1
      print (self.__secretCount)

counter = JustCounter()
counter.count()
counter.count()
print (counter.__secretCount)

Wykonanie powyższego kodu daje następujący wynik -

1
2
Traceback (most recent call last):
   File "test.py", line 12, in <module>
      print counter.__secretCount
AttributeError: JustCounter instance has no attribute '__secretCount'

Python chroni tych członków, zmieniając wewnętrznie nazwę, tak aby zawierała nazwę klasy. Możesz uzyskać dostęp do takich atrybutów, jak object._className__attrName . Jeśli chcesz zamienić ostatnią linię następującą, to działa dla Ciebie -

.........................
print (counter._JustCounter__secretCount)

Wykonanie powyższego kodu daje następujący wynik -

1
2
2