Pythonist pythonic way

Pythonist pythonic way
Tempo di lettura: 6 minuti

Segui la pythonic way e diventa un Pythonist

Se chiedi ad un programmatore Python cosa gli piace di più di Python, probabilmente ti risponderà l’alta leggibilità del codice.

L‘alta leggibilità del codice è il cuore del design del linguaggio di programmazione. C’è un paradigma riconosciuto da tutti i programmatori:

il codice viene letto molto di più di quanto viene scritto.

Uno dei motivi dell’alta leggibilità del codice Python è il suo insieme relativamente completo di linee guida Code Style e idiomiPythonic“.

Se un pythonist dice che il codice non è pythonic, significa che non rispetta le linee guida comuni.

La best way è considerata l’alta leggibilità abbinata alla sinteticità.

In verità esistono dei casi limite, per i quali non ci sono ancora delle linee guida, ma sono pochi.

Concetti generali

Codice esplicito

Sebbene qualsiasi tipo di magia nera sia possibile con Python, è preferibile il modo più esplicito e diretto.

Bad (da non fare)

def make_complex(*args):
    x, y = args
    return dict(**locals())

Good (da fare)

def make_complex(x, y):
    return {'x': x, 'y': y}

Nella codice sopra (Good), x e y sono valorizzate esplicitamente nella funzione, e viene ritornato un dizionario esplicito.

Il programmatore usando questa funzione sa esattamente cosa fare leggendo solo la prima riga e l’ultima riga di codice.

Non si può dire lo stesso del primo caso (Bad).

Un’istruzione per riga

Alcune istruzioni composte come le list comprehensions sono permesse ed apprezzate per la loro brevità e la loro espressività.

Ma resta una cattiva pratica avere due dichiarazione separate sulla stessa linea di codice.

Bad

print('one'); print('two')

if x == 1: print('one')

if complex_comparison and other_complex_comparison:
    # do something

Good

print('one')
print('two')

if x == 1:
    print('one')

cond1 = complex_comparison
cond2 = other_complex
if cond1 and cond2:
    # do something

Argomenti di funzione

Gli argomenti possono essere passati alle funzioni in quattro modi diversi.

Argomenti posizionali

1. Positional arguments (argomenti posizionali) sono necessari e non hanno valori di default. Sono la più semplice forma di argomenti e vanno bene per pochi argomenti. Fanno pienamente parte del significato della funzione e il loro ordine è naturale.
Per esempio, in send(message, recipient) o point(x, y) l’utente non ha difficoltà a ricordare che queste due funzioni richiedono due argomenti e il loro ordine.

Per scambiare l’ordine degli argomenti, è utile usare le etichette.

Per esempio send(recipient=’World’, message=’Hello’) e point(y=2, x=1).

Ma questo riduce la leggibilità ed è inutilmente prolisso se confrontato con la sintassi più immediata, send(‘Hello’, ‘World’) e point(1, 2).

Argomenti con parole chiave

2. Keyword arguments (argomenti con parole chiave) non sono necessari e hanno valori di default.
Sono spesso utilizzati per inviare parametri opzionali alle funzioni. Quando una funzione ha più di due o tre parametri posizionali diventa difficile ricordare la successione di parametri. Quindi è utile utilizzare keyword arguments con valori di default.
Per esempio una versione più elaborata della funzione send potrebbe essere così definita send(message, to, cc=None, bcc=None). In questo caso cc e bcc sono opzionali e valutati a None se non sono passati altri valori.

Invocare una funzione con keyword arguments può essere fatto in diversi modi in Python; Per esempio è possibile seguire l’ordine degli argomenti senza esplicitare il nome, in questo modo send(‘Hello’, ‘World’, ‘Cthulhu’, ‘God’). Si potrebbe cambiare l’ordine dei parametri  specificando il nome, in questo modo send(‘Hello again’, ‘World’, bcc=’God’, cc=’Cthulhu’). È meglio evitare queste due possibilità se non c’è un motivo valido per farlo. La sintassi più vicina alla definizione della funzione send(‘Hello’, ‘World’, cc=’Cthulhu’, bcc=’God’), è più leggibile.

… seguendo il principio YAGNI, è spesso più difficile rimuovere un argomento opzionale (e la sua logica all’interno della funzione), che è stato aggiunto “per ogni evenienza”, piuttosto che aggiungere un nuovo argomento opzionale, e la sua logica, quando necessario.

lista di Argomenti arbitrari

3. Arbitrary argument list (lista di argomenti arbitrari) è il terzo modo di passare argomenti ad una funzione. Se è necessario passare un numero opzionale di argomenti, questo può essere fatto con il costrutto *args, per esempio send(message, *args). Nel corpo della funzione args sarà una tupla con tutti gli argomenti posizionali passati. Per esempio send(‘Hello’, ‘God’, ‘Mom’, ‘Cthulhu’) e nel corpo della funzione args sarà uguale alla tupla (‘God’, ‘Mom’, ‘Cthulhu’).

Comunque questo costrutto ha degli inconvenienti e dovrebbe essere usato con cautela.

Se una funzione riceve più argomenti di stessa natura, è meglio impostare un unico argomento, per esempio una lista o qualsiasi sequenza.

Ecco, se send ha più destinatari è meglio definirla in questo modo send(message, recipients) e chiamarla con send(‘Hello’, [‘God’, ‘Mom’, ‘Cthulhu’]). In questo modo chi chiama la funzione può manipolare liberamente la lista dei destinatari.

dizionario arbitrario di Argomenti con parole chiave

4.  Arbitrary keyword argument dictionary (dizionario arbitrario di argomenti con parole chiave) è l’ultimo modo di passare gli argomenti ad una funzione. Se la funzione richiede una lista indeterminata di argomenti, è possibile utilizzare il costrutto **kwargs.  Nel corpo della funzione kwargs sarà un dizionario di tutti gli argomenti. L’unica regola è che non vengano utilizzate le stesse parole chiave di altri argomenti presenti nella definizione della funzione.

Per simili motivazioni è necessaria la stessa attenzione esposta per Arbitrary argument list. Queste potenti tecniche devono essere utilizzate quando c’è una comprovata necessità per farlo.

Non dovrebbero essere usate se esiste un modo più semplice e chiaro per le esigenze della funzione.

Spetta al programmatore scegliere come passare gli argomenti, e decidere se utilizzare le tecniche avanzate.

Se si seguono i questi consigli saggiamente, è possibile e divertente scrivere funzioni Python che siano:

      • Di facile lettura (il nome e gli argomenti non hanno bisogno di spiegazioni);
      • Facile da modificare (l’aggiunta di un nuovo argomento della parola chiave non interrompe altre parti del codice).

Evitare la bacchetta magica

Python ha un insieme molto ricco di strumenti ed attrezzi che permettono di fare quasi tutti i trucchi difficili. Per esempio è possibile fare ognuno di questi che segue:

      • Cambiare il modo col quale gli oggetti sono creati ed instanziati;
      • Cambiare il modo col quale l’interprete di Python importa i moduli;
      • È anche possibile (e raccomandato se necessario) incorporare una routine di codice C in Python.

Comunque, tutte queste opzioni hanno degli inconvenienti e sempre meglio usare un modo più semplice per raggiungere il proprio obiettivo. Il principale inconveniente è che la leggibilità viene penalizzata fortemente. Molti strumenti di analisi del codice, come pylint o pyflakes, non saranno in grado di valutare correttamente questo codice “magico”.

Un programmatore Python dovrebbe conoscere bene queste possibilità, perché infonde fiducia sapere che nessun problema è insormontabile disponendo dei mezzi giusti.

Resta importante sapere come e quando non usarli.

Come un maestro di Kung Fu, un Pythonista sa come uccidere con un solo dito, ma non lo farà mai per davvero.

Noi siamo tutti utenti responsabili

Come visto sopra, Python permette molti trucchi, e alcuni di loro sono potenzialmente dannosi. Un buon esempio è dato dal codice che può sovrascrivere le proprietà e i metodi di un oggetto: in Python non esiste la keyword “private”. Questa filosofia, molto diversa da altri linguaggi di programmazione. Alcuni sono altamente protettivi, come Java, che fornisce un mucchio di meccanismi per prevenire  qualsiasi uso improprio. Python preferisce dire: “Noi siamo tutti utenti responsabili”.

Questo non significa che, per esempio, nessuna proprietà è considerata privata, e che in Python non è possibile una sorta di incapsulamento. Piuttosto, invece di fare affidamento su muri di cemento armato eretti dagli sviluppatori, per altri sviluppatori, la comunità Python preferisce utilizzare convenzioni parlanti.

La convenzione principale per le proprietà private è anteporre a tutti gli “interni” un trattino basso (underscore). Qualsiasi comportamento scorretto o problema riscontrato è responsabilità del codice client che accede agli elementi contrassegnati come privati.

È incoraggiato usare questa convenzione: ogni metodo o proprietà che non dovrebbe essere usata da un codice client deve avere un underscore come prefisso. Questo garantirà una migliore separazione dei compiti ed una più semplice manutenzione del codice esistente;  Sarà sempre possibile rendere nota una proprietà privata, ma rendere privata una proprietà pubblica potrebbe essere un’operazione molto più difficile.

Ritornare valori, istruzioni di return

Quando una funzione cresce di complessità, non è raro usare molteplici istruzioni di return all’interno del corpo della funzione. È preferibile evitare di ritornare valori significativi da più punti; occorre mantenere un intento chiaro e un livello di leggibilità sostenibile.

Esistono due casi principali per ritornare valori da una funzione:

      • Quando è stata eseguita correttamente;
      • Quando va in errore per un parametro errato o per altro motivo che non permette di portare a termine il compito.

Se non si desidera sollevare eccezioni per il secondo caso, potrebbe essere necessario restituire un valore. Per esempio utilizzando None o False, per indicare che la funzione non può essere elaborata correttamente. In questo caso, è meglio ritornare il valore non appena è stato rilevato il contesto di errore. Aiuterà a semplificare la struttura della funzione.

Nel codice che segue l’istruzione di errore di return da notizia alla parti successive. Spesso è necessario disporre di più dichiarazioni di ritorno di questo tipo.

def complex_function(a, b, c):
    if not a:
        return None  # Raising an exception might be better
    if not b:
        return None  # Raising an exception might be better
    # Some complex code trying to compute x from a, b and c
    # Resist temptation to return x if succeeded
    if not x:
        # Some Plan-B computation of x
    return x  # One single exit point for the returned value x will help
              # when maintaining the code

Tuttavia, quando una funzione ha più punti di uscita principali per il suo corso normale, diventa difficile eseguire il debug del risultato restituito. Quindi potrebbe essere preferibile mantenere un unico punto di uscita. Ciò aiuterà anche a scomporre in funzioni alcune parti del codice (refactoring).

… Idiomi di Python e lo Zen di Python …

Fonte: python-guide.org

2 pensieri riguardo “Pythonist pythonic way”

Rispondi

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.

%d blogger hanno fatto clic su Mi Piace per questo: