Lo Zen di Python afferma che dovrebbe esserci un solo modo per fare le cose, ma spesso mi imbatto nel problema di decidere quando usare una funzione rispetto a quando usare un metodo.
Facciamo un esempio banale: un oggetto ChessBoard. Diciamo che abbiamo bisogno di un modo per ottenere tutte le mosse legali di King disponibili sul tabellone. Scriviamo ChessBoard.get_king_moves () o get_king_moves (chess_board)?
Ecco alcune domande correlate che ho esaminato:
- Perché Python usa i "metodi magici"?
- C'è una ragione per cui le stringhe Python non hanno un metodo di lunghezza delle stringhe?
Le risposte che ho ottenuto sono state in gran parte inconcludenti:
Perché Python usa metodi per alcune funzionalità (ad esempio list.index ()) ma funzioni per altre (ad esempio len (list))?
La ragione principale è la storia. Le funzioni venivano usate per quelle operazioni che erano generiche per un gruppo di tipi e che dovevano funzionare anche per oggetti che non avevano affatto metodi (es. Tuple). È anche conveniente avere una funzione che può essere prontamente applicata a una raccolta amorfa di oggetti quando si utilizzano le caratteristiche funzionali di Python (map (), apply () e altri).
In effetti, implementare len (), max (), min () come funzione incorporata è in realtà meno codice rispetto all'implementazione come metodi per ciascun tipo. Si può cavillare su casi individuali, ma fa parte di Python ed è troppo tardi per apportare modifiche così fondamentali ora. Le funzioni devono rimanere per evitare una massiccia rottura del codice.
Sebbene interessante, quanto sopra non dice molto sulla strategia da adottare.
Questo è uno dei motivi: con i metodi personalizzati, gli sviluppatori sarebbero liberi di scegliere un nome di metodo diverso, come getLength (), length (), getlength () o qualsiasi altra cosa. Python applica una denominazione rigorosa in modo che la funzione comune len () possa essere utilizzata.
Leggermente più interessante. La mia opinione è che le funzioni siano, in un certo senso, la versione pitonica delle interfacce.
Infine, dallo stesso Guido :
Parlare delle Abilità / Interfacce mi ha fatto pensare ad alcuni dei nostri nomi di metodi speciali "canaglia". In Language Reference, si dice: "Una classe può implementare determinate operazioni che vengono richiamate da una sintassi speciale (come operazioni aritmetiche o indici e affettamenti) definendo metodi con nomi speciali". Ma ci sono tutti questi metodi con nomi speciali come
__len__
o__unicode__
che sembrano essere forniti a beneficio delle funzioni incorporate, piuttosto che per il supporto della sintassi. Presumibilmente in un Python basato su interfaccia, questi metodi si trasformerebbero in metodi con nomi regolari su un ABC, in modo che__len__
diventinoclass container: ... def len(self): raise NotImplemented
Tuttavia, riflettendoci ancora un po ', non vedo perché tutte le operazioni sintattiche non si limiterebbero a richiamare il metodo appropriato con nome normale su uno specifico ABC. "
<
", per esempio, dovrebbe presumibilmente invocare "object.lessthan
" (o forse "comparable.lessthan
"). Quindi un altro vantaggio sarebbe la capacità di svezzare Python da questa stranezza dal nome alterato, che mi sembra un miglioramento dell'HCI .Hm. Non sono sicuro di essere d'accordo (figurati questo :-).
Ci sono due parti della "logica di Python" che vorrei spiegare prima.
Prima di tutto, ho scelto len (x) su x.len () per motivi HCI (è
def __len__()
venuto molto dopo). Ci sono due ragioni intrecciate in realtà, entrambe HCI:(a) Per alcune operazioni, la notazione del prefisso si legge meglio di quella del suffisso: le operazioni del prefisso (e dell'infisso!) hanno una lunga tradizione in matematica a cui piacciono le notazioni in cui le immagini aiutano il matematico a pensare a un problema. Confrontare la facile con cui riscriviamo una formula come
x*(a+b)
inx*a + x*b
per la goffaggine di fare la stessa cosa usando una notazione OO crudo.(b) Quando leggo un codice che dice
len(x)
che so che sta chiedendo la lunghezza di qualcosa. Questo mi dice due cose: il risultato è un numero intero e l'argomento è una sorta di contenitore. Al contrario, quando leggox.len()
, devo già sapere chex
è una sorta di contenitore che implementa un'interfaccia o eredita da una classe che ha uno standardlen()
. Testimone la confusione che di tanto in tanto avere quando una classe che non sta realizzando una mappatura ha unaget()
okeys()
metodo, o qualcosa che non è un file dispone di unwrite()
metodo.Dire la stessa cosa in un altro modo, vedo 'len' come un built-in funzionamento . Non vorrei perderlo. Non posso dire con certezza se lo intendevi o no, ma "def len (self): ..." sembra certamente che tu voglia degradarlo a un metodo ordinario. Sono fortemente -1 su questo.
La seconda parte della logica di Python che ho promesso di spiegare è il motivo per cui ho scelto metodi speciali per guardare
__special__
e non semplicementespecial
. Prevedevo molte operazioni che le classi potrebbero voler sovrascrivere, alcune standard (ad esempio__add__
o__getitem__
), altre non così standard (ad esempio, pickle__reduce__
per molto tempo non ha avuto alcun supporto nel codice C). Non volevo che queste operazioni speciali usassero nomi di metodi ordinari, perché allora classi preesistenti, o classi scritte da utenti senza una memoria enciclopedica per tutti i metodi speciali, sarebbero passibili di definire accidentalmente operazioni che non intendevano implementare , con possibili conseguenze disastrose. Ivan Krstić lo ha spiegato in modo più conciso nel suo messaggio, che è arrivato dopo che avevo scritto tutto questo.- --Guido van Rossum (home page: http://www.python.org/~guido/ )
La mia comprensione di questo è che in alcuni casi, la notazione del prefisso ha più senso (cioè, Duck.quack ha più senso di ciarlatano (Duck) da un punto di vista linguistico) e ancora una volta, le funzioni consentono "interfacce".
In tal caso, la mia ipotesi sarebbe di implementare get_king_moves basandosi esclusivamente sul primo punto di Guido. Ma questo lascia ancora molte domande aperte riguardo, ad esempio, l'implementazione di una classe stack e queue con metodi push e pop simili: dovrebbero essere funzioni o metodi? (qui immagino funzioni, perché voglio davvero segnalare un'interfaccia push-pop)
TLDR: Qualcuno può spiegare quale dovrebbe essere la strategia per decidere quando utilizzare funzioni e metodi?
X.frob
oX.__frob__
e indipendentifrob
.