jueves, 27 de enero de 2011

Persistencia de imágenes con Python: SQLServer y cPickle

Hola. En este artículo vamos a ver cómo guardar y recuperar imágenes de una base de datos Microsoft SQLServer 2005 Express Edition, con Python, así como guardar y recuperar también, imágenes, en un objeto que se serializa, guardando dicha información en disco, mediante cPickle.

Antes de nada comentar que en este blog se han tratado varios tipos de SGBD, tales como MySQL, Firebird,  ó SQLServer 2008. El elegir SQLServer 2005 Express Edition es simplemente para probar que Python funciona, mediante pyodbc, con la mayoría de los sistemas gestores de bases de datos actuales. Y para ver si funciona lo mejor es probarlo uno mismo, ya que no es lo mismo conocer el camino que andar el camino.

NOTA: Este post lo he escrito en la plataforma Microsoft Windows XP SP3, con Python 2.6.6.

Imágenes en SQLServer 2005 con Python

Lo primero de todo es descargar el SGBD Microsoft SQLServer 2005 Express Edition.


Se puede hacer desde aquí:

http://www.microsoft.com/downloads/details.aspx?FamilyID=220549b5-0b07-4448-8848-dcc397514b41&displayLang=es

También (recomendable) es posible descargarse el Microsoft SQL Server Management Studio Express, el cual es un entorno gráfico para gestionar las bases de datos que vayamos creando, de la siguiente URL:

http://www.microsoft.com/downloads/details.aspx?displaylang=es&FamilyID=c243a5ae-4bd1-4e3d-94b8-5a0f62bf7796#filelist

Instalamos los dos paquetes de software....


Una vez instalado, nos vamos al Management Studio y creamos una base de datos, llamada imagenes, con una tabla, llamada registro.


Como podemos observar, se crea una tabla con tres campos, un int, un nvarchar y un tipo image.

Una vez tenemos instalado SQLServer 2005, con nuestra base de datos y nuestra tabla, vamos a instalar pyodbc, el software de Python para crear conexiones ODBC a orígenes de datos, en este caso, el software de Microsoft. Para ello, descargamos el pyodbc, desde http://code.google.com/p/pyodbc/. Creamos a continuación, mediante las Herramientas Administrativas/Orígenes de datos (ODBC), nuestra conexión:


NOTA: El lector debe de tener en cuenta que existe un módulo específico para la conexión de SQLServer con Python, llamado pymssql, que se puede obtener de http://code.google.com/p/pymssql/.

NOTA 2: Pudiera ser que con SQLServer 2005 Express Edition aparezcan ciertos errores a la hora de crear la conexión ODBC referidos a la configuración del mismo, con el propósito de permitir conexiones remotas. Bien, la solución la tenéis aquí: http://support.microsoft.com/kb/914277/es.

Una vez creado el ODBC y la conexión correctamente realizada, ya tenemos todo listo para programar en Python.

El código para crear una conexión desde Python a SQLServer 2005, mediante pyodbc, podría ser tal que así:


import pyodbc


# Cadena de conexión.
cadenaConexion = 'DSN=imagenes;UID=ABUSIMBEL\familia'


try:
    conexionSQLServer = pyodbc.connect(cadenaConexion)
    cursorSQLServer = conexionSQLServer.cursor()
    print "Conectado con SQLServer 2005 Express!"
except:
    print "No he podido conectar con base de datos SQLServer!"

# Cerramos cursor y conexión.
print "Cerrando conexiones!"
cursorSQLServer.close()
conexionSQLServer.close()

Si todo ha ido bien, debe de aparecer algo como esto:

Conectado con SQLServer 2005 Express!
Cerrando conexiones!

A continuación vamos a insertar una imagen en la tabla registro de la base de datos imagenes. El código podría ser tal que así:

import os

fichero_imagen = os.path.realpath('alvaro.jpg')
imagen = open(fichero_imagen,'rb').read()

Cargamos un fichero jpg, y lo referenciamos mediante imagen. Se supone que el fichero ha de existir. El código para insertar una imagen en la base de datos es el siguiente:

# Cadena SQL a ejecutar.
cadenaSQL = "insert into registro values (1,'alvaro',?)"
# Lanzamos Sentencia.
cursorSQLServer.execute(cadenaSQL, (pyodbc.Binary(imagen),))
# Confirmamos escritura.
conexionSQLServer.commit()

Cabe destacar que transformamos en binario la imagen cargada del fichero. Además hacemos commit, no olvidarlo. Si nos vamos al Management Studio y hacemos una consulta a la tabla tenemos la solución:


Bien, ya sabemos como insertar imágenes en la base de datos. Ahora toca como recuperar la información, mediante el select correspondiente. El código para hacer esto podría parecerse a lo siguiente:

# Creamos el nombre de un fichero destino.
fichero_destino = os.path.realpath('destino.jpg')
# Lanzamos la consulta de selección SQL.
imagen = cursorSQLServer.execute('SELECT * FROM registro WHERE id = 1')
# Creamos un fichero de destino (temporal).
fichero_final = open(fichero_destino,'wb')
# Escribimos en el fichero y lo cerramos.
fichero_final.write(imagen.next()[2])
fichero_final.close()

Cabe destacar que se crea un fichero de salida temporal (destino.jpg) para obtener en disco el fichero seleccionado de la base de datos. También decir que utilizamos next() para iterar sobre el cursor y obtener la imagen, que se encuentra en la posición 3 de la tupla (es decir, imagen.next()[2]), ya que la tabla tiene 3 campos y el campo imagen es el tercero.

Decir que si se quiere que se vea automáticamente la imagen recuperada, se puede utilizar:

fichero_destino = os.path.realpath('destino.jpg')
os.startfile(fichero_destino)

En mi sistema Windows, automáticamente se abre el programa asociado a ficheros de imágenes, Visor de imágenes y faz de Windows, dando como resultado:


Imágenes serializadas en objetos con Python

Bien. Estamos en la segunda parte del post. Ahora lo que vamos a hacer es guardar una imagen dentro de un objeto y dicho objeto guardarlo en disco. Sería así como un wrapper de la imagen que se guardará en disco. Lo bueno que tiene esto es que se pueden guardar múltiples imágenes en cualquier tipo de contenedor (lista, tupla, diccionario) y guardarla directamente en disco, a capón. Para ello vamos a crear primero una clase, llamada persistencia, que tendrá la funcionalidad de cargar y guardar un objeto.

El código que define la clase persistencia es el siguiente:


import cPickle


class persistencia(object):
    def nombre_clase(self):
        return str(self).split(' ')[0].split('.')[1]


    def cargar(self, nombre_fichero = None):
        if nombre_fichero is None:
            nombre_fichero = self.nombre_clase()
        manejador_fichero = open(nombre_fichero,'r')
        objeto = cPickle.load(manejador_fichero)
        manejador_fichero.close()
        return objeto


    def salvar(self, objeto, nombre_fichero = None):
        if nombre_fichero is None:
            nombre_fichero = self.nombre_clase()
        # Fichero que contendrá el objeto.
        manejador_fichero = open(nombre_fichero,'w')
        # Volcamos el objeto de memoria al fichero.
        cPickle.dump(self, manejador_fichero)
        # Cerramos fichero.
        manejador_fichero.close()


Ahora, únicamente hay que crear una clase que herede de persistencia, para obtener su funcionalidad, e implementar las estructuras necesarias para guardar imágenes. Para ello implementamos la clase registro, tal que así:


class registro(persistencia):
    def __init__(self):
        self._registro = []


    def insertar_persona(self, nombre, foto):
        self._registro.append([nombre, foto])


    def devolver_persona(self, nombre = None):
        if nombre is None:
            return self._registro
        else:
            for i in self._registro:
                if i[0] == nombre:
                    return i
        return None

Ahora falta, por fin, utilizar las clases que hemos diseñado, para poder ver que efectivamente funciona la idea. El código para serializar una imagen en un objeto podría tener el siguiente:

import os

# Cargamos imagen.
fichero_imagen = os.path.realpath('alvaro.jpg')
imagen = open(fichero_imagen,'rb').read()

# Instanciamos la clase que contendrá el registro de personas.
personas = registro()
personas.insertar_persona('alvaro',imagen)
personas.salvar(personas)

Como podemos observar la funcionalidad heredada nos permite guardar la imagen en un fichero, mediante la serialización del objeto. Decir que hemos incluido una imagen únicamente. Podríamos incluir tantas imágenes como quisiéramos. Evidentemente se crea un fichero, llamado registro, el cual tiene el siguiente contenido:


El formato del fichero nos da igual. Lo importante es que podamos recuperar la información guardada en las estructuras de datos de alto nivel que dispone Python. Vamos por último a ver cómo recuperar la información guardada en el objeto serializado. Podríamos utilizar un código como este: 

import os

# Obtenemos datos.
personas = registro()
personas = personas.cargar()
individuo = personas.devolver_persona('alvaro')

# Creamos fichero.
fichero_destino = os.path.realpath('destino.jpg')
imagen = open(fichero_destino,'wb')
imagen.write(individuo[1])
imagen.close()

# Y lo mostramos por pantalla, para ver que efectivamente funciona la idea.
os.startfile(fichero_destino)

Esto es todo por el momento. Saludos.

domingo, 9 de enero de 2011

Ejercicios resueltos de programación, en Python y en español, I.

Hola. Este es el primero de la serie de artículos que presentaré estos días sobre una colección de ejercicios muy  básicos de programación, para resolver con Python (o cualquier otro lenguaje). Decir que son muy sencillos y sirven para afianzar conocimientos básicos de sintaxis y razonamiento. Si eres novel en programación ó en Python, este post es para tí. De lo contrario, te resultarán triviales los planteamientos expuestos.

Los enunciados los he obtenido del libro Esquemas Algorítmicos Fundamentales - Secuencias e Iteración, de P.C. Scholl y J.P. Peyrin, editorial Masson.


Ejercicio 1.
Escribir un algoritmo que, para cualquier número de segundos inferior a un millón, calcule su equivalente en días, horas, minutos y segundos.

En este ejercicio tenemos que tener en cuenta que:

1 minuto = 60 segundos.
1 hora = 60 minutos = 3600 segundos.
1 dia = 24 horas = 1440 minutos = 86400 segundos.

# Pedimos datos.
dato = raw_input(u'Número de segundos: ')

# Hacemos un cast, convirtiendo la cadena en un número largo.
dato = long(dato)

# Comprobamos si el número introducido es menor a un millón.
if dato >= 1000000:
    print "El número debe de ser menor a 1000000"
else:
    # Días.
    ndias, aux = divmod(dato, 86400)
    # Horas.
    nhoras, aux = divmod(aux, 3600)
    # Minutos y segundos.
    nmin, nseg = divmod(aux, 60)
    # Mostramos resultado.
    print '%d días, %d horas, %d minutos, %d segundos' % \
          (ndias, nhoras, nmin, nseg)

Ejercicio 2.
Escribir un algoritmo que imprima el mínimo, el máximo y la media de tres números.

# Obtenemos números.
numeros = []
for i in ['primer', 'segundo','tercer']:
    texto = u'Introduce el %s número: ' % (i)
    numeros.append(float(raw_input(texto)))

# Ordenamos.
numeros.sort()

# Obtenemos máximo y mínimo.
minimo = numeros[0]
maximo = numeros[2]

# Obtenemos la media aritmética.
media = round((numeros[0] + numeros[1] + numeros[2]) / float(3),3)

# Visualizamos resultados.
cadena = u'Mínimo: %d, Máximo: %d, Media: %f' % (minimo, maximo, media)
print cadena

Ejercicio 3.
Escribir un algoritmo que, dado el infinitivo de un verbo regular de la primera conjugación, obtenga la conjugación en singular y plural de presente de indicativo. Por ejemplo, para el verbo cantar el resultado es yo canto, tu cantas, el canta, nosotros cantamos, vosotros cantáis, ellos cantan.


pronombre = ['yo','tu','el','nosotros','vosotros','ellos']

terminaciones = {'yo':'o',
                 'tu':'as',
                 'el':'a',
                 'nosotros':'amos',
                 'vosotros':u'áis',
                 'ellos':'an'}

# Pedimos datos.
palabra = raw_input(u'Verbo regular 1ra. conjugación: ')

# Recorremos la tabla hashing (diccionario), construyendo la conjugación verbal.
for i in pronombre:
    print i, palabra[0:len(palabra)-2] + terminaciones[i]

Ejercicio 4.
Escribir un algoritmo que, para un número binario de 4 cifras, imprima su valor en base 10. Se estudiarán dos formas del problema según la representación de los datos:
    -forma 1: los datos son cuatro enteros (0 ó 1). Por ejemplo: 1,1,0,1.
    -forma 2: el dato es un entero cuya representación decimal con cuatro
              cifras no contenga más que 0 ó 1: Por ejemplo: 1101.


# Obtenemos datos.
nbinario = raw_input(u'Número binario (4 cifras): ')

# Obtenemos los dígitos.
nbinario = nbinario.split(',')
if len(nbinario) == 1: nbinario = list(nbinario[0])

# Inicializamos algunos contadores.
decimal = 0
potencia = 0

# Le damos la vuelta al número binario.
nbinario.reverse()

# Calculamos el número decimal, a partir del número binario.
for i in nbinario:
    decimal += pow(2,potencia) if i == '1' else 0
    potencia += 1

# Visualizamos resultado.
cadena = u'Su representación decimal es %d' % (decimal)
print cadena

Ejercicio 5.
Escribir un algoritmo que decodifique fechas del siglo XXI. El dato es un entero comprendido entre 10100 y 311299. El resultado es una secuencia de caracteres: número del día dentro del mes, del mes dentro del año y del año dentro del siglo. Por ejemplo, para el dato 30485, el resultado es el texto 3-4-2085.


# Obtenemos dato.
dato = raw_input('Introduce dato: ')

# Decodificamos año dentro del siglo XXI.
anyo = 2000 + int(dato[len(dato)-2:])

# Decodificamos mes.
mes = int(dato[len(dato)-4:len(dato)-2])

# Decodificamos día.
dia = int(dato[:len(dato)-4])

# Mostramos resultado.
cadena = '%d-%d-%d' % (dia, mes, anyo)
print cadena

Ejercicio 6.
Escribir un algoritmo que, para una suma de dinero dada, indique cómo descomponerla en billetes y monedas corrientes. Se desea utilizar el mínimo de billetes y monedas. No hay ninguna limitación respecto al número de billetes y monedas disponibles.


# Declaramos datos básicos (euros) con los que trabajar.
billetes_y_monedas = [500,200,100,50,20,10,5, 2, 1, 0.50, 0.20, 0.10,
0.05, 0.02, 0.01]

# Creamos un monedero, donde meter el dinero.
monedero = []

# Pedimos la cantidad.
dato = raw_input(u'Introducir importe (euros): ')

# Buscamos parte decimal y entera.
dato = dato.split('.')

# Decimal...
try: parte_decimal = float('0.'+dato[1])
except: parte_decimal = 0.0

# Entera...
parte_entera = long(dato[0])

# Algoritmo de asignación de dinero.
for i in billetes_y_monedas:
    # Obtenemos número de billetes ó monedas y los guardamos.
    unidades, resto = divmod(parte_entera, i)
    if unidades != 0:
        monedero.append((i,unidades))
        # Asignamos lo que nos queda.
        parte_entera = resto

if parte_decimal > 0:
    for i in billetes_y_monedas:
        # Obtenemos número de monedas y los guardamos.
        unidades, resto = divmod(parte_decimal, i)
        if unidades != 0:
            monedero.append((i,unidades))
            # Asignamos lo que nos queda.
            parte_decimal = round(resto,2)

# Visualizamos el resultado.
cadena = ''
for i in monedero:
    if i[0] >= 5: cadena += '%d billete/s de %d euros' % (i[1],i[0])
    if i[0] < 5: cadena += '%d moneda/s de %s euros' % (i[1],i[0])
    cadena += '\n'
print cadena

Ejercicio 7.
Escribir un algoritmo que simule el funcionamiento de una calculadora. El dato es una serie de tres caracteres: una cifra, un símbolo de operación y otra cifra. El resultado es el valor de la expresión dada.

# Pedimos datos.
dato = raw_input(u'Introduce operación: ')

# Inicializamos resultado.
resultado = 0

# ¿Suma?
serie = dato.split('+')
if len(serie) == 2: resultado = long(serie[0]) + long(serie[1])
else:
    # ¿Resta?
    serie = dato.split('-')
    if len(serie) == 2: resultado = long(serie[0]) - long(serie[1])
    else:
        # ¿Multiplicación?
        serie = dato.split('*')
        if len(serie) == 2: resultado = long(serie[0]) * long(serie[1])
        else:
            # ¿División?
            serie = dato.split('/')
            if len(serie) == 2: resultado = long(serie[0]) / long(serie[1])

# Mostramos resultado.
print "El resultado es",resultado

Ejercicio 8.
Consideremos una hora expresada en forma de tripleta . Escribir un algoritmo que imprima la hora correspondiente al siguiente segundo. Por ejemplo, para la entrada 13,43,24 tiene que devolver 13,43,25.

Se supone que lo que introducimos es una hora correcta. Darse cuenta que no se chequean los datos introducidos, por cuestión de simplicidad.

# Pedimos datos.
tiempo = raw_input('Introduce tiempo: ')

# Obtenemos datos.
aux = tiempo.split(',')
horas = int(aux[0])
minutos = int(aux[1])
segundos = int(aux[2])

# Aumentamos un segundo.
segundos += 1

# Casos.
if segundos != 60:
    tiempo = (horas, minutos, segundos)
else:
    if minutos == 59 and horas == 23:
        tiempo = (0, 0, 0)
    else:
        if minutos == 59:
            tiempo = (horas + 1, 0, 0)
        else:
            tiempo = (horas, minutos + 1, 0)

# E imprimimos resultado (una tupla).
print tiempo

Ejercicio 9.
Escribir una función que determine si una letra dada es consonante.

A la función le faltaría controlar las letras con acentuación (tildes, diéresis, etc).

def es_consonante(letra):
    if letra.lower() not in ['a','e','i','o','u']:
        return True
    else:
        return False

Ejercicio 10.
Escribir una función que calcule el factorial de un número. Por ejemplo, 5! = 5*4*3*2*1 = 120.


def factorial(numero):
    return 1 if numero <=0 else numero * factorial(numero -1)

Saludos.

sábado, 1 de enero de 2011

Python, de programadores para programadores

Yo no soy un programador web, aunque tenga idea, ni soy diseñador web, que no tengo idea. Pero si soy desarrollador de software desde hace algunos años.

Python es framework un lenguaje de programacion de desarrollo multidimensional, esto es, sirve tanto para crear scripts de mantenimiento de sistemas (Windows, Unix, Mac), como creación de aplicaciones para móviles (Android, Symbian), aplicaciones gráficas de escritorio (wxPython, Qt, GTK, Tkinter), desarrollo web (Django, web2py, TurboGears, …).

En el mercado de desarrollo de software hay cantidad de productos, tales como PHP para programación web, y frameworks como Symfony, muy potentes, y todo lo hacen con MVC, como debería ser. Al igual que  está C++, que es el mejor lenguaje de programación, referido en cuestiones de potencia y flexibilidad, y el más difícil de aprender para mi.

Evidentemente, cada problemática tiene una solución, y cada lenguaje aporta ciertas características técnicas que lo hacen ideal para la solución propuesta.

Ahora bien, como lenguaje general, con aportes a web y demás, Python es uno de los lenguajes de programación más completos, que se diferencia en ciertos aspectos a otros lenguajes del mercado, y que algunas personas tenemos muy en cuenta, a saber:

1) Sintaxis fácil de aprender.
2) POO integrada desde sus inicios.
3) Multiparadigma y multiplataforma.
4) Dinámico, fuertemente tipado.
5) Multidimensional (referido a su campo de aplicación).
6) Ampliamente documentado.
7) Proyecto vivo (multitud de módulos, packages).
8) Respaldado por empresas grandes (Google, etc.).

Esta afirmación que hago es muy personal. Cuando se apuesta por un lenguaje como Python (y más en España, donde apenas existen programadores en comparación con otros países, como Argentina), significa varias cosas. Primero, que posiblemente la persona que se inicia en esta andadura ya haya visto muchas cosas en su vida (lenguajes), y necesita hacer las cosas bien. Segundo, los tiempos de desarrollo se acortan (y no me refiero a que un framework lleve CRUD incorporado), tanto para cualquier script como aplicación de la naturaleza que sea. Esto es debido a que es un lenguaje de muy alto nivel (de abstracción): lenguaje dinámico y estructuras de alto nivel, como listas y tablas hashing (también llamadas diccionarios).

Yo he trabajado con herrramientas de Microsoft (Visual Basic 6) y Sybase (PowerBuilder). No se mucho de desarrollo de software, es más, cada día que pasa y leo y leo sé que sé menos…

…pero al esforzarme por hacer las cosas bien, veo que Python me ayuda… y un lenguaje que implementa ciertos paradigmas de programación como POO tan fácilmente, ó en donde los bloques de código se escriben con tanta naturalidad (y esta palabra es muy importante en el desarrollo de software)… no se… da que pensar…

…y es que la legibilidad cuenta, que ya vendrán otros a mantener tu software, y deben saber lo que has hecho.

Yo he sido muy fanático de PowerBuilder, para gestión empresarial, donde sinceramente digo que no hay actualmente herramientas más potentes que los DataWindows para acceso a datos. Sin embargo, PB se ha hecho más complicado con el tiempo. Desde la versión 6 (la mejor) hasta la 12 (la más complicada), ha pasado mucho. Y yo necesitaba mejorar como desarrollador de software. Python hace mejor al desarrollador, ya que ayuda a escribir mejor, esto es, a escribir menos, para hacer lo mismo, lo que indica recortar tiempos de desarrollo, y en definitiva, acortar proyectos, lo que implica a su vez, dinero.

Pero la verdad, la realidad, es muy distinta. En España por lo menos. Python se utiliza muy poco, PHP lo que más, junto a las herramientas de Microsoft y Java.

Después de llevar con Python un año, de escribir en este blog por puro amor al arte, me niego a emprender otra andadura diferente en la que estoy. Creo firmemente que apostar por Python y sucedáneos, tales como Django para web, wxPython para escritorio, Plone para CMS, etcétera, es una apuesta a caballo ganador.

Las distancias hoy día no son barreras para asociarse y trabajar juntos, para eso está Internet...

Si eres una persona responsable, muy comprometida, que quiere embarcarse en algo diferente, apostar por el talento, el esfuerzo y hacer las cosas de la forma correcta, aquí espero para que hablemos.

Resumiendo, Python, siempre desde mi punto de vista, es un framework lenguaje de programación de desarrollo, de programadores para programadores, donde prima el hacer las cosas de la manera correcta, en el menor tiempo posible (¡y en este orden!).

Saludos.