Diferència entre revisions de la pàgina «POO Classes»
(Hi ha 28 revisions intermèdies del mateix usuari que no es mostren) | |||
Línia 1: | Línia 1: | ||
− | ... | + | Anem a revisar els aspectes més pràctics dels fonaments de la POO: com crear classes, instanciar objectes i interactuar amb ells. |
+ | |||
+ | Per Python disposem del següent link de referència sobre les classes: http://docs.python.org/reference/datamodel.html | ||
+ | |||
== Classe i instància == | == Classe i instància == | ||
Línia 5: | Línia 8: | ||
* Una '''instància''' és un objecte real creat a partir del model d'una classe. | * Una '''instància''' és un objecte real creat a partir del model d'una classe. | ||
* Els '''atributs''' (variables internes) els creem inicialitzant-los a un valor dins de la classe. | * Els '''atributs''' (variables internes) els creem inicialitzant-los a un valor dins de la classe. | ||
− | * Els '''mètodes''' (funcions internes) es creen amb def i TOTES tenen com a primer argument '''self''' | + | * Els '''mètodes''' (funcions internes) es creen amb def i TOTES tenen com a primer argument '''self''' (= la instància que el crida). |
Creem la classe: | Creem la classe: | ||
<syntaxhighlight lang="python"> | <syntaxhighlight lang="python"> | ||
− | class Pilota(): | + | class Pilota(object): |
+ | # atributs | ||
velocitat = 10 | velocitat = 10 | ||
posx = 10 | posx = 10 | ||
posy = 12 | posy = 12 | ||
+ | # mètodes | ||
def accelera(self): | def accelera(self): | ||
self.velocitat = self.velocitat + 5 | self.velocitat = self.velocitat + 5 | ||
Línia 19: | Línia 24: | ||
self.posy = y | self.posy = y | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | De fet, la versió més elemental seria canviant la 1a linia per '''class Pilota():'''. A partir de la v2.1 de Python sempre heredem de la classe '''object'''. Llegiu l'apartat d'[[POO_Her%C3%A8ncia#.22Noves_classes.22_de_Python|herència i "noves classes"]] més avall per més detalls. | ||
− | Creem la instància "p" i accedim als atributs: | + | Creem la instància "p" i accedim als atributs (distingiu entre l'accés i la assignació): |
>>> p = Pilota() | >>> p = Pilota() | ||
>>> p.velocitat | >>> p.velocitat | ||
Línia 26: | Línia 32: | ||
>>> p.posy | >>> p.posy | ||
12 | 12 | ||
+ | >>> p.posy = 8 | ||
+ | >>> p.posy | ||
+ | 8 | ||
Per executar una funció s'han de posar els parèntesis (): | Per executar una funció s'han de posar els parèntesis (): | ||
Línia 39: | Línia 48: | ||
>>> p.posy | >>> p.posy | ||
30 | 30 | ||
+ | |||
+ | == Introspecció == | ||
+ | Podem conèixer els atributs i mètodes d'una classe a través de la sentència '''dir''' (ho podem fer sobre una instància o sobre una classe): | ||
+ | >>> dir(Pilota) | ||
+ | ['__doc__', '__module__', 'accelera', 'posicio', 'posx', 'posy', 'velocitat'] | ||
+ | |||
+ | Ull, aquest resultat és d'una classe no heredada de "object". Si heu heredat d'object us sortiran uns quants mètodes més. | ||
+ | |||
+ | Si féssim dir(p) (instància) tindria el mateix resultat: | ||
+ | >>> p = Pilota() | ||
+ | >>> dir(p) | ||
+ | ['__doc__', '__module__', 'accelera', 'posicio', 'posx', 'posy', 'velocitat'] | ||
+ | |||
+ | La introspecció és una important característica d'un llenguatge orientat a objecte. No tots els llenguatges en disposen i és molt útil al treballar amb la consola d'instruccions i per depurar (debug). | ||
<br> | <br> | ||
Línia 44: | Línia 67: | ||
== Constructors i destructors == | == Constructors i destructors == | ||
*El '''constructor''' és un mètode que es crida al crear l'objecte. En Python és '''__init__'''. | *El '''constructor''' és un mètode que es crida al crear l'objecte. En Python és '''__init__'''. | ||
− | *El '''destructor''' es crida al destruïr l'objecte. En Python és '''__del__'''. | + | *El '''destructor''' es crida al destruïr l'objecte. En Python és '''__del__''', ja que per destruïr un objecte s'utiltiza la sentència "del". |
Exemple: | Exemple: | ||
<syntaxhighlight lang="Python"> | <syntaxhighlight lang="Python"> | ||
Línia 55: | Línia 78: | ||
Comprovem que realment els mètodes son cridats: | Comprovem que realment els mètodes son cridats: | ||
− | >>> a = A() | + | >>> '''a = A()''' |
construint l'objecte... | construint l'objecte... | ||
− | >>> del a | + | >>> a |
+ | <__main__.A instance at 0xb6ebba2c> | ||
+ | >>> '''del a''' | ||
destruim-lo! | destruim-lo! | ||
+ | >>> a | ||
+ | Traceback (most recent call last): | ||
+ | File "<stdin>", line 1, in <module> | ||
+ | NameError: name 'a' is not defined | ||
+ | |||
+ | Queda't amb què per destruïr un objecte utiltizem '''del'''. | ||
+ | |||
+ | Python (a diferència d'altres llenguates com C++) disposa de '''recol·lecció automàtica de basura'''. Això vol dir que quan un objecte no està referenciat per ningú més el sistema l'esborra de memòria. Resulta molt còmode per evitar "memory leaks", però té un cert impacte en el rendiment, raó pel qual alguns llenguatges no ho implementen. | ||
+ | |||
+ | NOTA PER CLASSES HEREDADES: el constructor de la subclasse sobreescriu el de la superclasse. Per controlar si el cridem es pot fer servir la sentència "super", veure en [[POO Herència#Constructors i herència:instrucció super()]] | ||
+ | |||
+ | <br> | ||
− | + | == Atributs estàtics == | |
+ | Els atributs estàtics (o també funcions estàtiques) son valors que es defineixen per la classe sencera i no per una instància. Així, totes les instàncies tindran el mateix valor per aquella variable. | ||
+ | * En Java o C++ es defineixen amb la sentència "static". En C++ tenen algunes utilitzacions particulars com les ''callback functions''. | ||
+ | * En Pyton es defineixen en el cos de l'objecte. | ||
− | == | + | Hem de ser curosos ja que en molts casos ens poden portar problemes, com per exemple a l'utilitzar llistes. Prenem l'exemple: |
− | + | <syntaxhighlight lang="Python"> | |
+ | class Dades(): | ||
+ | # atributs | ||
+ | nombres = [] | ||
+ | noms = {} | ||
+ | id = 0 | ||
+ | # mètodes | ||
+ | def __init__(self): | ||
+ | pass | ||
+ | </syntaxhighlight> | ||
− | ... | + | En aquest cas ens trobariem un problema: tant la llista "nombres" com el diccionari "noms" son estàtics i comuns a totes les classes. Ho podem veure en aquest exemple: |
+ | >>> a = Dades() | ||
+ | >>> b = Dades() | ||
+ | >>> | ||
+ | >>> a.nombres.append(10) #afegim un element a la llista "nombres" de la instància "a" | ||
+ | >>> | ||
+ | >>> a.nombres | ||
+ | [10] | ||
+ | >>> b.nombres | ||
+ | [10] #sorpresa: la llista ''nombres'' de "b" és com la de "a", sense haver-la tocat | ||
+ | >>> | ||
+ | >>> a.id = 3 | ||
+ | >>> | ||
+ | >>> a.id | ||
+ | 3 | ||
+ | >>> b.id | ||
+ | 0 #aqui no hi ha sorpresa: cada instància té el seu id propi | ||
+ | >>> | ||
− | + | IMPORTANT: podem veure diferent comportament pel membre ''integer'' (id) que el de la llista (nombres): | |
− | ... | + | * Pel membre "id" funciona correctament. Això és perquè hem canviat el valor de l'atribut i li hem assignat un altre (3 enlloc de 0). |
+ | * Per la llista no ens funciona bé perquè volem una la llista de "b" independent de la de "a", i en canvi, quan afegim un element a la llista d'una instància "a" afecta també a la de l'altra instància "b". Al ser membre estàtic, és compartit per les 2 instàncies. | ||
+ | * Pel diccionari "noms" passaria el mateix. | ||
+ | La diferència entre el nombre (id) i la llista (nombres) és que al id li assignem un nou nombre. En canvi a la llista no li assignem una altra llista, sinó que agafem la que tenim i utiltizem un mètode propi (append). | ||
+ | |||
+ | Per solventar aquest problema inicialitzem els atributs en el constructor: | ||
+ | |||
+ | <syntaxhighlight lang="Python"> | ||
+ | class Dades(): | ||
+ | # atributs | ||
+ | nombres = None # com que l'inicialitzem al constructor no cal posar-ho aquí | ||
+ | noms = None # només ho fem per clarificar que tenim un atribut | ||
+ | id = 0 | ||
+ | # mètodes | ||
+ | def __init__(self): | ||
+ | self.nombres = [] | ||
+ | self.noms = {} | ||
+ | </syntaxhighlight> | ||
+ | En aquest cas sí que assignem una nova llista a cada instància. | ||
+ | |||
+ | I ara sí que es comporta com volem: | ||
+ | >>> a = Dades() | ||
+ | >>> b = Dades() | ||
+ | >>> | ||
+ | >>> a.nombres.append(33) | ||
+ | >>> | ||
+ | >>> a.nombres | ||
+ | [33] | ||
+ | >>> b.nombres | ||
+ | [] | ||
+ | >>> | ||
+ | |||
+ | <div class="exercici"> | ||
+ | === Conclusió === | ||
+ | Aquest comportament no només passa per les llistes i diccionaris, també per qualsevol objecte. Per tant: | ||
+ | |||
+ | '''Els objectes dintre d'objectes cal inicialitzar-los en el constructor!''' | ||
+ | |||
+ | Com a norma d'estil podem efectuar la declaració dels atributs amb ''None'''s com a l'exemple anterior per tal que d'un cop de vista puguem veure quins atributs té una classe. Però no és estrictament necessari, només una convenció. | ||
+ | </div> | ||
<br> | <br> | ||
− | == | + | == Visibilitat == |
− | . | + | La visibilitat d'un atribut, mètode o classe determina qui pot accedir-hi. Per exemple, en C++ i Java, si volem que un atribut no sigui accessible el podem ocultar als altres objectes amb la clàusula "private". |
− | == | + | === Visibilitat en C++ i Java === |
− | ... | + | Hi ha 3 nivells de visibilitat: |
+ | * '''public''': qualsevol hi pot accedir. | ||
+ | * '''protected''': només hi pot accedir la pròpia classe i les seves classes derivades. | ||
+ | * '''private''': només es pot accedir des d'un mètode de la pròpia classe. | ||
− | == | + | Per exemple: |
− | ... | + | <syntaxhighlight lang="cpp"> |
+ | class A | ||
+ | { | ||
+ | private: // atributs ocults a les altres classes | ||
+ | int a; | ||
+ | int b; | ||
+ | public: // funcions accessibles per tothom | ||
+ | void set_a(int val) { | ||
+ | a = val; | ||
+ | } | ||
+ | void set_b(int val) { | ||
+ | b = val; | ||
+ | } | ||
+ | protected: // funcions disponibles només per les classes derivades | ||
+ | ... | ||
+ | } | ||
+ | </syntaxhighlight> | ||
− | == | + | === Visibilitat en Python === |
− | ... | + | De nou, és una característica que no és gaire rellevant. '''En Python tots els atributs i mètodes son públics'''. Es pot "marcar" com a privat un atribut si el comencem amb un ''underscore'', per exemple: |
+ | <syntaxhighlight lang="python"> | ||
+ | class Bicicleta(object): | ||
+ | _velocitat = 0 | ||
+ | def accelera(self): | ||
+ | self._velocitat = self.velocitat + 5 | ||
+ | </syntaxhighlight> | ||
+ | El fet de posar el ''underscore'' no afecta a la visibilitat, però és una "convenció" de que si trobem un atribut d'aquest tipus, no convé accedir-hi directament (per obtenir el valor potser sí, per canviar-lo no). |
Revisió de 15:33, 22 nov 2012
Anem a revisar els aspectes més pràctics dels fonaments de la POO: com crear classes, instanciar objectes i interactuar amb ells.
Per Python disposem del següent link de referència sobre les classes: http://docs.python.org/reference/datamodel.html
Contingut
Classe i instància[modifica]
- Una classe defineix un tipus de dades, però no la crea.
- Una instància és un objecte real creat a partir del model d'una classe.
- Els atributs (variables internes) els creem inicialitzant-los a un valor dins de la classe.
- Els mètodes (funcions internes) es creen amb def i TOTES tenen com a primer argument self (= la instància que el crida).
Creem la classe:
class Pilota(object):
# atributs
velocitat = 10
posx = 10
posy = 12
# mètodes
def accelera(self):
self.velocitat = self.velocitat + 5
def posicio(self,x,y):
self.posx = x
self.posy = y
De fet, la versió més elemental seria canviant la 1a linia per class Pilota():. A partir de la v2.1 de Python sempre heredem de la classe object. Llegiu l'apartat d'herència i "noves classes" més avall per més detalls.
Creem la instància "p" i accedim als atributs (distingiu entre l'accés i la assignació):
>>> p = Pilota() >>> p.velocitat 10 >>> p.posy 12 >>> p.posy = 8 >>> p.posy 8
Per executar una funció s'han de posar els parèntesis ():
>>> p.accelera() >>> p.velocitat 15
Quan els mètodes tenen paràmetres cal tenir en compte que el primer sempre serà self. Aquest és necessari per accedir als atributs de la instància que crida l'objecte. Però quan cridem la funció s'ha d'obviar el "self". Per exemple, en el nostre cas tenim posicio(self,x,y) , però per cridar la funció posició ho fem només amb la x i la y:
>>> p.posicio(40,30) >>> p.posx 40 >>> p.posy 30
Introspecció[modifica]
Podem conèixer els atributs i mètodes d'una classe a través de la sentència dir (ho podem fer sobre una instància o sobre una classe):
>>> dir(Pilota) ['__doc__', '__module__', 'accelera', 'posicio', 'posx', 'posy', 'velocitat']
Ull, aquest resultat és d'una classe no heredada de "object". Si heu heredat d'object us sortiran uns quants mètodes més.
Si féssim dir(p) (instància) tindria el mateix resultat:
>>> p = Pilota() >>> dir(p) ['__doc__', '__module__', 'accelera', 'posicio', 'posx', 'posy', 'velocitat']
La introspecció és una important característica d'un llenguatge orientat a objecte. No tots els llenguatges en disposen i és molt útil al treballar amb la consola d'instruccions i per depurar (debug).
Constructors i destructors[modifica]
- El constructor és un mètode que es crida al crear l'objecte. En Python és __init__.
- El destructor es crida al destruïr l'objecte. En Python és __del__, ja que per destruïr un objecte s'utiltiza la sentència "del".
Exemple:
class A():
def __init__(self):
print "construint l'objecte..."
def __del__(self):
print "destruim-lo!"
Comprovem que realment els mètodes son cridats:
>>> a = A() construint l'objecte... >>> a <__main__.A instance at 0xb6ebba2c> >>> del a destruim-lo! >>> a Traceback (most recent call last): File "<stdin>", line 1, in <module> NameError: name 'a' is not defined
Queda't amb què per destruïr un objecte utiltizem del.
Python (a diferència d'altres llenguates com C++) disposa de recol·lecció automàtica de basura. Això vol dir que quan un objecte no està referenciat per ningú més el sistema l'esborra de memòria. Resulta molt còmode per evitar "memory leaks", però té un cert impacte en el rendiment, raó pel qual alguns llenguatges no ho implementen.
NOTA PER CLASSES HEREDADES: el constructor de la subclasse sobreescriu el de la superclasse. Per controlar si el cridem es pot fer servir la sentència "super", veure en POO Herència#Constructors i herència:instrucció super()
Atributs estàtics[modifica]
Els atributs estàtics (o també funcions estàtiques) son valors que es defineixen per la classe sencera i no per una instància. Així, totes les instàncies tindran el mateix valor per aquella variable.
- En Java o C++ es defineixen amb la sentència "static". En C++ tenen algunes utilitzacions particulars com les callback functions.
- En Pyton es defineixen en el cos de l'objecte.
Hem de ser curosos ja que en molts casos ens poden portar problemes, com per exemple a l'utilitzar llistes. Prenem l'exemple:
class Dades():
# atributs
nombres = []
noms = {}
id = 0
# mètodes
def __init__(self):
pass
En aquest cas ens trobariem un problema: tant la llista "nombres" com el diccionari "noms" son estàtics i comuns a totes les classes. Ho podem veure en aquest exemple:
>>> a = Dades() >>> b = Dades() >>> >>> a.nombres.append(10) #afegim un element a la llista "nombres" de la instància "a" >>> >>> a.nombres [10] >>> b.nombres [10] #sorpresa: la llista nombres de "b" és com la de "a", sense haver-la tocat >>> >>> a.id = 3 >>> >>> a.id 3 >>> b.id 0 #aqui no hi ha sorpresa: cada instància té el seu id propi >>>
IMPORTANT: podem veure diferent comportament pel membre integer (id) que el de la llista (nombres):
- Pel membre "id" funciona correctament. Això és perquè hem canviat el valor de l'atribut i li hem assignat un altre (3 enlloc de 0).
- Per la llista no ens funciona bé perquè volem una la llista de "b" independent de la de "a", i en canvi, quan afegim un element a la llista d'una instància "a" afecta també a la de l'altra instància "b". Al ser membre estàtic, és compartit per les 2 instàncies.
- Pel diccionari "noms" passaria el mateix.
La diferència entre el nombre (id) i la llista (nombres) és que al id li assignem un nou nombre. En canvi a la llista no li assignem una altra llista, sinó que agafem la que tenim i utiltizem un mètode propi (append).
Per solventar aquest problema inicialitzem els atributs en el constructor:
class Dades():
# atributs
nombres = None # com que l'inicialitzem al constructor no cal posar-ho aquí
noms = None # només ho fem per clarificar que tenim un atribut
id = 0
# mètodes
def __init__(self):
self.nombres = []
self.noms = {}
En aquest cas sí que assignem una nova llista a cada instància.
I ara sí que es comporta com volem:
>>> a = Dades() >>> b = Dades() >>> >>> a.nombres.append(33) >>> >>> a.nombres [33] >>> b.nombres [] >>>
Conclusió[modifica]
Aquest comportament no només passa per les llistes i diccionaris, també per qualsevol objecte. Per tant:
Els objectes dintre d'objectes cal inicialitzar-los en el constructor!
Com a norma d'estil podem efectuar la declaració dels atributs amb None's com a l'exemple anterior per tal que d'un cop de vista puguem veure quins atributs té una classe. Però no és estrictament necessari, només una convenció.
Visibilitat[modifica]
La visibilitat d'un atribut, mètode o classe determina qui pot accedir-hi. Per exemple, en C++ i Java, si volem que un atribut no sigui accessible el podem ocultar als altres objectes amb la clàusula "private".
Visibilitat en C++ i Java[modifica]
Hi ha 3 nivells de visibilitat:
- public: qualsevol hi pot accedir.
- protected: només hi pot accedir la pròpia classe i les seves classes derivades.
- private: només es pot accedir des d'un mètode de la pròpia classe.
Per exemple:
class A
{
private: // atributs ocults a les altres classes
int a;
int b;
public: // funcions accessibles per tothom
void set_a(int val) {
a = val;
}
void set_b(int val) {
b = val;
}
protected: // funcions disponibles només per les classes derivades
...
}
Visibilitat en Python[modifica]
De nou, és una característica que no és gaire rellevant. En Python tots els atributs i mètodes son públics. Es pot "marcar" com a privat un atribut si el comencem amb un underscore, per exemple:
class Bicicleta(object):
_velocitat = 0
def accelera(self):
self._velocitat = self.velocitat + 5
El fet de posar el underscore no afecta a la visibilitat, però és una "convenció" de que si trobem un atribut d'aquest tipus, no convé accedir-hi directament (per obtenir el valor potser sí, per canviar-lo no).