Python: scriviamo un quine

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!

4 thoughts on “Python: scriviamo un quine”

  1. 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:

    #!/usr/bin/env python
    def quine(s):
    print '%s(%s%s%s)' % (s, chr(34)*3, s, chr(34)*3)
    
    quine("""#!/usr/bin/env python
    def quine(s):
    print '%s(%s%s%s)' % (s, chr(34)*3, s, chr(34)*3)
    
    quine""")
    

Leave a Reply

Your email address will not be published. Required fields are marked *