Il video non sembra essere disponibile a partire da dicembre 2016, ma sono d'accordo con te sul fatto che l'ereditarietà dell'interfaccia non è problematica come l'ereditarietà dell'implementazione. Vedi link per esempio.
Tuttavia, un senso in cui la relazione "fornitore" di zope.interface
ci libera dalla gerarchia dell'ereditarietà è che consente agli oggetti di fornire un'interfaccia anche se la loro classe non lo fa. Il link mostra esempi di oggetti che forniscono direttamente un'interfaccia sebbene le loro rispettive classi non lo facciano.
Ad esempio, se un'interfaccia IWriter
richiede i metodi write
e flush
, allora un modulo che definisce le funzioni write
e flush
(e chiama zope.interface.moduleProvides(IWriter)
) fornisce quell'interfaccia, anche anche se i moduli in generale non lo fanno.
Questo è anche il motivo per cui i metodi di interfaccia Zope non nominano self
come parametro - self
è un dettaglio di implementazione particolare per i metodi di istanza di classe.
Questa capacità per i singoli oggetti di fornire un'interfaccia diventa particolarmente utile con oggetti che racchiudono un altro oggetto e mappano dinamicamente i suoi metodi.
Ad esempio, supponiamo di avere:
class IWriter(zope.interface.Interface):
def write(s):
"""Write stuff"""
def flush():
"""Flush stuff"""
class TextWriter:
zope.interface.implements(IWriter)
def write(self, s):
...
def flush(self):
...
tw = TextWriter()
do_something(tw)
Se visualizziamo un problema in cui l'output tw
non viene scaricato a volte, possiamo utilizzare lo strumento per i metodi TextWriter per visualizzare le informazioni di debug (ad esempio log(timestamp, caller, methodname)
). Tuttavia, il codice per generare le informazioni timestamp
e caller
è complesso e deve essere aggiunto in più punti. E ora ogni istanza di TextWriter
mostrerà le informazioni di debug.
Invece, potremmo racchiudere il particolare TextWriter
di interesse in un oggetto wrapper che registra le sue chiamate di metodo.
class DebugWrapper:
def __init__(self, wrapped):
self._wrapped = wrapped
def __getattr__(self, name):
... # generate timestamp and caller
log(timestamp, caller, name)
return getattr(self._wrapped, name)
dtw = DebugWrapper(tw)
do_something(dtw)
Tieni presente che DebugWrapper
è generico. Può avvolgere qualsiasi oggetto. Potrebbe essere importato da una libreria separata. È indipendente dall'interfaccia IWriter
, e ciò è positivo poiché possiamo riutilizzarlo più facilmente altrove.
Ma se la funzione do_something
si aspetta di ricevere un fornitore dell'interfaccia IWriter
ora abbiamo il problema che dtw
soddisfa la sintassi dell'interfaccia ma non fornisce "ufficialmente" l'interfaccia.
Questa è una distinzione importante.
Le interfacce non definiscono solo una firma sintattica. Definiscono anche il significato semantico della sintassi. Con le interfacce possiamo specificare se due oggetti che forniscono gli stessi metodi forniscono effettivamente la stessa semantica. Questa è una grande differenza per l'uso comune di Python della digitazione anatra, dove la stessa sintassi implica la stessa semantica.
Ad ogni modo, la soluzione al nostro problema è informare tutti che l'oggetto dtw
fornisce effettivamente l'interfaccia IWriter
, anche se la sua classe DebugWrapper
non lo fa.
zope.interface.directlyProvides(dtw, IWriter)