Un indovinello su print

Mentre scrivevo perché non bisogna insegnare a usare print, mi era venuto in mente un esempio divertente che poi ho dovuto tagliare per non allungare troppo il discorso. Lo propongo qui, come una specie di appendice al discorso su print.

Potete proporlo come un indovinello, se volete. Viene meglio se avete davanti una shell di Python aperta. Scrivete questo nella shell (sono sicuro che non è la prima volta che lo vedete):
>>> print('hello world')
hello world
E adesso chiedetevi, rapidamente: in Python 3, print è una funzione, giusto? E allora qual è il valore di ritorno di "print('hello world')"?

C'è una probabilità abbastanza alta che abbiate pensato: il valore di ritorno è appunto "hello world". E guarda caso, avete pensato la risposta sbagliata.
Vorrei subito chiarire una cosa importante: questa non è una delle tante domande a trabocchetto su un aspetto molto oscuro di Python. Dare la risposta sbagliata qui rivela una profonda incomprensione di che cosa fa davvero print. Ora, se siete un principiante che ha appena imparato le funzioni in Python, è perfettamente naturale cadere nella trappola: anzi, mi meraviglierebbe sentire la risposta giusta da un principiante.
Ma se avete appena finito di scrivere un tutorial o registrare l'ennesimo corso su YouTube... forse vi conviene fare una riflessione.

Se siete uno studente e avete voglia di metterci un po' di pepe, provate a fare la domanda al vostro prof. Se volete contestualizzare meglio il problema (o forse... confondere ancora di più le idee), fate il confronto con altre funzioni, sempre nella shell... ricordate: dovete provare queste cose nella shell, altrimenti non è esattamente lo stesso. Poi chiariremo perché.
>>> abs(-42)    # una funzione predefinita
42
>>> def mysum(a, b):    # una funzione scritta da voi
        return a + b
>>> mysum(40, 2)
42
>>> print('hello world')   # la funzione "print"
hello world
Se il valore di ritorno di "abs(-42)" è "42", allora il valore di ritorno di "print('hello world')" è "hello world", giusto? Se riuscite a metterla nel modo più ingenuo possibile, così che il prof non sospetti niente, è probabile che si sbaglierà anche lui.

A questo punto, se volete, potete fermarvi un attimo a pensare qual è la risposta giusta. Se avete letto il mio articolo sull'uso di "print", forse siete già sulla buona strada.
Vediamo un po'.

Per prima cosa, in Python non esiste la distinzione tra funzione e procedura, come in Pascal. Una funzione restituisce sempre qualcosa, in ogni caso. Se la funzione non ha un punto di "return" esplicito, allora restituisce comunque l'oggetto "None". Ovvero, scrivere una funzione senza "return" è come scrivere la stessa funzione e terminarla con "return None".

E qui c'è un inghippo interessante. Python non scrive mai "None" nella shell interattiva. Se eseguite nella shell una funzione che restituisce "None", allora Python non scrive nulla:
>>> def f():   # questa funzione restituisce None
        pass
>>> f()
<nulla!>
anche, ancora più semplicemente:
>>> a = None
>>> a
<nulla!>
Se volete "vedere" in qualche modo il "None", potete forzare la conversione a stringa:
>>> str(f())
'None'
>>> str(a)
'None'
questo, come vedremo alla fine, ci porta a un altro trucchetto buffo.

Ma intanto, perché Python, nella shell interattiva, si vergogna così tanto a scrivere "None"?
Bisogna capire che restituire "None" è abbastanza comune: ci sono molte funzioni predefinite (e metodi di oggetti predefiniti) che lo fanno. In particolare, tutte i metodi che modificano sul posto un oggetto mutabile restituiscono "None":
>>> a = [1, 2, 3]
>>> a.append(4)
<nulla!>
>>> str(a.append(4))
'None'
Questa è una precisa scelta di design di Python, ed è anche una trappola frequente per i principianti:
>>> b = a.append(4)
>>> b  # ci aspettiamo [1, 2, 3, 4] ma invece...
<nulla!>
In altri linguaggi le funzioni che modificano oggetti sul posto restituiscono l'oggetto modificato: per esempio, nella shell di Ruby
> a = [1, 2, 3]
> b = a.append(4)
> b
=> [1, 2, 3, 4]

E adesso ci avviciniamo al dunque. Operazioni come la modifica di un oggetto sul posto sono dei side-effect: la funzione sta facendo qualcosa, ma questo qualcosa non riguarda il valore di ritorno restituito. Piuttosto che eseguire il side-effect e inoltre restituire l'oggetto modificato, Python preferisce mantenere le cose separate: se fa un side-effect (come modificare l'oggetto), allora restituisce "None". Come dicevo, è una scelta di design.

Ed eccoci al dunque. Se avete letto l'articolo su print, già lo avrete capito: la funzione print emette un messaggio nello standard output, e questa operazione è un side-effect. E quindi, per una scelta di design, la funzione print emette il messaggio nello standard output, e poi restituisce None. Ed ecco la risposta al nostro indovinello.

Ed ecco anche perché la shell preferisce non scrivere "None": perché altrimenti per ogni print dovrebbe scrivere entrambe le cose:
>>> print('hello world')
hello world
None
E capite che non è proprio bello da vedere.
Il problema qui è che la modalità interattiva di Python è un po' fuorviante, in effetti. Scrive nello stesso posto (la shell) sia i messaggi dello standard output, sia i valori della valutazione di un'espressione. Ed ecco perché è facile confondersi tra un valore di ritorno pubblicato nella shell
>>> abs(-42)
42
e un side-effect nello standard output (che è sempre la shell!)
>>> print('hello world')
hello world
Ed ecco anche perché è meglio non usare print nella shell, ed è meglio non mostrare print ai principianti finché non è assolutamente necessario, come dicevo nell'articolo precedente.

Se volete vedere il valore di ritorno di print, potete convertire in stringa, come abbiamo fatto prima:
>>> str(print('hello world'))
hello world
'None'
Ecco in effetti la "doppia scrittura": vediamo sia il side-effect sia il valore di ritorno.

E questo ci porta all'ultima piccola bizzarria che vi avevo promesso. Siccome print utilizza dietro le quinte proprio la funzione "str" per convertire a stringa ciò che deve pubblicare nello standard output, potete usare usare print invece di "str" per vedere il valore di ritorno.

Quindi potete divertirvi a chiedere al vostro prof di Python perché
>>> print('hello world')
hello world
ma
>>> print(print('hello world'))
hello world
None

Fatemi sapere che cosa risponde...


Commenti

  1. Salve Signor Polignieri, sono Federico. Ho provato a contattarla attraverso il suo indirizzo gmail in merito al suo nuovo libro Python in windows.
    Mi chiedevo se avesse tempo di rispondere a qualche domanda di chiarimento. Cari Saluti, Federico

    RispondiElimina

Posta un commento

Post più popolari