Poo Programare Orientata Ob
Poo Programare Orientata Ob
În capitolul anterior am văzut cum declarăm funcții individual, pentru a lucra într-o manieră procedurală. În continuare ne vom referi la organizarea acestor funcții în
cadrul unor clase, programare orientată pe obiecte (OOP).
Pentru o scurtă introducere despre modul de funcționare al claselor și obiectelor, precum și o diferențiere clara între programarea (clasică) procedurală și cea
orientată pe obiect, consultati paragraful de aici [http://ro.wikipedia.org/wiki/Programare_orientat%C4%83_pe_obiecte#Clase_.C8.99i_obiecte].
Clase și obiecte
E important de stiut ca totul in python este un obiect. O functie de exemplu este tot un obiect, sa vedem rapid de ce:
def a():
"""Un text descriptiv al functiei"""
pass
>>> a.__doc__
Asadar vedeti ca o functie are anumite atribuite precum __doc__, si multe altele interne (dir(a) va arata alte metode interne ale obiectului a). E important sa retineti ca
totul este un obiect.
Se definește prin cuvântul rezervat class. Este recomandat ca numele clasei să fie cu litere mari, dar nu este o eroare dacă numiți o clasă cu litere mici.
class NumeClasa:
"""Adăugarea unei descrieri (doc string) despre scopul clasei este recomandat."""
<declaratie-1>
<declaratie-N>
Codul definit in cadrul unei clase trebuie, ca si in cadrul functiilor (cu declaratia def), trebuie executat inainte sa aiba vreun efect. O clasa are propriul namespace (spatiu
de nume), deci orice atribuire de variabile in cadrul unei clase intra in spatiul de nume local acelei functii.
Obiectul Clasa
class MyClass:
i = 12345
def f(self):
return 'hello world'
Apoi MyClass.i si MyClass.f sunt referentieri valide (returnand un intreg si respectiv un obiect de tip functie), pentru ca atributele i si f au fost definite in spatiul de nume
al clasei MyClass. Putem atribui alte valori variabilelor claselor, deci putem schimba valoarea lui MyClass.i daca dorim.
>>> MyClass.i
12345
>>> MyClass.f
>>> MyClass.i = 1
>>> MyClass.i
>>> MyClass.__doc__
Instantierea unei clase in python se face ca apelul unei functii. Exemplul de cod:
>>> x = MyClass()
>>> x.i
>>> x.f
creeaza o noua instanta a clasei si atribuie acest obiect unei variabile locale x. Observati ca atributul i este un atribut al clasei, deci va fi partajat tuturor instantelor clasei.
Instantierea va produce un obiect x gol insa, fara variabile interne proprii. De multe ori clasele vor sa personalizeze obiectele create cu o stare initiala anume. Astfel o clasa
poate defini metoda speciala __init__:
def __init__(self):
self.data = []
Cand o clasa defineste o metoda __init__, instantierea clasei apeleaza aceasta metoda. Aceasta metoda poate avea orice numar de argumente, bineinteles:
class Complex:
self.r = realpart
self.i = imagpart
Reluam exemplul
class MyClass:
i = 12345
def f(self):
return 'hello world'
In python, o instanta poate referi doua tipuri de atribute: atribute de date si metode.
Atributele de date nu trebuie declarate inainte, ele sunt create atunci cand le este atribuita o valoare prima oara. De exemplu, daca folosim instanta x a clasei MyClass,
codul urmator va printa 16 fara a lasa vreo urma (pentru ca in final stergem atributul counter abia creat):
>>> x.counter = 1
x.counter = x.counter * 2
16
Al doilea tip de atribut este o metoda, care nu este altceva decat o functie ce apartine unui obiect. Asta inseamna ca o metoda trebuie sa contina cumva si o referinta catre
obiect.
O functie pe o clasa determina la randul sau o metoda in cadrul unei instante a acelei clase. Deci MyClass.f fiind o functie, determina sa existe o metoda pe x.f, in timp ce
x.i nu este o metoda, pentru ca nici MyClass.i nu este. x.f este o metoda obiect, in timp ce MyClass.f este o functie obiect.
Metode obiect
O metoda poate fi chemata imediat, iar in exemplul anterior x.f() ar produce 'hello world'. Putem insa amana acest lucru pentru mai tarziu:
xf = x.f
while True:
print xf()
Cand o metoda obiect este apelata, obiectul (referinta catre obiect) este pasat automat de catre python pentru noi. Astfel ca x.f() fiind o metoda, este perfect echivalent cu
a face MyClass.f(x), care este apelul unei functii, dar careia ii pasam noi manual referinta catre acel obiect.
>>> x.f()
'hello world'
>>> MyClass.f(x)
'hello world'
Acel cuvant self din cadrul unei metode reprezinta chiar instanta curenta ce este pasata automat de python cand apelam o metoda obiect. Ca o analogie, poate fi acelasi
lucru ca this din C++. Numele de self este o conventie, insa prin nerespectarea ei, codul poate deveni mai greu de inteles si de citit.
self.nume = nume
def salut(self):
print('Salut, numele meu este %s.' % self.nume)
>>> p = Persoana()
>>> p = Persoana('Gigel')
>>> p.salut()
Daca incercam sa instantiem clasa fara parametrul nume, vom primi o eroare.
Aici atributul nume este la un atribut al instantei, fiind propriu obiectului, nu clasei (spre
deosebire de MyClass.i).
class MyClass:
i = 'ceva'
>>> MyClass.i
'ceva'
>>> MyClass.i
>>> MyClass.j
'altceva'
return min(x,y)
>>> MyClass.minim = f1
>>> a = MyClass()
>>> a.minim(2,3)
2
>>> a.minim
>>> MyClass.minim
Practic trebui sa retineti ca atat clasele cat si instantele nu sunt altceva decat niste dictionare, deci pot fi modificate oricand.
Vom defini o instanta a clasei MyClass, pentru a vedea distinctiile intre atribute ale clasei si cele ale instantei. Vom folosi metoda vars() pentru a inspecta ce atribute locale
contine un obiect.
class MyClass:
i = 1
>>> MyClass.i
>>> vars(MyClass)
>>> a = MyClass()
>>> a.i
>>> vars(a)
{}
Ce se intampla aici? Vedem ca MyClass contine atributul i, in timp ce instanta a nu il contine. Cu toate astea a.i afisaza valoarea corecta.
Pentru a intelege mai bine, sa incercam sa modificam variabila clasei, iar apoi a instantei:
>>> MyClass.i += 1
(2, 2)
>>> vars(a)
{}
>>> a.i = 42
>>> vars(a)
{'i': 42}
>>> vars(MyClass)
Deci putem vedea ca instanta a tocmai fiindca nu avea un atribut cu numele i, incearca sa il gaseasca in cadrul clasei MyClass. Insa odata ce am creat un atribut i pe
instanta cu a.i = 42, obiectul va gasi valoarea in atributele interne, si nu va mai cauta pe atributul clasei (care a ramas neschimbat, observati!).
Un ultim exemplu ar trebui sa faca tot mai clara distinctia intre atributele claselor si cele ale obiectelor.
class A:
def __init__(self):
self.i = 2
>>> vars(A)
>>> a = A()
>>> vars(a)
{'i': 2}
Mostenire
class Persoana(object):
self.nume = nume
def salut(self):
print('Salut, numele meu este %s.' % self.nume)
# ClasaVeche.
class Profesor(Persoana):
Persoana.__init__(self, nume)
self.liceu = liceu
def preda(self):
print('Voi preda python in liceul %s.' % self.liceu)
In python extindem o clasa folosind paranteze, ca in exemplul in care Profesor extinde Persoana.
Observati cum clasa Persoana extinde clasa object. Este din nou o recomandare
in python ca clasa de baza sa extinda object.
>>> profesor.salut()
Salut, numele meu este Andrei.
>>> profesor.preda()
Voi preda python in liceul Sincai.
Functii builtin
Vom folosi urmatorul cod pentru demonstrare. pass ne permite sa nu definim conținutul unei clase sau al unei functii, altfel am primi erori fiindca python se bazeaza pe
indentare, nu putem lasa locul gol complet.
class Person(object):
pass
class Person1(Person):
pass
class Person2(Person):
pass
isinstance
Adevarat daca object este o instanta a tipului type sau a oricarei subclase a tipului type.
True
>>> isinstance(Person1(), Person2)
False
False
issubclass
True
>>> issubclass(Person, Person1)
False
super
super(type) → type
class A(object):
def show(self):
class B(A):
def show(self):
>>> A().show()
>>> B().show()
Duck Typing
Acest concept se refera la modul dinamic de “tipare” (dynamic typing) in python, in care metodele si proprietatile unui obiect determina semantica acestuia, si nu
mostenirea. Pe scurt, “nu verifica daca este o rata, verifica daca face ca o rata, daca merge ca o rata etc.”. Mai multe pe Wikipedia [https://en.wikipedia.org/wiki/Duck_typing].
class Duck:
def quack(self):
print "Quaaaaaack!"
def feathers(self):
class Person:
def quack(self):
print "Persoana imita rata."
def feathers(self):
def in_the_forest(duck):
duck.quack()
duck.feathers()
Si dupa ce definim doua clase, Duck si Person care ambele implementeaza aceleasi metode quack si feathers, putem observa faptul ca metodei in_the_forest nu-i pasa ce
tip de obiect/instanta primeste, atata timp cat are acele metode implementate.
>>> in_the_forest(donald)
Quaaaaaack!
>>> in_the_forest(john)
Alte resurse
Alte materiale:
Exercitii
1. Dandu-se aceste definitii de scos si de adaugat bani la un cont bancar, pastrati un comportament similar prin folosirea paradigmei de programare OOP. Definiti o clasa
numita BankAccount.
def make_account():
return {'balance': 0}
account['balance'] += amount
return account['balance']
account['balance'] -= amount
return account['balance']
>>> a = make_account()
>>> b = make_account()
100
50
40
90
2. Peste BankAccount sa adaugam o clasa ChargingAccount care adauga o taxa in plus (de 3 lei sa zicem) la fiecare retragere de numerar. Ar trebui sa se comporte:
class ChargingAccount(BankAccount):
def __init__(self):
# TODO
# TODO
>>> a = ChargingAccount()
>>> a.deposit(100)
100
>>> a.withdraw(10)
>>> b = BankAccount()
>>> b.deposit(50)
50
>>> b.withdraw(10)
40
Soluție
class ChargingAccount(BankAccount):
def __init__(self):
BankAccount.__init__(self)
self.fee = 3
3. Stiind ca o clasa Point ce primeste doua coordonate (x, y) are metode interne pentru operatii aritmetice (__add__, __sub__), definiti aceste metode astfel incat sa putem
aduna/scadea doua puncte.
class Point(object):
def __init__(self, x, y):
pass
def __add__(self, other):
pass
def __sub__(self, other):
pass
>>> p1 = Point(0,1)
>>> p2 = Point(1,2)
>>> p3 = p1+p2
>>> p3
>>> p3.x
1
>>> p3.y
3
self.x = x
self.y = y
4. Implementați clasa Complex care definește un număr complex. Clasa va permite realizarea următoarelor operații:
>>> x = Complex(2, 3)
>>> y = Complex(3, 6)
>>> x + y
5 + 9i
>>> y - x
1 + 3i
>>> x / y
0 + 0i
>>> x / y
0.0329218106996 + 0.00411522633745i
>>> abs(x)
3.1462643699419726
>>> x * y
-12.0 + 21.0i
>>>
self.real = real
self.imaginar = imaginar
def __abs__(self):
def __str__(self):
__repr__ = __str__
5. Avand o clasa Persoana, faceti in asa fel incat atunci cand este printata sau reprezentata la consola instanta unei clase, sa afiseze un mesaj ca cel de mai jos. In mod
implicit python va afisa ceva de genul <__main__.Person object at 0x1004a94d0>.
class Person(object):
pass
#TODO
>>> p = Person('gicu')
>>> p
My name is gicu
>>> print p
My name is gicu
self.name = name
def __str__(self):
__repr__ = __str__
(Hint: citi mai multe despre __call__ aici [http://docs.python.org/2/reference/datamodel.html#object.__call__], aici [http://stackoverflow.com/questions/111234/what-is-a-callable-
in-python])
class Persoana(object):
pass
# TODO
>>> p = Persoana('Gigel')
>>> p()
Soluție
class Persoana(object):
self.name = name
def __call__(self):
7. Creati o clasa `Zar` care intoarce un numar random intre 1 si 6 la apelarea metodei `roll`. Creati o alta clasa `ZarNecinstit` care intoarce intotdeauna ceva intre 1 si 6 care
nu e random, ci fix, la apelarea metodei `roll`. Creati o lista de 5 zaruri atat corecte si incorecte si iterati pe ele apeland metoda `roll` (duck typing).
Bonus!
8. Implementat o clasa care de oricate ori e instantiata, se va intoarce aceeasi instanta mereu (singleton).
Pentru asta ar fi bine sa cititi ce este un __metaclass__ in python
aici [http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python].
class Person(object):
# TODO
>>> p1 = Person()
>>> p2 = Person()
>>> p1
>>> p2
>>> p1 == p2
True
Soluție
class Singleton(type):
_instance = {}
return cls._instance[cls]
class Person(object):
__metaclass__ = Singleton