domingo, 25 de outubro de 2009

Capturando a saída interativa do interpretador Python em Python

Tudo tem que ser automatizado. Como estou escrevendo um pequeno livro sobre Python já há muito tempo, tenho que atualizar a versão da linguagem todo ano :-D. Com o Python 3.1 o problema ficou mais sério, pois agora tenho que testar os exemplos do livro com essa nova versão que não é totalmente compatível. O livro está sendo escrito em Latex, usando o TextMate e o Skim no Mac OS X. Eu fiz a besteira de inserir manualmente o código fonte Python no texto Latex, agora tenho que revisar os scripts um a um. Como vou revisar, aproveito para consertar e desta vez gravar os programas Python em arquivos separados.

Resolvi então utilizar o SCONS para ajudar na tarefa. Para isso, um builder customizado precisa ser criado. No SConstruct:
PYTHON_BIN="/opt/local/bin/python2.6"

python_output=Builder(action= '$PYTHON_BIN capture.py $SOURCE $TARGET',
                  suffix     = '.pyt',
                  src_suffix = '.py')

env = Environment(BUILDERS = {'python_output': python_output})
env["PYTHON_BIN"]=PYTHON_BIN

env.python_output("p1")

e depois o programa em Python que faz a captura, capture.py:
import pexpect
import sys

PYTHON_BIN="/opt/local/bin/python3.1"

interacao = file(sys.argv[1], "r").readlines()

python = pexpect.spawn(PYTHON_BIN)
python.logfile_read = file(sys.argv[2], "w")

for l in interacao:
   python.expect([">>> ", "... "])
   python.send(l)

python.sendline("exit()")
python.expect(pexpect.EOF)

O script capture.py recebe dois parâmetros: o nome do arquivo Python e o nome do arquivo de saida.
Para testar, crie um arquivo p1.py:
a=5
b=10
print(a+b)
c = a + b
d = c * a / 100
print(c,d)
for x in range(3):
    print ("Oi")

#Erro:
=d=d
rrrrr

Deve produzir a seguinte saída em p1.pyt:
Python 3.1.1 (r311:74480, Oct 24 2009, 17:11:04) 
[GCC 4.2.1 (Apple Inc. build 5646)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> a=5
>>> b=10
>>> print(a+b)
15
>>> c = a + b
>>> d = c * a / 100
>>> print(c,d)
15 0.75
>>> for x in range(3):
...     print ("Oi")
... 
Oi
Oi
Oi
>>> #Erro:
... =d=d
  File "", line 2
    =d=d
    ^
SyntaxError: invalid syntax
>>> rrrrr
Traceback (most recent call last):
  File "", line 1, in 
NameError: name 'rrrrr' is not defined
>>> 
>>> exit()

Se você utilizar este arquivo, não esqueça de modificar as variaveis PYTHON_PATH. Eu uso MacPorts e devo especificar o caminho completo da versão de Python que quero utilizar. Isso evita problemas com outras versões instaladas no sistema, como o Python da Apple. Eu utilizei o Python 3.1 nos exemplos do livro, mas ainda uso o 2.6 para instalar meus módulos no Mac Ports.

Eu espero que isto ajude alguém com o mesmo tipo de problema: capturar a saída interativa de um programa como se fosse um terminal comum.

Um comentário:

Fernanda disse...

Oi !

Seu blog é bem bacana, parabéns !
Encontrei algumas matérias suas através do diHITT!
Você podia enviar também pro PC Chip né?, o link dele é http://www.pcchip.com.br

Eu acompanho notícias de blogs por lá também, ele é tipo o diHITT e é muito bacana, e bem bonito.

Beijo !

Fernanda