Ereditarietà multipla

Introduzione

A volte capita di voler definire delle classi che ereditano alcune caratteristiche da una determinata classe ed altre da una classe diversa. Si parla in questo caso di ereditarietà multipla.

Supponiamo ad esempio di avere la nostra classe Rectangle come presentata nalla lezione precedente, e un'altra classe, chiamata Logged, che ci consente di registrare in un file su disco alcuni messaggi, con identificativo dell'oggetto e data e ora dell'evento, come nel seguente esempio

(176041292) 2011-02-06 15:04:52.738374: creato oggetto
(176041292) 2011-02-06 15:04:52.738412: assegnato nome a oggetto

ottenuto eseguendo questo codice

l=Logged('/tmp/llog')
l.startlogger()
l.write('creato oggetto')
l.name='Foo'
l.write('assegnato nome a oggetto')
l.stoplogger()

Una possibile implementazione della classe Logged è la seguente:

class Logged(object):
    def __init__(self, path):
        self._path=path

    def startlogger(self):
        self._file=open(self._path, 'a')

    def stoplogger(self):
        self._file.close()

    def write(self, line):
        if not line.endswith('\n'):
            line+='\n'
        output = '(%d) %s: %s' % (id(self), str(datetime.now()), line)
        self._file.write(output)

Ereditarietà multipla

A questo punto, desideriamo creare un'istanza di un oggetto che abbia tutte le caratteristiche dei rettangoli, ma anche la possibilità di sfruttare le abilità di logging della classe Logged. Per ottenere questo risultato, sarà sufficiente definire una classe che deriva da entrambe le classi a nostra disposizione:

class LoggedRectangle(Rectangle, Logged):
    def __init__(self, width, height, path, name="A logged rectangle"):
        Rectangle.__init__(self, width, height, name)
        Logged.__init__(self, path)

    def place(self, x, y):
        super().place(x, y)
        self.write('Placed in (%f, %f) position' %(x,y))

Come si può vedere nel codice, è stato definito un costruttore che coniuga i costruttori delle due classi di base, nel quale essi vengono richiamati in maniera esplicita. Per l'overriding del metodo place(), invece, si può fare tranquillamente uso di super(), visto che non c'è nessun problema di ambiguità.

Quando vogliamo creare un'istanza di tipo LoggedRectangle, e usarne i vari metodi, non dovremo fare altro che scrivere un codice simile al seguente:

lr=LoggedRectangle(20, 30, '/tmp/rlog', 'MyRect')
lr.startlogger()
lr.write('Object "%s" with area %f has been created.' % (lr.name, lr.area))
lr.place(-15,12)
lr.stoplogger()

Il file di log conterrà righe come le seguenti:

(175305164) 2011-02-06 15:15:50.284788: Object "MyRect" with area 600.000000 has been created.
(175305164) 2011-02-06 15:15:50.284857: Placed in (-15.000000, 12.000000) position.

Ordine di risoluzione dei metodi

Se, per ipotesi, esistesse un metodo con un determinato nome in entrambe le classi base, quale verrebbe invocato?

Immaginiamo, ad esempio, di avere definito nella classe Rectangle e nella classe Logged il metodo setName(), con un'implementazione diversa:

class Rectangle(Shape):
    ...
    def setName(self, value):
        self._rname=value

class Logged(object):
    ...
    def setName(self, value):
        self._lname=value

Un codice come il seguente

lr=LoggedRectangle(20, 30, '/tmp/rlog', 'MyRect')
lr.setName('Foo')

eseguirà il metodo definito in Rectangle, in Logged, o entrambi? La risposta dipende unicamente dall'ordine in cui sono specificate le classi di base quando si definisce la classe derivata. Se, come nel nostro caso, abbiamo scritto

class LoggedRectangle(Rectangle, Logged):

allora verrà eseguito esclusivamente il codice di Rectangle. Se invece avessimo scritto

class LoggedRectangle(Logged, Rectangle):

verrebbe eseguito il codice di Logged.

Possiamo avere informazioni sull'ordine in cui i metodi vengono cercati nella gerarchia delle classi visualizzando la proprietà speciale __mro__ (method resolution order) della classe che ci interessa.

print(LoggedRectangle.__mro__)

(<class '__main__.LoggedRectangle'>, <class '__main__.Rectangle'>, <class '__main__.Shape'>, <class '__main__.Logged'>, <class 'object'>)

results matching ""

    No results matching ""