Am văzut în episodul XVII cum putem grupa diferite informații în obiecte, dar modul în care erau create obiectele respective era puțin greoi. Am lucrat cu obiecte simple, dar este ușor să ne imaginăm că, pe măsură ce obiectele devin mai complexe, e destul de complicat să stabilim valori pentru fiecare proprietate în parte.
Constructori
Avem la dispoziție posibilitatea de a scrie funcții care creează obiectele noastre. De exemplu, pentru o persoană ale cărei proprietăți sunt numele și vârsta, am putea avea o funcție cu doi parametri (un string și un număr întreg) care să creeze obiectul respectiv.
Putem scrie o funcție simplă care să returneze un obiect:
1 2 3 4 5 |
def creeaza_persoana(nume, varsta): persoana = Persoana() persoana.nume = nume persoana.varsta = varsta return persoana |
Este o variantă bună, chiar foarte utilă în unele situații. Funcția ar putea fi folosită astfel:
1 |
elev = creeaza_persoana("Dragoș", 17) |
Există și o alternativă: un constructor. Constructorii sunt funcții speciale care creează obiecte. Ele sunt definite în interiorul clasei.
De asemenea, numele constructorului este: __init__. O clasă poate avea mai mulți constructori, cu parametri diferiți, atâta timp cât nu există pericolul să apară confuzii (vom vedea puțin mai încolo).
Să transformăm funcția noastră într-un constructor:
1 2 3 |
def __init__(self, nume, varsta): self.nume = nume self.varsta = varsta |
Observăm că nu apare o instrucțiune return; se va returna automat, la sfârșit, obiectul creat.
Mai observăm că pentru proprietăți nu mai specificăm obiectul, ci ne folosim de self pentru a înțelege că se operează asupra obiectului care este creat.
Am spus că un constructor apare în interiorul clasei; întreaga noastră clasă ar arăta acum așa:
1 2 3 4 |
class Persoana(object): def __init__(self, nume, varsta): self.nume = nume self.varsta = Varsta |
Pentru a utiliza constructorul, va trebui să îl apelăm în momentul creării obiectului. Un exemplu ar putea fi:
1 |
persoana = Persoana("Dragoș", 17) |
Poate v-ați dat seama că și în episodul anterior am apelat, de fapt, un constructor: unul fără parametri. Acesta există întotdeauna și nu face nimic special în afară de crearea obiectului. În Python nu putem defini mai mulți constructori, ci doar să înlocuim acel constructor implicit, cu unul definit de noi.
Obiecte în obiecte
Proprietățile obiectelor au tipuri. Ce fel de tipuri? În principiu, de orice fel, inclusiv diferite clase. Așadar, o proprietate a unui obiect poate fi, la rândul său, un obiect.
Să ne imaginăm că persoanele noastre au și o adresă. O adresă ar fi un obiect care să conțină informații cum ar fi strada, numărul, orașul etc. Pentru simplitate, ne vom limita la stradă și număr. Clasa noastră ar putea arăta astfel:
1 2 3 4 |
class Adresa(obeject): def __init__(self, strada, numar): self.strada = strada self.numar = numar |
Acum, putem modifica clasa Persoana pentru ca ea să conțină și o adresă; vom avea doar un constructor care să ne permită să precizăm numele, vârsta și adresa.
1 2 3 4 5 |
class Persoana(object): def __init__(self, nume, varsta, adresa): self.nume = nume self.varsta = varsta self.adresa = adresa |
Să vedem cum accesăm obiectele din obiecte. Nu e foarte complicat; trebuie doar să adaugăm puncte...
Mai exact, dacă elev este obiectul, atunci elev.adresa este obiectul din interior (o proprietate o primului obiect) și elev.adresa.strada, respectiv elev.adresa.numar, sunt proprietățile acestui obiect din interior.
Nu este nepărat nevoie să ne oprim aici. Putem avea obiecte în obiecte în obiecte în obiecte etc.
Să vedem acum un program complet:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Adresa(object): def __init__(self, strada, numar): self.strada = strada self.numar = numar class Persoana(object): def __init__(self, nume, varsta, adresa): self.nume = nume self.varsta = varsta self.adresa = adresa adresa = Adresa("Ferdinand I", "47") elev = Persoana("Dragoș", 17, adresa) print(elev.nume, ":", elev.varsta) print(elev.adresa.strada, "-", elev.adresa.numar) |
Observăm că am ales să nu punem clasa Adresa în interiorul clasei Persoana deși am fi putut. Așa, o putem utiliza independent în alte scopuri. Alternativa ar fi fost:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
class Persoana(object): def __init__(self, nume, varsta, adresa): self.nume = nume self.varsta = varsta self.adresa = adresa class Adresa(object): def __init__(self, strada, numar): self.strada = strada self.numar = numar adresa = Persoana.Adresa("Ferdinand I", "47") elev = Persoana("Dragoș", 17, adresa) print(elev.nume, ":", elev.varsta) print(elev.adresa.strada, "-", elev.adresa.numar) |
Observați modificarea esențială din linia 13. Clasa Adresa nu mai există decât în interiorul obiectului Persoana și nu mai poate fi utilizată independent.
Tot în linia 13 observăm că avem o variabilă pe care nu o folosim decât în linia 15; nu avem nevoie de ea. Putem scăpa de ea dacă ne gândim că un constructor returnează un obiect și în linia 16 avem nevoie de doar un astfel de obiect. Am putea rescrie liniile 13 și 14 astfel:
1 |
elev = Persoana("Dragoș", 17, Adresa("Ferdinand I", "47")) |
sau (dacă am ales varianta în care clasa Adresa este în interiorul obiectului Persoana):
1 |
elev = Persoana("Dragoș", 17, Persoana.Adresa("Ferdinand I", "47")) |
E mai bine să definim clasele în interiorul altor clase sau nu? Depinde, dacă au sens să existe separat! În cazul nostru, probabil că e bine ca adresele să existe separat de persoane fiindcă s-ar putea să lucrăm și cu instituții, care au la rândul lor adrese și ar fi de dorit să nu trebuiască să avem o nouă clasă Adresa în interiorul clasei Institutie, care să fie la fel ca cea din interiorul clasei Persoana.