PDA

View Full Version : [Python] duck typing e semantica


mad_hhatter
23-02-2009, 16:07
ciao a tutti, volevo intavolare una discussione su un aspetto di Python che ancora non ho ben metabolizzato: la mancanza dell'overloading dei metodi.

Dato che in Python un metodo è identificato univocamente dal suo nome (all'interno di un dato namespace), non è consentito l'overloading dello stesso.

Poco male, visto che Python si affida al duck typing. Ma sorgono comuqnue due ordini di problemi.

Il primo problema nasce quando il duck typing non può funzionare perché oggetti di tipo diverso espongono una stessa funzioalità tramite metodi aventi nomi differenti.

In tal caso ho due strade:
1. verificare il tipo del parametro
2. verificare quale metodo viene esposto dal parametro

Il primo approccio ha il difetto di obbligare il programmatore a elencare esplicitamente TUTTI i tipi che il metodo dovrà supportare.

Il secondo approccio è un'estensione del concetto di duck typing e, in quanto tale, credo sia preferibile, ma apre la strada al secondo problema a cui accennavo poco fa: cosa succede quando due oggetti diversi possiedono metodi con lo stesso nome e diverse liste di parametri?

Ad esempio, un oggetto QuerySet in Django è simile a un oggetto di tipo list, ma mentre QuerySet ha un metodo count() che restituisce il numero di elementi nel QuerySet, il metodo count di list necessita di un parametro e restituisce il numero di volte in cui tale parametro compare nella lista.

In un caso del genere, la semplice verifica della presenza del metodo count non può funzionare.


Veniamo alle domande:
1. esiste un approccio che permetta di gestire entrambe le situazioni senza dover cambiare idioma?

2. è stato introdotto in python 3.0 un metodo per gestire queste situazioni? (mi pare ne avesse accennato raymond hettinger alla PyCon2 italia, forse le annotation?)

cdimauro
23-02-2009, 17:02
ciao a tutti, volevo intavolare una discussione su un aspetto di Python che ancora non ho ben metabolizzato: la mancanza dell'overloading dei metodi.

Dato che in Python un metodo è identificato univocamente dal suo nome (all'interno di un dato namespace), non è consentito l'overloading dello stesso.

Poco male, visto che Python si affida al duck typing. Ma sorgono comuqnue due ordini di problemi.

Il primo problema nasce quando il duck typing non può funzionare perché oggetti di tipo diverso espongono una stessa funzioalità tramite metodi aventi nomi differenti.
Se gli oggetti hanno metodi diversi, c'è poco da fare. Al limite puoi creare un metodo di una classe con lo stesso nome di quello usato nell'altra, rimappandolo se l'interfaccia è diversa, oppure riciclando direttamente quello col nome diverso che espone la medesima funzionalità.
In tal caso ho due strade:
1. verificare il tipo del parametro
2. verificare quale metodo viene esposto dal parametro

Il primo approccio ha il difetto di obbligare il programmatore a elencare esplicitamente TUTTI i tipi che il metodo dovrà supportare.

Il secondo approccio è un'estensione del concetto di duck typing e, in quanto tale, credo sia preferibile, ma apre la strada al secondo problema a cui accennavo poco fa: cosa succede quando due oggetti diversi possiedono metodi con lo stesso nome e diverse liste di parametri?

Ad esempio, un oggetto QuerySet in Django è simile a un oggetto di tipo list, ma mentre QuerySet ha un metodo count() che restituisce il numero di elementi nel QuerySet, il metodo count di list necessita di un parametro e restituisce il numero di volte in cui tale parametro compare nella lista.

In un caso del genere, la semplice verifica della presenza del metodo count non può funzionare.

Veniamo alle domande:
1. esiste un approccio che permetta di gestire entrambe le situazioni senza dover cambiare idioma?
Se i metodi si chiamano allo stesso modo, ma hanno parametri e/o funzionamento diversi, non si possono conciliare le due cose.

Al limite potresti estendere uno dei due oggetti in modo da sovrascrivere il metodo count di uno e farlo funzionare esattamente come l'altro. Ma in questo modo se passi l'oggetto a una funzione che si aspetta il count originale, non funzionerebbe più nulla.

La soluzione è di avere un'interfaccia comune che implementano entrambe le classi, e personalmente mi affiderei alla funzione built-in len(), che già funziona con le liste restituendo il numero di elementi che contiene.

Estenderei poi QuerySet in questo modo:
class MyQuerySet(QuerySet):
def __len__(self):

return QuerySet.count(self)
Oppure:
class MyQuerySet(QuerySet):

__len__ = QuerySet.count
Visto che __len__ e count hanno esattamente la stessa interfaccia (non hanno nessun parametro, a parte self, e restituiscono un valore numerico che è proprio ciò che si vuole).

A questo punto utilizzerei len() per entrambi i tipi di oggetto.
2. è stato introdotto in python 3.0 un metodo per gestire queste situazioni? (mi pare ne avesse accennato raymond hettinger alla PyCon2 italia, forse le annotation?)
Le annotation servono soltanto per annotare, appunto, informazioni sui parametri di una funzione (o metodo) e/o sul valore restituito. Ma sono a puro scopo informativo: Python non le usa per determinare il tipo di oggetto passato a una funzione, perché ciò avviene sempre e comunque in maniera dinamica, a runtime e al momento preciso in cui viene poi usato l'oggetto passato come parametro.

Tutto ciò ovviamente se ho capito cosa intendevi. :p

Se ti servono altre informazioni, chiedi pure.

mad_hhatter
23-02-2009, 17:42
ciao cesare, grazie per le risposte.

Avevo già preso in considerazione le tue soluzioni, il problema è che a volte sono decisamente overkill rispetto al problema da risolvere. Inoltre nel cas di django il problema è che QuerySet non si crea esplicitamente ma viene restituito attraverso dei factory method quindi oltre a estendere QuerySet dovrei modificare il framework per restituire il nuovo tipo esteso...

Per le annotazioni, probabilmente avevo frainteso il discorso di Raymond.

Grazie per l'aiuto, prezioso come sempre

cdimauro
23-02-2009, 22:13
ciao cesare, grazie per le risposte.

Avevo già preso in considerazione le tue soluzioni, il problema è che a volte sono decisamente overkill rispetto al problema da risolvere. Inoltre nel cas di django il problema è che QuerySet non si crea esplicitamente ma viene restituito attraverso dei factory method quindi oltre a estendere QuerySet dovrei modificare il framework per restituire il nuovo tipo esteso...
Ah, ho capito. In questo caso le soluzioni che ti propongo sono due, e non necessitano di estendere nessuna classe, perché sfruttano appunto la dinamicità intrinseca di Python.

Soluzione 1. Supposto che la classe QuerySet si trovi nel modulo x.y.z:
x.y.z.QuerySet.__len__ = x.y.z.QuerySet.count
da piazzare ovviamente dopo l'import di x.y.z e prima di utilizzare QuerySet.

Soluzione2. Supponiamo che foo() sia la funzione che torni un'istanza di QuerySet:
Records = x.y.z.foo()
Records.__len__ = Records.count

Si tratta di due soluzioni simili, visto che sostanzialmente fanno la stessa cosa: aggiungere il metodo __len__.
Solo che la prima lo fa alla classe base, per cui una volta aggiunto ad essa verrà automaticamente ereditato da tutte le istanze di QuerySet, incluse quelle già create.
La seconda soluzione invece lo aggiunge soltanto all'istanza appena creata.

Una parziale riscrittura della seconda soluzione potrebbe essere questa:
def DecoratedQS(Instance):
Instance.__len__ = Instance.count
return Instance

Records = DecoratedQS(x.y.z.foo())
O, meglio ancora, creando una funzione foo "locale" che esegue il wrapping di quella che usi nel modulo x.y.z:
def foo():
Instance = x.y.z.foo()
Instance.__len__ = Instance.count
return Instance

Records = foo()

Personalmente mi piace di più quest'ultima, perché non "sporca" né la classe QuerySet, che mantiene la propria identità, né allunga la stesura del codice dovendo passare l'istanza creata a una funzione per poi finalmente poterla utilizzare.
Per le annotazioni, probabilmente avevo frainteso il discorso di Raymond.
Può essere, perché sono soltanto elementi decorativi.

Per curiosità: ma hai sentito Raymond alla scorsa PyCon2, oppure da qualche altra parte?
Grazie per l'aiuto, prezioso come sempre
Figurati. E' un piacere cercare di essere utile, nei limiti del tempo che ho a disposizione.

Come vedi soluzioni ai problemi ne esistono diverse (probabilmente ce ne saranno altre). La cosa bella che trovo in Python è che grazie alla sua dinamicità è possibile crearne di utili e interessanti che con altri linguaggi nemmeno mi sarei sognato di fare, in quanto non possibili o estremamente macchinosi. Il tutto mantenendo il codice sempre pulito ed elegante.

Per lo meno, questa è l'impressione che ho dopo un po' di anni che ci lavoro.

mad_hhatter
23-02-2009, 23:01
mmm... queste ultime soluzioni mi piacciono particolarmente: sono concise e molto chiare. Grazie molte!

Si, ho sentito Raymond alla PyCon2, ma all'epoca della conferenza avevo alle spalle solo una settimana di python per cui molto probabilmente ho frainteso alcuni discorsi su python 3.0

Da allora python è il linguaggio che uso ogni giorno al lavoro (devo ringraziare il mio capo e un collega per questo :) ) e non smetto di rendermi conto di quanto sia divertente e stimolante, soprattutto non smette di sorprendermi con soluzioni impensabili con altri linguaggi. A volte mi rendo conto che è necessario spogliarsi di alcuni costrutti acquisiti essendo cresciuto con Java all'università per poter apprezzare appieno python.

cdimauro
23-02-2009, 23:07
E' esattamente lo stesso motivo che mi ha portato a "innamorarmi" di Python, che ormai uso quasi esclusivamente per lavorare. Con lui ho ritrovato il piacere di programmare in maniera "creativa".

Comunque alla PyCon2 c'ero pure io, ed ero quello che ha sparato a Raymond le ultime tre domande di fila e a cui volevano strappare via il microfono. :asd:

mad_hhatter
24-02-2009, 10:12
E' esattamente lo stesso motivo che mi ha portato a "innamorarmi" di Python, che ormai uso quasi esclusivamente per lavorare. Con lui ho ritrovato il piacere di programmare in maniera "creativa".

Comunque alla PyCon2 c'ero pure io, ed ero quello che ha sparato a Raymond le ultime tre domande di fila e a cui volevano strappare via il microfono. :asd:

:D purtroppo non ricordo, ma magari ci vedremo a maggio a PyCon 3