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


La rata Lali

Qué buen ritmo que tiene este tema:


Ayer me encontré a la rata Lali

estaba sentada en el cordón

estaba sola triste y desdichada

y de sus ojos le colgaba un lagrimon

había sido hechada de su cueva

por su padre el gran ratón.

Porque la había encontrado estimulada

y acostada y acostada con Pepon (Topón)

mi padre es un burgués capitalista

(su padre es un ingles imperialista)

y de la marca de la marca roquefort

Pepon era el líder del frente

roedor de liberación, oh yeah!!!


Tiempo de valientes

-¿Y vos no hicisteis el curso de piloto de helicópteros?

No, yo hice Iniciación a la Computación. Ya sabés; Logo. Avanza tortuga, gira tortuga... no me sirvió para nada.

Tiempo de Valientes. Damian Szifron. 2005

(No estoy seguro del a exactitud textual de la cita, pero es realmente muy gracioso. Si alguien llega a encontrar ese fragmento en YouTube, no deje de avisar!)



SQL Debug en Django

¿Cómo saber en Django qué sentencias SQL se están ejecutando detrás de su ORM? Según la FAQ, podemos hacerlo de esta forma:

Make sure your Django DEBUG setting is set to True. Then, just do this:

>>> from django.db import connection

>>> connection.queries

[{'sql': 'SELECT polls_polls.id,polls_polls.question,polls_polls.pub_date FROM polls_polls',

'time': '0.002'}]

connection.queries is only available if DEBUG is True. It's a list of dictionaries in order of query execution. Each dictionary has the following:

sql -- The raw SQL statement

time -- How long the statement took to execute, in seconds.

connection.queries includes all SQL statements -- INSERTs, UPDATES, SELECTs, etc. Each time your app hits the database, the query will be recorded.

Cada vez que se realiza una nueva petición, esa variable es sobre escrita con las consultas que se ejecutaron en la vista asociada. La forma de verlas es accediendo a connection.queries en cada vista de nuestro interés. Para facilitar esta tarea y no tener código intrusivo, escribí un decorador:


from django.db import connection

def sql_debug(f):

    '''

    Decorador útil para inspeccionar las sentencias SQL que se ejecutan en

    cada request.

    '''

    def inner(*args, **kwargs):

        r = f(*args, **kwargs)

        for d in connection.queries:

            print "time: %s\n sql:%s\n\n" % (d['time'], d['sql'])

        return r

    return inner


Alimentando un Intrepid Ibex

Hoy salió Ubuntu 8.10, Intrepid Ibex. Cómo es costumbre, cada distribución de Ubuntu tiene un animal en su code name. El Ibex, o Capra Ibex es un tipo de Cabra que habita en Los Alpes.

Nosotros, en Argentina, tenemos nuestra propia versión, también conocida como Chivo:


Diagrama de capas de Django

¿Cuál sería un diagrama de capas correcto para representar una instalación de Django? Ensayé el siguiente gráfico; es para una instalación en particular, pero se puede cambiar GNU/Linux por Sistema Operativo, PostgreSQL por Base de Datos y Apache por Servidor Web para hacerlo más genérico:

La idea detrás de un diagrama de capas es expresar que los componentes de una capa le brindan servicios a los de la capa superior mediante alguna interfaz.

¿Mejores ideas? ¿Conviene otro tipo de diagrama?


Recomendaciones al programar en Python

Esta es una traducción de la última sección del PEP 8 de Python, Recomendaciones al programar:

Programming Recommendations

El código debe ser escrito de forma que no tenga desventajas en otras implementaciones de Python aparte de CPython (PyPy, Jython, IronPython, Pyrex, Psyco).

Por ejemplo, no te bases en la implementación eficiente que tiene CPython de concatenación in-place de strings en sentencias de la forma a+=b o a=a+b. Esas sentencias corren más despacio en Jython. En su lugar la forma ''.join() debe ser usada en bibliotecas, en las partes dónde la performance sea importante. Esto asegura que la concatenación ocurra en tiempo lineal en todas las implementaciones.

La comparación con singletons como None siempre debe hacerse con is o is not, nunca con los operadores de igualdad.

También tené cuidado al escribir if x cuando realmente querés decir if x is not None, por ejemplo al testear si a una variable o argumento que por defecto es None le fue asignado a otro valor. El otro valor puede tener un tipo (por ejemplo, un contenedor) que puede ser falso en un contexto booleado! (NdT: por ejemplo [] o {}).

Usá excepciones basadas en clases

Las excepciones basadas en strings en código nuevo deben desaparecer ya que esta característica del lenguaje fue eliminada en Python 2.6.

Los módulos o paquetes deben definir su propia clase base para exepciones específica de su dominio, la cual debe extender la clase built-in Exception. Siempre incluí un docstring a la clase, por ejemplo:


class MessageError(Exception):

"""Clase base para errores en el paquete mail."""

La convención para el nombramiento de clases se aplica aquí, aunque deberías agregar el sufijo 'Error' a tus clases de excepciones, si la excepción es un error. Las excepciones que no sean errores no necesitan un sufijo especial.

Al lanzar una excepción, usá raise ValueError('mensaje') en lugar de raise ValueError, 'message'.

La forma usando paréntesis es preferida porque cuando los argumentos de la excepción son largos o incluyen formateo de strings, no necesitás usar el carácter de continuación de línea () gracias al paréntesis. La forma vieja será quitada en Python 3000.

Al atrapar excepciones, mencioná la excepción específica cuando puedas, en lugar de usar la clausula except:.

Por ejemplo, usá:


try:

    import platform_specific_module

except ImportError:

    platform_specific_module = None

Una clausula except: atrapará las excepciones SystemExit y KeyboardInterrupt, haciendo que sea más difícil interrumpir al programa con Control-C, y puede traer otros problemas. Si querés atrapar todas las excepciones que señalan errores en el programa, usar except Exception:.

Una buena regla es limitar el uso de la clausula except: a dos casos:

  1. Si el manejador de excepción estará imprimiendo o logeando el traceback; al menos el usuario se dará cuenta de que ocurrió un error.
  2. Si el código necesita hacer algún trabajo de limpieza, pero después deja que la excepción se propague hacia arriba con raise. try...finally es la mejor forma de manejar este caso.

Adicionalmente, para todas las clausulas try/except, limitá la clausula try a la absolutamente mínima cantidad de código necesario. De nuevo, esto evita esconder bugs.

Si:


try:

    value = collection[key]

except KeyError:

    return key_not_found(key)

else:

    return handle_value(value)

No:


try:

    # Too broad!

    return handle_value(collection[key])

except KeyError:

    # Will also catch KeyError raised by handle_value()

    return key_not_found(key)

Usar los métodos de string en lugar del módulo string.

Los métodos de string son siempre más rápidos y comparten la misma API con los strings unicode. Sobreescribí esta regla si necesitás compatibilidad hacia atrás con versiones de Python menores a 2.0.

Usá ''.startswith() y ''.endswith() en lugar de rebanamiento de strings (string slicing) para checkear prefijos y sufijos.

startswith() y endswith() son más limpios y menos propensos a errores. Por ejemplo:

Si: if foo.startswith('bar'):

No: if foo[:3] == 'bar':

La excepción es sin tu código debe funcionar con Python 1.5.2 (pero esperemos que no!).

La comparación entre tipos de objetos debe usar siempre isinstance() en lugar de comparar tipos directamente.

Si: if isinstance(obj, int):

No: if type(obj) is type(1):

Al chequear si un objeto es un string, recordá que hay strings Unicode también! En Python 2.3, str y unicode tienen una clase base común, basestring, así que podés hacer:

if isinstance(obj, basestring):

En Python 2.2, el módulo types tiene el tipo StringTypes definido para ese propósito, por ejemplo:


from types import StringTypes

if isinstance(obj, StringTypes):

En Python 2.0 y 2.1, tenés que hcaer:

from types import StringType, UnicodeType

if isinstance(obj, StringType)

o


isinstance(obj, UnicodeType) :

Para secuencias, (strings, listas, tuplas), hacé uso del hecho de que las secuencias vacías tienen valor de verdad falso:

Si:


if not seq:

if seq:

No:


if len(seq):

if not len(seq):

No escribas strings literales que hagan uso de espacios en blanco al final de una línea. Esos espacios en blanco son visualmente indistingibles y algunos editores (o más recientemente, reindent.py) los eliminan.

No compares valores booleanso con True o False usando ==.

Si:

if greeting:

No:

if greeting == True:

Peor:

if greeting is True:


Listas por comprensión en Python

Las listas por comprensión o list comprehension es una característica muy práctica de Python (también incluida en otros lenguajes de programación). Es una herramiente de mucha utilidad y fácil de usar, muchas veces desconocida por quienes vienen de lenguajes como PHP o Java.

Este artículo tiene como objetivo explorar su uso y plantear ejemplos para que los nuevos usuarios puedan incorporarlas rápidamente a su cajita de herramientas.

Cómo lo indica el PEP 202, es una construcción sintáctica que permite crear listas en situaciones en las que se usaría map, filter o for anidados; pero de forma mas concisa.

map

Al llamar a la función map con argumentos f y l ,dónde f es una función y l una lista, retorna una lista con los resultados de aplicar f a cada elemento de l.

En el primer ejemplo usamos la función len, que dado un string retorna su longitud:


>>> len('hola')

4

Esto es lo que sucede al aplicar map a todos los elementos de una lista de palabras:


>>> palabras = ['uno', 'dos', 'Santa Fe', 'Python', '...', 'Soleado']

>>> map(len, palabras)

[3, 3, 8, 6, 3, 7]

La versión equivalente es:


>>> [len(p) for p in palabras]

[3, 3, 8, 6, 3, 7]

La sintaxis de la listas por comprensión es más flexible. Si queremos la lista de palabras, pero en mayúsculas hacemos:


>>> [p.upper() for p in palabras]

['UNO', 'DOS', 'SANTA FE', 'PYTHON', '...', 'SOLEADO']

Para hacer lo anterior utilizando la función map, antes tendríamos que definir una función que llame al método upper de la clase string:


>>> def upper(s):

    return s.upper()

>>> map(upper, palabras)

['UNO', 'DOS', 'SANTA FE', 'PYTHON', '...', 'SOLEADO']

filter

La función filter recibe como argumento una función f y una lista l. Al igual que map, aplica f a todos los elementos de l pero retorna una lista con los elementos de l para los cuales la función f retornó True o un objeto con valor de verdad True.


>>> def incluye_n(s):

    return 'N' in s.upper()

>>> incluye_n('Python')

True

>>> incluye_n('Soleado')

False

>>> filter(incluye_n, palabras)

['uno', 'Santa Fe', 'Python']

La forma de hacer lo anterior con listas por comprensión es:


>>> [p for p in palabras if incluye_n(p)]

['uno', 'Santa Fe', 'Python']

Incluso muchas veces podemos hacerlo sin necesitar de definir una función auxiliar:


>>> [p for p in palabras if 'N' in p.upper()]

['uno', 'Santa Fe', 'Python']

for

También podemos anidar fors de forma similar a cómo lo haríamos en:


>>> range(6)

[0, 1, 2, 3, 4, 5]

>>> for n in range(6):

for p in palabras:

    if len(p) == n:

        print p,n

uno 3

dos 3

... 3

Pero con una sintaxis más cómoda:


>>> [(p,n) for n in range(6) for p in palabras]

[('uno', 0), ('dos', 0), ('Santa Fe', 0), ('Python', 0), ('...', 0), ('Soleado', 0), ('uno', 1), ('dos', 1), ('Santa Fe', 1), ('Python', 1), ('...', 1), ('Soleado', 1), ('uno', 2), ('dos', 2), ('Santa Fe', 2), ('Python', 2), ('...', 2), ('Soleado', 2), ('uno', 3), ('dos', 3), ('Santa Fe', 3), ('Python', 3), ('...', 3), ('Soleado', 3), ('uno', 4), ('dos', 4), ('Santa Fe', 4), ('Python', 4), ('...', 4), ('Soleado', 4), ('uno', 5), ('dos', 5), ('Santa Fe', 5), ('Python', 5), ('...', 5), ('Soleado', 5)]

>>> [(p,n) for n in range(6) for p in palabras if len(p) == n]

[('uno', 3), ('dos', 3), ('...', 3)]