Programarea orientată pe obiecte permite gruparea datelor și funcțiilor care operează asupra lor într-o singură structură. Noțiunea centrală în programarea orientată pe obiecte este cea de clasă. O clasă este un domeniu în interiorul căruia se definesc o serie de atribute (metode și variabile). Obiectele construite de o clasă poartă numele de instanțe ale clasei.

Definirea unei clase

Se folosește cuvântul rezervat class, astfel:

class ClassName:
    pass  # se va înlocui cu corpul clasei

Scrierea numelui clasei CamelCase este o convenție, nu o cerință a limbajului.

Instanțierea

Orice clasă este un obiect callable care construiește o instanță a sa la apelare.

f = Foo()

Astfel, se creează o instanță a clasei Foo și se păstrează o referință către aceasta în variabila f.

Membrii unei clase

Metode

O metodă este o funcție definită în cadrul unei clase. Primul argument pe care îl primesc metodele este o instanță a clasei de care aparțin; în acest scop se folosește, la definirea metodei, cuvântul rezervat self.

class Foo:
    bar = 0

    def setx(self, x):
        self.x = x

    def printx(self):
        print self.x

Apelarea metodelor

Apelarea metodelor este similară cu un apel de funcție; transmiterea instanței ca prim parametru se face folosind funcția ca atribut al instanței.

>>> f.setx(2)
>>> f.printx()
2

Date

Membrii clasei de tip dată pot fi:

  • variabile ale clasei
  • variabile ale instanței

În definiția clasei Foo de mai sus, bar este o variabilă a clasei; valoarea acesteia este aceeași pentru toate instanțele clasei și poate fi accesată folosind construcția <nume clasă>.<nume variabilă>.

>>> Foo.bar                                                                    
0                                                                              
>>> Foo.bar += 1                                                               
>>> f.bar                                                                      
1 

Un exemplu de variabilă a instanței este x; acest tip de variabilă este propriu fiecărei instanțe în parte și se inițializează în cadrul unei metode (aici setx). Variabilele la nivel de instanță nu pot fi accesate în lipsa unei instanțe a clasei.

>>> Foo.x
AttributeError: class Foo has no attribute 'x'

Structura dinamică a claselor

În Python, membrii unei clase se pot schimba în timpul rulării (nu doar valoarea acestora, ca în C sau Java). Astfel, obiectul f nu avea atributul x înainte de apelarea metodei setx:

>>> f = Foo()                                                               
>>> f.x                                                                     
AttributeError: Foo instance has no attribute 'x'                           
>>> f.setx(5)                                                               
>>> f.x                                                                     
5                                                                           

Putem chiar șterge f.x după rularea codului de mai sus:

>>> del f.x                                                                 
>>> f.printx()                                                              
AttributeError: Foo instance has no attribute 'x'

Datorită structurii dinamice a claselor, se poate schimba definiția unei clase în timpul execuției programului. Mai jos se adaugă clasei Foo deja definite un atribut y. Orice nouă instanță a clasei Foo va avea acest nou atribut.

>>> Foo.y = 10                                                              
>>> g = Foo()                                                               
>>> g.y                                                                     
10

Moștenire

Moștenirea permite unei clase să extindă comportamentul uneia sau mai multor clase. Se folosește sintaxa:

class ClassName(superclass1, superclass2, ...):
    ...

Clasa derivată va moșteni toate atributele clasei părinte. Dacă o metodă este definită atât în clasa părinte, cât și în cea derivată, metoda clasei derivate o suprascrie pe cea a părintelui.

>>> class Foo:                                                              
...     x = 10                                                              
...                                                                         
...     def bar(self):                                                      
...         print 'I am Foo.bar'                                            
...                                                                         
>>> class Bar(Foo):                                                         
...     y = 9                                                               
...                                                                         
...     def bar(self):                                                      
...         print 'I am Bar.bar'                                            
...                                                                         
>>> b = Bar()                                                               
>>> b.bar()                                                                 
I am Bar.bar                                                                
>>> b.x                                                                     
10                                                                          
>>> b.y                                                                     
9 

Încapsulare

Încapsularea este un conctept cheie în programarea orientată pe obiecte. În Python, pentru a ascunde membrii unei clase, se folosește următoarea convenție:

  • _member - prefixarea cu un underscore, pentru membri protected, permite modificarea atributului doar în interiorul clasei sau de către un moștenitor;
  • __member - prefixarea cu două underscore-uri, pentru membri private, interzice accesul la atribut din afara clasei.

Exemplu:

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

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

La execuția codului de mai sus, rezultatul este următorul:

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

Python protejează membrii privați prefixându-le numele cu numele clasei din care fac parte. Așadar, încapsularea este o convenție, încurajând programatorii să fie responsabili, și nu restricționând complet accesul. În exemplul de mai sus, se poate accesa membrul privat astfel:

counter._JustCounter__secretCount

Metode speciale

__init__()

Similar unui constructor în Java sau C++, Python pune la dispoziție metoda __init__. Aceasta primește ca prim argument o instanță a clasei (self), și poate primi și alte argumente, care pot fi folosite pentru setarea unor atribute ale instanței.

>>> class Foo:                                                              
...     def __init__(self, x):                                              
...         self.x = x                                                      
...                                                                         
>>> foo = Foo('This is x')                                                  
>>> foo.x                                                                   
'This is x' 

__del__()

Această metodă se apelează de fiecare dată când un obiect este șters din memorie, fiind similară cu un destructor.

__str__()

Pentru a genera o reprezentare text a unei instanțe a unei clase se va defini metoda __str__ care primește ca argument instanța clasei și returnează un string.

>>> class Bar:                                                                      
...     def __init__(self, x):                                                      
...         self.x = x                                                              
...     def __str__(self):                                                          
...         return "X = {}".format(self.x)                                  
...                                                                         
>>> bar = Bar(4)                                                            
>>> print bar                                                               
X = 4                                                                       
>>> bar = Bar('Y')                                                          
>>> str(bar)                                                                
'X = Y'

New style classes

Odată cu apariția Python 2.2 a fost introdus un nou mod de a defini și utiliza clasele. O clasă new style este o clasă care este derivată dintr-un built-in, cel mai adesea object. Cea mai importantă diferență între cele două tipuri de clase ține de tipul instanțelor. O instanță a unei clase clasice va avea tipul instance, în timp ce instanțele claselor new style vor avea tipul returnat de <nume clasă>.__class__, ceea ce le situează pe același nivel cu clasele built-in. Mai mult, clasele new style introduc noțiuni ca proprietate și metodă statică.

>>> class NewStyleFoo(object):                                              
...     pass

Proprietăți

Proprietățile sunt atribute pentru care s-au definit metode setter și getter.

>>> class Student(object):                                                      
...     def __init__(self):                                                     
...         self.__grade = 0                                                    
...                                                                             
...     def get_grade(self):                                                    
...         return self.__grade                                                 
...                                                                             
...     def set_grade(self, grade):                                             
...         self.__grade = grade                                                
...                                                                            
...     grade = property(get_grade, set_grade) 

>>> s = Student()                                                       
>>> s.grade                                                             
0                                                                       
>>> s.grade = 10                                                        
>>> s.grade                                                             
10  

Metode statice

În Python, acestea se comportă exact ca în C++ sau Java. Metodele statice nu folosesc parametrul self și nu necesită instanțierea clasei în prealabil. Se definesc utilizând metoda built-in staticmethod().

>>> class Student(object):
...     def static_info():
...         print 'This is a static method.'
...     info = staticmethod(static_info)

>>> Student.info()
This is a static method.

Moștenire pentru new-style classes

Atunci când o clasă new-style este derivată din altă clasă, aceasta are posibilitatea de a invoca clasa bază folosind metoda built-in super().

>>> class Bird(object):                                                     
...     def __init__(self, can_fly):                                        
...         self.can_fly = can_fly                                          
...                                                                         
>>> class Parrot(Bird):                                                     
...     def __init__(self, can_fly, known_words):                           
...         super(Parrot, self).__init__(can_fly)                           
...         self.known_words = known_words   

Funcții built-in

Există o serie de funcții built-in care au sens în contextul programării orientate pe obiecte. Am exemplificat deja câteva dintre ele (property, staticmethod, super) care funcționează pentru new-style classes. Cele enumerate mai jos sunt valabile atât pentru clasele new-style, cât și pentru cele old-style.

Funcții pentru accesarea atributelor

  • getattr(obj, name[, default]) - pentru a aceesa un atribut al unui obiect; returnează default dacă obiectul nu are atribut cu numele name;
  • hasattr(obj, name) - verifică dacă obiectul are un atribut;
  • setattr(obj, name, value) - setează un atribut; daca atributul nu există, este creat;
  • delattr(obj, name) - șterge un atribut

Exemplu:

>>> class Pet:                                                              
...     pass                                                                
...                                                                         
>>> my_pet = Pet()                                                          
>>> if not hasattr(my_pet, 'name'):                                         
...     setattr(my_pet, 'name', 'Sasha')                                    
...                                                                         
>>> getattr(my_pet, 'name')                                                 
'Sasha'                                                                     
>>> delattr(my_pet, 'name')                                                 
>>> getattr(my_pet, 'name', 'Unknown')                                      
'Unknown' 

Funcții de verificare

  • isinstance(object, type) - retunează True dacă object este o instanță a tipului type sau a oricărei subclase a tipului type;
  • issubclass(class, base) - returnează True dacă clasa class este subclasă a clasei base.

Exemplu:

>>> class Animal(object):                                                   
...     pass                                                                
...                                                                         
>>> class Pet(Animal):                                                      
...     pass                                                                
...                                                                         
>>> lion = Animal()                                                         
>>> cat = Pet()                                                             
>>> isinstance(cat, Pet)                                                    
True                                                                        
>>> isinstance(cat, Animal)                                                 
True                                                                        
>>> isinstance(cat, object)                                                 
True                                                                        
>>> isinstance(lion, Animal)                                                
True                                                                        
>>> isinstance(lion, Pet)                                                   
False                                                                       
                                                                            
>>> issubclass(Pet, Animal)                                                 
True                                                                        
>>> issubclass(Animal, Pet)                                                 
False