[Python] Ereditarietà tra classi: un semplice esempio pratico da chiarire

Daniele Varrazzo piro a develer.com
Ven 21 Nov 2014 20:41:58 CET


On 2014-11-21 17:53, Marco Ippolito wrote:
> Ciao Daniele,
> ti ringrazio per l'aiuto.
> 
> Ho modificato l'esempio, passando tutte le variabili locali (nome,
> cognome, citta) al costruttore di sub. E coś funziona (come vedi
> sotto).
> 
> #!/usr/bin/python
> 
> class Super:
>     def __init__(self, nome, cognome, indirizzo):
>         self.nome = nome
>         self.cognome = cognome
>         self.indirizzo = indirizzo
>         self.nome_intero = '%s %s' % (self.nome, self.cognome)
> 
>     def super_meth_1(self):
>         return '%s abita in %s' % (self.nome_intero, self.indirizzo)
> 
>     def get_super_result(self):
>         return self.super_meth_1()
> 
> class Sub(Super):
>     def __init__(self, nome, cognome, indirizzo, cosa_fa):
>         Super.__init__(self, nome, cognome, indirizzo)
>         self.cosa_fa = cosa_fa
> 
>     def sub_meth_1(self):
>         return '%s  %s' % (self.nome_intero, self.cosa_fa)
> 
>     def get_sub_meth_1(self):
>         return self.sub_meth_1()
> 
> 
> if __name__ == '__main__':
> 
>     nome_f = 'Marco'
>     cognome_f = 'Ippolito'
>     abita_f = 'Milano'
>     super_class = Super(nome_f, cognome_f, abita_f)
>     ris_super = super_class.get_super_result()
>     print "ris_super: ", ris_super
>     cosa_f = 'suona'
>     sub = Sub(nome_f, cognome_f, abita_f, cosa_f)
>     ris_sub_1 = sub.get_sub_meth_1()
>     print "ris_sub_1: ", ris_sub_1
>     ris_sub_2 = sub.get_super_result()
>     print "ris_sub_2: ", ris_sub_2
> 
> ./classInheritage.py
> ris_super:  Marco Ippolito abita in Milano
> ris_sub_1:  Marco Ippolito  suona
> ris_sub_2:  Marco Ippolito abita in Milano
> 
> C'è un modo per passare far ś che nel main passi a Sub solo le
> variabili che lo "differenziano" (specializzano) rispetto a Super?
> Cioè c'è un modo per far ś che io possa passare in "main" solo la
> variabile "cosa_f"?

Nel caso in esempio si fa male. Il pattern migliore e` quello di 
elencare gli argomenti.

La cosa comincia ad essere utile se stabilisci che i costruttori delle 
tue classi debbano essere chiamati solo con argomenti keyword, ovvero, 
se stabilisci che chiamerai

     sub = Sub(nome=nome_f, cognome=cognome_f, abita=abita_f, 
cosa_fa=cosa_f)

allora puoi usare **kwargs e fare:

   class Super:
       def __init__(self, nome, cognome, indirizzo):
           self.nome = nome
           self.cognome = cognome
           self.indirizzo = indirizzo
           self.nome_intero = '%s %s' % (self.nome, self.cognome)

   class Sub(Super):
       def __init__(self, cosa_fa, **kwargs):
           Super.__init__(self, **kwargs)
           self.cosa_fa = cosa_fa

considerando che tipicamente definirai il costruttore solo in un punto 
ma lo chiamerai in diversi punti ti conviene essere piu` verboso nelle 
definizioni degli __init__ e risparmiare nell'invocazioe, quindi 
lasciare le cose come stanno.

Questa non e` l'unica cosa che si potrebbe tenere in considerazione 
pero`. Il tuo e` un esempio di studio, che non ha veri vincoli. In casi 
piu` reali potresti avere una gerarchia di oggetti dove pochi argomenti 
(tipo fino a 3, ma meno sono e meglio e`) andranno *sempre* specificati 
per ogni oggetto della gerarchia, mentre ci puo` essere un turbinare di 
argomenti che sono a) opzionali e b) specifici solo di certe 
sottoclassi. In questo caso il modo migliore (secondo me, YMMV) di 
organizzare il codice e` quello di passare gli argomenti fondamentali in 
maniera posizionale (potrebbero anche essere passati con keyword, 
l'importante e` mantenere la possibilita` di fare entrambe le cose) e 
usare **kwargs per tutti gli altri, in modo da ignorare quelli che non 
si conoscono ma di propagarli.

Per esempio (non testato, ovviamente):

     class Persona(object):
         def __init__(self, nome):
             self.nome = nome

     class Lavoratore(Persona):
         def __init__(self, nome, ruolo, salario=None, **kwargs):
             super(Lavoratore, self).__init__(nome, **kwargs)
             self.ruolo = ruolo
             self.salario = salario

     class Studente(Persona):
         def __init__(self, nome, scuola, classe=None, **kwargs):
             super(Studente, self).__init__(nome, **kwargs)
             self.scuola = ...

     class StudenteUniversitario(Studente):
         def __init__(self, nome, fuoricorso_dal=None, **kwargs):
             super(StudenteUniversitario, self).__init__(nome, **kwargs)
             ...

Il nome puoi passarlo con keyword o meno, gli altri argomenti devono 
avere una keyword.

     p1 = StudenteUniversitario("Tizio Caio", scuola="Anormale", 
fuoricorso_dal=1996)
     p2 = Lavoratore(nome="Pinco Pallini", ruolo=...)


Avere classi che richiedono un gran numero di parametri posizionali da 
passare non e` una buona cosa: e` facile sbagliare nel passaggio. Se gli 
argomenti cominciano ad essere tanti e` meglio richiedere che vengano 
passati con keyword (ma e` ancora meglio chiedersi come mai ci siano 
tanti parametri e cambiare qualcosa nel codice).


-- Daniele



Maggiori informazioni sulla lista Python