Diferència entre revisions de la pàgina «POO Classes»

De Cacauet Wiki
Salta a la navegació Salta a la cerca
Línia 130: Línia 130:
  
 
=== super (constructor del pare) ===
 
=== super (constructor del pare) ===
...cridant al constructor pare... (super)
+
Quan creem una classe derivada i sobreescrivim el constructor (mètode __init__ en Python), el constructor del pare no es crida. Per exemple:
...
+
<syntaxhighlight lang="Python">
 +
class A(object):
 +
  def __init__(self):
 +
      print "construint l'objecte..."
  
 +
class B(A):
 +
  def __init__(self):
 +
      print "hola què tal?"
 +
</syntaxhighlight>
 +
 +
Ara comprovem què passa:
 +
>>> a = A()
 +
construint l'objecte...
 +
>>>
 +
>>> b = B()
 +
hola què tal?
 +
 +
Com podem veure, el constructor de A() no es crida al crear un objecte B(). Com que molt sovint es necessita cridar-lo, es pot fer amb la instrucció super(). Oju que aquest exemple és per Python 2.x. En Python 3.0 s'ha simplificat a super(), sense arguments, molt més elegant:
 +
 +
<syntaxhighlight lang="Python">
 +
# exemple en Python 2.x
 +
class A(object):
 +
  def __init__(self):
 +
      print "construint l'objecte..."
 +
 +
class B(A):
 +
  def __init__(self):
 +
      super(B,self).__init__()
 +
      print "hola què tal?"
 +
</syntaxhighlight>
 +
 +
ULL!: si no creem l'objecte base A(object) heredat de "object", l'exemple no funcionarà.
 +
 +
Comprovem-ho:
 +
>>> a = A()
 +
construint l'objecte...
 +
>>> b = B()
 +
construint l'objecte...
 +
hola què tal?
 +
 +
<br>
  
 
=== "noves classes" de Python ===
 
=== "noves classes" de Python ===

Revisió del 21:44, 12 set 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


Classe i instància

  • 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ó

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

  • 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 l'apartat d'herència més avall.


Herència

Herència és el mecanisme pel qual podem crear objectes (fills) que adopten les característiques d'un altre (pare) i que les extenen o sobreescriuen amb nous mètodes o atributs.

En Python la herència s'introdueix entre parèntesi. En aquest exemple Pilota() no hereda de ningú i PilotaDeTennis hereda les propietats (atributs i mètodes) de Pilota.

class Pilota():
   # ...veure definició més amunt...
class PilotaDeTennis(Pilota):
   color = "verd"
   def accelera(self):
      self.velocitat = self.velocitat + 12

Tinguem en comtpe que:

  • PilotaDeTenis hereda els atributs posx, posy i velocitat de Pilota.
  • Sobreescrivim el mètode accelera(), i enlloc d'accelerar 5 unitats (el mètode definit en Pilota), la PilotaDeTennis accelerarà 20 unitats.
>>> pelo = Pilota()
>>> ptennis = PilotaDeTennis()
>>> pelo.velocitat
10
>>> pelo.accelera()
>>> pelo.velocitat
15                          # Pilota ha accelerat 5 punts
>>> ptennis.velocitat
10
>>> ptennis.accelera()
>>> ptennis.velocitat
22                          # PilotaDeTennis ha accelerat 12 punts


super (constructor del pare)

Quan creem una classe derivada i sobreescrivim el constructor (mètode __init__ en Python), el constructor del pare no es crida. Per exemple:

class A(object):
   def __init__(self):
      print "construint l'objecte..."

class B(A):
   def __init__(self):
      print "hola què tal?"

Ara comprovem què passa:

>>> a = A()
construint l'objecte...
>>> 
>>> b = B()
hola què tal?

Com podem veure, el constructor de A() no es crida al crear un objecte B(). Com que molt sovint es necessita cridar-lo, es pot fer amb la instrucció super(). Oju que aquest exemple és per Python 2.x. En Python 3.0 s'ha simplificat a super(), sense arguments, molt més elegant:

# exemple en Python 2.x
class A(object):
   def __init__(self):
      print "construint l'objecte..."

class B(A):
   def __init__(self):
      super(B,self).__init__()
      print "hola què tal?"

ULL!: si no creem l'objecte base A(object) heredat de "object", l'exemple no funcionarà.

Comprovem-ho:

>>> a = A()
construint l'objecte...
>>> b = B()
construint l'objecte...
hola què tal?


"noves classes" de Python

Llegir: http://docs.python.org/reference/datamodel.html#new-style-and-classic-classes

A partir de la versió 2.1 tots els objectes els heredem de "object". Es solventen alguns problemes antics. Es podria haver posat per defecte, però s'ha mantingut per la compatibilitat amb les aplicacions anteriors.

L' "estil antic" de classes Python (prèvies a 2.1) es defineixen:

class Pilota():
   ...

Ara, per definir un objecte "a la manera nova": OJU! sempre ho farem així en principi

class Pilota(object):
   ...

La diferència apreciable més important son els mètodes que heredem de "object":

>>> dir(Pilota)
['__class__', '__delattr__', '__dict__', '__doc__', '__format__',
 '__getattribute__', '__hash__', '__init__', '__module__', '__new__',
 '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__',
 '__str__', '__subclasshook__', '__weakref__', 'accelera', 'posicio',
 'posx', 'posy', 'velocitat']

Herència múltiple

...


Polimorfisme

El polimorfisme és una important propietat de la POO, molt rellevant sobretot en llenguatges on no hi ha tipus dinàmics com el C++ o el Java. El què significa és que una referència (punter) a un objecte genèric (pare), al cridar a una funció que està sobrescrita per un objecte derivat, executarà la funció més "nova", és a dir, la de l'objecte instanciat realment.

El cas típic és tenir un array de punters a objectes pare que no sabem de quin tipus derivat exactament son, per exemple, un array de Pilotes. Segons com l'haguem creat, el punters apuntaran a objectes dels tiups PilotaDeTennis, PilotaDeFutbol i PilotaDeBasket, però no tenim perquè saber-ho, tot i que sabem segur que tots hereden de Pilota. Quan cridem al mètode accelera() s'executarà el de l'objecte derivat, no el del pare genèric, encara que el punter apunti a Pilota.

En el cas del Python no ens resulta molt rellevant ja que tenim tipus dinàmics amb total introspecció. Això vol dir que en tot moment sabem de quin tipus és cada objecte. A sobre, en Python podem tenir arrays d'objectes heterogenis, cosa que no es pot fer amb Java o C++. Així, el polimorfisme no en aquests llenguates és molt important, però no en Python.

Per saber més podeu llegir:


Sobrecàrrega

...

Operadors

...

Visibilitat

...

Classes i mètodes virtuals

...