sábado, 29 de mayo de 2010

Problema de algoritmia, en Python

Hola. En este post vamos a ver la resolución de un problema de Algoritmia típico, resuelto en Python. El enunciado me lo ha enviado un navegante, entusiasta de Python, como yo. Es el siguiente:


A partir de una cadena introducida por el usuario, se trata de pintar una caja de texto con la propia cadena, utilizando espacios en blanco. La complejidad reside en cómo pintar las cadenas reversas, esto es, escritas al revés.

Hay varias formas de resolver el problema. Con listas parece obvio, aunque yo lo he resuelto de la manera más fácil para mí, claro.

Este es el código:

# -*- coding: cp1252 -*-
# Ángel Luis García García
# Algoritmia

while True:
# Introducimos palabra.
cadena = raw_input('Introduce palabra: ')
# Si no escribimos nada, salimos.
if len(cadena) == 0: break
# Longitud de la cadena.
long_cadena = len(cadena)
# Espacio en blanco.
blanco = " " * (long_cadena - 2)
# Pintamos la cadena de arriba.
print cadena
# Pintamos las columnas.
puntero = long_cadena - 1
for i in range(1,long_cadena):
puntero -= 1
if i == long_cadena - 1:
# Pintamos la cadena de abajo.
a = list(cadena)
a.reverse()
cadena_down = ""
for i in a: cadena_down = cadena_down + i
print cadena_down
else:
# Columnas
print cadena[i] + blanco + cadena[puntero]

Y ejemplos de la solución:


Si crees tener una solución alternativa puedes enviarla, si lo deseas, a este blog, y la publicaré, para de esta manera aprender todos y ver que hay varias formas de dar solución a un mismo problema.

Saludos.

sábado, 8 de mayo de 2010

Persistencia y herencia de objetos, con Python.

Hola. En este artículo vamos a ver las ventajas de uno de los pilares fundamentales de la programación orientada a objetos, la herencia, así como la persistencia de objetos, una forma de guardar información en disco sin necesidad de bases de datos ni ficheros estructurados. ¿Cómo? Guardando directamente el objeto en disco.

Como tantas veces se ha comentado en este blog, hay que ser ordenado, y aquí vamos a poner en práctica uno de mis pilares fundamentales de programación, a saber:

Si un objeto puede guardarse y volver a estados diferentes, podría ser interesante.

En primer lugar vamos a crear una clase base, de la cual se pueda heredar ciertos métodos que nos serán de utilidad en todas las clases que hagamos después. Es decir, la estrategia consiste en crear nuestra propia librería de clases, con ciertas clases y/o funciones si se precisara, lo más genéricas posibles, para poder reutilizar el código escrito.

Una clase base genérica heredará del objeto base de Python, esto es, object. ¿Y qué métodos podemos incluir en nuestra clase base? Pues lo métodos que nos permitan guardar ó cargar un objeto hacia ó de disco.


En este diagrama podemos ver lo que se pretende. Partimos de un objeto object que nos brinda Python. A partir de él, vamos a crear una clase base, que tendrá ciertos métodos, a saber, de para cargar y guardar los objetos que se instancien a partir de dicha clase. Y finalmente cuando queramos crear una clase, en vez de heredar de object, heredaremos de nuestra clase base.

Creación de una clase base

Una librería de clases no es ni más ni menos que un conjunto de clases y funciones genéricas a partir de las cuales heredar para poder trabajar. Esto se hace para realizar funciones específicas, ahorrándonos código y por ende, tiempo y dinero.

Nuestra librería de clases se encierra en un fichero, que denominaremos, libreria_clases.py. Aquí definiremos el siguiente código:

# -*- coding: cp1252 -*-
# Librería de clases.
# El Viaje del Navegante.
# 08-05-2010. Murcia. España

import cPickle

class clase_base(object):
# ---------------------------------------------------------------------------
# MÉTODOS DE PERSISTENCIA DE DATOS
# ---------------------------------------------------------------------------

# --------------------------------------
# METÓDOS PÚBLICOS
# --------------------------------------

def cargar_objeto(self, nombre_fichero):
'''
Devuelve un objeto guardado en el fichero cuyo nombre es nombre_fichero.
'''
return self.__cargar_objeto__(nombre_fichero)

def guardar_objeto(self, objeto, nombre_fichero):
'''
Guarda el objeto pasado como parámetro en disco con el nombre nombre_fichero.
'''
self.__guardar_objeto__(objeto, nombre_fichero)

# --------------------------------------
# METÓDOS DE SOPORTE
# --------------------------------------

def __guardar_objeto__(self, objeto, nombre_fichero):
'''
Método de persistencia de datos para guardar el estado de un objeto en disco.
La forma normal de uso es objeto.__guardar_objeto__(objeto, nombre_fichero),
en donde objeto es el objeto a guardar y nombre_fichero es el nombre del fichero
en disco que contendrá la información del objeto.
Se podría dar el caso de utilizar este método para guardar otros objetos
con el mismo nombre. Sería útil para traspasar información a partir de la
persistencia en disco.
'''
# Nombre del fichero que contendrá el objeto.
nombre_fichero = nombre_fichero
# Fichero que contendrá el objeto.
manejador_fichero = open(nombre_fichero,'w')
# Volcamos el objeto de memoria al fichero.
cPickle.dump(objeto, manejador_fichero)
# Cerramos fichero.
manejador_fichero.close()

def __cargar_objeto__(self, nombre_fichero):
'''
Método de persistencia de datos para cargar el estado de un objeto en memoria.
Se pasa como parámetro el nombre del fichero en donde está el objeto.
'''
# Nombre del fichero.
nomfich = nombre_fichero
# Abrimos fichero donde reside el objeto.
manejador_fichero = open(nombre_fichero,'r')
# Pasamos el objeto del fichero en disco a memoria.
objeto = cPickle.load(manejador_fichero)
# Cerramos el fichero.
manejador_fichero.close()
# Devolvemos el objeto.
return objeto

Lo que estamos definiendo aquí es una clase, denominada clase_base, que hereda del objeto object de Python. Vemos que tiene 4 métodos: dos de ellos ocultos (con __ al principio y al final), que llamamos de soporte, esto es, que se utilizan para realizar operaciones internas dentro de la clase; y otros dos públicos, esto es, que se pueden acceder directamente. Esta forma de trabajar es particular mía, con dos métodos hubiera bastado. Sin embargo, si se escribe un poco más de código la legibilidad aumenta.

¿Qué realiza esta clase? Esta clase implementa los métodos para guardar y cargar, una vez que se instancie, el objeto creado en disco a partir de dicha instanciación. Para ello utilizamos el módulo de persistencia de datos cPickle. En Python existen varios módulos relacionados con la persistencia de datos como pickle, copy_reg, shelve, marshal, anydbm, whichdb, dbm, gdbm, dbhash, bsbdb, dumbdbm, sqlite3, además de cPickle.

Creación de una clase a partir de nuestra clase base

Para ver el potencial de nuestra clase base vamos a realizar un sencillo ejemplo, que el lector entenderá sin ningún problema. Creamos una clase que defina un ser humano (evidentemente faltarían muchos atributos, que he obviado). Dicha clase, en vez de heredarla de un objeto object se va a heredar de la clase clase_base. Esta clase se define en un fichero diferente, denominado ejemplo_ser_humano.py. Contiene el siguiente código:

# -*- coding: cp1252 -*-
# Ejemplo de herencia y persistencia de datos.

from libreria_clases import clase_base

class ser_humano(clase_base):
def __init__(self,color_ojos, color_pelo,altura,peso):
'''
Definición de un ser humano. Parámetros:
color de ojos, coloe de pelo, altura, peso.
'''
self.__color_ojos__ = color_ojos
self.__color_pelo__ = color_pelo
self.__altura__ = altura
self.__peso__ = peso

def devolver_datos(self):
'''
Devuelve diccionario de datos sobre el ser humano.
'''
lista = [("color_ojos",self.__color_ojos__),\
("color_pelo",self.__color_pelo__),\
("altura",self.__altura__),\
("peso",self.__peso__)]
return dict(lista)


Lo primero que vemos es que importamos la clase clase_base. Esto es necesario debido a que estamos definiendo la clase ser_humano a partir de la clase clase_base.

class ser_humano(clase_base):

Esta clase es trivial, esto es, contiene un __init__ en donde se definen los atributos ocultos y un método que devuelve un diccionario con los valores actuales de los atributos que definen al ser humano (se le recuerda al lector que he abreviado los atributos para no alargar en exceso el ejemplo).

Utilización de clases con nuevos métodos incorporados

Bien, ha llegado el momento que todos esperábamos. Tenemos una clase, ser_humano, que contiene, no solamente el método que devuelve el diccionario; además tiene los métodos guardar y cargar que heredó de la clase base de la que hereda. Si nos vamos a cualquier editor con completitud de código, como IDLE, podremos verlo:

Para ver cómo guardar un objeto creado podemos crear un nuevo fichero, llamado, por ejemplo, humano.py, que contendrá lo siguiente:

# Ejemplo de utilización de cPickle.
# El Viaje del Navegante

from ejemplo_ser_humano import ser_humano

humano = ser_humano("azul","marron",1.80,65)
humano.guardar_objeto(humano,"datos_humano.obj")

Si nos vamos al directorio en donde está nuestro fichero de código fuente veremos que se nos ha creado un nuevo archivo, llamado datos_humano.obj (la extensión obj no es obligatoria, yo la pongo por convenio). Si editamos su contenido obtenemos lo siguiente:

Vemos que el fichero contiene, además de cierta información propia, nuestros datos introducidos. Para ver cómo cargar dicho objeto hagamos alguna modificación al código de humano.py, tal que así:

# Ejemplo de utilización de cPickle.
# El Viaje del Navegante

from ejemplo_ser_humano import ser_humano

#humano = ser_humano("azul","marron",1.80,65)
humano = ser_humano("-","-",0,0)
#humano.guardar_objeto(humano,"datos_humano.obj")
humano = humano.cargar_objeto("datos_humano.obj")
print humano.devolver_datos()

Como el lector puede apreciar, se han comentado las líneas de creación de un humano específico, así como la utilización del método para guardar el objeto. Hemos creado un objeto humano con valores diferentes y nos disponemos a cargar el objeto, para a continuación imprimir el diccionario de datos con los valores de los atributos en disco. Como no podría ser de otra manera el resultado es:

{'peso': 65, 'altura': 1.8, 'color_pelo': 'marron', 'color_ojos': 'azul'}
Con esto queda demostrado que la persistencia de datos funciona.

Un ejemplo más serio: Gestión de personal

Puesto que ya hemos visto como funciona la persistencia de datos y la filosofía a la hora de trabajar con librerías de clases (o al menos como debería de ser), nos disponemos a crear una aplicación consistente con dicha técnica. En primer lugar dicha aplicación se hará en modo texto, sin implementación en entorno gráfico. Esto lo hago así para que el lector pueda apreciar que hay que intentar ser lo más genérico posible a la hora de desarrollar una aplicación. Es decir, si conseguimos crear una aplicación que funcione, indenpendientemente de la plataforma gráfica y de la plataforma del sistema operativo (con Python es muy fácil), tendremos mucho ganado, ya que pasar de wxPython a GTK, por ejemplo, será cuestión de minutos (siempre y cuando la interfaz gráfica ya esté hecha, claro).

En las aplicaciones que desarrollemos se intentará, por todos los medios, que haya una independencia de la plataforma gráfica en la que trabajemos, en lo máximo que sea posible.
¿En qué consiste nuestra aplicación? Nuestro programa gestionará una base de datos de personas. La funcionalidad será la de dar de alta, baja y búsqueda de personas. Además, se podrán guardar las modificaciones en disco. En consecuencia, se podrá recuperar información almacenada anteriormente.

Nuestra aplicación se basará en la clase clase_base. Por tanto tomaremos en cuenta el fichero, tal como está implementado anteriormente, libreria_clases.py.

En un segundo fichero, llamado, clase_persona.py, tendremos la definición de las clases:

persona: Clase, que hereda de object, que define a una persona. Contiene todos los métodos necesarios para gestionar su información.

total_personas: Clase, que hereda de clase_base, que define a un conjunto de objetos instanciados a partir de la clase persona. Esta clase contiene una lista de objetos, y los métodos para guardar y cargar objetos, heredados de la clase_base. Se utiliza esta clase para guardar una lista de objetos persona, es decir, el conjunto, ya que de lo contrario se tendria que crear un fichero para cada persona (a la hora de guardar), y no es muy operativo.

Por último, en un tercer fichero, llamado aplicacion_texto_personas.py, tendremos las funciones necesarias para crear un menú amigable de texto, así como aquellas funciones en las que se utilicen las clases (los objetos instanciados a partir de ellas) definidas en los anteriores ficheros.

clase_persona.py: Definiendo las clases fundamentales

A continuación se presenta el contenido del código de clase_persona.py, que contiene las clases fundamentales de nuestra aplicación:

# -*- coding: cp1252 -*-
# Clase genérica persona.
# El Viaje del Navegante.
# 08-05-2010. Murcia. España.

from libreria_clases import clase_base
import re

class persona(object):
'''
Clase persona. Identifica a una entidad persona.
'''
def __init__(self):
'''
Declaramos aquí todos los atributos de la persona.
'''
self.__nombre__ = ''
self.__apellido1__ = ''
self.__apellido2__ = ''
self.__nif__ = ''
self.__direccion__ = ''
self.__poblacion__ = ''
self.__codigo_postal__ = ''
self.__provincia__ = ''
self.__pais__ = ''
self.__telefono1__ = ''
self.__telefono2__ = ''
self.__email__ = ''
self.__web__ = ''

def dato(self, opcion):
'''
Devuelve un atributo del objeto, especificado según el parámetro "opcion".
"opcion" puede ser:
nombre: Nombre
apellido1: Primer apellido
apellido2: Segundo apellido
nif: NIF
direccion: Dirección
poblacion: Población
cp: Código postal
provincia: Provincia
pais: País
tlfno1: Primer número de teléfono
tlfno2: Segundo número de teléfono
email: Correo electrónico
web: Sitio web ó blog personal
todo: Devuelve una lista con todos los atributos.
'''
if opcion == 'nombre': return self.__nombre__
if opcion == 'apellido1': return self.__apellido1__
if opcion == 'apellido2': return self.__apellido2__
if opcion == 'nif': return self.__nif__
if opcion == 'direccion': return self.__direccion__
if opcion == 'poblacion': return self.__poblacion__
if opcion == 'cp': return self.__codigo_postal__
if opcion == 'provincia': return self.__provincia__
if opcion == 'pais': return self.__pais__
if opcion == 'tlfno1': return self.__telefono1__
if opcion == 'tlfno2': return self.__telefono2__
if opcion == 'email': return self.__email__
if opcion == 'web': return self.__web__
if opcion == 'todo': return [self.__nombre__,self.__apellido1__,self.__apellido2__,\
self.__nif__,self.__direccion__,self.__poblacion__,\
self.__codigo_postal__,self.__provincia__,self.__pais__,\
self.__telefono1__,self.__telefono2__,self.__email__,\
self.__web__]

def buscar(self, opcion, expresion_regular):
'''
Función para buscar una expresión regular en el campo dado por el
parámetro "opcion".
"opcion" puede ser:
nombre: Nombre
apellido1: Primer apellido
apellido2: Segundo apellido
nif: NIF
direccion: Dirección
poblacion: Población
cp: Código postal
provincia: Provincia
pais: País
tlfno1: Primer número de teléfono
tlfno2: Segundo número de teléfono
email: Correo electrónico
web: Sitio web ó blog personal
Devuelve cierto si se ha encontrado una corcondancia con la expresión regular
ó falso en caso contrario.
'''
# No se diferencia entre mayúsculas y minúsculas.
re.IGNORECASE
# Se ignoran espacios en blanco.
re.VERBOSE
# Buscamos la cadena en el campo indicado.
try:
if re.search(expresion_regular, self.dato(opcion)) is None:
# No se ha encontrado concordancia. Se devuelve Falso.
return False
else:
# Hay concordancia, ya que re.search devuelve un objeto.
# Se devuelve cierto.
return True
except:
return False

def modificar(self, opcion, valor = None):
'''
Asigna el valor "valor" al atributo "opcion".Si no se especifica "valor", por defecto
se asigna None.
"opcion" puede ser:
nombre: Nombre
apellido1: Primer apellido
apellido2: Segundo apellido
nif: NIF
direccion: Dirección
poblacion: Población
cp: Código postal
provincia: Provincia
pais: País
tlfno1: Primer número de teléfono
tlfno2: Segundo número de teléfono
email: Correo electrónico
web: Sitio web ó blog personal
'''
if opcion == 'nombre': self.__nombre__ = valor
if opcion == 'apellido1': self.__apellido1__ = valor
if opcion == 'apellido2': self.__apellido2__ = valor
if opcion == 'nif': self.__nif__ = valor
if opcion == 'direccion': self.__direccion__ = valor
if opcion == 'poblacion': self.__poblacion__ = valor
if opcion == 'cp': self.__codigo_postal__ = valor
if opcion == 'provincia': self.__provincia__ = valor
if opcion == 'pais': self.__pais__ = valor
if opcion == 'tlfno1': self.__telefono1__ = valor
if opcion == 'tlfno2': self.__telefono2__ = valor
if opcion == 'email': self.__email__ = valor
if opcion == 'web': self.__web__ = valor

def cabecera(self):
'''
Devuelve una lista de cabecera, relacionando campo y texto asociado.
'''
cabecera = [("nombre","Nombre: "),("apellido1","1er. Apellido: "),\
("apellido2","2do. Apellido: "),("nif","DNI/NIF: "),\
("direccion","Dirección: "),("poblacion","Población: "),\
("cp","Código postal: "),("provincia","Provincia: "),\
("pais","País: "),("tlfno1","Teléfono 1: "),\
("tlfno2","Teléfono 2: "),("email","Correo electrónico: "),\
("web","Sitio web / blog: ")]
# Devuelve cabecera.
return cabecera

class total_personas(clase_base):
'''
Clase que define una lista de personas. Se utiliza para serializar el
conjunto de clientes en un objeto.
'''
def __init__(self, lista_personas):
self.__lista_personas__ = lista_personas
self.__persona__ = 0

def devolver_persona(self, movimiento = "actual"):
'''
Método que devuelve una persona, a partir del valor
de "movimiento", que puede tomar los siguientes valores:
actual: Devuelve la persona actual.
siguiente: Devuelve la persona siguiente, y se posiciona en ella.
anterior: Devuelve la persona anterior, y se posiciona en ella.
ultimo: Devuelve la última persona y se posiciona en ella.
primero: Devuelve la primera persona y se posiciona en ella.
'''
if movimiento == "siguiente": self.__persona__ += 1
if movimiento == "anterior": self.__persona__ -= 1
if movimiento == "ultimo": self.__persona__ = len(self.__lista_personas__)
if movimiento == "primero": self.__persona__ = 1
# Devolvemos a la persona requerida.
return self.__lista_personas__[self.__persona__]

def devolver_lista_personas(self):
'''
Devuelve la lista de personas.
'''
return self.__lista_personas__

Como se ha comentado se diseñan dos clases, la que define a la persona y la que define al conjunto de personas. Decir que en la búsqueda de objetos persona se utiliza el módulo de expresiones regulares re, más concretamente con el método search. El código está lo suficientemente comentado como para que no haya problemas en su comprensión.

Por último, vamos a ver el código que maneja las clases definidas.

Juntando todo: la aplicación

Finalmente tenemos el código de aplicacion_texto_personas.py, en donde están las funciones necesarias para ejecutar la aplicación.

# -*- coding: cp1252 -*-
import os

from clase_persona import persona
from clase_persona import total_personas

# Menú de opciones.
def menu():
os.system('cls')
print '''

*** GESTIÓN DE PERSONAS ***

Elige una de las siguientes opciones:

1) Alta de personas.
2) Baja de personas.
3) Modificación de persona.
4) Búsqueda.
5) Impresión de persona.
6) Salvar.
7) Cargar.
0) Salir.

Elige opción:
'''
opcion = raw_input("==> ")
return opcion

def pedir_parametros_busqueda(cabecera):
'''
Función para pedir parámetros de búsqueda.
Devuelve una lista de parámetros.
'''
lista_parametros = []
for i in cabecera:
lista_parametros.append((i[0],raw_input(i[1])))
return lista_parametros

def modificar_persona(persona, opcion, lista_personas):
'''
Función para modificar a una persona, según el valor de opción.
Si opcion = 0 se pide la inserción de datos, normal.
Si opcion = 1 se pide, mediante confirmacion, la modificación de datos.
'''
cabecera = persona.cabecera()
for i in cabecera:
seguir = True
while seguir:
if opcion == 0: persona.modificar(i[0], raw_input(i[1]))
if opcion == 1:
j = raw_input("¿Modificar " + i[0] + " (S/N)?")
if j in ("S","s"):
persona.modificar(i[0], raw_input("("+ persona.dato(i[0])+") " + i[1]))
else:
# No se modifica nada. Se continúa.
# Hacemos break para salir del while.
break
# Aquí incluimos las restricciones de inserción de datos.
# El NIF nunca puede estar vacío.
if len(str(persona.dato("nif"))) == 0 and i[0] == 'nif':
print "Hay que incluir obligatioramente el NIF."
else:
seguir = False
# El NIF no puede estar repetido.
if i[0] == 'nif' and not seguir:
contador = 0
for k in lista_personas:
if k.dato("nif") == persona.dato("nif"): contador += 1
if (contador == 1 and opcion == 0) or (contador > 1 and opcion == 1):
print "Hay una persona con un mismo NIF ", str(persona.dato("nif")).strip()
seguir = True
else:
seguir = False
# Devolvemos a la persona.
return persona

def aplicacion():
#Nombre del fichero de disco en donde se guarda la información de las personas.
nombre_fichero = "bd_personas.obj"
# Lista de objetos clientes.
lista_personas = []
opcion = ""
while opcion != "0":
opcion = menu()
if opcion == "7":
# Cargar información del disco.
if len(lista_personas) != 0:
print "¡Atención! Hay datos en memoria. Si continúas borrarás"+ \
"lo que hayas realizado has ahora."
aux = raw_input("¿Cargar información de disco (S/N)?")
if aux not in ("S","s"): return
personas = total_personas([])
try:
personas = personas.cargar_objeto(nombre_fichero)
aux = personas.devolver_lista_personas()
if len(aux) != 0: lista_personas = aux
except:
# Error en la carga del fichero.
print "No he podido cargar el fichero ", nombre_fichero

if opcion == "6":
# Guardar información en disco.
if len(lista_personas) != 0:
personas_aux = total_personas(lista_personas)
personas_aux.guardar_objeto(personas_aux,nombre_fichero)

if opcion == "1":
# Alta de persona.
persona_nueva = persona()
# Info.
print "Introduce valores para crear una nueva persona:"
persona_nueva = modificar_persona(persona_nueva,0,lista_personas)
# Añadimos persona a la lista de personas.
lista_personas.append(persona_nueva)
if opcion == "5":
# Listado de todas las personas.
for i in lista_personas:
print i.dato('todo')

if opcion == "2" or opcion == "3":
# Baja ó modificación de una persona, a partir de su NIF.
# Info.
if opcion == "2": aux = " dar de baja "
if opcion == "3": aux = " modificar los datos de "
print "Para"+aux+"una persona, introduzca su NIF."
nif = raw_input("NIF: ")
if len(str(nif)) != 0:
for i in lista_personas:
if i.dato("nif") == nif:
if opcion == "2": lista_personas.remove(i)
if opcion == "3": modificar_persona(i,1,lista_personas)
# Info.
if opcion == "2": aux = "eliminó"
if opcion == "3": aux = "modificó"
print "Se "+aux+" la persona con NIF ", str(nif)
# Salimos.
break

if opcion == "4":
# Info.
print """
Para buscar hay que definir parámetros de búsqueda. Si se quiere
obviar un campo simplemente pulse Enter. Defina parámetros de búsqueda:
"""
pedir_datos = True
# Inicializamos testigo de búsqueda.
encontrado = True
# Búsqueda de personas en la lista de personas.
for persona_actual in lista_personas:
if pedir_datos:
# Pedimos inserción de información.
lista_parametros_busqueda = pedir_parametros_busqueda(persona_actual.cabecera())
pedir_datos = False
# Para cada persona, buscamos en la lista de expresiones
encontrado = True
for i in lista_parametros_busqueda:
# Campo a buscar.
opcion = i[0]
# Expresión regular asociada al parámetro.
expresion_regular = i[1]
# Si la expresión regular no contiene nada, seguimos.
if len(str(expresion_regular)) == 0: continue
# Evaluamos la expresión para la persona actual.
if persona_actual.buscar(opcion, expresion_regular):
# La expresión regular coincide. Seguimos.
continue
else:
# No hay coincidencia. La persona no coincide
# con los parámetros de búsqueda especificados.
encontrado = False
break
if encontrado:
# Aquí se podría guardar el resultado de la búsqueda.
# En nuestro caso se imprime el resultado.
print persona_actual.dato('todo')

# Ejecutamos la aplicación.
aplicacion()


Como antes, el código está lo suficientemente comentado como para que no haya excesivos impedimentos para su entendimiento.

Funcionamiento de la aplicación

Y para terminar, unas capturas de pantalla de cómo debería de funcionar esta prototipo de aplicación de gestión de personal. Decir que en la impresión no me he esmerado nada, solo imprimo la lista de personas. Dejo al lector su desarrollo.

La pantalla principal.

Alta de algunas personas.

Impresión.

Búsqueda.

Modificación.

Y por supuesto la eliminación de una persona, así como guardar y cargar lista de personas.

Esta aplicación es un prototipo, en modo texto, por lo que habría que pulir la interfaz del usuario. Darse cuenta que hemos utilizado la clase clase_base para guardar nuestro objeto en disco, de manera que nos olvidamos de bases de datos y ficheros con lógica estructurada.

Un Bug!!!!

Me he dado cuenta que si introduces datos de una determinada forma la aplicación se vuelve inconsistente. Esto es, es obligatorio el NIF de una persona, ya que lo trato como clave de identificación. Sin embargo, pudiera darse el caso de modificar una persona y no asignarle NIF.

Véase aquí:



CONCLUSIONES

En este artículo hemos visto como implementar una clase que, al instanciarse, se puede guardar, y al recuperarse, puede cambiar de estado. Hemos visto las ventajas de trabajar con clases bases y por ende, de la necesidad de crear una librería de clases para poder trabajar, ya que de esta manera podemos reutilizar código ya escrito. Es más, si necesitamos modificar alguna clase base, se podría versionar, siempre que se pueda.

Se ha trabajado con cPickle, uno de los módulos que tiene Python para la serialización de objetos, esto es, la persistencia de datos.

Espero que os pueda servir de ayuda mi forma de ver la construcción de aplicaciones.

Un cordial saludo.

domingo, 2 de mayo de 2010

Manual de Python para aprender a programar

Hola. He encontrado un manual gratuito para aprender a programar, con la ayuda de Python. Se titula:

Aprenda a Pensar Como un Programador con Python

Se puede descargar el PDF desde http://manuales.gfc.edu.co/python/thinkCSpy.es.pdf.

Está escrito en español. Orientado a aprender a programar, no el lenguaje Python en sí, aunque al final se aprenda, sino que se utiliza Python como guía para el aprendizaje de los fundamentos de la programación, viendo todos los conceptos propios de la misma.

Esta obra está dirigida tanto a aprendices de programador, profesionales que necesiten de conocimientos básicos de programación, pasando por profesionales que necesiten hacer consultas puntuales. Debido a la facilidad de aprendizaje del lenguaje Python, su nivel pedagógico es elevado.

Estamos hablando de un curso completo de fundamentos de la programación, siendo Python el eje vertebrador de las exposiciones y ejemplos conceptuales. Así pues se habla de tipos de datos como listas, cadenas ó diccionarios, pasando por TAD's (Tipos abstractos de datos), como árboles, colas, sin olvidar clases y objetos. Un curso muy completo y recomendado.

Por otra parte he adquirido recientemente un libro sobre Python, que me ha resultado de lo más llamativo y agradable. Está en inglés, pero si se quiere tener un manual extraordinariamente bueno, y en papel, recomiendo Object-Oriented Programming in Python, de Goldwasser y Letscher, editorial Pearson. La manera que tiene de explicar los conceptos de clases y objetos es sencillamente de sobresaliente. Un nivel pedagógico excelente.



Saludos.

sábado, 1 de mayo de 2010

IronPython y Sybase DataWindow .NET, en SharpDevelop, y 3.

Hola. Seguimos con la construcción de una aplicación de gestión con IronPython y Sybase DataWindow .NET en SharpDevelop. Este post es continuación del anterior en El Viaje del Navegante.

CREANDO CONEXIONES ENTRE UN CONTROL DATAWINDOW Y LA BASE DE DATOS MEDIANTE UN OBJETO TRANSACTION

Vamos a conectar el Control DataWindow a la base de datos de MySQL. Darse cuenta del juego que da esto, ya que podemos tener una serie de DataWindows conectados a origenes de datos diferentes, siempre y cuando el SGBD sea el mismo, esto es, podemos cambiar, en una base de datos, de producción a pruebas únicamente cambiando la cadena de conexión del objeto transacción, sin que afecte en absoluto al DataWindow.

Arrastramos un objeto Transaction a la ventana.


En las propiedades del objeto Transaction le indicamos las características de la conexión (¿Recuerda el lector como se obtenía la cadena de conexión en el Designer?).

¿Dónde decirle al DataWindow que coga la conexión del objeto Transaction? Lo normal es conectar el DataWindow en la carga de la ventana, pero podemos hacerlo en cualquier lugar, siempre y cuando le de tiempo al DataWindow de construirse (evidentemente, si intentamos asignar una conexión a un DataWindow que no está creado, fallará). Así que vamos a incluir el código de conexión en el evento Load de la ventana Windows.



PROGRAMANDO LÓGICA DE OPERACIÓN DEL MANTENIMIENTO

Ya tenemos todo listo, solo hace falta programar las operaciones propias de un mantenimiento de clientes, esto es, crear, borrar, imprimir, etcétera. Insertamos los botones apropiados y ¡¡¡les damos vida!!!.

Finalmente nuestro mantenimiento de clientes queda así:

He incluido código en cada evento del click del ratón sobre los botones incluidos en la ventana:



¿Y los métodos y propiedades de un DataWindow? ¿Cómo investigo dónde están?

SharpDevelop lleva instalado un plugin de Reflector de serie, por lo que podemos navegar por las clases de los componentes deseados para ver sus propiedades y métodos. Yo lo he usado para ver la disponibilidad de métodos del componente DataWindow. Antes hay que cargar las referencias, claro está.


Si vemos que el DataWindow no queda como nosotros quisiéramos siempre podemos ir al Designer, a modificarlo y automáticamente, sin necesidad de hacer nada, se actualiza en el Control DataWindow del SharpDevelop. Un ejemplo sería el siguiente, en donde he incluido botones de recorrido de registros.


Evidentemente la funcionalidad de este mantenimiento de clientes es limitada ya que no me he esmerado demasiado, por falta de tiempo. Decir que la impresión podría hacerse registro a registro, en vez de todos los clientes. Faltaría una búsqueda de clientes por cualquier campo. En definitiva, espero la comprensión del lector a la hora de valorar el ejercicio, ya que he buscado la pedagogía más que el refinamiento, aunque son conceptos que no tienen porqué ir separados. Otra razón es que no quier hacer largo en exceso este post.

FUNCIONAMIENTO DEL MANTENIMIENTO DE CLIENTES

Ejecutamos nuestra aplicación y tenemos lo siguiente:


Insertamos un nuevo cliente:


Y otro:

Y otro más:


Y una impresión de todos los clientes:


Con este último artículo queda demostrada la potencia de la conjunción de estas herramientas. En un cuarto post crearé el mantenimiento del maestro - detalle para facturas, que se hace prácticamente con los mismos pasos con los que he realizado este mantenimiento maestro.

CONCLUSIONES

IronPython es una implementación de Python escrita en C# para acceder a las clases del FrameWork .NET de Microsoft. Sybase, empresa líder en sistemas de gestión ha sacado al mercado una versión de su objeto estrella en PowerBuilder, el DataWindow, en una versión independiente para el Framework de Redmond, el componente DataWindow .NET.

IronPython funciona y se integra con especial facilidad en SharpDevelop. El componente DataWindow .NET se puede agregar al entorno de desarrollo de SharpDevelop. IronPython puede acceder sin la menor complejidad al componente Sybase DataWindow .NET, en el entorno de desarrollo SharpDevelop.

Python y DataWindow, dos de las plataformas de desarrollo mejor construidas y más sencillas de utilizar, juntas. Una respuesta para los desarrolladores de sistemas de gestión y de bases de datos que les vendrá muy bien. Merece la pena valorarlo al menos.

Inconvenientes: Sybase DataWindow .NET es de pago, rondando los 500 dólares.

Esta mezcla tan explosiva de potencia de desarrollo es especialmente recomendable para proyectos de alto nivel empresarial. Tal vez, y solo tal vez, en esta ocasión, el gasto esté justificado.

Saludos.

IronPython y Sybase DataWindow .NET, en SharpDevelop, y 2.


Hola. Continuamos con la construcción de una aplicación de gestión, que empezamos en el anterior artículo de El Viaje del Navegante, con IronPython, Sybase DataWindow .NET en el entorno de desarrollo SharpDevelop.

Creación de un proyecto

Lo primero es crear un proyecto, así que nos vamos a File/New, y seleccionamos el crear un proyecto.


Le damos un nombre al proyecto y automáticamente genera el nombre de la librería (Library), tal que así:


Incluir en la librería del proyecto objetos DataWindows

Nos vamos a File/New, y creamos nuestro primer DataWindow. Aquí se nos presentan varios tipos de DataWindow, es decir, varias formas de presentar la información. Elegimos Freeform y continuamos.


En la siguiente ventana se nos da a elegir el origen de donde van a venir los datos. Si se trata de hacer un mantenimiento de una sola tabla es recomendable seleccionar Quick Select, por temas de rendimiento. Si se trata de varias tablas (un join), hay que elegir SQL Select. Si el origen es cualquier otro, como una query ó procedimiento almacenado, se elige la opción apropiada. En nuestro caso se selecciona Quick Select, ya que vamos a crear un DataWindow para el mantenimiento de clientes.


Y finalmente le decimos el origen de los datos:


Click en Connect para poder conectarnos a la base de datos. Por último se nos muestran las tablas con las que queremos trabajar. Puesto que el primer mantenimiento va a ser el de gestión de clientes, seleccionamos la tabla clientes.


Y dentro de aquí, todos los campos. Click en OK y continuamos.


Se nos muestra una ventana para configurar colores y bordes. Continuamos, ya que esto se puede configurar más adelante.


Y finalmente se nos comunica que estamos preparados para diseñar. Click en Finalizar, ¡y a diseñar!


Diseñando un DataWindow

Se nos presenta la siguiente pantalla:


Diseñar DataWindows es la cosa mas simple que se puede hacer un Sábado por la tarde. En la parte de arriba, tenemos la ventana Design para diseñar nuestro DataWindow. En la ventana de abajo (Preview) tenemos como va quedando nuestro diseño. A la derecha tenemos las propiedades de los objetos que están contenidos en el DataWindow (campos, botones, o lo que sea).

Quito algunas ventanas que no me hacen falta y me pongo a diseñar.

Finalmente salvamos nuestro diseño en la librería especificada.


Listo, ya tenemos diseñada nuestra interfaz con la tabla de clientes. Vamos a insertarla ahora en nuestro proyecto en SharpDevelop.

INSERTAR UN DATAWINDOW EN UN CONTROL DATAWINDOW EN SHARPDEVELOP

Una vez que tenemos nuestro DataWindow diseñado nos proponemos insertarlo en el Control DataWindow que está en la ventana anteriormente creada en SharpDevelop. ¿Cómo hacerlo? Si nos vamos a las propiedades del Control DataWindow podemos ver la sección DataWindow, que contiene: DataWindowObject y LibraryList.

En LibraryList hay que ir a buscar la librería PBL en donde se encuentra nuestro DataWindow.

En DataWindowObject elegimos el DataWindow a cargar de la libreria. Como en nuestra librería solo hemos creado uno, no es demasiado difícil el encontrarlo.


Y aquí tenemos nuestra ventana con un Control DataWindow y el Datawindow que hemos diseñado anteriormente, cargado.


En el siguiente post veremos como crear la conexión entre el Control DataWindow y la base de datos, así como la implementación de las operaciones básicas de mantenimiento. Conespecial relevancia se verá como con unas pocas líneas de código se puede hacer un mantenimiento sencillo.

Saludos.