Vimos como utilizar tuplas, listas y diccionarios e incorporamos una nueva estructura de control mediante el bucle for en el proceso de creación y utilización de listas de precios para nuestro programa.

En el capítulo de hoy, veremos cómo logramos utilizar plantillas para generar nuestros presupuestos y el proceso para guardarlos.

Comenzaremos con algo sencillo y sumamente práctico: lectura y escritura de archivos.

Lectura y escritura de archivos

Al trabajar con archivos, existen cuatro acciones básicas que podemos hacer con un archivo:

  • Abrir un archivo
  • Leer un archivo
  • Escribir/sobrescribir un archivo
  • Cerrar un archivo abierto

 
Cuando abrimos un archivo podemos hacerlo con diversos fines:

  • Abrirlo para leerlo
  • Abrirlo para escribirlo
  • Abrirlo para leerlo y escribirlo, etc

 
Para abrir un archivo se realiza mediante la clase open() de Python, cuyo método constructor recibirá como primer parámetro, la ruta del archivo a ser abierto y como segundo parámetro, el modo de apertura (es decir, el objetivo para el cual lo abrimos: escritura, solo lectura, lectura y escritura, etc.).

En la línea 103 abrimos un archivo para leerlo:

filename = open(archivo, 'r')

Donde archivo es la ruta del archivo pasada como parámetro en el método leer_plantilla() y la cadena ‘r’ representa el modo de apertura “solo lectura”.

Mientras tanto, en la línea 74, abrimos un archivo no solo para leerlo, sino también para escribirlo:

contador = open('contador.txt', 'r+')

En este caso, ‘r+’ significa “lectura y escritura”.

Sin embargo, en la línea 92, estamos abriendo un archivo, pero para crearlo. Utilizando en este caso el modo ‘w’, que creará el archivo si no existe (o lo reemplazará si existe, creando uno nuevo con el mismo nombre), preparándolo para ser escrito:

presupuesto = open(filename, 'w')

Tanto con la variable filename como con la variable contador y presupuesto, lo que estamos haciendo es crear un objeto archivo, para luego utilizar los métodos necesarios:

objeto.read() para leer el contenido de un archivo;
objeto.write('nuevo contenido') para escribir en el archivo;
objeto.seek(numero_de_byte) para mover el cursor hacia el byte indicado en el archivo;
y objeto.close() para cerrar el archivo.

Cuando leemos el contenido de un archivo con el método read(), éste, retorna una “cadena de texto”. Si el contenido del archivo que estamos leyendo, es un número (como en nuestro contador.txt), obtendremos el literal de ese número y para poder utilizarlo como “número” (por ejemplo, para hacer operaciones matemáticas), será necesario convertirlo a entero o flotante, como en la línea 75, donde mediante int() convertimos el literal leído a número entero:

ultimo_num = int(contador.read())

Aprendiendo a utilizar templates en Python

Importación de módulos

  • presupuesto.py es nuestro módulo.
  • Cualquier otro archivo con extensión .py, también es un módulo.
  • Python, tiene sus propios módulos.
  • Cualquier módulo Python (propio o provisto por Python) puede ser importado.

Importar módulos

Importar un módulo significa incluir un archivo .py dentro de otro, para utilizar sus métodos.

Para importar todo un módulo, se escribe:
import modulo

Para importar solo una clase de un módulo, se escribe:
from modulo import Clase

Para importar varias clases de un módulo, se separan los nombres de las clases con comas:
form modulo import ClaseUno, ClaseDos, ClaseVeinte

En nuestro ejemplo:

form string import Template

estamos importando la clase Template del módulo string de Python.

Esta clase, es la que nos va a permitir utilizar una plantilla para nuestro presupuesto y luego hacer un render (reemplazo dinámico) de datos.

© XKCD This work is licensed under a Creative Commons Attribution-NonCommercial 2.5 license.

© XKCD This work is licensed under a Creative Commons Attribution-NonCommercial 2.5 license.

La clase Template del módulo string de Python

Mira la líneaa 131 y 132 del módulo presupuesto.py:

txt = Template(txt).safe_substitute(diccionario)
html = Template(html).safe_substitute(diccionario)
  • Template() es una clase del módulo string de Python.

  • El constructor de esta clase (es decir, el método __init__) recibe como parámetro una plantilla: Template(txt) y Template(html)

  • Esta plantilla, debe tener ciertos “comodines” que indicarán los datos que deben ser reemplazados.

  • Estos “comodines” se denominan identificadores.

  • Los identificadores, se escriben con un signo $ delante: $nombre_del_identificador. Estos identificadores los hemos colocado en nuestro archivo template.txt y también en template.html

  • Para escribir un signo $ sin hacer referencia a un identificador, debe escaparse con otro signo $. Esto: $$ imprime $, mientras que $nombre_identificador será reemplazado por un identificador cuyo nombre sea nombre_identificador

 
Los métodos substitute() y safe_substitute() de la clase Template

  • Como nuestra clase Presupuesto, Template, tiene sus propios métodos: substitute() y safe_substitute()

  • Ambos métodos, reciben como parámetros, un diccionario de datos.

  • Este diccionario se compone de clave=valor donde clave, será el nombre de un identificador y valor, el dato por el cuál será reemplazada la clave.

Un ejemplo simple
from string import Template
print Template('Reemplazar $identificador').substitute(identificador='valor de reemplazo')

La salida de lo anterior, será:
Reemplazar valor de reemplazo

Métodos de la clase template

La diferencia entre el método substitute() y safe_substitute(), es que si nos olvidamos de colocar el valor para un identificador, el primer método generará un error, mientras que el segundo, simplemente lo dejará sin reemplazar.

Es decir que si escribimos

Template('Este es el $id1 y este $id2).safe_substitute(id1='Identificador Uno')

La salida será:
Este es el Identificador Uno y este $id2
Mientras que substitute provocaría un error.

dict(): creación de diccionario de datos

Ve a al línea 116 del módulo presupuesto:

        diccionario = dict(nombre=self.encabezado_nombre,
                           web=self.encabezado_web,
                           email=self.encabezado_email,
                           titulo=self.titulo,
                           numero=self.numero_presupuesto,
                           fecha=self.fecha,
                           empresa=self.empresa,
                           cliente=self.cliente,
                           plan=self.plan,
                           servicio=self.servicio,
                           precio=self.importe,
                           iva=self.monto_iva,
                           total=self.neto,
                           limite=self.vencimiento)

Aquí lo que hicimos, fue armar un diccionario de datos con la clase dict() de Python.
Podríamos haber colocado estos pares de clave=valor dentro del método safe_substitute() directamente. Pero para hacerlo más legible y prolijo, creamos este diccionario de datos, que luego lo pasamos como parámetro a safe_substitute():

txt = Template(txt).safe_substitute(diccionario)
html = Template(html).safe_substitute(diccionario)

Chuleta para utilizar Templates

Para utilizar una plantilla, debemos:

  1. Crear la plantilla en un archivo, colocando identificadores antecedidos por el signo $ en los lugares donde necesitemos reemplazar los datos dinámicamente.
  2. Importar la clase Template del módulo string de Python:
    from string import Template
  3. Guardar el contenido de la plantilla en una variable:
    # Crear un objeto archivo: Abrir el archivo en modo de solo lectura
    archivo = open('archivo', 'r')
    # Leer el archivo
    contenido = archivo.read()
    # No olvidarse de cerrar el archivo
    archivo.close()
    
  4. Crear un diccionario de datos con dict():
    diccionario = dict(id='un valor', otro_id='Otro valor')
  5. Hacer un render de datos con la clase Template y el método substitute() o safe_substitute() (este último, es mejor para prevenir errores):
    Template(plantilla).safe_substitute(diccionario)
    Dónde plantilla debe ser el contenido del archivo leído previamente y diccionario el creado mediante la clase dict()

Chuleta de operaciones básicas con archivos

Chuleta de operaciones básicas con archivos

Estructuras de control en Pytho: if… elif… y else

¿Guardamos el presupuesto, lo imprimimos en pantalla, o…? En el capítulo anterior estuvimos hablando sobre estructuras de control con el bucle for. Hoy, incorporamos una nueva estructura de control: el condicional if:

        if respuesta.lower() == 'n':
            print txt
        # si en cambio el usuario indica "n"
        elif respuesta.lower() == 's':
            filename = 'presupuestos/'+self.numero_presupuesto+'.html'
            presupuesto = open(filename, 'w')   # Creo el archivo
            presupuesto.write(html)             # escribo el contenido
            presupuesto.close()                 # cierro el archivo
            print '\n\tEl archivo se ha guardado en '+filename+'\n\n'
        # sino
        else:
            print '\n\tOpción incorrecta. No se guardó el presupuesto.\n\n'
            self.guardar_presupuesto(txt, html)

(ver líneas 87 a 99)

El condicional if, elif y else, es uno de los que más utilizarás en tu vida como programador. Básicamente, al igual que otras estructuras de control, permite tomar decisiones si una determinada condición se cumple. El razonamiento de condicionales puede representarse como sigue:

si condicion X se cumple:
    hacer esto
sino, si condicion Y se cumple:
    hacer esto otro
sino (si no se cumple ni X ni Y):
    hacer tal otra cosa

No necesariamente el condicional debe cumplir esa estructura. A veces solo es necesario evaluar una única condición y tomar una decisión SOLO sobre la base de si esta condición se cumple:

si condición X se cumple:
    hacer esto
# fin 

Ejemplo:

a = 10
if a == 10:
    print 'a es igual a 10'

O también, tomar una decisión si la condición se cumple y otra si no se cumple:

a = 10
if a == 10:
    print 'a es igual a 10'
else:
    print 'a no es igual a 10'

Con elif se pueden tomar tantas decisiones como condiciones quieran evaluarse::

a = 10
if a == 10:
    print 'a es igual a 10'
elif a == 9:
    print 'a es igual a 9'
elif a == 75:
    print 'a no es ni 9 ni 10, es 75'

Y si a lo anterior le agregamos else estaríamos cubriendo todas las posibilidades:

a = 10
if a == 10:
    print 'a es igual a 10'
elif a == 9:
    print 'a es igual a 9'
elif a == 75:
    print 'a no es ni 9 ni 10, es 75'
else:
    print 'a no es ni 9, ni 10 ni 75. Es un valor que no evalué'

Retomemos nuestro código. En el método guardar_presupuesto() (línea 84), lo primero que hago es preguntar si se desea guardar o no el presupuesto, siendo las respuestas esperadas “s” (sí, lo deseo guardar) o “n” (no, no deseo guardarlo):

respuesta = raw_input('\n\t¿Desea guardar el presupuesto? (s/n): ')

Solo espero una de esas dos respuestas. Es necesario evaluar la respuesta ingresada.

if respuesta.lower() == 'n':

El método lower() me convierte el valor ingresado por el usuario a minúsculas, para que me sea más sencillo evaluar la condición. Básicamente la condición que estoy evaluando es: “si la respuesta fue no”.

Al evaluar una condición, la estructura de control responderá con True (verdadero) o False (falso). Si la respuesta ingresada fue “n”, entonces la condición cumple.

Sino, si la respuesta fue “s” elif respuesta.lower() == 's': se está cumpliendo esta condición (se eligió si).

Pero también puede suceder, que ni “s” ni “n” sean ingresados. Ésto, lo evalúo genericamente con else:

Razonando la estructura completa:

Si la respuesta es “n”, imprimo el presupuesto en pantalla

        if respuesta.lower() == 'n':
            print txt

En cambio, si la respuesta es “s”, creo un nuevo archivo y lo guardo.

        elif respuesta.lower() == 's':
            filename = 'presupuestos/'+self.numero_presupuesto+'.html'
            presupuesto = open(filename, 'w')
            presupuesto.write(html)
            presupuesto.close()
            print '\n\tEl archivo se ha guardado en '+filename+'\n\n'

Pero si la respuesta no es ni “n” ni “s”, vuelvo a ejecutar este mismo método desde el comienzo, es decir que el método guardar_presupuesto() se llama a sí mismo:

            print '\n\tOpción incorrecta. No se guardó el presupuesto.\n\n'
            self.guardar_presupuesto(txt, html)

Recursividad

Cuando un método se llama a sí mismo, se denomina recursividad. Ésto, genera una iteración (un bucle) del método en sí mismo. Por lo tanto, se debe ser muy cuidadoso al emplear una llamada recursiva y hacerlo solo cuando sea estrictamente necesario y no se corra el riesgo de caer en un bucle infinito.

Operadores lógicos y relacionales

Para evaluar condiciones no solo podemos recurrir a si “X es igual a Y”. Existen otros operadores que nos permitirán evaluar diferentes condiciones. Estos operadores se denominan operadores lógicos que nos permitirán evaluar múltiples condiciones en un mismo proceso y operadores relacionales, que nos permitirán evaluar la relación existente en una condición dada.

Operadores Relacionales

	==   Igual que
	!=   Distinto que
	<    Menor que
	>    Mayor que
	>=   Mayor o igual que
        <=   Menor o igual que

Usos:
if a == b # si a es igual que b
if a != b # si a es distinto que b
if a > b # si a es mayor que b
if a >= b # si a es mayor o igual que b
if a < b # si a es menor que b
if a <= b # si a es menor o igual que b

Operadores Lógicos

	and   (y)
	or    (o)
	not   (no)

Los operadores lógicos permitirán evaluar múltiples condiciones en una misma instancia:
si condicion_1 and condicion_2 or condicion_3

and evalúa si todas las condiciones se cumplen, es decir, que todas las condiciones deben retornar True (verdadero)

a = 10
b = 5
if (a == 10) and (b != 10):
    print 'Hola Mundo'

Se lee: Si (a es igual que 10) y (b es distinto que 10)
Como ambas condiciones se cumplen se imprimirá Hola Mundo.

Pero en el siguiente código:

a = 10
b = 5
if (a != 10) and (b == 5):
    print 'Hola Mundo'

La primera condición NO se cumple, entonces no se imprimirá nada.

or evalúa si alguna de las condiciones se cumple. Con que una sola condición se cumpla, se ejecutará la acción.

a = 10
b = 5
if (a != 10) or (b == 5):
    print 'Hola Mundo'

Se lee: si (a es distinto que 10) o (b es igual a 5).
La primera condición, no se cumple. Pero la segunda sí se cumple. Entonces, imprimirá Hola Mundo.

not evalúa si la condición NO se cumple:

a = 10
b = 5
if not a == 10:
    print 'Hola Mundo'

Se lee: si NO ES a igual que 10 (similar a escribir if a != 10)
Como la condición NO se cumple, no se imprimirá nada.

Métodos del objeto String

Vimos anteriormente que para evaluar la respuesta dada al preguntar si se desea guardar o no el presupuesto en un archivo, utilizamos el método lower(). Este método pertenece a la clase string de Python, y su función es la de convertir una cadena de texto en minúsculas.
La clase string de Python, además del método lower() tiene muchos otros métodos que frecuentemente podrás utilizar. Para hacerlo, simplemente debes utilizar la sintaxis: tu_cadena.metodo()

Otros métodos de uso frecuente del objeto string son:
capitalize()
Convierte el primer carácter de una cadena en mayúscula
upper()
Convierte toda la cadena a mayúsculas
lower()
Convierte toda la cadena a minúsculas
swapcase()
Convierte minúsculas a mayúsculas y mayúsculas a minúsculas

Más métodos del objeto string, puedes verlos en este enlace.

Nuevo reto: condicionales hasta en la sopa!

Dado el siguiente código:

# edades
Ana = 43
Juan = 43
Pedro = 8
Roberto = 12
Melisa = 15
Helena = 3

if (Ana == Juan) and ((Helena < Melisa) or (Melisa < Roberto)) and not (Pedro > Roberto):
    print 'Veo condicionales hasta en la sopa!'
else:
    print 'Soñaré con condicionales'

El resultado, imprimirá la frase “Veo condicionales hasta en la sopa!”.

El desafío

Modificando únicamente un operador lógico, debes lograr que se imprima la frase “Soñaré con condicionales”. No se puede quitar ningún operador. Solo puede cambiarse un operador lógico por otro.