Upload
wozgeass
View
1.030
Download
0
Embed Size (px)
Citation preview
Programación en Python
Parte 3: Persistencia y BB.DD.Mariano Reingart
Persistencia y serialización: picklePickle: convertir objeto python en secuencia de bytesFunciones de pickle:
dumps(objeto, proto): serializa a un stringdump(objeto, archivo, proto): guarda en archivoloads(cadena, proto): des-serializa un stringload(archivo, proto): carga desde archivo
>>> import pickle>>> s = pickle.dumps({1:'a',2:'b'},0) >>> s "(dp0\nI1\nS'a'\np1\nsI2\nS'b'\np2\ns.">>> print pickle.loads(s) {1: 'a', 2: 'b'}
Persistencia y serialización: pickleimport pickledata1 = {'a': [1, 2.0, 3, 4+6j], 'b': ('string', u'Unicode'),'c': None} selfref_list = [1, 2, 3] selfref_list.append(selfref_list) output = open('data.pkl', 'wb')pickle.dump(data1, output) # Serializar un dict pickle.dump(selfref_list, output, -1) # Serializar list output.close() pkl_file = open('data.pkl', 'rb') data1 = pickle.load(pkl_file) # Deserializar el dict pprint.pprint(data1) data2 = pickle.load(pkl_file) # Deserializar la lista pprint.pprint(data2) pkl_file.close()
Persistencia y serialización: shelveShelve: objeto similar a un diccionario persistente
open(filename, flag='c', protocol=None, writeback=False): crea un diccionario persistenteflag= 'r': solo lectura, 'w': lectura/escritura, 'c': creación y lectura/escritura, 'n': nuevoShelve.sync(): sincronizar (writeback=True)Shelve.close(): grabar y cerrar diccionario
>>>import shelve>>> d = shelve.open(filename) # abrir archivo >>> d['clave'] = 'datos' # almacenar datos en una clave >>> data = d['clave'] # leer una COPIA de los datos >>> del d['clave'] # borra los datos almacenados
Ejemplo práctico: universidad.pyEnunciado: administrar las cursadas y calificaciones de los alumnos de una universidad "simplificada".
Profesores (dni, nombre, apellido, legajo)Materias (código, nombre)Cursos (materia, año, profesor)Exámenes (materia, nota, fecha)Alumnos (dni, nombre, apellido, nro_matrícula, cursos, exámenes)Universidad (alumnos, profesores, plan_estudio)
Ejemplo práctico: universidad.py
Diagrama de clases:
Ejemplo práctico: universidad.pyclass Persona: "Clase que representa una persona" def __init__(self, dni, nombre, apellido): "Constructor del objeto" self.dni = dni self.nombre = nombre self.apellido = apellido def __str__(self): "Devuelvo la representación como string" return "%s, %s" % (self.nombre, self.apellido) def __repr__(self): "Devuelve la representación interna" return "Persona(dni=%s, nombre=%s, apellido=%s)" % ( repr(self.dni), repr(self.nombre),...) def __hash__(self): "Devuelvo el identificador único del objeto" return self.dni
Ejemplo práctico: universidad.pyclass Profesor(Persona):
def __init__(self, dni, nombre, apellido, legajo): Persona.__init__(self, dni, nombre, apellido) self.legajo = legajo
Ejemplo práctico: universidad.pyclass Materia: "Clase para representar una materia"
def __init__(self, codigo, nombre): self.codigo = codigo self.nombre = nombre
def __str__(self): return "%s (%s)" % (self.nombre, self.codigo)
def __repr__(self): return "Materia(codigo=%s, nombre=%s)" % ( repr(self.codigo), repr(self.nombre))
def __hash__(self): return self.codigo
Ejemplo práctico: universidad.pyclass Curso: def __init__(self, materia, anio, profesor, cupo=30): self.materia = materia self.anio = anio self.profesor = profesor self.cupo = cupo self.alumnos = []
def inscribir(self, alumno): if self.cupo<len(self.alumnos): raise RuntimeError("No hay mas lugar!") self.alumnos.append(alumno) alumno.inscribir(curso=self)
def __repr__(self): return "Curso(materia=%s, profesor=%s)" % ( repr(self.materia), repr(self.profesor))
Ejemplo práctico: universidad.pyclass Examen: def __init__(self, materia, nota, fecha): self.materia = materia self.nota = nota self.fecha = fecha
@property def aprobado(self): return self.nota >= 4
def __repr__(self): return "Examen(materia=%s, nota=%s, fecha=%s)" % (repr(self.materia), repr(self.nota), repr(self.fecha))
Ejemplo práctico: universidad.pyclass Alumno(Persona): "Clase para representar un Alumno"
def __init__(self, dni, nombre, apellido, nro_matricula): Persona.__init__(self, dni, nombre, apellido) self.nro_matricula = nro_matricula self.cursos = [] # lista de materias cursadas self.examenes = [] # lista de exámenes rendidos
def inscribir(self, curso): self.cursos.append(curso)
def rendir(self, materia, nota, fecha): examen = Examen(materia, nota, fecha) # agrego el examen a la lista de los exámenes self.examenes.append(examen)
Ejemplo práctico: universidad.pyclass AlumnoRegular(Alumno):
def rendir(self, materia, nota, fecha): if self.buscar_cursadas(materia): Alumno.rendir(self, materia, nota, fecha) else: raise RuntimeError("El alumno no curso la materia %s" % materia)
class AlumnoLibre(Alumno): "Los alumnos libres no necesitan inscribirse previamente"
pass
Ejemplo práctico: universidad.pyclass Universidad:
def __init__(self): self.alumnos = [] self.profesores = [] self.plan_estudio = [] self.cursos = []
def guardar(self, nombre_archivo="universidad.dat"): "Grabo la instancia en un archivo" archivo = open(nombre_archivo, "wb") # serializo el objeto universidad (max.protocolo) pickle.dump(universidad, archivo, -1)
@staticmethod def cargar(nombre_archivo="universidad.dat"): "Cargg la instancia desde un archivo" archivo = open(nombre_archivo, "rb") return pickle.load(archivo)
Ejemplo práctico: universidad.py def analitico(self, alumno): "Muestro el estado de las materias del alumno" print "Notas para el alumno: %s" % alumno for materia in self.plan_estudio: cursada = alumno.buscar_cursadas(materia) and True or False # obtengo el último exámen rendido examenes = alumno.buscar_examenes(materia) if examenes: # extraigo la última nota y fecha de exámen nota = examenes[-1].nota fecha = examenes[-1].fecha else: nota = fecha = '' print "%3s: %-20s %5s %10s %s" % ( materia.codigo, materia.nombre, nota, fecha, cursada and 'Cursada' or '') print
Ejemplo práctico: universidad.py def recibido(self, alumno): "Reviso que todas las materias del plan de estudio esten aprobadas" for materia in self.plan_estudio: # busco los examenes aprobados de esta materia if not [examen for examen in alumno.buscar_examenes(materia) if examen.aprobado]: raise RuntimeError("%s no aprobo %s" % (alumno, materia)) #return False # no aprobó ningún/no tiene exámen return True # aprobó todas las meterias
Ejemplo práctico: universidad.pyif __name__ == "__main__": # si existen los datos, los deserealizo if os.path.exists("universidad.dat"): universidad = Universidad.cargar() print "Cursos:" pprint.pprint(universidad.cursos) print "Alumnos" pprint.pprint(universidad.alumnos) else: universidad = Universidad() alumno1 = AlumnoLibre(dni=1234, nombre="Juan", apellido="Perez", nro_matricula=1234) alumno2 = AlumnoRegular(dni=5678, nombre="Luis", apellido="Gonzales", nro_matricula=1234) universidad.alumnos.extend([alumno1, alumno2]) ... universidad.guardar()
Ejemplo práctico: universidad.py>>> ================================ RESTART >>> Notas para el alumno: Juan, Perez 1: Bases de datos 5 2009-01-03 Cursada 2: Programacion I 3 2009-02-03 Cursada 3: Programacion II 7 2009-02-03
Notas para el alumno: Luis, Gonzales 1: Bases de datos 7 2009-01-03 Cursada 2: Programacion I 3: Programacion II 10 2009-02-03 Cursada
Juan, Perez Juan, Perez no aprobo Programacion I (2)Luis, Gonzales Luis, Gonzales no aprobo Programacion I (2)>>> alumno1.rendir(materia2, nota=4, fecha=datetime.date.today())>>> universidad.guardar()
Ejemplo práctico: universidad.py>>> ================================ RESTART >>> alumno1
Traceback (most recent call last): File "<pyshell#9>", line 1, in <module> alumno1NameError: name 'alumno1' is not defined>>> alumno1=universidad.alumnos[0]>>> universidad.recibido(alumno1)True>>> universidad.analitico(alumno1)Notas para el alumno: Juan, Perez 1: Bases de datos 5 2009-01-03 Cursada 2: Programacion I 4 2009-08-20 Cursada 3: Programacion II 7 2009-02-03
>>>
Ejemplo práctico: universidad.py>>> ================================ RESTART >>> Notas para el alumno: Juan, Perez 1: Bases de datos 5 2009-01-03 Cursada 2: Programacion I 3 2009-02-03 Cursada 3: Programacion II 7 2009-02-03
Notas para el alumno: Luis, Gonzales 1: Bases de datos 7 2009-01-03 Cursada 2: Programacion I 3: Programacion II 10 2009-02-03 Cursada
Juan, Perez Juan, Perez no aprobo Programacion I (2)Luis, Gonzales Luis, Gonzales no aprobo Programacion I (2)>>> alumno1.rendir(materia2, nota=4, fecha=datetime.date.today())>>> universidad.guardar()
DB-API: Interfase Estándar de BB.DD.
�Funciones y atributos del Módulo: �connect(parametros...): crea una conexiónparamstyle: formato de los parámetros
'qmark': ej. '...WHERE name=?''numeric': ej. '...WHERE name=:1''named': ej. '...WHERE name=:name''format': ej. '...WHERE name=%s''pyformat': ej. '...WHERE name=%(name)s'
ExcepcionesTipos de datos avanzados
DB-API: Interfase Estándar de BB.DD.
�Funciones y atributos del Módulo: Excepciones�Warning: advertencia (ej. texto truncado)Error: clase base de erroresInterfaseError: error con la comunicaciónDatabaseError: error con la base en siOperationalError: error en "inesperado"IntegrityError: integridad (PK, FK, etc.)InternalError: error interno de la base de datosProgrammingError: error en el SQLNotSupportedError: funcionalidad no soportada
DB-API: Interfase Estándar de BB.DD.
�Funciones y atributos del Módulo: Tipos/constructores
�Date(año,mes,día): fechaTime(hora,minuto,segundo): horaTimestamp(año,mes,día,hora,minuto,segundo): fecha y horaBinarycasdena): objetos grandes (blobs)STRING: tipo para cadenasBINARY: tipo para bloblsNUMBER: numerosDATETIME: fechas y/o horasROWID: identificador de filas
DB-API: Interfase Estándar de BB.DD.
�Objetos Conexión:.close(): cierra la conexión (.rollback si hay datos pendientes).commit(): confirma transacción pendiente.rollback(): deshace una transacción pendiente.cursor(): crea un objeto Cursor
DB-API: Interfase Estándar de BB.DD.
�Objetos Cursor:.execute(sql, params): ejecuta una consulta sql aplicando los parámetros params.description: secuencia de datos de los campos: (name, type_code, display_size, internal_size, precision, scale, null_ok).rowcount: cantidad de filas devueltas o afectadas.fechone(): devuelve un registro.fechall(): devuelve todos los registros.close(): cierra la conexión
DB-API: Interfase Estándar de BB.DD.
�Pasos:1. Importar el conector2. Conectarse a la base de datos (función connect del
módulo conector)3. Abrir un Cursor (método cursor de la conexión)4. Ejecutar una consulta (método execute del cursor)5. Obtener los datos (método fetch o iterar sobre el
cursor)6. Cerrar el cursor (método close del cursor)
DB-API: Ejemplo SqLiteimport sqlite3
con = sqlite3.connect(":memory:")cur = con.cursor()cur.execute(""" CREATE TABLE persona (nombre, apellido, edad)""")con.commit()cur.execute(""" INSERT INTO persona VALUES ('Juan', 'Perez', 25);""")cur.execute(""" INSERT INTO persona VALUES ('Nilda', 'Rodriguez', 25);""")con.commit()cur.execute("SELECT nombre, apellido, edad FROM persona")for nombre, apellido, edad in cur: print nombre, apellido, edad
DB-API: Ejemplo SqLiteimport sqlite3
con = sqlite3.connect("mydb")# acceder por nombre a las filas del cursor:con.row_factory = sqlite3.Row
cur = con.cursor()cur.execute("select apellido, edad from persona")for row in cur: assert row[0] == row["apellido"] assert row["apellido"] == row["aPeLLido"] assert row[1] == row["edad"] assert row[1] == row["EdaD"]
DB-API: Ejemplo SqLiteimport sqlite3
con = sqlite3.connect(":memory:")con.execute("create table persona (id integer primary key, nombre varchar unique)")
# con.commit() se llama automáticamente si ok (con with)with con: con.execute("insert into persona(nombre) values (?)", ("Juan",))
# con.rollback() se llama si hay excepción:try: with con: con.execute("insert into persona(firstname) values (?)", ("Juan",))except sqlite3.IntegrityError: print "no se puede agregar Juan dos veces"
DB-API: Ejemplo MySql>>> import MySQLdb
>>> db = MySQLdb.connect(host="localhost", user="root",... passwd="mypassword", db="PythonU")>>> cursor = db.cursor()cursor.execute("SELECT * FROM Students")
>>> cursor.fetchone()(1L, 'Joe', 'Campbell', datetime.date(2006, 2, 10), 'N')
>>> cursor.fetchall()((1L, 'Joe', 'Campbell', datetime.date(2006, 2, 10), 'N'),(2L, 'Joe', 'Doe', datetime.date(2004, 2, 16), 'N'),(3L, 'Rick', 'Hunter', datetime.date(2005, 3, 20), 'N'),(4L, 'Laura', 'Ingalls', datetime.date(2001, 3, 15), 'Y'),(5L, 'Virginia', 'Gonzalez', datetime.date(2003, 4, 2), 'N'))
DB-API: Ejemplo PostgreSQL>>> import psycopg2
>>> conn = psycopg2.connect(database='test', user='postgres', password='pass', host='localhost')
>>> # Crear un cursor para obtener y ejecutar consulta:>>> cur = conn.cursor()>>> cur.execute("SELECT * FROM estudiante")>>> rows=cur.fetchall()>>> print rows
[['Joe', 'Capbell', datetime.date(2006, 2, 10), False, 1], ['Joe', 'Doe', datetime.date(2004, 2, 16), False, 2], ['Rick', 'Hunter', datetime.date(2005, 3, 20), False, 3], ['Laura', 'Ingalls', datetime.date(2001, 3, 15), True, 4], ['Virginia', 'Gonzalez', datetime.date(2003, 4, 2), False, 5]]
DB-API: Ejemplo PostgreSQL>>> import psycopg2.extras>>> cur = conn.cursor(cursor_factory = psycopg2.extras.DictCursor) >>> cur.execute("SELECT * FROM estudiante")>>> for row in cur: # itero sober cada fila>>> # row es un diccionario, con las claves = campos>>> print "Nombre y Apellido: %s, %s " % (row['nombre'],row['apellido']) Nombre y Apellido: Joe, Capbell Nombre y Apellido: Joe, Doe Nombre y Apellido: Rick, Hunter Nombre y Apellido: Laura, Ingalls Nombre y Apellido: Virginia, Gonzalez
>>> print cur.description(('nombre', 1043, 8, -1, None, None, None), ('apellido', 1043, 8, -1, None, None, None), ('fecha', 1082, 10, 4, None, None, None), ('booleano', 16, 1, 1, None, None, None), ('legajo', 23, 1, 4, None, None, None))
DB-API: Ejemplo PostgreSQL>>> #Parámetros:>>> cur = conn.cursor()>>> cur.execute("insert into personas (apellido) values (%s)" , ["D'agostino"])
>>> cur.execute("insert into personas (apellido) values (%(apellido)s)" , {"apellido":"D'agostino"})
PlPython: Procedimientos almacenadosEl lenguaje procedural plpython permite escribir funciones python para la base de datos relacional PostgreSQL.El cuerpo de una funcion plpythonu es simplemente un script de Python.Los argumentos son pasados como elementos de una lista args; los argumentos por nombre son pasados como variables ordinarias. El resultado es devuelto con un return o un yieldLos valores NULL equivalen a None.Diccionario SD y GD (global)
PlPython: Procedimientos almacenadosEjemplo Simple:>>> CREATE FUNCTION pymax (a integer, b integer) RETURNS integerAS $$ if (a is None) or (b is None): return None if a > b: return a return b$$ LANGUAGE plpythonu;
-- invoco la función:SELECT pymax(2, 3);-- devuelve 3
PlPython: Procedimientos almacenadosRecibir tipos compuestos:CREATE TABLE empleado ( nombre TEXT, salario INTEGER, edad INTEGER);
CREATE FUNCTION sueldo_alto (e empleado) RETURNS booleanAS $$ if e["salario"] > 200000: return True if (e["edad"] < 30) and (e["salario"] > 100000): return True return False$$ LANGUAGE plpythonu;
PlPython: Procedimientos almacenadosDevolver tipos compuestos:CREATE FUNCTION crear_persona (nombre TEXT, apellido TEXT) RETURNS personaAS $$ return [ nombre, apellido ] # o como tupla: return ( nombre, apellido ) # o como diccionario: return { "nombre": nombre, "apellido": apellido } # return Persona(nombre, apellido)$$ LANGUAGE plpythonu;
PlPython: Procedimientos almacenadosDevolver multiples tipos escalares o compuestos:CREATE TYPE saludo AS ( mensaje TEXT, -- hola a_quien TEXT -- mundo);CREATE FUNCTION saludar (mensaje TEXT) RETURNS SETOF saludoAS $$ # devolver una tupla conteniendo lista de tipos # todas las otras combinaciones son posibles return ( [ mensaje, "Mundo" ], [ mensaje, "PostgreSQL" ], [ mensaje, "PL/Python" ] )
##for a_quien in [ "Mundo","PostgreSQL","PL/Python"]: ## yield ( mensaje, a_quien )
$$ LANGUAGE plpythonu;
PlPython: Procedimientos almacenadosDisparadores (triggers), recibe diccionario TD:
TD["new"]: valores nuevos de la fila afectada TD["old"]: valores viejos de la fila afectada TD["event"]: tipo de evento "INSERT", "UPDATE", "DELETE", o "UNKNOWN"TD["when"]: momento en que se ejecutó: "BEFORE" (antes del commit), "AFTER" (despues del commit), o "UNKNOWN"TD["args"]: argumentos adicionalesSi TD["when"] es BEFORE: se puede devolver None or "OK", "SKIP o "MODIFY"
PlPython: Procedimientos almacenadosAcceso a la base de datos: módulo plpy
plpy.error y plpy.fatal disparan una excepción python, si no se controla, se propaga y causa que la transacción se aborte. Equivalente a llamar raise plpy.ERROR(msg) y raise plpy.FATAL(msg)plpy.debug(msg), plpy.log(msg), plpy.info(msg), plpy.notice(msg), plpy.warning(msg) informan mensajes de distinta prioridadplpy.execute(sql, limite): ejecutar consultasplpy.prepare(sql, [tipos]): preparar consultas
PlPython: Procedimientos almacenadosEjecutar consulta con execute:
se obtienen las filas recorriendo el resultado por numero de fila y nombre de columnanrows: devuelve cantidad de filasstatus: estado interno
rv = plpy.execute("SELECT * FROM mi_tabla", 5)for fila in rv: print fila['columna']
PlPython: Procedimientos almacenadosPreparar y ejecutar consulta con execute:# preparo el planplan = plpy.prepare("SELECT apellido FROM usuario WHERE nombre = $1 AND casado = $2 ", [ "text", "boolean" ])
# ejecuto el plan con los parámetrosrv = plpy.execute(plan, [ "Mariano", True ], 5)
CREATE FUNCTION usar_plan() RETURNS trigger AS $$ if SD.has_key("plan"): plan = SD["plan"] # está el plan, lo reutilizo else: # no esta el plan, lo creo y almaceno plan = plpy.prepare("SELECT 1") SD["plan"] = plan # continua la función...$$ LANGUAGE plpythonu;
PlPython: Procedimientos almacenadosPreparar y ejecutar consulta con execute:# preparo el planplan = plpy.prepare("SELECT apellido FROM usuario WHERE nombre = $1 AND casado = $2 ", [ "text", "boolean" ])
# ejecuto el plan con los parámetrosrv = plpy.execute(plan, [ "Mariano", True ], 5)
CREATE FUNCTION usar_plan() RETURNS trigger AS $$ if SD.has_key("plan"): plan = SD["plan"] # está el plan, lo reutilizo else: # no esta el plan, lo creo y almaceno plan = plpy.prepare("SELECT 1") SD["plan"] = plan # continua la función...$$ LANGUAGE plpythonu;
PlPython: Procedimientos almacenadosEjemplo: analizador de direccionesCREATE TYPE direccion AS ( calle1 TEXT, calle2 TEXT, altura INTEGER);CREATE OR REPLACE FUNCTION analizar_dir(dir text) RETURNS direccion AS$BODY$ def is_numeric(x): return not [y for y in x if not '0'<=x<='9'] ... return nombres[0], nombres[1], altura and int(altura[-6:]) or None$BODY$ LANGUAGE 'plpythonu' IMMUTABLE;
select calle1, calle2, altura from analizar_dir('balcarce 50 esquina rivadavia')
Documentación y Ayuda
Documentación Oficial: http://docs.python.org/Libro Python para todosPython Argentina: Aprendiendo Python