Una sfida che intriga molti programmatori è quella di scrivere un quine, ovvero quello di scrivere un programma che stampi il proprio sorgente.

Partiamo con la definizione più semplice, e man mano aggiungiamo i vincoli necessari. Un programmatore furbo potrebbe stampare il contenuto del sorgente semplicemente aprendolo e stampando il contenuto:

import sys
print open(sys.argv[0],'r').read()

Per agevolare il compito di confronto, usiamo diff (in modalità unified), e quindi inseriamo il Python hashbang e modifichiamo il print in modo che non vada a capo:

#! /usr/bin/env python
import sys
print ''.join(open(sys.argv[0],'r').readlines()),

Il confronto è presto fatto:

$ ./quine.py | diff -u - quine.py
$

(D’ora in poi, avendo inserito un alias nel .bashrc, assumerò di usare sempre diff unified).

Abbiamo creato il nostro primo quine, ma abbiamo imbrogliato: leggere il file di input è considerato come barare; dobbiamo quindi ideare un metodo alternativo.

Consideriamo quindi un programma che, data una stringa hardcoded, la stampi. Il problema non è banale: potremmo addentraci in una ricorsione infinita se non studiamo un modo “furbo” per stampare il sorgente del programma:

#! /usr/bin/env python
import sys
source = [
"import sys" ,
# source = [ ... ] , # ricorsione infinita!
"if __name__ == '__main__':" ,
"for i in source:" ,
"print i"
]

if __name__ == '__main__':
    for i in source:
        print i

Studiamo un metodo alternativo per stampare il sorgente: nella stringa che rappresenta il sorgente (source), il segreto è usare un carattere speciale (es. $$) per dire al programma di stampare la stringa stessa!

#! /usr/bin/env python
import sys
source = [
"import sys" ,
"source = $$" ,
"if __name__ == '__main__':" ,
"for i in source:" ,
"print i"
]

if __name__ == '__main__':
    for i in source:
        if '$$' in i:
            # stampiamo la rappresentazione della stringa che rappresenta il codice sorgente
            pass
        else:
            print i

Per terminare il quine, dobbiamo espandere la sezione che manca; è importante prestare attenzione agli string literal (lettura consigliatissima!) per effettuare una stampa corretta delle stringhe nel programma (con i caratteri di escape corretti). Dopo aver ottenuto un primo esempio, armati di diff e di pazienza, dovremo correggere eventuali piccoli problemi (ad esempio: le virgole a fine riga per gli elementi della lista source (la virgola c’è per tutti gli elementi, tranne che per l’ultimo!)). Volete la soluzione?

#!/usr/bin/env python
import string
source = [
'#!/usr/bin/env python' ,
'import string' ,
'source = $$' ,
'if __name__ == "__main__":' ,
'for i in source:' ,
'if i == "source = $$":' ,
'print i[0:i.find("$")] + "["' ,
'for i in source:' ,
'''if string.count(i,"'") != 0:''' ,
'''print "'"'"'"'"'"*3 + i + "'"'"'"'"'"*3,''' ,
'else:' ,
'''print "'"'"'" + i + "'"'"'",''' ,
'if source.index(i) < len(source)-1:' ,
'print ","' ,
'else:' ,
'print ""' ,
'print "]"' ,
'else:' ,
'print i'
]
if __name__ == "__main__":
    for i in source:
        if i == "source = $$":
            print i[0:i.find("$")] + "["
            for i in source:
                if string.count(i,"'") != 0:
                    print "'"*3 + i + "'"*3,
                else:
                    print "'" + i + "'",
                if source.index(i) < len(source)-1:
                    print ","
                else:
                    print ""
            print "]"
        else:
            print i

[Notate come ho evitato alcuni problemi degli string literal tramite triple quoting (''') e il costrutto di ripetizione ("'"*3)].

Da un rapido confronto tra l’output del programma e il sorgente, a meno di indentazioni del codice sorgente (richieste da Python), possiamo dire che dopo tanta fatica abbiamo creato il nostro primo quine!

Il prossimo passo sarebbe quello di ottimizzare il codice: short is better. Ma io non mi dilungherò oltre: aprite un editor e datevi da fare!