Automágica: durante 2017 estoy trabajando bastante en Automágica, mi software para editar libros: Más información - Posts relacionados

Una experiencia en PythonBugDay

Hoy se llevó a cabo el octavo Python Bug Day. Sabía que era en estos días pero no lo tenía presente ni se me había ocurrido participar. Al mediodía Facundo Batista me lo recuerda por chat y me pregunto... ¿Por qué no? Puedo probar unas horas, ver que pasa, nunca compilé Python tal vez tenga que hacerlo para probar una solución.

Me dieron una lista de bugs fáciles. Y elejí este: 1779. El bug en particular podría haber sido resuelto en pocos minutos por un desaarrollador de Python. Es más, el mismo había sido reportado por el creador del lenguaje :D Pero como bien dice en uno de los comentarios, es un buen bug para el Python Bug Day. A un experto, resolverlo no le habría costado nada, pero tampoco le habría aportado nada. A un novato como a mi me sirvió para conocer algo del nucleo del lenguaje y el proceso que hay que seguir para resolver un bug:

    <li>Bajar la última versión del código fuente</li>
    
    <li>Compilar</li>
    
    <li>Correr los tests para ver que todo ande bien</li>
    
    <li> Encontrar el bug y arreglarlo</li>
    
    <li>Correr los tests nuevamente</li>
    
    <li>Arreglar los tests que fallan</li>
    
    <li>Agregar tests que prueben el arreglo</li>
    
    <li>Correr los tests nuevamente</li>
    
    <li>Armar un parche</li>
    
    <li>Envair el parche</li>
    

El bug, que continuará en las versiones previas de Python pero ya está corregido en Python 3000, es el siguiente:

>>> int("- 1")

-1

>>> float("- 1")

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

ValueError: invalid literal for float(): - 1

Cuando se quiere crear un número a partir de un string, no pueden haber espacios en blanco entre el signo (+ o -) y el número. El comportamiento correcto es el de la función float. El de int es errorneo. Necesita ser arreglado.

Obtener el código fuente

Bajé del servidor svn la última versión de Python 3000, la nueva versión del lenguaje, para el cual el bug estaba abierto:

svn co http://svn.python.org/projects/python/branches/py3k/

(Esto llevó bastante tiempo. Mientras tanto cociné y almorcé.)

Compilar

cd py3k/

./configure && make

Correr el intérprete compilado

./python

Correr los tests

make test

o

./python Lib/test/regrtest.py

Arreglar el bug

En los comentarios sel bug se decía que era simple de resolver y que no consistía más que en comentar 2 líneas en un archivo en C.

Revisé el archivo Objects/longobject.c y luego de entender como funcionaba la función PyLong_FromString(char str, char *pend, int base), comenté las líneas 1688 y 1689. Problema resuelto!

Compilé para que mis cambios se vean reflejados.

make

Y probé en el intérprete.

>>> int("- 1")

Traceback (most recent call last):

File "<stdin>", line 1, in <module>

ValueError: invalid literal for int() with base 10: '- 1'

Perfecto!

Luego corrí las pruebas afectadas por mi cambio y fallaron.

./python Lib/test/test_builtin.py

La función en particular era test_int(self). Luego de una mirada rápida, comenté los valores de una lista que hacían fallar la prueba y pensé que con eso era suficiente. Consulté y me dijeron que también agregue mi cambio en Misc/NEWS. Lo hice. Lo siguiente era generar el parche.

svn diff | tee bug_fix_for_1779.diff

El mismo está en http://bugs.python.org/file9220.

Luego lo vio Facundo y me dijo que tenía que tenía que agregar pruebas para mi parche. Me llevó bastante tiempo entender como funcionaba la prueba. Finalmente, luego de preguntar bastante conseguí el resultado deseado. Volví a generar un parche y a subirlo.

svn diff | tee bug_fix_for_1779-plustests.diff

http://bugs.python.org/file9228

La experiencia

La verdad es que la experiencia fue muy buena. En unas 5 horas aprendí mucho sobre Python en si y sobre su ciclo de desarrollo, interactué con una comunidad despuesta a darte una mano cuando la necesitás y arreglé un error en el lenguaje que más uso. Voilá! Muchas gracias Comunidad de Python por organizar este Python Bug Day.



Ejemplos de PLY

Acabo de crear un sitio web con todos los ejemplos de PLY que acompañan su distribución.

En distribuciones derivadas de Debian los pueden encontrar en:

/usr/share/doc/python-ply-doc/examples/

luego de haber instalado python-ply y su documentación:

apt-get install python-ply python-ply-doc

PLY Examples

Los archivos .py de los ejemplos se encuentran en una versión html con su sintaxis coloreada. Esto lo hice con el comando pygmentize. Para poder usarlo, en Debian y similares, debemos instalarlo el paquete python-pygments. Más información.

En particular utilicé OpenOffice para editar el README que acompaña la distribución de los ejemplos y crear una página html y este oneliner para crear todos los .py.html de una pasada:

for d in ls -d * | grep -v index.html; do cd $d; for f in ls *.py; do pygmentize -f html -O full -o $f.html $f; done; cd ..; done

Espero sea útil, cree el sitio a partir de una sugerencia en la lista de correos de PLY.


MiniLisp (un ejemplo de ply)

PLY

The asteroid to kill this dinosaur is still in orbit.

  • Lex Manual Page

PLY o python-ply (cómo se llama su paquete en Debian) es una implementación de las herramientas lex y yacc para análisis léxico y sintáctico. Está enteramente escrito en Python y su primera versión fue desarrollado por David Beazley en el año 2001 para ser usado en un curso de Introducción a los Compiladores.

Lex

Lex es una creador de analizadores léxicos (lexers). La función principal de un lexer es tomar un flujo de caracteres entrada y devolver un flujo de tokens como salida. Ejemplos de tokens en un programa escrito en algún lenguaje de programación podrían ser: un número, un paréntesis, un identificador o una palabra clave. Por ejemplo: 17, ), miVarible, if.

Para definir los tokens utilizamos expresiones regulares.

Yacc

Yacc, Yet Another Compilers Compiler, nos permitirá crear un programa que tome un flujo de tokens como entrada y reconozca a partir de ellos un lenguaje. Notemos por ejemplo que si bien if { 555 ;; for printf i++[] es un flujo de tokens válidos de C, no es una sentencia válida del lenguaje como si lo es for(i=0; i<5; i++){}.

Para definir la gramática de un lenguaje de programación vamos a usar una notación conocida como BNF (Backus–Naur form).

MiniLisp

Intérpretes de lenguajes de programación como Python o PHP son escritos en C por razones de eficiencia. ¿Vamos a usar Python para escribir uno? Bueno.. ¿por qué no? La filosofía de Python consiste en escribir rápido una solución para un problema y poder probarla enseguida. En el tiempo en que implementás en C una idea podes escribir 3 soluciones diferentes en Python, probarlas y elegir con cual continuar. En caso de que en algún momento se detecte que el programa resultante corre lento (más lento de lo necesario), siempre podés:

  • realizar optimizaciones en el código Python
  • reescribir en C alguna parte critica del mismo.

El intérprete que voy a construir para aprender ply va a tener sabor a Lisp y va a ser muy sencillo. Va a ser un MiniLisp! En particular:

  • Usará notación prefija mediante paréntesis de la forma (fun-name arg [arg]). Esto significa que el intérprete podrá resolver expresiones como (+ 1 1) y responderá 2, (= 1 1) y responderá #t (la forma en que voy a representar el valor de verdad True), pero también (+ 1 2 3 4 5) que da como resultado 15 y (or #t #f #f) que da como resultado #t.
  • Permitirá resolver expresiones anidadas. Esto significa que además de las expresiones anteriores, podrá resolver expresiones como (+ 1 (+ 2 2) (- 5 4) 10) que da como resultado 16, (and (= 1 1) #t) que da como resultado #t y (and (or (= (+ 10 10) 20) #f (= #t #f)) (= 13 (+ 10 1 1 1 1 (- 2 1)))) cuyo resultado se deja a cargo del lector para que vaya entrando en clima :)
  • Contará con funciones de manejo de listas típicas en Lisp como car, cdr y cons:
    • (car '(1 2 3)) obtiene el primer elemento (o cabeza) de la lista '(1 2 3): 1
    • (cdr '(1 2 3)) obtiene la cola de la lista '(1 2 3): (2 3)
    • (cons 0 '(1 2 3)) crea una nueva lista con 0 como cabeza y '(1 2 3) como cola: (0 1 2 3)
    • Notar que (cons '(1 2) '(3 4)) da como resultado una lista cuya cabeza es la lista '(1 2) y su cola '(3 4): ((1 2) 3 4)
    • También se podrá usar (concat '(1 2) '(3 4)) para obtener una lista que sea la concatenación de ambas: (1 2 3 4)
    • y (list 1 2 3 4) para crear una lista de elementos: (1 2 3 4)
<li>La implementación tiene solo fines didácticos así que solo trabajará con enteros aunque añadir soporte para números reales (floats) debería ser fácil.</li>

<li>No tendrá un manejo de errores muy completo.</li>

<li>Pensé varias formas de implementar la función <code>define</code> pero no conseguí que funcione del todo bien :(, así que quité la funcionalidad.</li>

<li>Otra función común en las implementaciones de Lisp con la que contará MiniLisp es cond, la cual evalúa su primer argumento y si es verdadero retorna el segundo. Esta función puede usarse para implementar construcciones de control de flujo más complejas como if-else, while o for. Eso si en MiniLisp se pudieran definir funciones :(
  • (cond (= 1 1) 7) retorna 7
  • pero (cond (= 1 2) 7) no retorna nada.

Manos a la obra

PLY consiste en un paquete que contiene los módulos ply y yacc.

En Debian/Ubuntu podemos instalarlo con el comando (como root o mediante sudo)

apt-get install python-ply

lex.py

Al programa que generará un analizador léxico lo llamé lex.py y consiste en:

import ply.lex as lex

Debemos crear una tupla con todos los nombres de los tokens a reconocer (por convención escribimos los nombres en mayúsculas):

tokens = ('QUOTE', 'SIMB', 'NUM', 'LPAREN', 'RPAREN', 'NIL', 'TRUE', 'FALSE', 'TEXT')

Un diccionario en el cual la clave es una palabra reservada y el valor uno de los tokens de la tupla anterior:

reserved = {

'nil' : 'NIL',

}

En este ejemplo prácticamente no hay palabras reservadas, otros lenguajes podría haber palabras como if, while, for o return.

Lo siguiente es definir las expresiones regulares para cada token. Existen dos formas de hacerlo, mediante strings o mediante funciones.

El primer caso se usa cuando el token no requiere ningún tipo de procesamiento luego de ser econtrado:

t_LPAREN = r'\('

t_RPAREN = r'\)'

t_QUOTE = r'\''

t_TRUE = r'\#t'

t_FALSE = r'\#f'

Notar que se usan raw strings de Python para escribir las expresiones regulares que posteriormente serán compiladas y usadas (PLY utiliza el módulo re en su análisis léxico).

Para los tokens correspondientes a números podemos querer hacer alguna verificación antes de devolverlo, en ese caso la especificación del token puede hacerse mediante una función:

def t_NUM(t):

    r'd+'

    try:

        t.value = int(t.value)

    except ValueError:

        print "Line %d: Number %s is too large!" % (t.lineno,t.value)

        t.value = 0

    return t

Notar que en el docstring de la función se debe colocar la expresión regular correspondiente al token.

Otro ejemplo de esto se da para el token SIMBOL, pero con una particularidad. Este token se usa para los nombres de funciones o variables. car, cdr o and son ejemplos de símbolos en MiniLisp.

def t_SIMB(t):

    r'[a-zA-Z_+=*-][a-zA-Z0-9_+*-]*'

    t.type = reserved.get(t.value,'SIMB')    # Check for reserved words

    return t

Luego de encontrar una secuencia de caracteres que corresponda con la expresión regular de los símbolos, nos fijamos que no sea una palabra reservada. Si lo es, en t.type se guardará el nombre de token correspondiente, por ejemplo 'NIL', caso contrario 'SIMB'.

Si hubiesemos especificado t_NIL = r'nil' en lugar de usar el diccionario reserved, cadenas de caracteres como nillave (un símbolo válido) serían interpretadas como NIL seguido del símbolo lave.

El orden en que estas definiciones son usadas es el siguiente: primero los strings en orden descendiente de la longitud de la expresión regular y luego las funciones en el orden que fueron escritas.

Archivo completo: lex.py

yacc.py

El código en el que se define la gramática del lenguaje lo puse en un archivo llamado yacc.py. El siguiente BNF expresa la gramática de MiniLisp (las palabras en mayúsculas representan símbolos terminales y las palabras en minúsculas símbolos no terminales), a la izquierda de la regla siempre va un único elemento y el símbolo ::= puede leer se como 'es':

exp ::= atom

exp ::= quoted_list

exp ::= call

quoted_list ::= QUOTE list

list ::= LPAREN items RPAREN

items ::= item items

items ::=

item ::= atom

item ::= list

item ::= quoted_list

item ::= call

call ::= LPAREN SIMB items RPAREN

atom ::= SIMB

atom ::= bool

atom ::= NUM

atom ::= TEXT

atom ::=

bool ::= TRUE

bool ::= FALSE

atom ::= NIL

y lo siguiente es cómo queda la gramática expresa en código. Se debe definir una función por cada una de las reglas previas (el docstring de la función corresponde a la regla). Cada función recibe como parámetro un objeto iterable (muy parecido a una lista, pero que no se comporta totalmente como tal, así que cuidado con los subíndices negativos) que contiene los valores de cada símbolo de la regla.

# BNF



def p_exp_atom(p):

    'exp : atom'

    p[0] = p[1]



def p_exp_qlist(p):

    'exp : quoted_list'

    p[0] = p[1]



def p_exp_call(p):

    'exp : call'

    p[0] = p[1]



def p_quoted_list(p):

    'quoted_list : QUOTE list'

    p[0] = p[2]



def p_list(p):

    'list : LPAREN items RPAREN'

    p[0] = p[2]

    f = p[2][0]



def p_items(p):

    'items : item items'

    p[0] = [p[1]] + p[2]



def p_items_empty(p):

    'items : empty'

    p[0] = []



def p_empty(p):

    'empty :'

    pass



def p_item_atom(p):

    'item : atom'

    p[0] = p[1]



def p_item_list(p):

    'item : list'

    p[0] = p[1]



def p_item_list(p):

    'item : quoted_list'

    p[0] = p[1]



def p_item_call(p):

    'item : call'

    p[0] = p[1]



def p_call(p):

    'call : LPAREN SIMB items RPAREN'

    if DEBUG: print "Calling", p[2], "with", p[3]

    p[0] = lisp_eval(p[2], p[3])



def p_atom_simbol(p):

    'atom : SIMB'

    p[0] = p[1]



def p_atom_bool(p):

    'atom : bool'

    p[0] = p[1]



def p_atom_num(p):

    'atom : NUM'

    p[0] = p[1]



def p_atom_word(p):

    'atom : TEXT'

    p[0] = p[1]



def p_atom_empty(p):

    'atom :'

    pass



def p_true(p):

    'bool : TRUE'

    p[0] = True



def p_false(p):

    'bool : FALSE'

    p[0] = False



def p_nil(p):

    'atom : NIL'

    p[0] = None

En yacc.py pueden verse todos los detalles de implementación que logran hacer que esta estructura arbórea que podemos armar con yacc pueda manipularse para trabajar como un intérprete de Lisp. La implementación concreta fue hecha mediante funciones sencillas escritas en Python. Traté de mantener el engine del lenguaje lo más simple posible ya que el objetivo principal fue trabajar en el analizador léxico/sintáctico del mismo.

En particular destaco el uso de las listas de Python (una estructura de datos muy poderosa) como componente fundamental de este pequeño Lisp.

mini-lisp.py

Finalmente utilicé el módulo cmd (parte de la librería estándar de Python) para crear la interfaz de línea de comandos del intérprete:

# -*- coding: utf-8 -*-

from yacc import yacc, lisp_str

import cmd



class MiniLisp(cmd.Cmd):

    """

    MiniLisp evalúa expresiones sencillas con sabor a lisp,

    más información en https://viejoblog.juanjoconti.com.ar

    """



    def __init__(self):

        cmd.Cmd.__init__(self)

        self.prompt = "ml> "

        self.intro  = "Bienvenido a MiniLisp"



    def do_exit(self, args):

        """Exits from the console"""

        return -1



    def do_EOF(self, args):

        """Exit on system end of file character"""

        print "Good bye!"

        return self.do_exit(args)



    def do_help(self, args):

        print self.__doc__



    def emptyline(self):

        """Do nothing on empty input line"""

        pass



    def default(self, line):

        """Called on an input line when the command prefix

           is not recognized.

           In that case we execute the line as Python code.

        """

        result = yacc.parse(line)

        s = lisp_str(result)

        if s != 'nil':

            print s



if __name__ == '__main__':

        ml = MiniLisp()

        ml.cmdloop()

Con esto se logra tener algunas funcionalidades útiles como poder usar las flechas izquierda y derecha del teclado para movernos por la línea que estamos escribiendo y las flechas arriba y abajo para movernos por la historia de las expresiones que fuimos introduciendo.

Resultado

En las siguientes capturas de pantalla se ven ejemplos de MiniLisp en acción:

Operaciones básicas

Minilisp 1

Manejo de listas

Minilisp 2

Ejercicio planteado al lector

Minilisp 3

El código fuente completo de MiniLisp está empaquetado en: mini-lisp-0.1.tgz

También puede navegarse en: https://viejoblog.juanjoconti.com.ar/files/python/mini-lisp/

Conclusión

Si bien la implementación lograda dista mucho (en su funcionalidad) de una implementación real de Lisp, su sintaxis (aunque simple como la del propio Lisp) es lo que quería lograr y me sirvió para hacer muchas pruebas (algunos de sus resultados son reflejados en este artículo).

El objetivo de este desarrollo fue aprender PLY para poder utilizarlo en un proyecto que si bien no es un lenguaje de programación, se le parece en la necesidad de realizar un análisis léxico/sintáctico: un probador de teoremas (ATP).

Referencias

Desde el sitio web de ply se puede acceder a su documentación.

Sobre compiladores: Compiladores: técnicas, principios y herramientas.

Update: unas palabras sobre reducción

Supongamos que la expresión que se analizará sintácticamente (ya pasó por el analizador léxico, es decir que consiste de tokens válidos) es: (= (+ 1 1) 2).

En base a las reglas BNF definidas, esta expresión puede verse como el siguiente árbol:

La expresión se va resolviendo de abajo hacia arriba en el árbol (o lo que es lo mismo de adentro hacia afuera en la expresión) mediante la aplicación de las funciones definidas.


Harry Potter y los Lenguajes de Programación

Buscando información sobre diseño de lenguajes de programación (just for fun) me topé con un artículo que relaciona este tópico con los libros de Harry Potter (de los que soy un feliz lector). Para darle más fuerza a la casualidad, el autor del artículo es la misma persona que creó el lenguaje de programación que más me gusta.

Me tomo la libertad de traducir este artículo de Guido Van Rossum, creador del lenguaje de programación Python.

La Teoría de Harry Potter para el Diseño de Lenguajes de Programación

por Guido van Rossum

14 de Agosto de 2005

Resumen

Una comparación alegre de como exitosos autores de ficción y diseñadores de lenguajes de programación pueden por accidente encontrar en su primer trabajo las pistas de sus éxitos futuros.

La teoría

Me encanta leer los libros de Harry Potter, y recientemente se me ocurrió una analogía interesante entre escribir series de libros de ficción y diseñar lenguajes de programación.

Estoy seguro de esto: cuando J.K. Rowling escribió el primer libro de Harry Potter (como el primero de una serie de siete) había desarrollado una buena idea de las cosas que eventualmente iban a pasar en la serie pero no tenía la idea exacta de lo que iba a pasar en los restantes libros, tampoco todos los detalles de cómo funcionaría la magia en el mundo que estaba creando.

También estoy asumiendo que a medida que iba escribiendo los volúmenes sucesivos, volvía a los primeros libros para rescatar detalles que habían sido introducidos solo para darle color a la historia y darles un nuevo significado. Por ejemplo, dudo que cuando inventó el nombre Voldemort ya haya pensado que "I am Lord Voldemort" (Yo soy el Señor Voldemort) sería un anagrama de "Tom Marvolo Riddle" (Tom Riddle no es un nombre muy convincente para quien terminaría siendo la persona más mala del mundo, pero aparentemente no pudo encontrar un buen anagrama para Voldemort solo). Tampoco pienso que haya pensado que la patética rata de Ron, Scrabbers, sería un animago (el hecho de que le falte un dedo, al menos en lo que yo se, no aparece en ninguno de los dos primeros libros). Creo que ya captaron mi idea.

De forma similar, no había pensado en iteradores o generadores cuando se me ocurrió por primera vez el for-loop de Python o usar % como un operador para el formateo de strings, y de hecho, usar 'def' para definir tanto funciones como métodos tampoco fue parte del plan inicial.

De la misma forma en que los sucesivos libros de Harry Potter requieren tener continuidad (el gusto de Dumblendor por los dulces no puede cambiar drásticamente en el tercer libro), las versiones sucesivas de Python están limitadas por serios requerimientos de compatibilidad hacia atrás.

A veces es fácil volver y generalizar una característica, por ejemplo, trasnformar las funciones de conversión int(), str() y list() en clases. Por otro lado, una reciente discusión en python-dev sobre la jerarquía de las excepciones (PEP 348) ha mostrado que deciciones anteriores pueden ser menos que ideales. Las inconsistencias en la convención de nombres y la calidad del API son otro ejemplo.

Comentarios al artículo original: http://www.artima.com/forums/flat.jsp?forum=106&thread=123234

Correcciones: por desconocer cuestiones internas del lenguaje o de mi traducción en sí son bienvenidas.


Nos subimos al podio de PyWeek 5!!

En vísperas de un examen final y esperando encontrar a mi mamá en el departamento (pasó de visita luego de un curso todo el día en Paraná) llegué a mi casa esta noche a las 20:55. Mientras subía las escaleras recibí un mensajito:

Avisen los resultados, estoy en un casorio.

Diego Levental

Y ahí lo recordé: hoy sábado a las 21 hs (Argentina) estaban los resultados de la competencia PyWeek 5.

Entonces encendí mi computadora y entré al canal de chat de #pyweek, anunciaban que faltaba 1 minuto para que estén los resultados. Apunté mi navegador a la página del evento y me quedé unos momento apretando el botón de recargar.

Luego aparecieron los resultados. Estábamos en tercer lugar (Yupiii!!). Los resultados puede verse aquí. Y estos son los resultados de nuestro Twisted Zombie.

Si el viernes anterior a que empieza la competencia, cuando me anotaba para participar y empezaba a convocar a mis amigos para formar un equipo, me hubiesen dicho que lograríamos ese resultado no lo habría creído de seguro. Pero la semana de trabajo paso, fue productiva, nos divertimos, aprendimos y el sábado que entregamos nuestro juego estuvimos satisfechos con el trabajo realizado.

Creo que no me equivoco al decir que superamos nuestro objetivo.

Muchas gracias a todo el equipo:

  • Manu
  • Mariano
  • César
  • Diego
  • David
  • y Seba

Muchas gracias también a todos los que probaron el juego y nos dieron sus comentarios.

Felicitaciones a todos (más de 50) los otros equipos e individuales que participaron del concurso. Son los que le dan sentido al mismo. En especial, felicitaciones a los equipos argentinos que siempre dejan huella. Y gracias por incentivarlos a participar!

PS: Ho! llegaste a este blog por alguna misteriosa razón y no sabés de que estoy hablando? No dejes de jugar Twisted Zombie!


Twisted Zombie para Windows

Twisted Zombie, el juego que desarrollamos en una semana para la quinta edición del concurso PyWeek ya funciona en Windows. Está escrito en Python, un lenguaje multiplataforma, y hemos tenido en cuenta algunas consideraciones para que funcione sin problemas en distintos sistemas operativos. Incluso lo hemos probado en un par.

Pero para usarlo en Windows necesitás tener instalado Python y Pygame.

El siguiente es un paquete con todo lo necesario para correr el juego si necesidad de instalar nada más: Twisted-Zombie-1-Win.zip

Este es el código fuente del archivo setup.py que utilicé para crear el paquete:

# setup.py es usado para generar un paquete autocontenido

# para Windows. Incluye el interprete de Python, PyGame

# y otros modulos utilizados en el juego.

# Uso: C:Python25python.exe stup.py py2exe

# Version de py2exe: 0.66

# Nota: en run_game.pyw cambiar os.path.dirname(__file__)

# por la ruta al directorio donde esten los fuentes del

# juego. Ej: 'C:tz'



from distutils.core import setup

import py2exe

import os



options = {

    "py2exe": {

        #"compressed": 1,

        #"optimize": 2,

        #"excludes": excludes,

        "includes": ["pygame", "pygame.locals", "random", "pickle"],

    }

}



data_files = [ (x, [os.path.join(x, e) for e in z]) for d in ("lib", "data")

               for x,y,z in os.walk(d) if not ".svn" in x ] + [ "README.txt"]



setup( console=["run_game.pyw"], data_files=data_files, options=options)


irc2html.py

Ayer colgué del blog el log de un tutorial dictado a través del IRC. Cuando busqué en Google un formateador de logs de irc no encontré lo que buscaba. El principal feature que me interesaba era que se distinga a los diferentes usuarios con diferentes colores. Probablemente podría haber buscado con un poco más de empeño y habría encontrado lo que buscada, al fin de cuentas ya había usado un programa así hace unos años, pero me hubiera perdido de una hora entretenida programando y aprendiendo Python.

Script irc2html.py

Primero lo primero. Tal vez llegaste a este blog buscando lo mismo que yo pero más adelante en el tiempo. Good news: irc2html.py. UPDATE: irc2html-f.py es una versión mejorada tras recibir un comentario en este post.

Uso:

./irc2html.py charla.log

escribe en la salida estándar código html listo para embeber. También podés hacer:

./irc2html.py charla.log > charla.html

(prometo que tu browser no se va a quejar)

Un color para cada uno

Como decía más arriba, lo que más me interesaba era que se distinga, mediante colores, lo que los distintos usuarios dicen. Esto se hace más difícil a medida que aumenta el número de usuarios. La dificultad radica en encontrar colores lo suficientemente diferentes como para distinguir a los usuarios. Lo primero en lo que pensé fue en hacer una función que reciba un color inicial y que luego vaya saltando por la paleta de colores a medida que iba necesitando más colores.

Paleta de Colores

Hice algunas pruebas pero no prosperaron. Por suerte esto me sirvió como escusa para practicar yield y generadores y Python. Terminé con una implementación mucho menos ambiciosa que la original:


# Generador de colores:

#

# Para ayudar a distingir lo que dice un usuario de lo que dice otro,

# se utilizan distintos colores para cada uno.

#

# La idea original del generador de colores era que vaya saltando por

# la paleta de colores según un patrón matemático.

# La actual implementación es más simple y etática.



color_list = ['ff0000', 'fff200', '00ff00', '00fff2', '0000ff', 'aa18ff',

              'ff00fb', 'fbb636', 'b1466b', '3d3166',  'bfbf2e', '377972']



# Lighter colors:

#color_list = ['fff1df', 'feffdf', 'e3ffce', 'ffe4df', 'fcdfff', 'e7dfff',

#              'e7f1ff', 'e7fff9', 'f3ffe7', 'e9e9e9', 'ffedb9', 'f0e9d5']



def color_gen(l):

    colors = l[:]

    colors.reverse()

    while True:

        if colors:

            yield colors.pop()

        else:

            colors = l[:]

Expresiones regulares

En el script hago uso de expresiones regulares para parsear las líneas del archivo de log. El archivo sobre el que trabajé tenía dos tipos de líneas. Con mensajes de usuarios, como:

'<pablo_!~pablo@r190-64-130-143.dialup.adsl.anteldata.net.uy> [17:00] comprendido marga :)\n'

o de información como:

'-charm.oftc.net- [17:00] garaguas (~garaguas@host193.200-82-125.telecom.net.ar) joined the channel\n'

Me interesan la primeras, a las segundas las puedo ignorar. La expresión regular que usé fue:

'\<(?P<nombre>.+)!.*\>(?P<dicho>.+)$'

y se lee:

1) el carácter '<'.

2) de 1 a n caracteres de cualquier tipo (excepto '\n') y a las cadenas que matchen en esta parte de la expresión voy a referenciarlas luego como 'nombre'.

3) el carácter '!'.

4) de 0 a n caracteres de cualquier tipo (excepto '\n').

5) el carácter '>'

6) de 1 a n caracteres de cualquier tipo (excepto '\n') y a las cadenas que matchen en esta parte de la expresión voy a referenciarlas luego como 'dicho'.

7) final de línea ($).

Un ejemplo en el REPL de Python:

>>> import re

>>> a = '<pablo_!~pablo@r190-64-130-143....> [17:00] comprendido marga :)n'

>>> b = '-charm.oftc.net- [17:00] garaguas (~garaguas@host...) joined the channeln'

>>> rex = <(?P<nombre>.+)!.*>(?P<dicho>.+)$'

>>> pat = re.compile(rex)

>>> m = pat.match(b)

>>> m

>>> m == None

True

>>> m = pat.match(a)

>>> m

<_sre.SRE_Match object at 0x8707698>

>>> m.group('nombre')

'pablo_'

>>> m.group('dicho')

' [17:00] comprendido marga :)'

Resultado final

Comentando y descomentando las líneas:

#line_format = "<span style='color:#%s'>%s:</span>%s<br/>"

line_format = "<div style='background-color:#%s'><b>%s:</b> %s</div>"

se pueden obtener dos resultados distintos:

Formato 1:

Chat1

Formato 2:

Chat2

Notas finales

  • Espero este script les haya sido útil, ya sea para formatear logs de irc como para seguir aprendiendo Python (yo en particular casi no había usado yield y con re solo había hecho pruebas).
  • La primer imagen del post es una composición de una captura de pantalla y esta linda imagen lgpl de un gotero.


Fin de semana Pythonico

Es la noche del domingo del fin de semana en el que se llevó a cabo la 2ª Jornada Python en Santa Fe. La verdad es que tenía muchas cosas que quería contar aquí al respecto, pero también es mucho el sueño que tengo y las cosas que tengo que hacer mañana así que solamente voy a dejarles un fuerte GRACIAS, a los asistentes, disertantes, spónsores, colaboradore y al resto de los organizadores.

En la semana voy a ir dejando en formato-comentario algunas cosas/ideas/recursos sobre el evento.