8.9. Todo junto

Es hora de darle buen uso a todo lo que ha aprendido hasta ahora. Espero que haya prestado atención.

Ejemplo 8.20. La función translate, parte 1


def translate(url, dialectName="chef"): 1
    import urllib                       2
    sock = urllib.urlopen(url)          3
    htmlSource = sock.read()           
    sock.close()                       
1 La función translate tiene un argumento opcional dialectName, que es una cadena que especifica el dialecto que va a usar. Verá cómo se usa esto en un minuto.
2 ¡Eh!, espere un momento, ¡hay una sentencia import en esta función! Esto es perfectamente válido en Python. Está acostumbrado a ver sentencias import al comienzo de un programa, lo que significa que lo importado está disponible para todo el programa. Pero también puede importar módulos dentro de una función, lo que quiere decir que el módulo importado sólo está disponible dentro de la función. Si tiene un módulo que sólo se usa una vez en una función, ésta es una manera sencilla de hacer el código más modular (cuando se dé cuenta de que su hack del fin de semana se ha convertido en una obra de arte de 800 líneas y decida convertirlo en una docena de módulos reutilizables, agradecerá esto).
3 Ahora obtenemos la fuente de una URL dada.

Ejemplo 8.21. La función translate, parte 2: curiorífico y curiorífico

    parserName = "%sDialectizer" % dialectName.capitalize() 1
    parserClass = globals()[parserName]                     2
    parser = parserClass()                                  3
1 capitalize es un método de cadenas que aún no hemos visto; simplemente pone en mayúscula la primera letra de una cadena y obliga a que el resto esté en minúscula. Combinándola con algo de formato de cadenas, hemos tomado el nombre de un dialecto y lo transformamos en el nombre de la clase Dialectizer correspondiente. Si dialectName es la cadena 'chef', parserName será la cadena 'ChefDialectizer'.
2 Tiene el nombre de la clase en una cadena (parserName), y tiene el espacio global de nombres como diccionario (globals()). Combinándolos, podemos obtener una referencia a la clase que indica la cadena (recuerde, las clases son objetos y se pueden asignar a variables como cualquier otro objeto). Si parserName es la cadena 'ChefDialectizer', parserClass será la clase ChefDialectizer.
3 Por último, tiene el objeto de una clase (parserClass), y quiere una instancia de esa clase. Bien, ya sabe cómo hacerlo: invoque a la clase como si fuera una función. El hecho de que la clase esté almacenada en una variable local no supone diferencia alguna; simplemente invoque la variable como si fuera una función, y lo que obtendrá es una instancia de la clase. Si parserClass es la clase ChefDialectizer, parser tendrá una instancia de la clase ChefDialectizer.

¿Por qué molestarse? Al fin y al cabo, sólo hay tres clases Dialectizer; ¿por qué no usar simplemente una sentencia case? (bien, no hay sentencia case en Python pero, ¿no es lo mismo usar una serie de sentencias if?) Una razón: extensibilidad. La función translate no tiene la menor idea de cuántas clases Dialectizer ha definido. Imagine que mañana define una nueva FooDialectizer; translate funcionará correctamente pasando 'foo' como dialectName.

Incluso mejor, imagine que sitúa FooDialectizer en un módulo aparte, y que la importa con from módulo import. Ya ha visto que esto la incluye en globals(), así que translate funcionará aún sin modificaciones, aunque FooDialectizer esté en otro fichero.

Ahora imagine que el nombre del dialecto viene de alguna parte de fuera del programa, quizá de una base de datos o de un valor introducido por el usuario en un formulario. Puede usar cualquier cantidad de arquitecturas con Python como lenguaje para server-side scripting que generen páginas web de forma dinámica; esta función podría tomar una URL y el nombre de un dialecto (ambos cadenas) en la cadena de consulta de una petición de página web, y mostrar la página web “traducida”.

Por último, imagine un framework con arquitectura de plug-in. Podría colocar cada clase Dialectizer en un fichero aparte, dejando sólo la función translate en dialect.py. Asumiendo un esquema de nombres consistente, la función translate podría importar de forma dinámica la clase adecuada del fichero adecuado, sin darle nada excepto el nombre del dialecto (no ha visto el importado dinámico aún, pero prometo que lo veremos en el siguiente capítulo). Para añadir un nuevo dialecto, se limitaría a añadir un fichero con el nombre apropiado en el directorio de plug-ins (como foodialect.py qque contendría la clase FooDialectizer). Llamar a la función translate con el nombre de dialecto 'foo' encontraría el módulo foodialect.py, importaría la clase FooDialectizer, y ahí continuaría.

Ejemplo 8.22. La función translate, parte 3

    parser.feed(htmlSource) 1
    parser.close()          2
    return parser.output()  3
1 Tras tanta imaginación, esto va a parecer bastante aburrido, pero la función feed es la que hace toda la transformación. Tiene todo el HTML fuente en una única cadena, así que sólo tiene que llamar a feed una vez. Sin embargo, podemos llamar a feed con la frecuencia que queramos, y el analizador seguirá analizando. Así que si está preocupado por el uso de la memoria (o sabe que va a tratar con un número bien grande de páginas HTML), podría hacer esto dentro de un bucle, en el que leería unos pocos bytes de HTML para dárselo al analizador. El resultado sería el mismo.
2 Como feed mantiene un búfer interno, deberíamos llamar siempre al método close del analizador cuando hayamos acabado (incluso si alimentó todo de una vez, como hemos hecho). De otra manera, puede encontrarse con que a la salida le faltan los últimos bytes.
3 Recuerde, output es la función que definió en BaseHTMLProcessor que junta todas las piezas de saliad que ha introducido en el búfer y los devuelve en una única cadena.

Y de la misma manera, ha “traducido” una página web, dando sólo una URL y el nombre de un dialdecto.

Lecturas complementarias