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, infatti ci potremmo addentrare 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 è che 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
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 “,” a fine riga per gli elementi della lista sourcecode
— 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 literals 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!
Chapeau a te! Il tuo è un chiaro vincitore!
Ok ho creato un altro quine, molto più compatto ma meno leggibile:
Interessante!
Se usi una funzione (tecnica descritta in “Gödel, Escher, Bach: un’eterna ghirlanda brillante”), il quine diventa più conciso. Ecco un quine che scrissi tempo fa:
Chapeau al tuo quine! Non conoscevo la funzione; è davvero tosta comunque!
Ho modificato il tuo commento in modo che il codice sia formattato correttamente.