Python 3 al descubierto

    Python 3 al descubierto

    P4 months ago 251

    AIAI Summary

    toggle
    Bulleted
    toggle
    Text

    Key Insights

    • Bullet points not available for this slide.
    Python 3 al descubierto - Page 1
    1/381
    Python 3 al descubierto - Page 2
    2/381
    Python 3 al descubierto - Page 3
    3/381
    Python 3 al descubierto - Page 4
    4/381
    Python 3 al descubierto Arturo Fernández MontoroISBN: 978-84-939450-
4-6 edición original publicada por RC Libros, Madrid, EspañaDerechos
reservados © 2012 RC Libros
Segunda edición: Alfaomega Grupo Editor, México, septiembre 2013
© 2013 Alfaomega Grupo Editor, S.A. de C.V. Pitágoras 1139, Col. Del
Valle, 03100, México D.F.
Miembro de la Cámara Nacional de la Industria Editorial MexicanaRegistro
No. 2317
Pág. Web: http://www.alfaomega.com.mx E-mail:
atencionalcliente@alfaomega.com.mx
ISBN: 978-607-707-718-3eISBN: 978-607-62200-8-5
Datos CatalográficosFernández, ArturoPython 3 al descubiertoAlfaomega
Grupo Editor, S.A. de C.V.,México
ISBN: 978-607-707-718-3eISBN: 978-607-62200-8-5
Formato: 17 x 23 cmPáginas: 276
La transformación a libro electrónico del presente título fue realizada
porSextil Online, S.A. de C.V./ Editorial Ink ® 2016.+52 (55) 52 54 38 52
contacto@editorial-ink.com www.editorial-ink.com
Derechos reservados: Esta obra es propiedad intelectual de su autor y los
derechos de publicación en lenguaespañola han sido legalmente transferidos
al editor. Prohibida su reproducción parcial o totalpor cualquier medio sin
permiso por escrito del propietario de los derechos del copyright
Nota importante: La información contenida en esta obra tiene un fin
exclusivamente didáctico y, por lo tanto, noestá previsto su
aprovechamiento a nivel profesional o industrial. Las indicaciones técnicas
yprogramas incluidos, han sido elaborados con gran cuidado por el autor y
reproducidos bajoestrictas normas de control. ALFAOMEGA GRUPO
    5/381
    EDITOR, S.A. de C.V. no será jurídicamenteresponsable por: errores u
omisiones; daños y perjuicios que se pudieran atribuir al uso de
lainformación comprendida en este libro, ni por la utilización indebida que
pudiera dársele.
Edición autorizada para venta en México y todo el continente americano.
    6/381
    Impreso en México. Printed in México.
Empresas del grupo:México: Alfaomega Grupo Editor, S.A. de C.V. -
Pitágoras 1139, Col. Del Valle, México, D.F. -C.R 03100. Tel.: (52-55)
5575-5022 - Fax: (52-55) 5575-2420 / 2490. Sin costo: 01-800-020-4396Email: atencionalcliente@alfaomega.com.mx Colombia: Alfaomega
Colombiana S.A. - Calle 62 No. 20-46, Barrio San Luis, Bogotá,Colombia,
Tels.: (57-1) 746 0102 / 210 0415 - E-mail: cliente@alfaomega.com.co
Chile: Alfaomega Grupo Editor, S.A. -Av. Providencia 1443. Oficina 24,
Santiago, Chile Tel.: (56-2) 2235-4248 - Fax: (56-2) 2235-5786 - E-mail:
agechile@alfaomega.cl Argentina: Alfaomega Grupo Editor Argentino,
S.A. - Paraguay 1307 PB. Of. 11, C.R 1057,Buenos Aires, Argentina, -
Tel./Fax: (54-11) 4811-0887 y 4811 7183 - E-mail:
ventas@alfaomegaeditor.com.ar
    7/381
    PRÓLOGO
En la actualidad Python es uno de los lenguajes de programacióncon mayor
proyección. Su facilidad de uso, su librería estándar y lacantidad de librerías
adicionales que existen contribuyen a que seanmuchos los desarrolladores
de software que optan por su utilizaciónpara llevar a cabo sus
proyectos.Python es un lenguaje de propósito general, de alto
nivel,interpretado y que admite la aplicación de diferentes paradigmas
deprogramación, como son, por ejemplo, la programación
procedural,imperativa y la orientación a objetos.La programación científica,
la programación de sistemas o lasaplicaciones web son ámbitos en los que
habitualmente se empleaPython como lenguaje de programación principal.
También puede serempleado para desarrollar aplicaciones de escritorio con
interfazgráfica de usuario, integrar componentes escritos en
diferenteslenguajes de programación o incluso desarrollar juegos.Dadas sus
principales características, Python es un lenguaje idealpara el prototipado.
Para diversos tipos de aplicaciones puedenconstruirse rápidamente
prototipos, facilitando el desarrollo delmodelo final en otros lenguajes que
ofrezcan mayor rendimiento comoes el caso de C y C++. Todo ello sin
perder de vista el hecho de quePython puede utilizarse como un lenguaje
más de alto nivel.El presente libro no pretende ser un manual de referencia
al uso,sino ofrecer una completa visión del lenguaje desde un punto de
vistapráctico. Con ella se pretende que el lector consiga
familiarizarserápidamente con el lenguaje, aprendiendo sus fundamentos
ydescubriendo cómo utilizarlo para desarrollar diferentes tipos
deaplicaciones.Los primeros cinco capítulos están dedicados a los aspectos
másimportantes del lenguaje. En ellos aprenderemos sobre las estructurasy
tipos de datos básicos, sentencias de control, cómo aplicar la
    8/381
    programación orientada a objetos y detalles más avanzados sobre
ellenguaje. Los siguientes capítulos, que pueden ser considerados comouna
segunda parte, están orientados a utilizar Python para
desarrollaraplicaciones que interactúen con bases de datos, manejen
ficheros yutilicen diversos servicios de Internet. Seguidamente nos
centraremosen la instalación y distribución de programas desarrollados en
ellenguaje de programación que nos ocupa. Por último, descubriremoscómo
diseñar y ejecutar pruebas unitarias, formando parte estas deuna de las fases
más importantes en el desarrollo de software.Esperamos que el lector
disfrute aprendiendo los fundamentos deeste lenguaje de programación y
pueda rápidamente aplicar losconceptos aprendidos a sus propios proyectos.
    9/381
    ÍNDICE
PRÓLOGO
CAPÍTULO 1. PRIMEROS PASOS Introducción ¿Qué es Python? Un
poco de historia Principales características InstalaciónWindows Mac OS X
Linux Hola Mundo Código fuente y bytecode Herramientas de
desarrolloEditores Entornos integrados de desarrollo (IDE) Intérprete
interactivo mejorado Depuradores Profiling Novedades en Python 3
CAPÍTULO 2. ESTRUCTURAS Y TIPOS DE DATOS BÁSICOS
Introducción Conceptos básicos Tipado dinámico NúmerosEnteros, reales y
complejos Sistemas de representación
    10/381
    Operadores Funciones matemáticas Conjuntos Cadenas de textoTipos
Principales funciones y métodos Operaciones Tuplas Listas Inserciones y
borrados Ordenación Comprensión Matrices DiccionariosAcceso,
inserciones y borrados ComprensiónOrdenación
CAPÍTULO 3. SENTENCIAS DE CONTROL, MÓDULOS
YFUNCIONES Introducción Principales sentencias de controlif, else y elif
for y while pass y with Funciones Paso de parámetros Valores por defecto y
nombres de parámetros Número indefinido de argumentos Desempaquetado
de argumentos Funciones con el mismo nombre Funciones lambda Tipos
mutables como argumentos por defecto Módulos y paquetesMódulos
    11/381
    Funcionamiento de la importación Path de búsqueda Librería
estándarPaquetes Comentarios ExcepcionesCapturando excepciones
Lanzando excepcionesExcepciones definidas por el usuario Información
sobre la excepción
CAPÍTULO 4. ORIENTACIÓN A OBJETOS Introducción Clases y
objetos Variables de instancia Métodos de instanciaVariables de clase
Propiedades Visibilidad Métodos de clase Métodos estáticos Métodos
especiales Creación e inicialización Destructor Representación y formatos
Comparaciones Hash y bool Herencia Simple Múltiple Polimorfismo
Introspección
CAPÍTULO 5. PROGRAMACIÓN AVANZADA Introducción Iterators
y generators3
    12/381
    Iterators Funciones integradas Generators Closures DecoratorsPatrón
decorator, macros y Python decorators Declaración y funcionamiento
Decorators en clases Funciones como decorators Utilizando parámetros
Decorador sin parámetros Decorador con parámetros Programación
funcional Expresiones regularesPatrones y metacaracteres Búsquedas
Sustituciones SeparacionesModificadores Patrones para comprobaciones
cotidianas Ordenación de datos Método itemgetter() Funciones lambda
CAPÍTULO 6. FICHEROS Introducción Operaciones básicasApertura y
creación Lectura y escritura Serialización Ejemplo práctico Ficheros xml,
json y yamlXML JSON YAML
    13/381
    Ficheros CSV Analizador de ficheros de configuración Compresión y
descompresión de ficheros Formato ZIP Formato gzip Formato bz2
Formato tarball
CAPÍTULO 7. BASES DE DATOS Introducción Relacionales MySQL
PostgreSQL Oracle SQLite3 ORM Sqlalchemy Sqlobject Nosql Redis
MongoDB Cassandra
CAPÍTULO 8. INTERNET Introducción TELNET y FTPtelnetlib ftplib
XML-RPCxmlrpc.server xmlrpc.client Correo electrónicopop3 smtp imap4
Web
    14/381
    CGI WSGI Web scraping urllib.request lxml Frameworks pyramid pylatte
CAPÍTULO 9. INSTALACIÓN Y DISTRIBUCIÓN DE PAQUETES
Introducción Instalación de paquetesInstalación desde la fuente Gestores de
paquetes easy_install pip Distribución Entornos virtuales virtualenv
virtualenvwrapper pip y los entornos virtuales
CAPÍTULO 10. PRUEBAS UNITARIAS Introducción Conceptos básicos
UNITTEST DOCTEST Otros frameworks
APÉNDICE A. EL ZED DE PYTHON Traducción de “El zen de Python”
APÉNDICE B. CÓDIGO DE BUENAS PRÁCTICAS REGLAS
REFERENCIAS
    15/381
    ÍNDICE ALFABÉTICO
    16/381
    PRIMEROS PASOS
INTRODUCCIÓN
Este primer capítulo será nuestra primera toma de contacto
conPython.Comenzaremos con una sencilla descripción del lenguaje y
unaserie de datos que nos ayuden a tener una visión general del
mismo.Posteriormente, haremos un breve recorrido a su historia, para
pasardespués a examinar sus principales características.
Después,realizaremos la primera incursión práctica escribiendo nuestro
primercódigo en este lenguaje. Los dos últimos apartados los dedicaremos
aver con qué herramientas de desarrollo contamos y cuáles son
lasprincipales novedades de Python 3.
    17/381
    ¿QUÉ ES PYTHON?
Básicamente, Python es un lenguaje de programación de alto nivel,
interpretado y multipropósito. En los últimos años su utilización ha
idoconstantemente creciendo y en la actualidad es uno de los lenguajesde
programación más empleados para el desarrollo de software.Python puede
ser utilizado en diversas plataformas y sistemasoperativos, entre los que
podemos destacar lo más populares, comoWindows, Mac OS X y Linux.
Pero, además, Python también puedefuncionar en smartphones, Nokia
desarrolló un intérprete de estelenguaje para su sistema operativo Symbian.
¿Tiene Python un ámbito específico? Algunos lenguajes deprogramación sí
que lo tienen. Por ejemplo, PHP fue ideado paradesarrollar aplicaciones
web. Sin embargo, este no es el caso dePython. Con este lenguaje podemos
desarrollar software paraaplicaciones científicas, para comunicaciones de
red, para aplicacionesde escritorio con Interfaz gráfica de usuario (GUI),
para crear juegos,para smartphones y por supuesto, para aplicaciones web.
Fig. 1-1 Logo de Python
Empresas y organizaciones del calibre de Industrial Light & Magic,Walt
Disney, la NASA, Google, Yahoo!, Red Hat y Nokia hacen usointensivo de
este lenguaje para desarrollar sus productos y servicios.Esto demuestra que
Python puede ser utilizado en diversos tipos desectores, con independencia
de su actividad empresarial.Entre las principales razones para elegir Python,
son muchos losque argumentan que sus principales características lo
convierten en unlenguaje muy productivo. Se trata de un lenguaje potente,
flexible y
    18/381
    con una sintaxis clara y concisa. Además, no requiere dedicar tiempo asu
compilación debido a que es interpretado.Python es open source, cualquiera
puede contribuir a su desarrollo ydivulgación. Además, no es necesario
pagar ninguna licencia paradistribuir software desarrollado con este
lenguaje. Hasta su intérpretese distribuye de forma gratuita para diferentes
plataformas.La última versión de Python recibe varios nombres, entre ellos,
Python 3000 y Py3K, aunque, habitualmente, se le denominasimplemente
Python 3.
Un poco de historia
El origen del lenguaje Python se remonta a principios de losnoventa. Por
este tiempo, un investigador holandés llamado Guido vanRossum, que
trabajaba en el centro de investigación CWI (CentrumWiskunde &
Informática) de Ámsterdam, es asignado a un proyectoque consistía en el
desarrollo de un sistema operativo distribuidollamado Amoeba. Por aquel
tiempo, el CWI utilizaba un lenguaje deprogramación llamado ABC. En
lugar de emplear este lenguaje para elproyecto Amoeba, Guido decide crear
uno nuevo que pueda superarlas limitaciones y problemas con los que se
había encontrado altrabajar en otros proyectos con ABC. Así pues, es esta
la principalmotivación que dio lugar al nacimiento de Python.La primera
versión del lenguaje ve la luz en 1991, pero no es hastatres años después
cuando decide publicarse la versión 1.0. Inicialmenteel CWI decidió liberar
el intérprete del lenguaje bajo una licencia opensource propia, pero en
septiembre de 2000 y coincidiendo con lapublicación de la versión 1.6, se
toma la decisión de cambiar la licenciapor una que sea compatible con la
licencia GPL ( GNU General PublicLicense). Esta nueva licencia se
denominará Python Software FoundationLicense y se diferencia de la GPL
al ser una licencia no copyleft. Estehecho implica que es posible modificar
el código fuente y desarrollarcódigo derivado sin la necesidad de hacerlo
open source. Hasta el momento solo se ha liberado tres versiones
principales,teniendo cada una de ellas diversas actualizaciones. En lo que
respectaa la versión 2, la última en ser liberada fue la 2.7, en julio de 2010.
En el
    19/381
    momento de escribir estas líneas, la versión 3 cuenta con laactualización
3.2, liberada en febrero de 2011. Ambas versiones, la de 2y 3, son
mantenidas por separado. Esto implica que, tanto la 2.7 comola 3.2 se
consideran estables pero, lógicamente, correspondientes adiferentes
versiones. ¿Por qué mantener ambas versiones y no seguiruna evolución
lógica? La respuesta a esta pregunta es fácil deresponder: Entre ambas
versiones existen diferencias que las hacenincompatibles. Posteriormente,
nos centraremos en este aspecto,comentando las principales diferencias
entre ambas y viendo lasnovedades que supone la versión 3 con respecto a
su predecesora.Entre las características de las primeras versiones de Python
cabedestacar el soporte de la orientación a objetos, el manejo deexcepciones
y el soporte de estructuras de datos de alto nivel, como,por ejemplo, las
listas y los diccionarios. Además, desde su desarrolloinicial, se tuvo en
cuenta que el código escrito en este lenguaje fuerafácil de leer y de
aprender, sin que esto suponga renunciar acaracterísticas y funcionalidades
avanzadas.Muchos se preguntan el origen del nombre de este lenguaje
deprogramación. Guido van Rossum decidió darle este nombre en honora la
serie de televisión Monty Python's Flying Circus, de la cual era fan.Esta es
una serie cómica protagonizada por el grupo de humoristas Monty Python,
famoso por películas como La vida de Brian o El sentidode la vida. Desde
el principio de su diseño, se pretendía que Pythonfuera un lenguaje que
resultara divertido de utilizar, de ahí que en elnombre influyera la
mencionada serie cómica. También resulta curiosoque, tanto en tutoriales,
como en ejemplos de código, se suelan utilizarreferencias a los Monty
Python. Por ejemplo, en lugar de emplear lostradicionales nombres de
variables foo y bar, se suele utilizar spam y egss, en referencia a sketchs de
este grupo de cómicos.El desarrollo y promoción de Python se lleva a cabo
a través de unaorganización, sin ánimo de lucro, llamada Python Software
Foundation, que fue creada en marzo de 2001. Entre las actividades que
realiza estaorganización destacan el desarrollo y distribución oficial de
Python, lagestión de la propiedad intelectual del código y
documentosrealizados, así como la organización de conferencias y
eventosdedicados a poner en contacto a todas aquellas personas
interesadasen este lenguaje de programación.Python tiene un claro carácter
open source y la Python Software
    20/381
    Foundation invita, a cualquiera que quiera hacerlo, a contribuir aldesarrollo
y promoción de este lenguaje de programación. Aquelloslectores
interesados en contribuir pueden echar un vistazo a la páginaoficial
dedicada a la comunidad de Python (ver referencias).
Principales características
No hay duda de que a la hora de elegir un lenguaje es muyimportante
conocer sus características. Ver qué nos puede ofrecerresulta determinante
para tomar la decisión adecuada. Son muchas lasempresas que se plantean
esta cuestión a la hora de elegir un lenguajede programación para un
determinado proyecto. Esto también esextrapolable a proyectos open source
o aquellos proyectos personalesque requieren del uso de un lenguaje de
programación. Ya sabemosque Python es un lenguaje de propósito general,
dinámico einterpretado. Sin embargo, Python puede ofrecernos mucho más,
tal ycomo descubriremos a continuación.Dos de las principales
características del lenguaje Python son, porun lado que es interpretado y,
por otro lado, que es multiplataforma. Lo primero significa que no es
necesario compilar el código para suejecución, ya que existe un intérprete
que se encarga de leer el ficherofuente y ejecutarlo. Gracias a este
funcionamiento es posible ejecutarel mismo código en distintas plataformas
y sistemas operativos sinnecesidad de cambiar el código fuente, bastará con
tener instalado elintérprete. Eso sí, la versión de este intérprete es nativa
para cadaplataforma. En este sentido, Python es similar a Perl o a Ruby y
difierede otros lenguajes como C++ y Objective-C.Habitualmente, a los
programas en Python se les denomina scripts. En realidad, script es el
término que se suele emplear para los ficherosde código fuente escritos en
Python, pudiendo un programa contarcon uno o más de estos scripts.Los
programadores de Python suelen llamar indistintamente coneste nombre
tanto al lenguaje como al intérprete del mismo.Deberemos tener esto en
cuenta, debido a que es habitual escuchar"voy a instalar Python" o "la
versión que tengo instalada de Python esla 3.2". En estos casos se hace
referencia directa al intérprete y no al
    21/381
    lenguaje.La interacción con el intérprete del lenguaje se puede
hacerdirectamente a través de la consola. Tal y como
veremosposteriormente, durante la instalación de Python, se instala
uncomponente llamado shell o consola que permite ejecutardirectamente
código Python a través de una terminal o interfaz decomandos. En lo que
respecta a la sintaxis del lenguaje cabe destacar susimplicidad; es decir,
gracias a la misma, es sencillo escribir código quesea fácil de leer. Este
factor es muy importante, ya que, además defacilitar el aprendizaje del
lenguaje, también nos ayuda a que nuestrocódigo sea más fácil de
mantener.Python carece de tipos propiamente dichos, es decir, es un
lenguajecon tipado dinámico. Los programadores de C++ y Java
estánacostumbrados a declarar cada variable de un tipo específico.
Esteproceso no es necesario en Python, ya que el tipo de cada variable
sefija en el momento de su asignación. Como consecuencia de estehecho,
una variable puede cambiar su tipo durante su ciclo de vida sinnecesidad
explícita de ser declarado. Dado que puede ser interesanteconsultar el tipo
de una variable en un momento dado, Python nosofrece una serie de
funciones que nos dan este tipo de información.Además de soportar la
orientación a objetos, Python también nospermite utilizar otros paradigmas
de programación, como, porejemplo, la programación funcional y la
imperativa. En la actualidad,Python es considerado uno de los lenguajes
que más facilidadesofrecen para enseñar programación orientada a objetos.
A estocontribuyen su sintaxis, los mecanismos de introspección
queincorpora y el soporte para la implementación de herencia sencilla
ymúltiple.Con respecto a su sintaxis, una de las diferencias más
destacableses el uso de la indentación. Diferentes niveles de indentación
sonutilizados para marcar las sentencias que corresponden al mismobloque.
Por ejemplo, todas las sentencias que deban ser ejecutadasdentro de un
bloque if llevarán el mismo nivel de indentación, mientrasque el resto
utilizarán un nivel diferente, incluida la sentencia quecontiene la condición
o condiciones del mencionado if. Además, cadasentencia no necesita un
punto y coma (;), como sí ocurre en lenguajescomo C/C++, PHP y Java. En
Python basta con que cada sentencia vaya
    22/381
    en una línea diferente. Por otro lado, tampoco se hace uso de las llaves ({})
para indicar el principio y fin de bloque. Tampoco se empleanpalabras clave
como begin y end. Simplemente se utilizan los dospuntos (:) para marcar el
comienzo de bloque y el cambio deindentación se encarga de indicar el
final.Para facilitar la programación, Python incluye una serie deestructuras
de datos de alto nivel, como son, por ejemplo, las listas, losdiccionarios,
cadenas de texto (strings), tuplas y conjuntos. Por otrolado, su librería
estándar incorpora multitud de funciones que puedenser utilizadas en
diversos ámbitos, entre ellas podemos mencionar,desde aquellas básicas
para manejar strings, hasta las que pueden serusadas en programación
criptográfica, pasando por otros de nivelintermedio, como son las que
permiten manejar ficheros ZIP, trabajarcon ficheros CSV o realizar
comunicaciones de red a través de distintosprotocolos estándar. Todo ello,
sin necesidad de instalar libreríasadicionales. Comúnmente, se emplea la
frase batteries included pararesaltar este hecho.A diferencia de lenguajes
compilados, como C++, en Python existeun recolector de basura (garbage
collector). Esto significa que no esnecesario pedir y liberar memoria, de
forma explícita, para crear ydestruir objetos. El intérprete lo hará
automáticamente cuando seanecesario y el recolector se encargará de
gestionar la memoria paraevitar los temidos memory leaks. Otro de los
aspectos interesantes del lenguaje es su facilidad parainteractuar con otros
lenguajes de programación. Esto es posiblegracias a los módulos y
extensiones. ¿Cuándo puede ser útil esto?Supongamos que ya contamos con
un programa en C++ que seencarga de realizar, por ejemplo, una serie de
complejas operacionesmatemáticas. Por otro lado, estamos realizando un
desarrollo enPython y nos damos cuenta que sería interesante contar con
lafuncionalidad que nos ofrece el mencionado programa en C++. Enlugar
de reescribir este programa en Python, podemos comunicarambos a través
de la interfaz que Python incorpora para ello.Existen diversas
implementaciones del intérprete de Python, esdecir, el código escrito en
Python puede ejecutarse desde diferentessistemas preparados para ello. La
implementación más popular es lallamada CPython, escrita en el lenguaje
de programación C, aunqueexisten otras como Jython, la cual está
desarrollada en el lenguaje Java,
    23/381
    e IronPython, que permite la ejecución en la plataforma .NET deMicrosoft.
El siguiente apartado lo dedicaremos a la instalación delintérprete de
Python implementado en CPython y para la cual existenversiones para
diferentes sistemas operativos.
    24/381
    INSTALACIÓN
A continuación, nos centraremos en la instalación del intérprete dePython y
sus herramientas asociadas en las tres familias más popularesde sistemas
operativos. Dentro de las mismas y en concreto,explicaremos el proceso de
instalación en Windows, Mac OS X y lasprincipales distribuciones de
GNU/Linux.
Windows
Para la instalación de Python en Windows recurriremos al programade
instalación ofrecido desde el sitio web oficial (ver referencias) deeste
lenguaje de programación. En concreto, accederemos a la páginaprincipal
de descargas (ver referencias) y haremos clic sobre el enlaceque referencia
a la última versión liberada de Python 3. Dicho enlacenos llevará a una
nueva página web donde se nos ofrecen una serie deficheros, tanto binarios,
como fuentes, para diferentes sistemasoperativos y arquitecturas de
procesador. Antes de continuar esconveniente averiguar si nuestro Windows
7 es de 32 o de 64 bits. Lamayoría de fabricantes de PC instalan la versión
de este sistemaoperativo en función del tipo de arquitectura que incorpora
elprocesador de la máquina en cuestión. Los actuales PC suelen contarcon
procesadores de 64b. Podemos comprobar qué tipo de sistemaoperativo
tiene instalado nuestro PC accediendo a la opción de menú Panel de
Control > Sistema, apartado Tipo de sistema. Una vez queconocemos este
dato, podemos volver a la página web de descargas ybuscar el enlace para el
fichero de instalación de Python quecorresponde a Windows y al tipo de
arquitectura de nuestro PC. Porejemplo, si contamos con un sistema de 64b,
haremos clic sobre Windows x86-64 MSI Installer (3.2.2). Automáticamente
comenzará ladescarga del fichero binario apuntado por el enlace, que no es
otroque un programa de instalación guiado a través de un asistente o
wizard.
    25/381
    Figura 1-2. Selección de la instalación de Python para todos losusuarios o
solo para el actual
Al finalizar la descarga del programa de instalación, haremos dobleclic
sobre el mismo para comenzar el proceso de instalaciónpropiamente dicho.
El primer cuadro de diálogo (figura 1-2) nospregunta si deseamos realizar la
instalación para todos los usuarios delsistema o solamente para el usuario
que está ejecutando el asistente.Por defecto aparece seleccionada la primera
opción.Pulsando sobre el botón Next accederemos al siguiente paso, elcual
nos pide seleccionar el directorio donde serán instalados losficheros (figura
1-3).
    26/381
    Figura 1-3. Selección del directorio base para de la instalaciónde Python
Avanzamos un paso más y se nos ofrece la personalización de lainstalación,
siendo posible elegir qué componentes deseamos instalar(figura 1-4). Salvo
que tengamos muy claro cómo hacer esta selección,es recomendable utilizar
las opciones marcadas por defecto. Al pulsarsobre el botón Next se
procederá a la copia de ficheros al disco duro yal finalizar este proceso
veremos un mensaje informándonos de ello.Por último, el asistente nos pide
reiniciar el PC para completar lainstalación.Comprobar si la instalación de
Python 3 se ha realizadocorrectamente en nuestro Windows es sencillo,
basta con acceder almenú de inicio y teclear python en el cuadro de diálogo
para buscarprogramas. Como resultado de la búsqueda nos deben aparecer
variosprogramas, entre ellos, IDLE (Python GUI) y Python (command line).
Elprimero nos da acceso a una interfaz de comandos, en modo
gráfico,donde podemos interactuar con el intérprete del lenguaje. El
segundonos permite abrir la misma interfaz pero en modo consola, como
silanzáramos un comando a través de la interfaz de comandos deWindows
invocada a través del comando cmd.
    27/381
    Figura 1-4. Personalización de la instalación de Python
La interfaz gráfica presenta algunas ventajas funcionales conrespecto a la
textual, por ejemplo, el resaltado de sintaxis del código, elautocompletado
de palabras clave o la opción de utilizar undepurador. En ambas interfaces
de comandos, observaremos cómo enla primera línea aparece el número de
versión del intérprete de Pythonque tenemos instalado y que estamos
usando.En realidad, IDLE es algo más que una interfaz gráfica
parainteractuar con el intérprete de Python, ya que es un sencillo,
perofuncional entorno integrado de desarrollo. De ahí, que cuenta
concaracterísticas ya comentadas, como la posibilidad de depurar
código.También es posible editar ficheros y ejecutarlos directamente.
Paramás información sobre las características de este entorno dedesarrollo,
recomendamos echar un vistazo a la documentación oficialsobre el mismo
(ver referencias).Esta interfaz de comandos del intérprete de Python nos
será muyútil para llevar a cabo nuestra primera práctica toma de contacto
con ellenguaje. Además, podemos recurrir a ella siempre que lo
    28/381
    necesitemos,para, por ejemplo, probar ciertas líneas de código o sentencias
de
    29/381
    control.Obviamente, además de la mencionada interfaz de comandos,
elintérprete de Python ha sido instalado. Esto significa que podemoscrear
un fichero de texto con código Python, salvarlo con la extensión .py y
ejecutarlo haciendo clic sobre el mismo.
Mac OS X
El sistema operativo de Apple incluye Python preinstalado de serie.En
concreto, la versión Lion (10.7) incorpora la versión 2.7 de Python,mientras
que su predecesora, llamada Snow Leopard, cuenta, pordefecto, con la
versión 2.6. Sin embargo, para utilizar Python 3 ennuestro Mac deberemos
instalarlo. Para ello, basta con recurrir albinario de instalación ofrecido
desde la página web de descargas delsitio oficial de Python. Desde esta
página se ofrecen dos binariosdiferentes: uno para Mac OS X 10.6 y 10.7,
para ordenadores conprocesador Intel, y otro específico para la arquitectura
de procesador PPC. Haciendo clic sobre el correspondiente enlace,
deberemos elegiren función del sistema que tenga instalado nuestro Mac, se
procederáa la descarga de un fichero DMG, el cual podemos ejecutar una
vezdescargado. Para ello, bastará con hacer clic sobre el mismo.
Seráentonces cuando se abrirá una nueva ventana en Finder que
nosmostrará una serie de archivos (figura 1-5).
Figura 1-5. Ficheros contenidos en la imagen DMG delinstalador de
Python
Haciendo doble clic sobre el fichero Python.mpkg se lanzará elasistente que
nos guiará en el proceso de instalación. La primeraventana que aparece nos
describe los programas que van a serinstalados, nos invita a leer el fichero
ReadMe.text y nos propone
    30/381
    continuar a través del botón Continue. En el siguiente paso del asistente se
nos solicita que indiquemos launidad de disco donde se va a realizar la
instalación. Después depulsar el botón para continuar el proceso, el
software será instalado enla ubicación seleccionada. Finalmente, aparecerá
un mensajeindicándonos que la instalación se ha realizado correctamente.Al
abrir una ventana del Finder y acceder a Aplicaciones, observaremos que
tenemos una nueva carpeta llamada Python 3.2. Dentro de la misma
aparecen varios archivos. Entre ellos IDLE, unfichero HTML de
documentación y un script que nos permitirá fijar laversión 3.2 de Python
como el intérprete por defecto, sustituyendo asía la versión 2 que Apple
incluye por defecto en su sistema operativo.
Figura 1-6. Pantalla inicial del asistente para la instalación dePython
Aquellos programadores de Mac, acostumbrados a utilizar lainterfaz de
comandos, pueden lanzar Terminal y ejecutar el comando python3.2. Este
comando invocará al intérprete del lenguaje y nospermitirá utilizar la
terminal para interactuar con él.
    31/381
    Figura 1-7. Selección del disco para la instalación de Python
Linux
La mayoría de las distribuciones de GNU/Linux, como, por
ejemplo,Ubuntu, Fedora y Debian, incluyen e instalan Python por
defecto.Algunas de ellas utilizan la versión 2.6, mientras que otras se
decantanpor la 2.7. La instalación de Python 3 en Linux es sencilla, ya que
lasmencionadas distribuciones incluyen paquetes binarlos listos para
suinstalación. En función de la distribución que estemos utilizando,
bastacon emplear una de las herramientas de instalación de software con
lasque cuenta específicamente cada una de ellas. Por ejemplo, en
Ubuntu11.10 basta con acceder al Centro de Software y realizar una
búsquedapor python3. Entre los resultados de la búsqueda, veremos
queaparecerá un paquete llamado python3, haciendo doble clic sobre
elmismo se procederá a la instalación. Si preferimos utilizar la interfaz
decomandos, bastará con lanzar una consola y ejecutar el
siguientecomando:
$ sudo apt-get install python3
En distribuciones de GNU/Linux basadas en paquetes con formato RPM,
como, por ejemplo, Fedora, lanzaremos el siguiente comando,como usuario
root, desde una terminal:
# yum install python3
Una vez que finalice la instalación, con independencia de ladistribución que
estemos utilizando, bastará con acceder a la línea decomandos y lanzar el
    32/381
    siguiente comando para comenzar a utilizar la
    33/381
    consola interactiva del intérprete de Python:
$ python3
Al contrario que en Mac y en Windows, para utilizar IDLE en
Linuxdeberemos instalar el correspondiente binario ofrecido por
nuestradistribución. El nombre del paquete binario en cuestión se llama
idle3en Ubuntu. En el caso de Fedora, será necesario instalar un
paquetellamado python3-tools. Sin embargo, el nombre del ejecutable
paraambas distribuciones es idle3, lo que significa que,
lanzandodirectamente este comando desde la consola podemos disfrutar
deeste entorno integrado de desarrollo.Debemos tener en cuenta que la
invocación al comando python seguirá lanzando la versión 2 del intérprete.
Si deseamos cambiar estecomportamiento, podemos crear un enlace
simbólico para que elcomando python apunte directamente a la versión 3.
Para ello bastaejecutar, como usuario root, los siguientes comandos:
# mv /usr/bin/python /usr/bin/python2# ln -s /usr/bin/python3
/usr/bin/python
De esta forma, con el comando python2 estaremos invocando a laversión 2
del intérprete, y python será el encargado de lanzar la versión3. Como el
lector habrá podido averiguar, es posible disponer de dosversiones
diferentes del intérprete en la misma máquina.
    34/381
    HOLA MUNDO
La primera toma de contacto práctica con el lenguaje larealizaremos a
través del famoso Hola Mundo. Comenzaremoslanzando la interfaz de
comandos del intérprete de Python.Dependiendo del sistema operativo que
estemos utilizando,accederemos a la mencionada interfaz de forma
diferente. Porejemplo, en Linux comenzaremos abriendo una shell y
lanzando elcomando python. En Mac OS X procederemos de la misma
forma através del programa Terminal. Los usuarios de Windows
puedenacceder al menú Inicio y buscar el programa IDLE. Nada más lanzar
la interfaz de comandos del intérprete, tambiénllamado intérprete
interactivo, comprobaremos que aparece unmensaje inicial con el número
de versión del intérprete y una serie deinformación adicional que hace
referencia a la plataforma donde estásiendo ejecutado. Justo en la línea
siguiente aparece otro mensaje quenos indica de qué forma podemos
acceder a la información sobre lalicencia del intérprete. La última línea
comienza por los caracteres >>> y nos muestra un cursor parpadeando.
Este es el prompt del intérpreteque nos permite interactuar directamente con
él. Por ejemplo, sitecleamos copyright y pulsamos enter, veremos cómo se
lanzainformación sobre el copyright de Python y después, vuelve a
aparecerel prompt, invitándonos a lanzar otro comando o sentencia.A lo
largo de este libro, los ejemplos de código que comiencen porlos
mencionados caracteres >>> representarán sentencias que puedenser
lanzadas directamente en el intérprete. Si debajo de la mismaapareciera otra
más, sin los caracteres >>>, esta hará referencia alresultado obtenido como
consecuencia de la ejecución en el intérpretede la línea de código
correspondiente.Como es tradicional, cuando se está aprendiendo un
lenguaje deprogramación, nuestras primeras líneas de código imprimirán
enpantalla el mensaje Hola Mundo. Para ello, desde el prompt delintérprete
escribiremos el siguiente comando y pulsaremos enter:
>>> print ("Hola Mundo")
    35/381
    Veremos, entonces, cómo aparece el mencionado mensaje en lasiguiente
línea y después volverá a aparecer el prompt del intérprete.Obviamente, no
hace falta teclear los caracteres >>>, ya que estosaparecen por defecto y
nos indican que el prompt se encuentra enespera y listo para que tecleemos
y ejecutemos nuestro código.A pesar de que la interfaz del intérprete es
muy práctica y nospuede servir para realizar pruebas, habitualmente,
nuestro código seráejecutado desde un fichero de texto. Siguiendo con
nuestro ejemplo,crearemos un nuevo fichero con nuestro editor de textos
favorito alque añadiremos la misma línea de código que hemos ejecutado
desdeel intérprete (sin añadir los caracteres >>>). Lo salvaremos con
elnombre hola.py. Efectivamente, la extensión .py es la que se utiliza
paralos ficheros de código Python. Seguidamente, los usuarios de Mac OSX
y Linux pueden invocar directamente al intérprete desde la shell odesde
Terminal:
$ python hola.py
El resultado aparecerá directamente en la siguiente línea, cuando
elcomando finalice su ejecución. Los usuarios de Windows tendrán
quehacer un poco de trabajo extra para ejecutar el mismo comando. Estose
debe a que, por defecto, el ejecutable del intérprete de Python nose añade a
la variable de entorno PATH, como sí ocurre en los sistemasoperativos
basados en UNIX. Así pues, para modificar el valor de estavariable, en
Windows, accederemos a Panel de control > Sistema >Configuración
avanzada del sistema y pulsaremos el botón Variables deentorno... de la
pestaña Opciones Avanzadas. Dentro de Variables delsistema,
localizaremos la variable Path y haremos clic sobre el botón Editar....
Aparecerá una nueva ventana que nos permite modificar elvalor de la
variable, al final de la línea añadiremos el directorio dondese encuentra el
ejecutable del intérprete de Python. Por defecto, estedirectorio es
C:/Python32. Una vez realizada esta configuración, bastarácon lanzar el
comando cmd para poder acceder a la shell del sistema einvocar
directamente al comando, igual que en Mac OS X y en Linux.Asimismo, si
deseamos utilizar directamente la interfaz de comandosen Windows, sin
invocar a IDLE, podemos hacerlo desde la misma cmd, tecleando python.
    36/381
    Código fuente y bytecode
Hasta ahora solo hemos hablado de los ficheros de código Python,que
utilizan la extensión .py. También sabemos que este lenguaje esinterpretado
y no compilado. Sin embargo, en realidad, internamenteel intérprete Python
se encarga de generar unos ficheros binarios queson los que serán
ejecutados. Este proceso se realiza de formatransparente, a partir de los
ficheros fuente. Al código generadoautomáticamente se le llama bytecode y
utiliza la extensión .pyc. Asípues, al invocar al intérprete de Python, este se
encarga de leer elfichero fuente, generar el bytecode correspondiente y
ejecutarlo. ¿Porqué se realiza este proceso? Básicamente, por cuestiones de
eficiencia.Una vez que el fichero .pyc esté generado, Python no vuelve a
leer elfichero fuente, sino que lo ejecuta directamente, con el ahorro
detiempo que esto supone. La generación del bytecode es automática yel
programador no debe preocuparse por este proceso. El intérprete eslo
suficientemente inteligente para volver a generar el bytecodecuando es
necesario, habitualmente, cuando el fichero de códigocorrespondiente
cambia.Por otro lado, también es posible generar ficheros binarios
listospara su ejecución, sin necesidad de contar con el
intérprete.Recordemos que los ficheros Python requieren del intérprete para
serejecutados. Sin embargo, en ocasiones necesitamos ejecutar
nuestrocódigo en máquinas que no disponen de este intérprete. Este
casosuele darse en sistemas Windows, ya que, por defecto, tanto Mac OS
X,como la mayoría de las distribuciones de GNU/Linux, incorporan
dichointérprete. Para salvar este obstáculo contamos con programas como
py2exe (ver referencias), que se encarga de ejecutar un binario
paraWindows (.exe) a partir de un fichero fuente escrito en Python.
    37/381
    HERRAMIENTAS DE DESARROLLO
Uno de los factores importantes a tener en cuenta, a la hora deabordar el
desarrollo de software, es el conjunto de herramientas conel que podemos
contar para realizar el trabajo. Con independencia dela tecnología y el
lenguaje, existen diferentes tipos de herramientas dedesarrollo de software,
desde un sencillo editor de texto, hastacomplejos depuradores, pasando por
entornos integrados dedesarrollo que ofrecen bastantes funcionalidades en
un solo programa.Python no es una excepción y cuenta con diferentes
herramientas dedesarrollo que nos ayudarán a ser más productivos.Dado
que entrar en profundidad, en cada una de las herramientasde desarrollo que
podemos utilizar para trabajar con Python, escapa alámbito de este libro,
nos centraremos en mencionar y describir las máspopulares. El objetivo es
que el lector tenga un punto de referenciasobre las mismas y no se
encuentre perdido a la hora de elegir.Por funcionalidad hemos realizado una
agrupación en categorías.En concreto, se trata de editores, entornos
integrados de desarrollo,depuradores, herramientas de profiling y entornos
virtuales.
Editores
Podemos considerar a los editores de texto como las herramientasbásicas
para desarrollar software, ya que nos permiten escribir elcódigo fuente y
crear un fichero a partir del mismo.Dentro de este grupo, existen multitud
de programas, desde losbásicos como Bloc de Notas, hasta aquellos más
complejos como Vimo TextMate. Aunque cualquier editor de texto es válido
para escribircódigo, es interesante que este cuente con ciertas
funcionalidades quenos hagan el trabajo más fácil. Por ejemplo, el resaltado
de sintaxis (syntax highlighting), la búsqueda utilizando expresiones
regulares, laautoindentación, la personalización de atajos de teclado o
lanavegación de código, resultan muy prácticas a la vez que nos ayudana
mejorar la productividad.
    38/381
    En la actualidad existen multitud de editores de texto queincorporan otras
muchas funcionalidades, además de las mencionadasanteriormente, que nos
serán muy válidos para escribir código Python.Algunos son
multiplataforma, mientras que otros solo existen para unsistema operativo
concreto. Vim y Emacs son los editores más populares en el mundo UNIX y
delos cuales podemos encontrar versiones para Mac OS X, Linux
yWindows. En realidad, muchos consideran a ambos mucho más que
uneditor de texto, ya que ambos se pueden personalizar ampliando
susfuncionalidades hasta convertirlos en un moderno entorno integradode
desarrollo. En la red existen multitud de recursos (ver referencias)que
podemos añadir a ambos editores para convertirlos enherramientas
imprescindibles para desarrollar aplicaciones en Python.Aunque Vim y
Emacs son muy potentes, deberemos tener en cuentaque ambos tienen una
curva de aprendizaje elevada.Muchos desarrolladores que trabajan en Mac
OS X estánhabituados a TextMate (ver referencias). Se trata de un potente
editorque también cuenta con útiles herramientas para Python. Este editorno
es open source y deberemos adquirir una licencia para su
uso.Distribuciones de Linux, como Ubuntu y Fedora, instalan pordefecto un
sencillo y práctico editor que también podemos utilizarpara Python. Su
nombre es gedit y su funcionalidad puede serampliada a través de plugins.
Otro editor de código digno de mención es Notepad++. Sedistribuye bajo la
licencia GPL, aunque solo existe una versión parasistemas Windows.
Entornos integrados de desarrollo (IDE)
La evolución natural de los editores de código son los entornosintegrados
de desarrollo. Estos amplían la funcionalidad de los editoresañadiendo
facilidades para la depuración de código, la creación deproyectos, el auto
completado, la búsqueda de referencias en ladocumentación o el marcado
de sintaxis errónea. Dos de los máspopulares son Eclipse y NetBeans.
Aunque se hicieron populares para eldesarrollo Java, actualmente, ambos
soportan Python como lenguaje y
    39/381
    ofrecen funcionalidades específicas para él mismo. Entre las ventajasde
estos dos IDE caben destacar su carácter open source, la grancomunidad de
usuarios con la que cuentan y que existen versionespara distintas
plataformas. Por otro lado, la dependencia del runtime de Java y el
consumo de recursos hardware son algunas de susdesventajas.Aunque
menos conocido, Komodo es otra de las opciones.Desarrollado por la
empresa ActiveState, es multiplataforma, noconsume demasiados recursos y
ofrece bastantes prácticasfuncionalidades. A diferencia de Eclipse y
NetBeans, no es open source y requiere del pago de una licencia para su
uso. No obstante, existeuna versión más limitada en funcionalidades,
llamada Komodo Edit yque sí es gratuita y open source. En lo que respecta
a algunos IDE específicos para Python, son treslos más populares. El
primero de ellos es fríe, que está escrito enPython utilizando el toolkit
gráfico Qt. La última versión de este IDE esla 5 y requiere de Python 3 para
su ejecución. Por otro lado tenemos a PyCharm, desarrollado por la
empresa JetBrains y caracterizado portener un amplio soporte para el
desarrollo para Django, el popularframework web de Python. Wingware es
el tercero de este grupo yentre sus características cabe destacar el soporte
para populares toolkits y frameworks para Python, como son, Zope, PyQt,
PyGTK, Django y wxPython.
Intérprete interactivo mejorado
A pesar de que el intérprete interactivo estándar de Python es muypráctico
para ejecutar código escrito en este lenguaje sin necesidad decrear fichero,
tiene algunas carencias. Por ejemplo, no es posible usarel tabulador para
autocompletar código, no numera las líneas decódigo que se van
escribiendo, no contiene una ayuda interactiva y nopermite la introspección
dinámica de objetos. Con el objetivo dedisponer de una herramienta, similar
al intérprete interactivo estándar,pero que pudiera suplir las carencias de
este, se desarrolló IPython. Esta herramienta puede ser utilizada como
sustituta del mencionadointérprete, el cual está incluido en la instalación
estándar de Python.
    40/381
    La instalación de IPython puede realizarse como si de un módulo dePython
más se tratara, siendo, pues, posible su utilización en diferentessistemas
operativos. Recomendamos leer el capítulo 9 (Instalación ydistribución de
módulos) para realizar la instalación a través del gestorde paquetes
pip.IPython puede facilitarnos en gran medida el trabajo de desarrollo yes
recomendable su utilización como intérprete interactivo, sobre todopara
aquellos programadores avanzados de Python.Para más información sobre
las características, método deinstalación y documentación en general sobre
IPython, podemos visitarla página web (ver referencias) que existe a tal
efecto.
Depuradores
La acción de depurar código es de gran ayuda a la hora de resolver bugs.
Dentro del proceso de desarrollo de software es una de las tareasmás
habituales llevadas a cabo por los programadores.¿En qué consiste la
depuración? Básicamente se trata de seguirpaso a paso la ejecución de un
programa o una parte del mismo.Contar con una herramienta automática
que nos ayude a ello, resultaimprescindible. Al igual que para otros
lenguajes, para Pythoncontamos con la herramienta llamada pdb que
soporta la fijación de breakpoints, el avance paso a paso, la evaluación de
expresiones yvariables y el listado del código actual en ejecución. Esta
utilidadpuede ser invocada directamente desde la interfaz del intérprete
dePython o a través del ejecutable python. El funcionamiento básico de pdb
es sencillo. Podemos comenzarpor fijar un breakpoint en un punto
determinado de nuestro códigofuente. Esto se realiza a través de dos
sencillas líneas de código:
import pdbpdb.set_trace()
Al lanzar pdb y llegar al punto donde hemos puesto el breakpoint,entrará en
marcha el depurador, parando la ejecución del programa yesperando, a
través del prompt, para que introduzcamos un comandoque nos permita, por
ejemplo, evaluar una variable o continuar la
    41/381
    ejecución del programa paso a paso. El lanzamiento de pdb paranuestro
script de ejemplo se haría de la siguiente forma:
$ python -m pdb hola.py
Para una referencia completa sobre los comandos que puedenlanzarse desde
el prompt ofrecido por pdb, recomendamos visitar lapágina web oficial (ver
referencias) de este depurador.
Profiling
En ingeniería de software, un profiler es un programa que mide
elrendimiento de la ejecución de otro programa, ofreciendo una serie
deestadísticas sobre dicho rendimiento. Este tipo de herramientas es
muyútil para mejorar un determinado programa, debido a que lainformación
que nos proporciona es difícil obtenerla de otra manera.Además, en
ocasiones se da la circunstancia de que durante eldesarrollo es muy
complicado predecir que partes de una aplicacióncontribuirán a bajar su
rendimiento. Para averiguar cuáles son lassecciones o componentes de
código, tendremos que esperar al tiempode ejecución y es aquí donde los
profilers realizan su trabajo.Dentro de la librería estándar de Python
contamos con tres profilers diferentes: cProfile, profile y hotshot. El
primero de ellos fue introducidoen la versión 2.5 y es el más recomendado,
tanto por su facilidad deuso, como por la información que nos ofrece. Por
otro lado, profile estáescrito en Python, es más lento que cProfile y además
su funcionalidadestá limitada a este. El uso de hotshot no es aconsejable
paraprincipiantes, dado que es experimental, además hemos de tener
encuenta que será eliminado en futuras versiones del intérprete.El uso
básico de cProfile es bastante sencillo, bastará con invocar alintérprete de
Python pasando un parámetro específico, seguido delprograma que
deseamos comprobar. Por ejemplo, hagámoslo connuestro primer programa:
$ python -m cProfile hola.py
Como salida de la ejecución del comando anterior, obtendremos
losiguiente:
    42/381
    Hola Mundo8 function calls in 0.000 secondsOrdered by: standard
namencalls tottime percall cumtime percall filename:lineno(function)2
0.000 0.000 0.000 0.000 cp850.py:18(encode)1 0.000 0.000 0.000 0.000
hola.py:1(<module>)2 0.000 0.000 0.000 0.000 {built-inmethod
charmap_encode}1 0.000 0.000 0.000 0.000 {built-inmethod exec}1 0.000
0.000 0.000 0.000 {built-inmethod print}1 0.000 0.000 0.000 0.000
{method 'disable' of'_lsprof.Profiler' objects}
Dado que nuestro programa ejemplo es muy sencillo, noobtendremos
valiosa información, pero sí que nos servirá paradescubrir cómo funcionan
este tipo de herramientas.Otra herramienta que podemos utilizar para hacer
profiling es elmódulo timeit, el cual nos permite medir el tiempo que tarde
enejecutarse una serie de líneas de código. Esta herramienta forma partede
la librería estándar de Python, lo que significa que no tenemos querealizar
ninguna instalación adicional.
    43/381
    NOVEDADES EN PYTHON 3
La última versión de Python trae consigo una serie de clarasnovedades y
diferencias con respecto a la serie 2.x. Aquellosprogramadores de Python 2,
que deseen migrar sus aplicaciones paraque funcionen en la versión 3,
deberán tener en cuenta estasdiferencias. A continuación, resumiremos las
más significativas, loslectores no familiarizados con Python pueden pasar
por alto esteapartado y saltar hacia el siguiente capítulo.En lo que respecta a
los strings, el cambio más significativo es queen la versión 3 todos son
Unicode. Como consecuencia de ello, se hasuprimido la función unicode().
Además, el operador %, utilizado parala concatenación de strings, ha sido
reemplazado por la nueva función format(). Así pues, por ejemplo, la
siguiente sentencia en Python 2:
>>> cad = "%s %s" % (cadl, cad2)
Pasa a ser de esta forma en Python 3:
>>> cad = "{o} {1}".format(cadl, cad2)
Otra nueva función introducida en Python 3 es print(), siendo
ahoranecesario utilizar paréntesis cuando la invocamos. Igualmente
ocurrecon la función exec(), utilizada para ejecutar código a través de
unobjeto. Relacionada con esta funcionalidad, en Python 3 ha sidoeliminada
execfile(). Para simular su funcionamiento, deberemos leerun fichero línea a
línea y ejecutar exec() para cada una de ellas.En Python 2.x la operación
aritmética para realizar la división exactadebe hacerse entre dos números
reales, utilizando para ello eloperador /. Sin embargo, en la nueva versión
de Python esta operaciónpuede hacerse directamente con números enteros.
Para la divisiónentera utilizaremos el operador //. Veamos unos ejemplos al
respecto.La siguiente sentencia nos devolverá el número real 3.5 en Python
2.x:
>>> 7.0 / 2.0
La misma operación puede realizarse en Python 3:
    44/381
    >>> 7 / 2
    45/381
    Por otro lado, para la división entera, en Python 3, ejecutaríamos elsiguiente
comando, siendo el resultado 3:
>>> 7 // 2
La representación de números en octal (base 8) ha sido tambiéncambiada en
la nueva versión de Python. Ahora se debe poner la letrao justo detrás del 0
y antes del número que va a ser representado. Esdecir, la siguiente
expresión ha dejado de ser válida en Python 3:
>>> x = 077
En su lugar debe emplear esta otra:
>>> x = 0o77
Si implementamos clases iterator, deberemos escribir un método __next __
(), esto implica que no podremos utilizar el método next() denuestra clase.
Así pues, en Python 3, invocaremos directamente almencionado método
pasando como argumento la clase iterator. Con respecto a los diccionarios,
la forma de iterar entre sus claves yvalores ha cambiado. Ahora las
funciones iterkeys(), iteritems() y itervalues() no son necesarias, en su lugar
emplearemos las funciones keys(), ítems() y values(), respectivamente. Para
comprobar si una clavese encuentra en un diccionario, en lugar de invocar a
la función has_key(), bastará con preguntar directamente a través del
operador if:
>>> if mykey in mydict: print("Clave en diccionario")
Si trabajamos con comprensión de listas y dentro de ellas usamostuplas,
estas deberán, en Python 3, ir entre paréntesis. Además, lafunción sorted()
devuelve directamente una lista, sin necesidad deconvertir su argumento a
este tipo de dato. Para emplear esta funciónde ordenación deberemos tener
en cuenta que la lista o tupla debecontener elementos del mismo tipo. En
Python 3 la función sorted() y elmétodo sort() devolverán una excepción si
los elementos que van a serordenados son de diferentes tipos.La librería
estándar ha reemplazado los nombres de algunosmódulos, lo que significa
    46/381
    que debemos tenerlo en cuenta a la hora deutilizar la sentencia import. Por
ejemplo, el módulo Cookie ha sidorenombrado a http.Cookies . Otro
ejemplo es httplib que ahora se
    47/381
    encuentra dentro de http y se llama client (import http.Client) .Las
excepciones que capturan un objeto, en Python 3, requieren dela palabra
clave as. De esta forma, escribiremos:
try:
myfun()except ValueError as myerror:print(err)
En relación también con las excepciones, para invocar a raise
conargumentos necesitaremos paréntesis en la llamada. Además, los strings
no pueden ser usados como excepciones. Si necesitamos deesta
funcionalidad, podemos escribir:
raise Exception("Ha ocurrido un error")
La nueva versión del lenguaje no solo nos permite desempaquetar
diccionarios, también podemos hacerlo con conjuntos. Por ejemplo,
lasiguiente sentencia nos devuelve el valor 1 para la variable a y una
listacon los valores 2 y 3 para la variable b:
a, *b = (1, 2, 3)
Para migrar nuestro código de una versión a otra, existe unaherramienta
llamada 2to3 (ver referencias). Gracias a ella,automáticamente podemos
obtener una versión de nuestro códigocompatible con Python 3. Si bien es
cierto, que esta herramienta no esperfecta, es recomendable repasar el
código generadoautomáticamente para asegurarnos que el proceso se ha
realizadocorrectamente. 2to3.py es un script escrito en Python y que
sedistribuye junto al intérprete del lenguaje. Por ejemplo, en
Windowspodemos localizarlo en el subdirectorio Tools\Scripts, que se
encuentradentro del directorio donde, por defecto, fue instalado el
intérprete.Hasta aquí las novedades y diferencias más interesantes
entreversiones de este lenguaje. Si estamos interesados en obtener
unacompleta referencia de todas las novedades de Python 3, podemosechar
un vistazo a la página oficial dedicada a este efecto (verreferencias).
    48/381
    ESTRUCTURAS Y TIPOS DE DATOSBÁSICOS
INTRODUCCIÓN
Realizada una primera toma de contacto con Python en el capítuloanterior,
dedicaremos el presente a descubrir cuáles son las estructurasde datos y
tipos básicos con los que cuenta este lenguaje.Comenzaremos describiendo
y explicando una serie de conceptosbásicos propios de este lenguaje y que
serán empleados a lo largo dellibro. Posteriormente, pasaremos a centrarnos
en los tipos básicos,como son, los números y las cadenas de texto. Después,
llegará elturno de las estructuras de datos como las tuplas, las listas,
losconjuntos y los diccionarios.
    49/381
    CONCEPTOS BÁSICOS
Uno de los conceptos básicos y principales en Python es el objeto.
Básicamente, podemos definirlo como un componente que se aloja
enmemoria y que tiene asociados una serie de valores y operaciones
quepueden ser realizadas con él. En realidad, los datos que manejamos enel
lenguaje cobran vida gracias a estos objetos. De momento,
estamoshablando desde un punto de vista bastante general; es decir,
nodebemos asociar este objeto al concepto del mismo nombre que seemplea
en programación orientada a objetos (OOP). De hecho, un objeto en Python
puede ser una cadena de texto, un número real, undiccionario o un objeto
propiamente dicho, según el paradigma OOP,creado a partir de una clase
determinada. En otros lenguajes deprogramación se emplea el término
estructura de datos para referirse al objeto. Podemos considerar ambos
términos como equivalentes.Habitualmente, un programa en Python puede
contener varioscomponentes. El lenguaje nos ofrece cinco tipos de
estoscomponentes claramente diferenciados. El primero de ellos es el
objeto, tal y como lo hemos definido previamente. Por otro ladotenemos las
expresiones, entendidas como una combinación devalores, constantes,
variables, operadores y funciones que sonaplicadas siguiendo una serle de
reglas. Estas expresiones se suelenagrupar formando sentencias,
consideradas estas como las unidadesmínimas ejecutables de un programa.
Por último, tenemos los módulos que nos ayudan a formar grupos de
diferentes sentencias.Para facilitarnos la programación, Python cuenta con
una serie de objetos integrados (built-in). Entre las ventajas que nos
ofrecen, cabendestacar, el ahorro de tiempo, al no ser necesario construir
estasestructuras de datos de forma manual, la facilidad para crear
complejasestructuras basadas en ellos y el alto rendimiento y mínimo
consumode memoria en tiempo de ejecución. En concreto, contamos
connúmeros, cadenas de texto, booleanos, listas, diccionarios,
tuplas,conjuntos y ficheros. Además, contamos con un tipo de objeto
especialllamado None que se emplea para asignar un valor nulo. A lo largo
deeste capítulo describiremos cada uno de ellos, a excepción de los
    50/381
    ficheros, de los que se ocupa el capítulo 7.Antes de comenzar a descubrir
los objetos built-in de Python, esconveniente explicar qué es y cómo
funciona el tipado dinámico, delque nos ocuparemos en el siguiente
apartado.
Tipado dinámico
Los programadores de lenguajes como Java y C++ estánacostumbrados a
definir cada variable de un tipo determinado.Igualmente ocurre con los
objetos, que deben estar asociados a unaclase determinada cuando son
creados. Sin embargo, Python notrabaja de la misma forma, ya que, al
declarar una variable, no sepuede indicar su tipo. En tiempo de ejecución, el
tipo será asignado ala variable, empleando una técnica conocida como
tipado dinámico. ¿Cómo es esto posible, cómo diferencia el intérprete
entrediferentes tipos y estructuras de datos? La respuesta a estas
preguntashay que buscarla en el funcionamiento interno que el intérprete
realizade la memoria. Cuando se asigna a una variable un valor, el
intérprete,en tiempo de ejecución, realiza un proceso que consiste en
variospasos. En primer lugar se crea un objeto en memoria que
representaráel valor asignado. Seguidamente se comprueba si existe la
variable, sino es así se crea una referencia que enlaza la nueva variable con
elobjeto. Si por el contrario ya existe la variable, entonces, se cambia
lareferencia hacia el objeto creado. Tanto las variables, como los objetos,se
almacenan en diferentes zonas de memoria.A bajo nivel, las variables se
guardan en una tabla de sistema dondese indica a qué objeto referencia cada
una de ellas. Los objetos sontrozos de memoria con el suficiente espacio
para albergar el valor querepresentan. Por último, las referencias son
punteros que enlazanobjetos con variables. De esta forma, una variable
referencia a unobjeto en un determinado momento de tiempo. ¿Cuál es
laconsecuencia directa de este hecho? Es sencillo, en Python los tiposestán
asociados a objetos y no a variables. Lógicamente, los objetosconocen de
qué tipo son, pero no las variables. Esta es la forma con laque Python
consigue implementar el tipado dinámico.Internamente, el intérprete de
Python utiliza un contador de las
    51/381
    referencias que se van asignando entre objetos y variables. En funciónde un
algoritmo determinado, cuando estás van cambiando y ya noson necesarias,
el recolector de basura se encargará de marcar comodisponible el espacio de
memoria ocupado por un objeto que hadejado de ser referenciado.Gracias al
tipado dinámico podemos, en el mismo bloque decódigo, asignar diferentes
tipos de datos a la misma variable, siendo elintérprete en tiempo de
ejecución el que se encargará de crear losobjetos y referencias que sean
necesarios.Para clarificar el proceso previamente explicado, nos
ayudaremosde un ejemplo práctico. En primer lugar asignaremos el valor
numérico 8 a la variable x y seguidamente asignaremos a una nueva
variable y elvalor de x:
>>> x = 8>>> y = x
Después de la ejecución de las sentencias anteriores, en memoriatendríamos
una situación como la que muestra la figura 2-1.
Fig. 2-1 Variables y valor asignado
    52/381
    Posteriormente ejecutamos una nueva sentencia como la siguiente:
    53/381
    >>> x = "test"
Los cambios efectuados en memoria pueden apreciarse en la figura2-2,
donde comprobaremos cómo ahora las variables tienen distintovalor puesto
que apuntan a diferentes objetos.
Fig. 2-1 Cambio de valores
Sin embargo, para algunos tipos de objetos, Python realiza laasignación
entre objetos y variables de forma diferente a la que hemosexplicado
previamente. Un ejemplo de este caso es cuando se cambiael valor de un
elemento dentro de una lista. Supongamos quedefinimos una lista (un
simple array o vector) con una serie de valorespredeterminados y después,
asignamos esta nueva variable a otradiferente llamada lista_2:
>>> lista_1 = [9, 8, 7]>>> lista_2 = lista_1
    54/381
    Ahora modificamos el segundo elemento de la primera lista,ejecutando la
siguiente sentencia:
>>> lista_1[2] = 5
    55/381
    Como resultado, ambas listas habrán sido modificadas y su valorserá el
mismo. ¿Por qué se da esta situación? Simplemente, porque nohemos
cambiado el objeto, sino un componente del mismo. De estaforma, Python
realiza el cambio sobre la marcha, sin necesidad decrear un nuevo objeto y
asignar las correspondientes referencias entrevariables. Como consecuencia
de ello, se ahorra tiempo deprocesamiento y memoria cuando el programa
es ejecutado por elintérprete.Una función que nos puede resultar muy útil
para ver de qué tipoes una variable es type(). Como argumento recibe el
nombre de lavariable en cuestión y devuelve el tipo precedido de la palabra
clave class. Gracias a esta función y debido a que una variable puede
tomardistintos tipos durante su ejecución, podremos saber a qué
tipopertenece en cada momento de la ejecución del código. Veamos
unsencillo ejemplo, a través de las siguientes sentencias y el
resultadodevuelto por el intérprete:
>>> z = 35>>> type(z)<class 'int'>>>> z = "ahora es una cadena de texto"
<class 'str'>
    56/381
    NÚMEROS
Como en cualquier lenguaje de programación, en Python, larepresentación
y el manejo de números se hacen prácticamenteimprescindibles. Para
trabajar con ellos, Python cuenta con una seriede tipos y operaciones
integradas, de ambos nos ocuparemos en elpresente apartado.
Enteros, reales y complejos
Respecto a los tipos de números soportados por Python, contamoscon que
es posible trabajar con números enteros, reales y complejos.Además, estos
pueden ser representados en decimal, binario, octal yhexadecimal, tal y
como veremos más adelante.De forma práctica, la asignación de un número
entero a unavariable se puede hacer a través de una sentencia como esta:
>>> num_entero = 8
Por supuesto, en el contexto de los números enteros, la siguienteexpresión
también es válida:
>>> num_negativo = -78
Por otro lado, un número real se asignaría de la siguiente forma:
>>> num_real = 4.5
En lo que respecta a los números complejos, aquellos formados poruna
parte real y otra imaginaria, la asignación sería la siguiente:
>>> num_complejo = 3.2 + 7j
Siendo también válida la siguiente expresión:
>>> num_complex = 5J + 3
Como el lector habrá deducido, en los números complejos, la parte
    57/381
    imaginaria aparece representada por la letra j, siendo también
posibleemplear la misma letra en mayúscula.Python 2.x distingue entre dos
tipos de enteros en función deltamaño del valor que representan.
Concretamente, tenemos los tipos int y long. En Python 3 esta situación ha
cambiado y ambos han sidointegrados en un único tipo int. Los valores para
números reales que podemos utilizar en Python 3tienen un amplio rango,
gracias a que el lenguaje emplea para surepresentación un bit para el signo
(positivo o negativo), 11 para elexponente y 52 para la mantisa. Esto
también implica que se utiliza laprecisión doble. Recordemos que en
algunos lenguajes deprogramación se emplean dos tipos de datos para los
reales, quevarían en función de la representación de su precisión. Es el caso
de C,que cuenta con el tipo float y double. En Python no existe
estadistinción y podemos considerar que los números reales
representadosequivalen al tipo double de C.Además de expresar un número
real tal y como hemos vistopreviamente, también es posible hacerlo
utilizando notación científica.Simplemente necesitamos añadir la letra e,
que representa elexponente, seguida del valor para él mismo. Teniendo esto
en cuenta,la siguiente expresión sería válida para representar al número
0.5*10 -7 :
>>> num_real = 0.5e-7
Desde un punto de vista escrito los booleanos no son propiamentenúmeros;
sin embargo, estos solo pueden tomar dos valoresdiferentes: True
(verdadero) o False (falso). Dada esta circunstancia,parece lógico utilizar
un tipo entero que necesite menos espacio enmemoria que el original, ya
que, solo necesitamos dos números: 0 y 1.En realidad, aunque Python
cuenta con el tipo integrado bool, este noes más que una versión
personalizada del tipo int. Si necesitamos trabajar con números reales que
tengan unaprecisión determinada, por ejemplo, dos cifras decimales,
podemosutilizar la clase Decimal. Esta viene integrada en la librería básica
queofrece el intérprete e incluye una serie de funciones, para, por
ejemplo,crear un número real con precisión a través de un variable de tipo
float
    58/381
    Sistemas de representación
Tal y como hemos adelantado previamente, Python puederepresentar los
números enteros en los sistemas decimal, octal, binarloy hexadecimal. La
representación decimal es la empleada comúnmentey no es necesario
indicar nada más que el número en cuestión. Sinembargo, para el resto de
sistemas es necesario que el número seaprecedido de uno o dos caracteres
concretos. Por ejemplo, pararepresentar un número en binario nos basta con
anteponer loscaracteres Ob. De esta forma, el número 7 se representaría en
binariode la siguiente forma:
>>> num_binario = Ob111
Por otro lado, para el sistema octal, necesitamos los caracteres Oo,seguidos
del número en cuestión. Así pues, el número entero 8quedaría representado
utilizando la siguiente sentencia:
>>> num_octal = Oo1O
En lo que respecta al sistema hexadeclmal, el carácter necesario,tras el
número 0, es la letra x. Un ejemplo sería la representación delnúmero 255 a
través de la siguiente sentencia:
>>> num_hex = Oxff
Operadores
Obviamente, para trabajar con números, no solo necesitamosrepresentarlos
a través de diferentes tipos, sino también es importanterealizar operaciones
con ellos. Python cuenta con diversos operadorespara aplicar diferentes
operaciones numéricas. Dentro del grupo de lasaritméticas, contamos con
las básicas suma, división entera y real,multiplicación y resta. En cuanto a
las operaciones de bajo nivel y entrebits, existen tanto las operaciones NOT
y NOR, como XOR y AND.También contamos con operadores para
comprobar la igualdad ydesigualdad y para realizar operaciones lógicas
como AND y OR.Como en otros lenguajes de programación, en Python
también
    59/381
    existe la precedencia de operadores, lo que deberemos tener encuenta a la
hora de escribir expresiones que utilicen varios de ellos. Sinolvidar que los
paréntesis pueden ser usados para marcar lapreferencia entre unas
operaciones y otras dentro de la mismaexpresión.La tabla 2-1 resume los
principales operadores y operacionesnuméricas a las que hacen referencia,
siendo o y b dos variablesnuméricas.
Expresión con operador Operación
a + b Suma
a - b Resta
a * b Multiplicación
a % b Resto
a / b División real
a // b División entera
a ** b Potencia
a | b OR (bit)
    60/381
    ^ a b XOR (bit)
a & b AND (bit)
a == b Igualdad
a != b Desigualdad
a or b OR (lógica)
a and b AND (lógica)
not a Negación (lógica)
Tabla 2-1. Principales operaciones y operadores numéricos
Funciones matemáticas
A parte de las operaciones numéricas básicas, anteriormentemencionadas,
Python nos permite aplicar otras muchas funciones
    61/381
    matemáticas. Entre ellas, tenemos algunas como el valor absoluto, laraíz
cuadrada, el cálculo del valor máximo y mínimo de una lista o elredondo
para números reales. Incluso es posible trabajar conoperaciones
trigonométricas como el seno, coseno y tangente. Lamayoría de estas
operaciones se encuentran disponibles a través de unmódulo (ver definición
en capítulo 3) llamado math. Por ejemplo, elvalor absoluto del número
-47,67 puede ser calculado de la siguienteforma:
>>> abs(-47,67)
Para algunas operaciones necesitaremos importar el mencionadomódulo
math, sirva como ejemplo el siguiente código para calcular laraíz cuadrada
del número 169:
>>> import math>>> math.sqrt(169)
Otras interesantes operaciones que podemos hacer con números esel cambio
de base. Por ejemplo, para pasar de decimal a binario o deoctal a
hexadecimal. Para ello, Python cuenta con las funciones int(),hex(), oct() y
bin(). La siguiente sentencia muestra cómo obtener enhexadecimal el valor
del entero 16:
>>> hex(16)'0x10'
Si lo que necesitamos es el valor octal, por ejemplo, del número 8,bastará
con lanzar la siguiente sentencia:
>>> oct(8)'Oo1O'
Debemos tener en cuenta que las funciones de cambio de baseadmiten
como argumentos cualquier representación numéricaadmitida por Python.
Esto quiere decir, que la siguiente expresióntambién sería válida:
>>> bin(Oxfe)'Ob1111111O'
Conjuntos
    62/381
    Definir y operar con conjuntos matemáticos también es posible enPython.
La función para crear un conjunto se llama set() y acepta comoargumentos
una serie de valores pasados entre comas, como si setratara de una cadena
de texto. Por ejemplo, la siguiente línea decódigo define un conjunto tres
números diferentes:
>>> conjunto = set ('846')
Un conjunto también puede ser definido empleando llaves ({}) yseparando
los elementos por comas. Así pues, la siguiente definiciónes análoga a la
sentencia anterior:
>>> conjunto = {8, 4, 6}
Operaciones como unión, intersección, creación de subconjuntos
ydiferencia están disponibles para conjuntos en Python.
Algunasoperaciones se pueden hacer directamente a través de operadores
obien, llamando al método en cuestión de cada instancia creada. Unejemplo
de ello es la operación intersección. Creemos un nuevoconjunto, utilicemos
el operador & y observemos el resultado:
>>> conjunto_2 = set('785')>>> conjunto & conjunto_2('8')
Si en su lugar ejecutamos la siguiente sentencia, veremos que elresultado es
el mismo:
>>> conjunto.intersection(conjunto_2)
A través de los métodos add() y remove() podemos añadir y
borrarelementos de un conjunto.Si creamos un conjunto con valores
repetidos, estos seránautomáticamente eliminados, es decir, no formarán
parte del conjunto:
>>> duplicados = {2, 3, 6, 7, 6, 8, 2, 1}>>> duplicados
{1, 3, 2, 7, 6, 8}
    63/381
    CADENAS DE TEXTO
No cabe duda de que, a parte de los números, las cadenas de texto( strings )
son otro de los tipos de datos más utilizados enprogramación. El intérprete
de Python integra este tipo de datos,además de una extensa serie de
funciones para interactuar condiferentes cadenas de texto.En algunos
lenguajes de programación, como en C, las cadenas detexto no son un tipo
integrado como tal en el lenguaje. Esto implica unpoco de trabajo extra a la
hora de realizar operaciones como laconcatenación. Sin embargo, esto no
ocurre en Python, lo que hacemucho más sencillo definir y operar con este
tipo de dato.Básicamente, una cadena de texto o string es un
conjuntoinmutable y ordenado de caracteres. Para su representación
ydefinición se pueden utilizar tanto comillas dobles ("), como simples
(').Por ejemplo, en Python, la siguiente sentencia crearía una nuevavariable
de tipo string:
>>> cadena = "esto es una cadena de texto"
Si necesitamos declarar un string que contenga más de una línea,podemos
hacerlo utilizando comillas triples en lugar de dobles osimples:
>>> cad_multiple = """Esta cadena de texto... tiene más de una línea. En
concreto, cuenta
... con tres líneas diferentes"""
Tipos
Por defecto, en Python 3, todas las cadenas de texto son Unicode. Sihemos
trabajado con versiones anteriores del lenguaje deberemostener en mente
este hecho, ya que, por defecto, antes se empleabaASCII. Así pues,
cualquier string declarado en Python seráautomáticamente de tipo Unicode.
    64/381
    Otra de las novedades de Python 3 con referencia a las cadenas detexto es el
tipo de estas que soporta. En concreto son tres las incluidasen esta versión:
Unicode, byte y bytearray. El tipo byte solo admitecaracteres en
codificación ASCII y, al igual que los de tipo Unicode, soninmutables. Por
otro lado, el tipo bytearray es una versión mutable deltipo byte. Para
declarar un string de tipo byte, basta con anteponer la letra b antes de las
comillas:
>>> cad = b"cadena de tipo byte">>> type(cad)<class 'bytes'>
La declaración de un tipo bytearray debe hacerse utilizando lafunción
integrada que nos ofrece el intérprete. Además, esimprescindible indicar el
tipo de codificación que deseamos emplear.El siguiente ejemplo utiliza la
codificación de caracteres latin1 paracrear un string de este tipo:
>>> lat = bytearray("España", 'latin1')
Observemos el siguiente ejemplo y veamos la diferencia al
empleardiferentes tipos de codificaciones para el mismo string:
>>> print(lat)bytearray(b'Esp\xfla')>>> bytearray("España",
"utf16")bytearray(b'\xff\xfeE\xOOs\xOOp\xOOa\xOO\xf1\xOOa\xOO')
Para las cadenas de texto declaradas por defecto, internamente,Python
emplea el tipo denominado str. Podemos comprobarlosencillamente
declarando una cadena de texto y preguntando a lafunción type():
>>> cadena = "comprobando el tipo str">>> type(cadena)<class 'str'>
Realizar conversión entre los distintos tipos de strings es posiblegracias a
dos tipos de funciones llamadas encode() y decode(). Laprimera de ellas se
utiliza para transformar un tipo str en un tipo byte. Veamos cómo hacerlo en
el siguiente ejemplo:
>>> cad = "es de tipo str"
    65/381
    >>> cad. encode()b'es de tipo str'
La función decode() realiza el paso inverso, es decir, convierte unstring byte
a otro de tipo str. Como ejemplo, ejecutaremos lassiguientes sentencias:
>>> cad = b"es de tipo byte">>> cad.decode()'es de tipo byte'
Como el lector habrá podido deducir, cada una de estas funcionessolo se
encuentra definida para cada tipo. Esto significa que decode() no existe para
el tipo str y que encode() no funciona para el tipo byte. Alternativamente, la
función encode() admite como parámetro untipo de codificación específico.
Si este tipo es indicado, el intérpreteutilizará el número de bytes necesarios
para su representación enmemoria, en función de cada codificación.
Recordemos que, para surepresentación interna, cada tipo de codificación
de caracteresrequiere de un determinado número de bytes.
Principales funciones y métodos
Para trabajar con strings Python pone a nuestra disposición unaserie de
funciones y métodos. Las primeras pueden ser invocadasdirectamente y
reciben como argumento una cadena. Por otro lado,una vez que tenemos
declarado el string, podemos invocar adiferentes métodos con los que
cuenta este tipo de dato.Una de las funciones más comunes que podemos
utilizar sobrestrings es el cálculo del número de caracteres que contiene.
Elsiguiente ejemplo nos muestra cómo hacerlo:
>>> cad = "Cadena de texto de ejemplo">>> len(cad)26
Otro ejemplo de función que puede ser invocada, sin necesidad dedeclarar
una variable de tipo string, es print(). En el capítulo anteriormostramos
cómo emplearla para imprimir una cadena de texto por lasalida estándar.
    66/381
    Respecto a los métodos con los que cuentan los objetos de tipostring,
Python incorpora varios de ellos para poder llevar a cabofuncionalidades
básicas relacionadas con cadenas de texto. Entre ellas,contamos con
métodos para buscar una subcadena dentro de otra,para reemplazar
subcadenas, para borrar espacios en blanco, parapasar de mayúsculas a
minúsculas, y viceversa.La función find() devuelve el índice
correspondiente al primercarácter de la cadena original que coincide con el
buscado:
>>> cad ="xyza">>> cad.find("y")1
Si el carácter buscado no existe en la cadena, find() devolverá -1.Para
reemplazar una serie de caracteres por otros, contamos con elmétodo
replace(). En el siguiente ejemplo, sustituiremos la subcadena"Hola" por
"Adiós":
>>> cad = "Hola Mundo">>> cad.replace("Hola", "Adiós")'Adiós Mundo'
Obsérvese que replace() no altera el valor de la variable sobre elque se
ejecuta. Así pues, en nuestro ejemplo, el valor de la variable cad seguirá
siendo "Hola Mundo". Los métodos strip(), lstrip() y rstrip() nos ayudarán
a eliminar todoslos espacios en blanco, solo los que aparecen a la izquierda
y solo losque se encuentran a la derecha, respectivamente:
>>> cad = " cadena con espacios en blanco ">>> cad.strip()"cadena con
espacios en blanco">>> cad.lstrip()"cadena con espacios en blanco ">>>
cad.rstrip()" cadena con espacios en blanco"
El método upper() convierte todos los caracteres de una cadena detexto a
mayúsculas, mientras que lower() lo hace a minúsculas. Veamosun sencillo
ejemplo:
>>> cad2 = cad.upper()>>> print(cad2)"CADENA CON ESPACIOS EN
BLANCO">>> print(cad3.lower())
    67/381
    " cadena con espacios en blanco "
Relacionados con upper() y lower() encontramos otro métodollamado
capitalize(), el cual solo convierte el primer carácter de unstring a
mayúsculas:
>>> cad = "un ejemplo">>> cad.capitalize()'Un ejemplo'
En ocasiones puede ser muy útil dividir una cadena de textobasándonos en
un carácter que aparece repetidamente en ella. Estafuncionalidad es la que
nos ofrece split(). Supongamos que tenemosun cadena con varios valores
separadas por ; y que necesitamos unalista donde cada valor se corresponda
con los que aparecendelimitados por el mencionado carácter. El siguiente
ejemplo nosmuestra cómo hacerlo:
>>> cad = "primer valor;segundo;tercer valor">>> cad.split(";")['primer
valor', 'segundo', 'tercer valor']
join() es un método que devuelve una cadena de texto donde losvalores de
la cadena original que llama al método aparecen separadospor un carácter
pasado como argumento:
>>> "abc".join(',')'a,b,c'
Operaciones
El operador + nos permite concatenar dos strings, el resultadopuede ser
almacenado en una nueva variable:
>>> cad_concat = "Hola" + " Mundo!">>> print(cad_concat)Hola Mundo!
También es posible concatenar una variable de tipo string con unacadena o
concatenar directamente dos variables. Sin embargo,también podemos
prescindir del operador + para concatenar strings. Aveces, la expresión es
más fácil de leer si empleamos el método
    68/381
    format(). Este método admite emplear, dentro de la cadena de texto,los
caracteres {}, entre los que irá, número o el nombre de una variable.Como
argumentos del método pueden pasarse variables que seránsustituidas, en
tiempo de ejecución, por los marcadores {}, indicados enla cadena de texto.
Por ejemplo, veamos cómo las siguientesexpresiones son equivalentes y
devuelven el mismo resultado:
>>> "Hola " + cad2 + ". Otra " + cad3>>> "Hola {O}. Otra
{1}".format(cad2, cad3)>>> "Hola {cad2}. Otra
{cad3}".format(cad2=cad2,cad3=cad3)
La concatenación entre strings y números también es posible,siendo para
ello necesario el uso de funciones como int() y str(). Elsiguiente ejemplo es
un caso sencillo de cómo utilizar la función str():
>>> num = 3>>> "Número: " + str(num)
Interesante resulta el uso del operador * aplicado a cadenas detexto, ya que
nos permite repetir un string n veces. Supongamos quedeseamos repetir la
cadena "Hola Mundo" cuatro veces. Para ello,bastará con lanzar la siguiente
sentencia:
>>> print("Hola Mundo" * 4)
Gracias al operador in podemos averiguar si un determinadocarácter se
encuentra o no en una cadena de texto. Al aplicar eloperador, como
resultado, obtendremos True o False, en función de siel valor se encuentra o
no en la cadena. Comprobémoslo en elsiguiente ejemplo:
>>> cad = "Nueva cadena de texto">>> "x" in cadFalse
Un string es inmutable en Python, pero podemos acceder, a travésde
índices, a cada carácter que forma parte de la cadena:
>>> cad = "Cadenas">>> print(cad[2])d
Los comentados índices también nos pueden ayudar a obtenersubcadenas de
texto basadas en la original. Utilizando la variable cad
    69/381
    del ejemplo anterior podemos imprimir solo los tres primeroscaracteres:
>>> print(cad[:3])Cad
En el ejemplo anterior el operador : nos ha ayudado a nuestropropósito.
Dado que delante del operador no hemos puesto ningúnnúmero, estamos
indicando que vamos a utilizar el primer carácter dela cadena. Detrás del
operador añadimos el número del índice de lacadena de texto que será
utilizado como último valor. Así pues,obtendremos los tres primeros
caracteres. Los índices negativostambién funcionan, simplemente indican
que se empieza contar desdeel último carácter. La siguiente sentencia
devolverá el valor a :
>>> cad[-2]
A continuación, veamos un ejemplo donde utilizamos un númerodespués
del mencionado operador:
>>> cad [3:]'enas'
    70/381
    TUPLAS
En Python una tupla es una estructura de datos que representa unacolección
de objetos, pudiendo estos ser de distintos tipos.Internamente, para
representar una tupla, Python utiliza un array deobjetos que almacena
referencias hacia otros objetos.Para declarar una tupla se utilizan paréntesis,
entre los cuales debensepararse por comas los elementos que van a formar
parte de ella. Enel siguiente ejemplo, crearemos una tupla con tres valores,
cada unode un tipo diferente:
>>> t = (1, 'a', 3.5)
Los elementos de una tupla son accesibles a través del índice queocupan en
la misma, exactamente igual que en un array:
>>> t [1]'a'
Debemos tener en cuenta que las tuplas son un tipo de datoinmutable, esto
significa que no es posible asignar directamente unvalor a través del
índice.A diferencia de otros lenguajes de programación, en Python
esposible declarar una tupla añadiendo una coma al final del
últimoelemento:
>>> t = (1, 3, 'c', )
Dado que una tupla puede almacenar distintos tipos de objetos, esposible
anidar diferentes tuplas; veamos un sencillo ejemplo de ello:
>>> t = (1, ('a', 3), 5.6)
Una de las peculiaridades de las tuplas es que es un objeto iterable ;es decir,
con un sencillo bucle for podemos recorrer fácilmente todossus elementos:
>>> for ele in t:... print(ele)...1
    71/381
    ('a', 3)5.6
Concatenar dos tuplas es sencillo, se puede hacer directamente através del
operador + . Otros de los operadores que se pueden utilizares * , que sirve
para crear una nueva tupla donde los elementos de laoriginal se repiten n
veces. Observemos el siguiente ejemplo y elresultado obtenido:
>>> (’r', 2) * 3>>> ('r', 2, 'r', 2, 'r', 2)
Los principales métodos que incluyen las tupas son index() y count() El
primero de ellos recibe como parámetro un valor y devuelve elíndice de la
posición que ocupa en la tupla. Veamos el siguienteejemplo:
>>> t = (1, 3, 7)>>> t.index(3)1
El método count() sirve para obtener el número de ocurrencias deun
elemento en una tupla:
>>> t = (1, 3, 1, 5, 1,)>>> t.count(1)3
Sobre las tuplas también podemos usar la función integrada len(), que nos
devolverá el número de elementos de la misma. Obviamente,deberemos
pasar la variable tupla como argumento de la mencionadafunción.
    72/381
    LISTAS
Básicamente, una lista es una colección ordenada de objetos,similar al
array dinámico empleado en otros lenguajes deprogramación. Puede
contener distintos tipos de objetos, es mutable yPython nos ofrece una serie
de funciones y métodos integrados pararealizar diferentes tipos de
operaciones.Para definir una lista se utilizan corchetes ([]) entre los
cualespueden aparecer diferentes valores separados por comas. Esto
significaque ambas declaraciones son válidas:
>>> lista = []>>> li = [2, 'a' , 4]
Al igual que las tuplas, las listas son también iterables, así pues,podemos
recorrer sus elementos empleando un bucle:
>>> for ele in li:... print(ele)...2'a'4
A diferencia de las tuplas, los elementos de las listas pueden
serreemplazados accediendo directamente a través del índice que ocupanen
la lista. De este modo, para cambiar el segundo elemento denuestra lista li,
bastaría como ejecutar la siguiente sentencia:
>>> 1i [ 1] = ' b'
Obviamente, los valores de las listas pueden ser accedidosutilizando el
valor del índice que ocupan en la misma:
>>> li [2]4
Podemos comprobar si un determinado valor existe en una lista através del
operado in, que devuelve True en caso afirmativo y False encaso contrario:
>>> 'a' in li
    73/381
    True
Existen dos funciones integradas que relacionan las listas con lastuplas:
list() y tuple(). La primera toma como argumento una tupla ydevuelve una
lista. En cambio, tuple() devuelve una tupla al recibircomo argumento una
lista. Por ejemplo, la siguiente sentencia nosdevolverá una tupla:
>>> tuple(li)(2, 'a', 4)
Operaciones como la suma (+) y la multiplicación (*) tambiénpueden ser
aplicadas sobre listas. Su funcionamiento es exactamenteigual que en las
tuplas.
Inserciones y borrados
Para añadir un nuevo elemento a una lista contamos con el método
append(). Como parámetro hemos de pasar el valor que deseamosañadir y
este será insertado automáticamente al final de la lista.Volviendo a nuestra
lista ejemplo de tres elementos, uno nuevoquedaría insertado a través de la
siguiente sentencia:
>>> li.append('nuevo')
Nótese que, para añadir un nuevo elemento, no es posible utilizarun índice
superior al número de elementos que contenga la lista. Lasiguiente
sentencia lanza un error:
>>> li [4] = 23Traceback (most recent call last):File "<stdin>", line 1, in
<module>IndexError: list assignment index out of range
Sin embargo, el método insert() sirve para añadir un nuevoelemento
especificando el índice. Si pasamos como índice un valorsuperior al número
de elementos de la lista, el valor en cuestión seráinsertado al final de la
misma, sin tener en cuenta el índice pasadocomo argumento. De este modo,
las siguientes sentencias produciránel mismo resultado, siendo 'c' el nuevo
elemento que será insertado enla lista li:
    74/381
    >>> li.insert(3, 'c')>>> li.insert(12, 'c')
Por el contrario, podemos insertar un elemento en una posicióndeterminada
cuyo índice sea menor al número de valores de la lista.Por ejemplo, para
insertar un nuevo elemento en la primera posiciónde nuestra lista li, bastaría
con ejecutar la siguiente sentencia:
>>> li.insert(0, 'd')>>> li>>> ['d’, 2, 'a', 4]
Si lo que necesitamos es borrar un elemento de una lista, podemoshacerlo
gracias a la función del(), que recibe como argumento la listajunto al índice
que referencia al elemento que deseamos eliminar. Lasiguiente sentencia
ejemplo borra el valor 2 de nuestra lista li:
>>> del(li[1])
Como consecuencia de la sentencia anterior, la lista queda reducidaen un
elemento. Para comprobarlo contamos con la función len(), quenos
devuelve el número de elementos de la lista:
>>> len(li)2
Obsérvese que la anterior función también puede recibir comoargumento
una tupla o un string. En general, len() funciona sobre tiposde objetos
iterables.También es posible borrar un elemento de una lista a través de
suvalor. Para ello contamos con el método remove():
>>> li.remove('d')
Si un elemento aparece repetido en la lista, el método remove() soloborrará
la primera ocurrencia que encuentre en la misma.Otro método para eliminar
elementos es pop(). A diferencia de remove(), pop() devuelve el elemento
borrado y recibe comoargumento el índice del elemento que será eliminado.
Si no se pasaningún valor como índice, será el último elemento de la lista
eleliminado. Este método puede ser útil cuando necesitamos
ambasoperaciones (borrar y obtener el valor) en una única sentencia.
    75/381
    Ordenación
Los elementos de una lista pueden ser ordenados a través delmétodo sort()
o utilizando la función sorted(). Como argumento sepuede utilizar reverse
con el valor True o False. Por defecto, se utiliza elsegundo valor, el cual
indica que la lista será ordenada de mayor amenor. Si por el contrario el
valor es True, la lista será ordenadainversamente. Veamos un ejemplo para
ordenar una lista de enteros:
>>>>>>[1,>>>[9,>>>[3,
lista = [3, 1, 9, 8, 7]sorted(lista)3, 7, 8, 9]sorted(lista, reverse=True)8, 7, 3,
1]lista1, 9, 8, 7]
Como el lector habrá podido observar, la lista original ha
quedadoinalterada. Sin embargo, si en lugar de utilizar la función sorted(),
empleamos el método sort(), la lista quedará automáticamentemodificada.
Ejecutemos las siguientes setencias para comprobarlo:
>>> lista.sort()>>> lista[1, 3, 7, 8, 9]
Tanto para aplicar sort() como sorted() debemos tener en cuentaque la lista
que va a ser ordenada contiene elementos que son delmismo tipo. En caso
contrario, el intérprete de Python lanzará un error.No obstante, es posible
realizar ordenaciones de listas con elementosde distinto tipo si es el
programador el encargado de establecer elcriterio de ordenación. Para ello,
contamos con el parámetro key quepuede ser pasado como argumento. El
valor del mismo puede ser unafunción que fijará cómo ordenar los
elementos. Además, elmencionado parámetro también puede ser utilizado
para cambiar laforma de ordenar que emplee el intérprete por defecto,
aunque loselementos sean del mismo tipo. Supongamos que definimos
lasiguiente lista:
>>> lis = ['aA', 'Ab', 'Cc', ’ca']
Ahora ordenaremos con la función sorted() sin ningún parámetro
    76/381
    adicional y observaremos que el criterio de ordenación que utiliza
elintérprete, por defecto, es ordenar primero las letras mayúsculas:
>>> sorted(lis)['Ab', 'Cc' , 'aA', 'ca']
Sin embargo, al pasar como argumento un determinado criterio
deordenación, el resultado varía:
>>> sorted(lis, key=str.lower)['aA', 'Ab', 'ca', 'Cc']
Otro método que contienen las listas relacionado con la ordenaciónde
valores es reverse(), que automáticamente ordena una lista en ordeninverso
al que se encuentran sus elementos originales. Tomando elvalor de la última
lista de nuestro ejemplo, llamaremos al método paraver qué ocurre:
>>> lista.reverse()
>>> lista
[9, 8, 7, 3, 1]
Los métodos y funciones de ordenación no solo funcionan connúmeros,
sino también con caracteres y con cadenas de texto:
>>> lis = ['be', 'ab', 'cc', 'aa', 'cb']>>> lis.sort()>>> lis
['aa', 'ab','be', 'cb', 'cc']
Comprensión
La comprensión de listas es una construcción sintáctica de Pythonque nos
permite declarar una lista a través de la creación de otra. Estaconstrucción
está basada en el principio matemático de la teoría decomprensión de
conjuntos. Básicamente, esta teoría afirma que unconjunto se define por
comprensión cuando sus elementos sonnombrados a través de sus
características. Por ejemplo, definimos elconjunto S como aquel que está
formado por todos los meses del año: S = {meses del año}
    77/381
    Veamos un ejemplo prácticoconstrucción sintáctica en Python:
para
utilizar
la
mencionada
>>> lista = [ele for ele in (1, 2, 3)]
Como resultado de la anterior sentencia, obtendremos una lista contres
elementos diferentes:
>>> print(lista)[1, 2, 3]
Gracias a la comprensión de listas podemos definir y crear listasahorrando
líneas de código y escribiendo el mismo de forma máselegante. Sin la
comprensión de listas, deberíamos ejecutar lassiguientes sentencias para
lograr el mismo resultado:
>>> lista = []>>> for ele in (1, 2, 3):... lista.append(ele)...
Matrices
Anidando listas podemos construir matrices de elementos. Estasestructuras
de datos son muy útiles para operaciones matemáticas.Debemos tener en
cuenta que complejos problemas matemáticos sonresueltos empleando
matrices. Además, también son prácticas paraalmacenar ciertos datos,
aunque no se traten estrictamente derepresentar matrices en el sentido
matemático.Por ejemplo, una matriz matemática de dos dimensiones
puededefinirse de la siguiente forma:
>>> matriz = [[1, 2, 3],[4, 5, 6]]
Para acceder al segundo elemento de la primera matriz, bastaríacon ejecutar
la siguiente sentencia:
    78/381
    >>> matriz = [0][1]
Asimismo, podemos cambiar un elemento directamente:
>>> matriz[0][1] = 33>>> m
    79/381
    [[1, 33, 3],[4, 5, 6]]
    80/381
    DICCIONARIOS
Un diccionario es una estructura de datos que almacena una seriede valores
utilizando otros como referencia para su acceso yalmacenamiento. Cada
elemento de un diccionario es un par clave-valor donde el primero debe ser
único y será usado para acceder alvalor que contiene. A diferencia de las
tuplas y las listas, losdiccionarios no cuentan con un orden específico,
siendo el intérpretede Python el encargado de decidir el orden de
almacenamiento. Sinembargo, un diccionario es iterable, mutable y
representa unacolección de objetos que pueden ser de diferentes
tipos.Gracias a su flexibilidad y rapidez de acceso, los diccionarios sonuna
de las estructuras de datos más utilizadas en Python. Internamenteson
representadas como una tabla hash, lo que garantiza la rapidez deacceso a
cada elemento, además de permitir aumentar dinámicamenteel número de
ellos. Otros muchos lenguajes de programación hacenuso de esta estructura
de datos, con la diferencia de que es necesarioimplementar la misma, así
como las operaciones de acceso,modificación, borrado y manejo de
memoria. Python ofrece la granventaja de incluir los diccionarios como
estructuras de datosintegradas, lo que facilita en gran medida su
utilización.Para declarar un diccionario en Python se utilizan las llaves ({})
entrelas que se encuentran los pares clave-valor separados por comas.
Laclave de cada elemento aparece separada del correspondiente valorpor el
carácter : . El siguiente ejemplo muestra la declaración de undiccionario
con tres valores:
>>> diccionario = {'a': 1, 'b': 2, 'c': 3}
Alternativamente, podemos hacer uso de la función dict() quetambién nos
permite crear un diccionario. De esta forma, la siguientesentencia es
equivalente a la anterior:
>>> diccionario = dict(a=1, b=2, c=3)
Acceso, inserciones y borrados
    81/381
    Como hemos visto previamente, para acceder a los elementos delas listas y
las tuplas, hemos utilizado el índice en función de laposición que ocupa
cada elemento. Sin embargo, en los diccionariosnecesitamos utilizar la
clave para acceder al valor de cada elemento.Volviendo a nuestro ejemplo,
para obtener el valor indexado por laclave 'c' bastará con ejecutar la
siguiente sentencia:
>>> diccionario 'c']3
Para modificar el valor de un diccionario, basta con acceder a travésde su
clave:
>>> diccionario['b'] = 28
Añadir un nuevo elemento es tan sencillo como modificar uno yaexistente,
ya que si la clave no existe, automáticamente Python laañadirá con su
correspondiente valor. Así pues, la siguiente sentenciainsertará un nuevo
valor en nuestro diccionario ejemplo:
>>> diccionario['d'] = 4
Tres son los métodos principales que nos permiten iterar sobre
undiccionario: items(), values() y keys(). El primero nos da acceso tanto
aclaves como a valores, el segundo se encarga de devolvernos losvalores, y
el tercero y último es el que nos devuelve las claves deldiccionario. Veamos
estos métodos en acción sobre el diccionariooriginal que declaramos
previamente:
>>> for k, v in diccionario.items():... print("clave={0}, valor=
{1}".format(k, v))...clave=a, valor=1clave=b, valor=2clave=c, valor=3
>>> for k in diccionario.keys():... print("clave=
{0}".format(k))...clave=aclave=bclave=c
>>> for v in diccionario.values():... print("valor={0}".format(v))
    82/381
    ...valor=1valor=2valor=3
Por defecto, si iteramos sobre un diccionario con un bucle for, obtendremos
las claves del mismo sin necesidad de llamarexplícitamente al método
keys() :
>>> for k in diccionario:... print(k)abc
A través del método keys() y de la función integrada list() podemosobtener
una lista con todas las claves de un diccionario:
>>> list(diccionario.keys())
Análogamente es posible usar values() junto con la función list() para
obtener un lista con los valores del diccionario. Por otro lado, lasiguiente
sentencia nos devolverá una lista de tuplas, donde cada unade ellas contiene
dos elementos, la clave y el valor de cada elementodel diccionario:
>>> list(diccionario.items())[ ('a', 1), (’b', 2), ('c', 3)]
La función integrada del() es la que nos ayudará a eliminar un valorde un
diccionario. Para ello, necesitaremos pasar la clave que contieneel valor que
deseamos eliminar. Por ejemplo, para eliminar el valor quecontiene la clave
'c' de nuestro diccionario, basta con ejecutar:
>>> del(diccionario['b'])
El método pop() también puede ser utilizado para borrar eliminarelementos
de un diccionario. Su funcionamiento es análogo alexplicado en el caso de
las listas.Otra función integrada, en este caso len(), también funciona
sobrelos diccionarios, devolviéndonos el número total de
elementoscontenidos.El operador in en un diccionario sirve para comprobar
si una claveexiste. En caso afirmativo devolverá el valor True y False en
otro caso:
    83/381
    >>> 'x' in diccionarioFalse
Comprensión
De forma similar a las listas, los diccionarios pueden también sercreados
por comprensión. El siguiente ejemplo muestra cómo crear undiccionario
utilizando la iteración sobre una lista:
>>> {k: k+1 for k in (1, 2, 3)}{1: 2, 3: 4, 4: 5}
La comprensión de diccionarios puede ser muy útil para inicializarun
diccionario a un determinado valor, tomando como claves losdiferentes
elementos de una lista. Veamos cómo hacerlo a través delsiguiente ejemplo
que crea un diccionario inicializándolo con el valor 1 para cada clave:
>>> {clave: 1 for clave in ['x', 'y', 'z']}{'x': 1, ’y': 1, 'z': 1}
Ordenación
A diferencia de las listas, los diccionarios no tienen el método sort(), pero sí
que es posible utilizar la función integrada sorted() para obteneruna lista
ordenada de las claves contenidas. Volviendo a nuestrodiccionario ejemplo
inicial, ejecutaremos la siguiente sentencia:
>>> sorted(diccionario)['a', 'b', 'c']
También podemos utilizar el parámetro reverse con el mismoresultado que
en las listas:
>>> sorted(diccionario, reverse=True)['c', ' b' , 'a']
    84/381
    SENTENCIAS DE CONTROL,MÓDULOS Y FUNCIONES
INTRODUCCIÓN
Las sentencias de control es uno de los primeros aspectos quedeben ser
abordados durante el aprendizaje de un lenguaje deprogramación. Entre las
sentencias de las que dispone Python, lasbásicas son las que nos permiten
crear condiciones y realizariteraciones. Es por ello que dedicaremos el
primer apartado de estecapítulo a las mismas.Continuaremos entrando de
lleno en uno de los principalesconceptos de la programación procedural: las
funciones.Aprenderemos cómo se definen, cómo son tratadas por el
intérprete ycómo pasar parámetros. Además, presentaremos a un tipo
especialllamado lambda, muy utilizado en programación funcional.Python
permite agrupar nuestro código en módulos y paquetes, gracias a los cuales
podemos organizar adecuadamente nuestrosprogramas. De ellos hablaremos
a continuación de las funciones.Por último, veremos qué son las
excepciones y cómo podemostrabajar con ellas en Python. El mecanismo de
tratamiento deexcepciones nos ahorra muchos problemas en tiempos de
ejecución yse ha convertido en una de las más importantes funcionalidades
queincorporan los modernos lenguajes de programación.
    85/381
    PRINCIPALES SENTENCIAS DE CONTROL
Al igual que otros lenguajes de programación, Python incorporauna serie de
sentencias de control. Entre ellas, encontramos algunastan básicas y
comunes a otros lenguajes como if/else, while y for, yotras específicas como
pass y with. A continuación, echaremos unvistazo a cada una de estas
sentencias.
if, else y elif
La sentencia if/else funciona evaluando la condición indicada, si elresultado
es True se ejecutará la siguiente sentencia o sentencias, encaso negativo se
ejecutarán las sentencias que aparecen acontinuación del else. Recordemos
que Python utiliza la indentaciónpara establecer sentencias que pertenecen
al mismo bloque. Además,en el carácter dos puntos ( : ) indica el comienzo
de bloque. Acontinuación, vemos un ejemplo:
x = 4y = 0if x == 4:y = 5else:y = 2
Obviamente, también es posible utilizar solo la sentencia if paracomprobar
si se cumple una determinada condición y actuar enconsecuencia. Además,
podemos anidar diferentes niveles decomprobación a través de elif :
if X = = 4 :y = 1elif x = = 5y = 2elif x = = 6y = 3else:y = 5
    86/381
    Como el lector habrá podido observar y a diferencia de otroslenguajes de
programación, los paréntesis para indicar las condicioneshan sido omitidos.
Para Python son opcionales y habitualmente nosuelen ser utilizados. Por
otro lado, a pesar de que Python emplea laindentación, también es posible
escribir una única sentencia acontinuación del final de la condición. Así
pues, la siguiente línea decódigo es válida:
if a > b: print("a es mayor que b")
for y while
Para iterar contamos con dos sentencias que nos ayudarán a crearbucles,
nos referimos a for y a while. La primera de ellas aplica unaserle de
sentencias sobre cada uno de los elementos que contiene elobjeto sobre el
que aplicamos la sentencia for. Python incorpora unafunción llamada
range() que podemos utilizar para iterar sobre unaserie de valores. Por
ejemplo, echemos un vistazo al siguiente ejemplo:
>>> for x in range(1, 3):... print(x)...123
Asimismo, tal y como hemos visto en el capítulo anterior, es muycomún
iterar a través de for sobre los elementos de una tupla o de unalista:
>>> lista = ["uno", "dos", "tres"]>>> cad = "">>> for ele in lista:... cad +=
ele...>>> cad"unodostres"
Opcionalmente, for admite la sentencia else. Si esta aparece, todaslas
sentencias posteriores serán ejecutadas si no se encuentra otrasentencia que
provoque la salida del bucle. Por ejemplo, en laejecución de un bucle for
que no contiene ningún break, siempre serán
    87/381
    ejecutadas las sentencias que pertenecen al else al finalizar el bucle.
Acontinuación, veamos un ejemplo para ilustrar este caso:
>>> for item in (1, 2, 3):... print(item)... else:... print ("fin")...123fin
Otra sentencia utilizada para iterar es while, la cual ejecuta una seriede
sentencias siempre y cuando se cumpla una determinada condicióno
condiciones.Para salir del bucle podemos utilizar diferentes técnicas. La
mássencilla es cambiar la condición o condiciones iniciales para así
dejarque se cumplan y detener la iteración. Otra técnica es
llamardirectamente a break que provocará la salida inmediata del bucle.
Estaúltima sentencia también funciona con for. A continuación, veamos
unejemplo de cómo utilizar while:
>>> x = 0>>> y = 3>>> while x < y:... print(x)... x += 1012
Al igual que for, while también admite opcionalmente else. Observemos el
siguiente código y el resultado de su ejecución:
>>> x = 0>>> y = 3>>> while x < y:... print(x)... x+ = 1... if x == 2:...
break... else:... print("x es igual a 2")01
Si en el ejemplo anterior eliminamos la sentencia break,
    88/381
    comprobaremos cómo la última sentencia print es ejecutada.Además de
break, otra sentencia asociada a for y while es continue, la cual se emplea
para provocar un salto inmediato a la siguienteiteración del bucle. Esto
puede ser útil, por ejemplo, cuando nodeseamos ejecutar una determinada
sentencia para una iteraciónconcreta. Supongamos que estamos iterando
sobre una secuencia ysolo queremos imprimir los números pares:
>>> for i in range(l, 10):... if i % 2 != 0:... continue... print(i)2468
pass y with
Python incorpora una sentencia especial para indicar que no sedebe realizar
ninguna acción. Se trata de pass y especialmente útilcuando deseamos
indicar que no se haga nada en una sentencia querequiere otra. Por ejemplo,
en un sencillo while:
>>> while True:... pass
Muchos desarrolladores emplean pass cuando escriben esqueletos de
código que posteriormente rellenarán. Dado que inicialmente nosabemos
qué código contendrá una determinada sentencia, es útilemplear pass para
mantener el resto del programa funcional.Posteriormente, el esqueleto de
código será rellenado con códigofuncional y la sentencia pass será
reemplazada por otras que realicenuna función específica.La sentencia with
se utiliza con objetos que soportan el protocolode manejador de contexto y
garantiza que una o varias sentencias seránejecutadas automáticamente.
Esto nos ahorra varias líneas de código, ala vez que nos garantiza que
ciertas operaciones serán realizadas sinque lo indiquemos explícitamente.
Uno de los ejemplos más claros esla lectura de las líneas de un fichero de
texto. Al terminar esta
    89/381
    operación siempre es recomendable cerrar el fichero. Gracias a with esto
ocurrirá automáticamente, sin necesidad de llamar al método close(). Las
siguientes líneas de código ilustran el proceso:
>>> with open(r'info.txt') as myfile:... for line in myfile:... print(line)
    90/381
    FUNCIONES
En programación estructurada, las funciones son uno de loselementos
básicos. Una función es un conjunto de sentencias quepueden ser invocadas
varias veces durante la ejecución de unprograma. Las ventajas de su uso son
claras, entre ellas, laminimización de código, el aumento de la legibilidad y
la fomentaciónde la reutilización de código.Una de las principales
diferencias de las funciones en Python conrespecto a lenguajes compilados,
como C, es que estas no existenhasta que son invocadas y el intérprete pasa
a su ejecución. Estoimplica que la palabra reservada def, empleada para
definir unafunción, es una sentencia más. Como consecuencia de ello,
otrassentencias pueden contener una función. Así pues, podemos utilizaruna
sentencia if, definiendo una función cuando una determinadacondición se
cumple.Internamente, al definir una función, Python crea un nuevo objeto
yle asigna el nombre dado para la función. De hecho, una funciónpuede ser
asignada a una variable o almacenada en una lista.Como hemos comentado
previamente, la palabra reservada def nosservirá para definir una función.
Seguidamente deberemos emplear unnombre y, opcionalmente, una serie de
argumentos. Esta será nuestraprimera función:
def test():print("test ejecutada")
Para invocar a nuestra nueva función, basta con utilizar su nombreseguido
de paréntesis. En lugar de utilizar el intérprete para comprobarsu
funcionamiento, lo haremos a través de un fichero. Basta con abrirnuestro
editor de textos favorito y crear un fichero llamado test.py conel siguiente
código:
def test () :print("test ejecutada")test()nueva = testnueva()
    91/381
    Una vez que salvemos el fichero con el código, ejecutaremos elprograma
desde la línea de comandos a través del siguiente comando:
python test.py
Como resultado veremos cómo aparece dos veces la cadena detexto test
ejecutada.
Paso de parámetros
En los ejemplos previos hemos utilizado una función sinargumentos y, por
lo tanto, para invocar a la misma no hemos utilizadoningún parámetro. Sin
embargo, las funciones pueden trabajar conparámetros y devolver
resultados. En Python, el paso de parámetrosimplica la asignación de un
nombre a un objeto. Esto significa que, enla práctica, el paso de parámetros
ocurre por valor o por referencia enfunción de si los tipos de los
argumentos son mutables o inmutables.En otros lenguajes de programación,
como por ejemplo C, es elprogramador el responsable de elegir cómo serán
pasados losparámetros. Para ilustrar este funcionamiento, observemos el
siguienteejemplo:
>>> def test2 (a, b)... : a = 2... b = 3...>>> C = 5>>> d = 6>>> test2 (c,
d)>>> print("c={0}, d={l}". format(x, y))c=5, d=6
Como podemos observar, el valor de las variables c y d, que soninmutables,
no ha sido alterado por la función.Sin embargo, ¿qué ocurre si utilizamos un
argumento inmutablecomo parámetro? Veámoslo a través del siguiente
código de ejemplo:
>>>...>>>>>>[3,
def variable(lista):lista [0] = 3lista = [1, 2, 3]print(variable(lista))2, 3]
    92/381
    Efectivamente, al pasar como argumento una lista, que es demutable, y
modificar uno de sus valores, se modificará la variableoriginal pasada como
argumento.Internamente, Python siempre realiza el paso de parámetros
através de referencias, es decir, se puede considerar que el paso serealiza
siempre por variable en el sentido de que no se hace una copiade las
variables. Sin embargo, tal y como hemos mencionado, elcomportamiento
de esta asignación de referencias depende de si elparámetro en cuestión es
mutable o inmutable. Este comportamientoen funciones es similar al que
ocurre cuando hacemos asignaciones devariables. Si trabajamos con tipos
inmutables y ejecutamos lassiguientes sentencias, observaremos cómo el
valor de la variable a semantiene:
>>> a = 3>>> b = a>>> b = 2>>> print("a={0}, b={1}".format(a, b))a=3,
b=2
Por otro lado, si aplicamos el mismo comportamiento a un tipomutable,
veremos cómo varía el resultado:
>>> a = [0, 1]>>> b = a>>> b[0] = 1>>> print("a={0}; b={1}".format(a,
b))a= [1, 1] ; b= [1, 1]
Si necesitamos modificar una o varias variables inmutables a travésde una
función, podemos hacerlo utilizando una técnica, consistenteen devolver
una tupla y asignar el resultado de la función a lasvariables. Partiendo del
ejemplo anterior, reescribiremos la función dela siguiente forma:
def test(a, b):a = 2b = 3return(a, b)
Posteriormente, realizaremos la llamada a la función y la
asignacióndirectamente con una sola sentencia:
>>> c, d = test (c, d)
    93/381
    A pesar de ser el comportamiento por defecto, es posible nomodificar el
parámetro mutable pasado como argumento. Para ello,basta con realizar,
cuando se llama a la función, una copia explícita dela variable:
>>> variable(lista[:])
Dado el manejo que realiza Python sobre el paso de parámetros afunciones,
en lugar de utilizar los términos por valor o por referencia, sería más exacto
decir que Python realiza el paso de parámetros porasignación.
Valores por defecto y nombres de parámetros
En Python es posible asignar un valor a un parámetro de unafunción. Esto
significa que, si en la correspondiente llamada a lafunción no pasamos
ningún parámetro, se utilizará el valor indicadopor defecto. El siguiente
código nos muestra un ejemplo:
>>> def fun(a, b=1):... print(b)>>> fun(4)1
Hasta ahora hemos visto cómo pasar diferentes argumentos a unafunción
según la posición que ocupan. Es decir, la correspondenciaentre parámetros
se realiza según el orden. Sin embargo, Pythontambién nos permite pasar
argumentos a funciones utilizandonombres y obviando la posición que
ocupan. Comprobémoslo a travésdel siguiente código:
>>> def fun(a, b, c):... print("a={0}, b={1}, c={2}".format(a, b, c))>>>
fun(c=5, b=3, a=1)a=1, b=3, c=5
Obviamente, podemos combinar valores por defecto y nombres
deargumentos para invocar a una función. Supongamos que definimos
lasiguiente función:
>>> def fun(a, b, c=4):... print("a={0}, b={1}, c={2}".format(a, b, c))
    94/381
    Dada la anterior función, las siguientes sentencias son válidas:
>>> fun(1, 2, 4)>>> fun(a=1, b=2, c=4)>>> fun(a=1, b=2)
>>> fun(1, 2)
Número indefinido de argumentos
Python nos permite crear funciones que acepten un númeroindefinido de
parámetros sin necesidad de que todos ellos aparezcanen la cabecera de la
función. Los operadores * y ** son los que seutilizan para esta
funcionalidad. En el primer caso, empleando eloperador *, Python recoge
los parámetros pasados a la función y losconvierte en una tupla. De esta
forma, con independencia del númerode parámetros que pasamos a la
función, esta solo necesita elmencionado operador y un nombre. A
continuación, un ejemplo quenos muestra esta funcionalidad:
>>> def fun(*items):... for ítem in ítems:... print(ítem)...>> fun(1, 2,
3)123>>> fun(5, 6)56>>> t = ('a', 'b', 'c')>>> fun(t)'a''b''c'
Como podemos comprobar, en el código anterior, elcomportamiento de la
función siempre es el mismo, conindependencia del número de argumentos
que pasamos.Por otro lado, gracias al operador ** podemos pasar
argumentosindicando un nombre para cada uno de ellos. Internamente,
Pythonconstruye un diccionario y los parámetros pasados a la función son
    95/381
    tratados como tal. El siguiente ejemplo nos muestra cómo empleareste
operador en la cabecera de una función:
>>> def fun(**params):... print(params)...>>> fun(x=5, y=8){'x': 5, 'y':
8}>>> fun(x=5, y=8, z=4){'x': 5, 'y': 8, 'z': 4}
También es posible que la cabecera de una función utilice uno ovarios
argumentos posicionales, seguidos del operador * o **. Esto
nosproporciona bastante flexibilidad a la hora de invocar a una
función.Observemos la siguiente función:
def print_record(nombre, apellido, **rec):print("Nombre: ",
nombre)print("Apellidos:", apellidos)for k in rec:print("{0}: {1}".format(k,
rec[k]))
Las siguientes invocaciones a la función recién definida seríanválidas:
>>> print_record("Juan", "Coll", edad=43, localidad="Madrid")>>>
print_record("Manuel", "Tip", edad=34)
Desempaquetado de argumentos
En el apartado anterior hemos aprendido a utilizar los operadores * y ** en
la cabecera de una función. Sin embargo, dichos operadorestambién pueden
ser empleados en la llamada a la función. Elcomportamiento es similar y la
técnica empleada se conoce como desempaquetado de argumentos. Por
ejemplo, supongamos que unafunción tiene en su cabecera tres parámetros
diferentes. En la llamadaa la misma, en lugar de utilizar tres valores,
podemos emplear eloperador * , tal y como muestra el siguiente código:
>>> def fun(x, y, z):... print(x, y, z)...>>> t = (1, 2, 3)>>> fun(*t)
    96/381
    1 2 3
En lugar de una tupla, el operador ** se basa en el uso de undiccionario.
Tomando como ejemplo la función definida previamente,veamos el
comportamiento de este operador:
>>> d = {'y': 1, 'z': 2, ’x': 0}>>> fun(**d)0 1 2
También es posible combinar el paso de parámetros con eloperador **
utilizando valores por defecto en la cabecera de la función:
>>>......>>>>>>3 4
def fun(a=1, b=2, c=3):print(a, b, c)
d = {'a': 3, ’b’: 4}fun(**d)1
Funciones con el mismo nombre
Python permite definir diferentes funciones con el mismo nombre
ydiferente número de argumentos. Sin embargo, su comportamiento
esdistinto al que hacen otros lenguajes de programación, como es elcaso de
Java. Si definimos más de una función como el mismo nombrey con el
mismo o diferente número de argumentos, Python emplearásiempre la
última que ha sido definida. Este comportamiento se debe aque Python trata
las funciones como un tipo determinado de objeto.De esta forma, al volver
a definir una función, estamos creando unnueva variable que será asignada
a un nuevo valor. Ilustremos estehecho con un ejemplo, donde vamos a
definir dos funciones con elmismo nombre y diferente número de
argumentos:
>>> def fun(x, y):... print(x, y)...>>> def fun(x):... print(x)...
Seguidamente invocaremos a la función pasando dos parámetros:
    97/381
    >>> fun(1, 3)Traceback (most recent cali last):File "<stdin>", line 1, in
<module>TypeError: fun() takes exactly 1 positional argument (2 given)
Como el lector habrá podido observar, el intérprete de Pythonmuestra un
error indicándonos que debemos pasar exactamente unúnico argumento. Sin
embargo, la siguiente llamada a la función esválida:
>>> fun(4)
Para comprobar que Python trata a las funciones como un tipo dedato
específico, basta con ejecutar la siguiente sentencia y echar unvistazo a su
resultado:
>>> type(fun)<class 'function'>
Funciones lambda
Al igual que otros lenguajes de programación, Python permite eluso de
funciones lambda. Este tipo especial de función se caracterizapor devolver
una función anónima cuando es asignada a una variable.Aquellos lectores
que hayan trabajado con Lisp, u otros lenguajesfuncionales, seguro que
están familiarizados con su uso. En definitiva,las funciones lambda ejecutan
una determinada expresión, aceptandoo no parámetros y devuelven un
resultado. A su vez, la llamada a estetipo de funciones puede ser utilizada
como parámetros para otras.En Python, las funciones lambda no pueden
contener bucles y nopueden utilizar la palabra clave return para devolver un
valor. Lasintaxis para este tipo de funciones es del siguiente tipo:
lambda <parámetros>:<expresión>
Técnicamente, las funciones lambda no son una sentencia, sino
unaexpresión. Esto las hace diferentes de las funciones definidas con def, ya
que estas siempre hacen que el intérprete las asocie a un
nombredeterminado, en lugar de simplemente devolver un resultado, tal
ycomo ocurre con las lambda.
    98/381
    En la práctica, la utilidad de las funciones lambda es que nospermite definir
una función directamente en el código que va a haceruso de ella. Es decir,
nos permite definir funciones inline. Esto puedeser útil, por ejemplo, para
definir una lista con diferentes acciones queserán ejecutadas bajo demanda.
Supongamos que necesitamosejecutar dos funciones diferentes pasando el
mismo parámetro,estando ambas funciones definidas en una determinada
lista. En lugarde definir tres funciones diferentes utilizando def, vamos a
emplearfunciones lambda:
>>> li = [lambda x: x + 2, lambda x: x + 3]>>> param = 4>>> for accion in
li:... print(accion(param))...45
A continuación, veremos un ejemplo de asignación de funciónlambda a una
variable y su posterior invocación:
>>> lam = lambda x: x*5>>> print(lam(3))15
Equivalente en funcionalidad al anterior ejemplo, sería el siguientecódigo:
>>> def lam(x):... return x*5...>>> print(lam(3))15
Para ilustrar el paso como parámetro de una función lambda a otrafunción
convencional presentaremos primero a la función integrada dePython
llamada map(). Esta función recibe dos parámetros, el primeroes una
función que debe ser ejecutada para cada uno de los elementosque contiene
el segundo parámetro. Como ejemplo tomaremos unalista con una serie de
valores y aplicaremos sobre cada uno de ellosuna simple función: sumar el
número dos. Después imprimiremos porla salida estándar el resultado. El
código en cuestión sería el queaparece a continuación:
>>> li = [1, 2, 3]
    99/381
    >>> new_li = map(lambda x: x+2, li)>>> for item in new_li:
print(item)...34
5
Tipos mutables como argumentos por defecto
Cuando fijamos el valor de un argumento en la cabecera de unafunción es
conveniente tener en cuenta que este debería ser de tipoinmutable. En caso
contrario podríamos obtener resultadosinesperados. Es decir, no se debe
emplear objetos como listas,diccionarios o instancias de clase como
argumentos por defecto deuna función. Observemos el siguiente ejemplo,
donde fijamos una listavacía como argumento por defecto:
>>> fun(x=1, 1i = []) :li.append(x)...>>> fun()[1]>>> fun()[1, 1]>>> fun(2)
[1, 1, 2]
¿Inesperado resultado? En lugar de devolver una lista con un únicovalor,
dado que la lista es inicializada en el argumento por defecto,Python
devuelve una lista con un nuevo valor cada vez que la funciónes invocada.
Esto se debe a que el intérprete crea los valores pordefecto cuando la
sentencia que define la función es ejecutada y nocuando la función es
invocada. Sin embargo, ¿por qué elcomportamiento de nuestro parámetro x
es diferente? Simplementeporque x es inmutable, mientras que li es
mutable. Así pues, la listapuede cambiar su valor, que es precisamente lo
que hace nuestrafunción de ejemplo, añadiendo un nuevo elemento cada
vez que lamisma es invocada.¿Y si deseamos precisamente inicializar una
lista cada vez quenuestra función ejemplo es invocada? Bastaría con una
sencilla técnica
    100/381
    consistente en utilizar el valor None por defecto para nuestroparámetro y en
crear una lista vacía cuando esto ocurre. El siguientecódigo muestra cómo
hacerlo:
>>>...............>>>[1]>>>[1]>>>[2]>>>>>>[3,
def fun(x=1, li=None):if lis is None:lis = []lis.append(x)print(lis)
fun()
fun()
fun(2)
li = [3, 4]fun(6, li)4, 6]
    101/381
    MÓDULOS Y PAQUETES
La organización del código es imprescindible para su
eficientemantenimiento y posible reutilización. Cuanto mayor es el número
deficheros que forman parte de un programa, más importante esmantener su
organización. Para ello, Python nos ofrece dos unidadesbásicas: los
módulos y los paquetes. Comenzaremos aprendiendosobre los primeros.
Módulos
Básicamente, un módulo de Python es un fichero codificado en estelenguaje
y cumple dos roles principales: permitir la reusabilidad decódigo y
mantener un espacio de nombres de variables único. Paraexplicar estos
roles, debemos pensar en cómo Python estructura elcódigo. Un script de
Python tiene un punto de entrada para suejecución, consta de varias
sentencias y puede tener definidasfunciones que serán invocadas durante su
ejecución. Sin embargo, sitenemos definidos dos scripts diferentes, en uno
de ellos podemosinvocar a una o varias funciones que existan en el otro.
Esto sería unejemplo básico de reutilización de código. ¿Qué ocurre si
tenemosdefinidas funciones con el mismo nombre en ambos scripts? Aquí
esdonde entra en juego el espacio de nombres único, ya que cadafichero es
por sí mismo un módulo y como tal mantiene la unicidad desu espacio.
Pongamos esta teoría en práctica a través del siguienteejemplo. En primer
lugar crearemos un fichero llamado first.py con elsiguiente código:
def say_hello():print("Hola Mundo!")print("Soy el primero")
A continuación, crearemos nuestro segundo fichero, al quellamaremos
second.py y añadiremos este código:
import first
    102/381
    first.say_hello()
Ahora utilizaremos el intérprete para ejecutar este último scriptcreado.
Desde la línea de comandos ejecutamos la siguiente sentencia:
$ python second.py
Siendo el resultado mostrado el siguiente:
Soy el primeroHola Mundo!
Como habrá observado el lector, hemos introducido una nuevasentencia
llamada import. Esta nos permite indicar qué módulo va a serutilizado. A
partir de esta sentencia, como hemos invocado a unmódulo con su propio
espacio de nombres, podremos invocar acualquier objeto que esté definido
en el primero. Para ello, hacemosuso del carácter punto que actúa como
separador entre módulo yobjeto. Dado que una función es un objeto, esta
puede ser utilizada ennuestro segundo script, igual como ocurriría, por
ejemplo, con unavariable.Además de la sentencia import, también podemos
utilizar from conun comportamiento similar. Ambas difieren en que la
segunda nospermitirá utilizar objetos sin necesidad de indicar el módulo al
quepertenecen. De esta forma, el script second.py puede reescribirse de
lasiguiente forma:
from first import say_hellosay_hello()
Cuando utilicemos from hemos de tener cuidado de que no existaun objeto
definido con el mismo nombre, en cuyo caso se utilizaría elúltimo en ser
definido. Básicamente, en la práctica, el uso de import yfrom dependerán de
este hecho.El operador * puede ser utilizado para importar todos y cada
unode los objetos declarados en un módulo. Así pues, con una
únicasentencia tendríamos acceso a todos ellos. Por otro lado, si
solonecesitamos importar unos cuantos, podemos separarlos utilizando
lacoma. Supongamos que nuestro primer script de ejemplo tuvieradefinidas
cinco funciones, pero nosotros solo necesitamos utilizar dosde ellas.
Bastaría con utilizar la siguiente sentencia:
    103/381
    from first import say_hello, say_bye
Adicionalmente, un módulo puede ser importado para serreferenciado con
un nombre diferente. Para ello, se utiliza la palabraclave as seguida del
nombre que deseamos emplear. Por ejemplo,supongamos que deseamos
importar nuestro módulo first con elnombre de primero, bastaría con la
siguiente sentencia:
import first as primero
A partir de la redefinición anterior, todas las referencias al módulo first se
realizarán a través de primero, tal y como muestra el siguienteejemplo:
primero.say_hello()
Volviendo a nuestro ejemplo inicial, tal y como habremosobservado, la
sentencia print("Soy el primero") ha sidoejecutada a pesar de no formar
parte de la función say_hello(). Paraexplicar este comportamiento, debemos
entender cómo funciona la importación de módulos en Python.
FUNCIONAMIENTO DE LA IMPORTACIÓN
Algunos lenguajes permiten trabajar de forma estructurada deforma análoga
a como lo hace Python. Se pueden crear componentesy desde unos ficheros
invocar a funciones y/o variables declaradas enotros. Sin embargo, a
diferencia, por ejemplo del lenguaje C, en Pythonla importación de un
módulo no es simplemente la inserción decódigo de un fichero en otro, ya
que esta es una operación que ocurreen tiempo de ejecución. En concreto,
se llevan a cabo tres pasosdurante la importación. El primero de ellos
localiza el fichero físico decódigo que corresponde al módulo en cuestión.
Para llevar a cabo estepaso, se utiliza un path de búsqueda determinado del
que nosocuparemos en el apartado siguiente de este capítulo. Una
vezlocalizado el fichero se procede a la generación del bytecode asociadoal
mismo. Este proceso ocurre en función de las fechas del fichero decódigo
(.py) y del bytecode asociado (.pyc) . Si la del primero esposterior a la del
segundo, entonces se vuelve a generar todoautomáticamente. El último paso
durante la importación es la
    104/381
    ejecución del código del módulo importado. Esta es la razón por lacual se
ejecuta la sentencia print de nuestro ejemplo.Dado que los pasos llevados a
cabo durante la importación puedenser costosos en tiempo, el intérprete de
Python solo importa cadamódulo una vez por proceso. Esto implica que
sucesivas importacionesse saltarán los tres pasos y el Intérprete pasará
directamente a utilizarel módulo contenido en memoria. No obstante,
siendo este elcomportamiento por defecto, Python nos permite emplear la
función reload del módulo imp, que se encuentra en la librería
estándar.Gracias a la cual, es posible indicar que se vuelva a realizar
laimportación de un determinado módulo.
PATH DE BÚSQUEDA
Anteriormente hemos mencionado que el primer paso del procesode
importación utiliza un path para la búsqueda. Es decir, el intérpretenecesita
saber desde qué localización o path del sistema de ficherosdebe comenzar a
buscar. Por defecto, el primer lugar donde busca esel directorio donde
reside el fichero que está siendo ejecutado. Asípues, dado que nuestros
scripts de ejemplo han sido creados en elmismo directorio, la importación
se realiza correctamente. El segundolugar que comprueba el intérprete es el
directorio referenciado por elvalor de la variable de entorno
PYTHONPATH. Esta variable puedeestar creada o no, dependiendo ello del
sistema operativo y de siestamos utilizando la línea de comandos.
Finalmente, se examinan losdirectorios de la librería estándar. A la misma,
dedicaremos el siguienteapartado.Gracias al path de búsqueda que emplea
el intérprete, es posibleimportar cualquier módulo con independencia de su
localización en elsistema de ficheros. Obviamente, para ello es necesaria la
definición dela mencionada variable PYTHONPATH. Además, esta variable
nosaporta bastante flexibilidad, ya que la misma puede ser modificada
entiempo de ejecución.Comprobar cuáles son los directorios actuales en el
path debúsqueda es fácil gracias a la lista path que pertenece al módulo sys
dela librería estándar de Python. Por ejemplo, ejecutemos las
siguientessentencias en Windows y observemos el resultado:
    105/381
    >>> import sys>>>
sys.path['','C:\\Windows\\system32\\python32.zip','C:\\Python32\\DLLs',
'C:\\Python32\\lib','C:\\Python32', 'C:\\Python32\\lib\\site-packages']
Dado que sys.path nos devuelve una lista, esta puede sermodificada en
tiempo de ejecución, logrando así cambiar losdirectorios por defecto
asociados al path de búsqueda.
LIBRERÍA ESTÁNDAR
Python incorpora una extensa colección de módulos puestaautomáticamente
a disposición del programador. A esta colección se leconoce con el nombre
de librería estándar y, entre los módulos queincorpora, encontramos
utilidades para interactuar con el sistemaoperativo, trabajar con expresiones
regulares, manejar protocolos dered como HTTP, FTP y SSL, leer y escribir
ficheros, comprimir ydescomprimir datos, manejar funciones criptográficas
y procesarficheros XML.Toda la información referente a la librería estándar
de Pythonpuede encontrarse en la página web oficial de la misma
(verreferencias).
Paquetes
Hasta ahora hemos visto la relación directa entre ficheros ymódulos. Sin
embargo, los directorios del sistema de ficheros tambiénpueden utilizarse en
la importación. Esto nos lleva al concepto de paquete, que no es sino un
directorio que contiene varios ficheros decódigo Python que guardan entre
sí una relación conceptual basada ensu funcionalidad. La peculiaridad del
intérprete es que,automáticamente, genera un espacio de nombres de
variables a partirde un directorio. Ello también afecta directamente a cada
subdirectorioque exista a partir del directorio en cuestión. De esta forma,
podemosorganizar más cómodamente nuestro código.Para crear nuestro
primer paquete, comenzaremos creando unnuevo directorio al que
llamaremos mypackage. Copiaremos dentro
    106/381
    del mismo nuestros scripts first.py y second.py. Por último, crearemosun
fichero vacío llamado __init__.py . Ya tenemos listo nuestro paquete,ahora
probaremos su funcionamiento a través de un nuevo fichero (main.py), que
debe estar fuera del directorio creado, con el siguientecontenido:
from mymodule import firstfirst.say_hello()
La ejecución del último script creado nos mostrará cómo esrealizado el
correspondiente import a través de nuestro paquete:
$ python main.pySoy el primeroHola Mundo!
Seguro que al lector no se le ha pasado por alto el nuevo fichero __init.py__
.Todos los directorios que vayan a ser empleados como paquetesdeben
contenerlo. Se trata de un fichero en el que podemos añadircódigo y que se
puede utilizar sin ninguno. En la práctica, este ficherose utiliza para incluir
aquellas sentencias que son necesarias pararealizar acciones de
inicialización. Por otro lado, el fichero __init__.py también se utiliza para
que Python considere los directorios delsistema de ficheros como
contenedores de módulos. De esta forma,cualquier directorio del sistema de
ficheros puede ser un paquete dePython, permitiéndose utilizar diferentes
niveles de subdirectorios. Lassentencias import y from son las que son
empleadas para importar losdiferentes módulos que forman parte de un
paquete.
COMENTARIOS
Al igual que en otros lenguajes de programación, en Pythonpodemos añadir
comentarios a nuestro código. En realidad, estoscomentarios son un tipo de
sentencias especiales que el intérpreteentiende que es código que no debe
ejecutar. Los comentarios sonmuy útiles para describir qué acción lleva a
cabo determinado código.En general, es una buena práctica de
programación documentarnuestro código añadiendo comentarios. Estos son
muy prácticos para
    107/381
    entender el funcionamiento del código, tanto para otrosprogramadores,
como para nosotros mismos. Es habitual, cuandotranscurre cierto tiempo,
olvidar qué hace un determinado trozo decódigo, aunque lo haya escrito el
mismo programador. De forma quelos comentarios nos serán muy útiles en
casos como este.Python utiliza dos tipos de comentarios, los que se utilizan
paracomentar una única línea y los que se emplean para más de una línea.El
siguiente ejemplo muestra cómo utilizar los del primer tipo:
# Esto es un comentarioprint("Hola Mundo!")
Por otro lado, para los comentarios multilínea, emplearemos trescomillas
dobles (") seguidas, tal y como muestra el siguiente ejemplo:
"""Comienzo de primera línea de comentarioSegunda línea de
comentarioTercera línea de comentario"""
EXCEPCIONES
El control de excepciones es un mecanismo que nos ofrece ellenguaje para
detectar ciertos eventos y modificar el flujo original delprograma en
ejecución. Habitualmente, estos eventos son errores queocurren en tipo de
ejecución. Este control de errores nos permitedetectarlos, realizar una serie
de acciones en consecuencia y modificarel flujo de nuestro
programa.Cuando un error ocurre en tiempo de ejecución, el intérprete
dePython aborta la ejecución del programa. Python disparaautomáticamente
un evento cuando uno de estos errores ocurre. Elcontrol de excepciones del
lenguaje nos permitirá comprobar si uno deestos eventos ha sido lanzado.
Además de controlar los errores, lasexcepciones nos permiten notificar el
evento que se genera comoconsecuencia del error producido.
Capturando excepciones
Para explicar cómo funciona la captura de excepciones, partiremos
    108/381
    de una lista que contiene dos elementos. Si intentamos acceder a lalista
utilizando el valor de índice 2, ocurrirá un error:
>>> li = [0, 1]>>> li [2]Traceback (most recent call last):File "<stdin>",
line 1, in <module>IndexError: list index out of range
Suponiendo que las líneas anteriores de código forman parte de
unprograma, lo que ocurriría al ejecutar la última de ellas, es que
elintérprete detendría automáticamente la ejecución del programa.
Paraevitar esto, podemos capturar la excepción IndexError, la cual ha
sidolanzada al detectar el error:
>>> try:... print(li[2])... except IndexError:... print("Error: índice no
válido")...Error índice no válido
Empleando el código anterior la ejecución no será detenida y elintérprete
continuará ejecutando el programa. Efectivamente, elbloque try/except es el
utilizado para capturar excepciones. Justodespués de la palabra clave except
debemos indicar el tipo deexcepción que deseamos detectar. Por defecto, si
no indicamosninguna, cualquier excepción será capturada. A partir de la
sentencia try, se tendrá en cuenta cualquier línea de código y si,
comoconsecuencia de la ejecución de una de ellas, se produce unaexcepción
serán ejecutadas las sentencias de código que aparecendentro de la
sentencia except. En concreto, la sintaxis de la cláusula try/except es como
sigue:
try:
<sentencias_susceptibles_de_lanzar_error>except [<Nombre_excepcion>]:
<sentencias_ejecutadas_cuando_error>finally:
<sentencias_ejecutadas_siempre>else:<sentencias_ejecutadas_si_no_error>
Como ejemplo para ilustrar la sintaxis de try/except basta conmodificar
ligeramente el último ejemplo de código:
    109/381
    try:
<li [2]except:<print("Error: índice no válido")else:<print("Sin
error")finally:<print("Bloque ejecutado")
Si ejecutamos el código anterior, comprobaremos cómo elintérprete lanzará
la primera y tercera sentencia print. Sin embargo, sisustituimos la sentencia
li[2] por li[0] observaremos cómo son las dosúltimas sentencias print las
ejecutadas.
Lanzando excepciones
En determinadas ocasiones puede ser interesante que lancemosuna
excepción concreta desde nuestro código. Podemos lanzarcualquiera
definida en el lenguaje o una propia que nosotros creemos.De este último
tipo nos ocuparemos en el siguiente apartado.La sentencia raise es la
propuesta por Python para lanzarexcepciones. El ejemplo más sencillo
posible, sería invocarladirectamente seguida del nombre de la excepción
que deseamoslanzar. A continuación, he aquí el código en cuestión para
nuestroejemplo:
>>> try:... raise TypeError... except:... print("Error de tipo")...Error de tipo
Es importante tener en cuenta que si lanzamos una excepción y estano es
capturada, será propagada hacia el nivel superior de códigohasta encontrar
un bloque que la maneje. Si ningún bloque la captura,el intérprete mostrará
el error y detendrá la ejecución del código.
Excepciones definidas por el usuario
Antes de continuar, el lector deberá entender los fundamentos dela
programación orientada a objetos. En concreto, es interesante saber
    110/381
    cómo implementar una clase y cómo funciona la herencia. El
siguientecapítulo está dedicado a cómo emplear este paradigma
deprogramación en Python.El programador puede crear sus propios tipos de
excepciones ylanzarlas cuando sea necesario. El nombre de excepción
definido por elusuario debe corresponder a una clase que herede de la clase
Exception, la cual está definida en la librería estándar de Python. Laclase en
cuestión contendrá las acciones que deben ser ejecutadascuando la
excepción es lanzada.Para utilizar las excepciones que definamos
necesitaremos emplear raise para que puedan ser lanzadas en un momento
determinado.Tengamos en cuenta que el intérprete no podrá
lanzarlasautomáticamente, es por ello que raise es necesario.Trabajemos
con un sencillo ejemplo donde crearemos y lanzaremosuna excepción a la
que llamaremos ValorIncorrecto. Se trata decomprobar si un número está en
un rango determinado de valores. Encaso afirmativo lanzaremos nuestra
excepción. El primer paso es definirnuestra clase:
class ValorIncorrecto(Exception):def __init__(self, val):print(("{0} no
permitido").format(val))
En este caso concreto, la excepción definida simplemente imprimiráun
mensaje informando de que el valor no está permitido.Obviamente,
podemos utilizar diferentes sentencias para llevar a cabodistintas acciones.
Por otro lado, el constructor de la clase utiliza unparámetro, que deberá ser
el valor que estamos comprobando.Observemos cómo lanzar la excepción
definida y lo que nos devuelveel intérprete:
>>> val = 8>>> if val > 5 and val < 9:... raise ValorIncorrecto(val)...8 no
permitidoTraceback (most recent call last):File "<stdin>", line 1, in
<module>__main__.ValorIncorrecto
Información sobre la excepción
    111/381
    Python nos permite trabajar con la información generada cuando seproduce
una excepción. Esto puede ser útil para indicarle al usuariodetalles sobre lo
ocurrido. Si además utilizamos un fichero de log paravolcar esta
información, tendremos una útil herramienta para detectarlo sucedido en
tiempo de ejecución.El módulo sys de la librería estándar dispone de una
funciónllamada exc_info() que nos devuelve una tupla con tres
valoresprincipales: el tipo de la excepción, la instancia de
clasecorrespondiente a la excepción y un objeto traceback que contienetoda
la información que existe en el stack en el punto en el que seprodujo la
excepción. Si volvemos al primer ejemplo con el quecomenzamos nuestro
apartado sobre excepciones y justo después dela sentencia except añadimos
el siguiente código, al ejecutarlo,veremos cómo el intérprete muestra la
información requerida:
>>> try:... print(li[2])... except IndexError:... import sys... sys.exc.info()...
(<class 'IndexError'>, IndexError('list assignmentindex out of range',),
<traceback object at 0x00000000022AB848>)
Si quisiéramos solo mostrar el error ocurrido o bien volcarlo a unfichero de
log, podríamos obtenerlo utilizando la siguiente sentencia:
sys.exc.info()[2]
El resultado de sustituir la anterior línea de código por la de sys.exc.info()
nos lanzaría el siguiente resultado:
IndexError('list assignment index out of range')
Aquí finaliza este capítulo que sienta las bases para comenzar aprogramar
con Python. Los siguientes capítulos del libro nos mostraráncómo
profundizar en el lenguaje, comenzando por el paradigma de laorientación a
objetos.
    112/381
    ORIENTACION A OBJETOS
INTRODUCCIÓN
Entre los paradigmas de programación soportados por Pythondestacan el
procedural, el funcional y la orientación a objetos. De esteúltimo y de su
aplicación en el lenguaje nos ocuparemos en estecapítulo.En líneas
generales, la orientación a objetos se basa en la definicióne interacción de
unas unidades mínimas de código, llamadas objetos,para el diseño y
desarrollo de aplicaciones. Este paradigma comenzó apopularizarse a
mediados de los años 90 y, en la actualidad, es uno delos más utilizados.
Algunos lenguajes, como Java, están basadoscompletamente en el uso del
mismo. Otros como Ruby, C++ y PHP5también ofrecen un amplio y
completo soporte de la orientación aobjetos. A este paradigma se le conoce
también por sus siglas eninglés: OOP (Object Oriented Programming).La
orientación a objetos es ampliamente utilizada por losprogramadores de
Python, especialmente en aquellos proyectos que,por su tamaño, requieren
de una buena organización que ayude a sumantenimiento. Además,
modernos componentes, como son losframeworks web, hacen un uso
intensivo de este paradigma. Entre lasclaras ventajas de la orientación a
objetos podemos destacar lareusabilidad de código, la modularidad y la
facilidad para modelarcomponentes del mundo real.A los programadores de
C++ y Java les resultará familiar laterminología y conceptos explicados en
este capítulo. Sin embargo,deberán prestar atención a las diferencias que
presenta Python conrespecto a estos lenguajes.Para aquellos lectores que
nunca han trabajado con orientación aobjetos, recomendamos la lectura de
la página de Wikipediacorrespondiente (ver referencias).En este capítulo
veremos cómo definir y utilizar clases y objetos.Explicaremos qué tipos de
variables y métodos pueden ser declaradosen la definición de una clase.
Descubriremos cuáles son las diferencias
    113/381
    que presenta Python con respecto a otros lenguajes a la hora detrabajar con
el paradigma de la OOP. Los dos últimos apartados estándedicados a dos
conceptos fundamentales en la orientación a objetos:la herencia y el
polimorfismo. Comencemos aprendiendo cómo definirclases y cómo
instanciar objetos de las mismas.
    114/381
    CLASES Y OBJETOS
Hasta ahora hemos utilizado indistintamente el concepto de objetocomo
tipo o estructura de datos. Sin embargo, dentro del contexto dela OOP, un
objeto es un componente que tiene un rol específico y quepuede interactuar
con otros. En realidad, se trata de establecer unaequivalencia entre un
objeto del mundo real con un componentesoftware. De esta forma, el
modelado para la representación yresolución de problemas del mundo real a
través de la programación,es más sencillo e intuitivo. Si echamos un vistazo
a nuestro alrededor,todos los objetos presentan dos componentes
principales: un conjuntode características y propiedades y un
comportamiento determinado.Por ejemplo, pensemos en una moto. Esta
tiene características comocolor, marca y modelo. Asimismo, su
comportamiento puede serdescrito en función de las operaciones que puede
realizar, porejemplo, frenar, acelerar o girar. De la misma forma, en
programación,un objeto puede tener atributos y se pueden definir
operaciones quepuede realizar. Los atributos equivaldrían a las
características de losobjetos del mundo real y las operaciones se relacionan
con sucomportamiento.Es importante distinguir entre clase y objeto. Para
ello,introduciremos un nuevo concepto: la instancia. La definición de
losatributos y operaciones de un objeto se lleva a cabo a través de unaclase.
La instanciación es un mecanismo que nos permite crear unobjeto que
pertenece a una determinada clase. De esta forma,podemos tener diferentes
objetos que pertenezcan a la misma clase.Crear una clase en Python es tan
sencillo como emplear la sentencia class seguida de un nombre. Por
convención, el nombre de la claseempieza en mayúscula. También suele
estar contenida en un ficherodel mismo nombre de la clase, pero todo en
minúscula. La clase mássencilla que podemos crear en Python es la
siguiente:
class First:pass
Una vez que tenemos la clase definida, crearemos un objeto
    115/381
    utilizando la siguiente línea de código:
a = First ()
Al igual que otros lenguajes de programación, al declarar una clasepodemos
hacer uso de un método constructor que se encargue derealizar tareas de
inicialización. Este método siempre es ejecutadocuando se crea un objeto.
Python difiere con otros lenguajes aldisponer de dos métodos involucrados
en la creación de un objeto. Elprimero de ellos se llama __init__ y el
segundo __new__ . Este último esel primero en ser invocado al crear el
objeto. Después, se invoca a __init__ que es el que habitualmente se emplea
para ejecutar todas lasoperaciones iniciales que necesitemos. En la práctica
utilizaremos __init__ como nuestro método constructor. Sobre el método
__new__ nos ocuparemos en el apartado "Métodos especiales" del
presentecapítulo. Así pues, nuestra clase con su constructor quedaría de
lasiguiente forma:
class First:def __init__ (self):print("Constructor ejecutado")
Al crear una nueva instancia de la clase, es decir, un nuevo objeto,veríamos
cómo se imprime el mensaje:
>>> f = First()Constructor ejecutado
Seguro que el lector se ha percatado del parámetro formal quecontiene
nuestro método constructor, nos referimos a self. Esteparámetro contiene
una referencia al objeto que ejecuta el método y,por lo tanto, puede ser
utilizada para acceder al espacio de nombresdel objeto. Debemos tener en
cuenta que cada objeto contiene supropio espacio de nombres. Los métodos
de instancia deben contenereste parámetro y debe ser el primero en el
orden. Otros lenguajesutilizan un mecanismo similar a través de la palabra
clave this, porejemplo PHP; sin embargo, en Python self no es una palabra
clave ypodemos emplear cualquier nombre para esta referencia. Sin
embargo,esto no es aconsejable, ya que, por convención, está
ampliamenteaceptado y arraigado el nombre de self para este propósito. Por
otrolado, cuando se invoca a un método, no es necesario incluir la
    116/381
    mencionada referencia. Como ejemplo, modifiquemos nuestra
claseañadiendo el siguiente método:
def nuevo(self):print("Soy nuevo")
Posteriormente, creemos un nuevo objeto de nuestra clase einvoquemos al
método recién creado:
>>> f = First()Constructor ejecutado>>> f.nuevo()Soy nuevo
Variables de instancia
Anteriormente hemos mencionado a los atributos y los hemosseñalado
como la correspondencia a las características de los objetosdel mundo real.
En la terminología de Python, a estos atributos se lesdenomina variables de
instancia y siempre deben ir precedidos de lareferencia self. Volviendo a
nuestro ejemplo de la moto, modelemosuna clase que contenga unas
cuantas variables de instancia:
class Moto:def __init__ (self, marca, modelo, color):self.marca =
marcaself.modelo = modeloself.color = color
Los atributos de nuestra clase son creados al inicializar el objeto,utilizando
tres variables diferentes que son pasadas como parámetrosdel constructor.
De esta forma, la siguiente creación e inicialización deobjetos sería válida:
>>> bmw_r1000 = Moto("BMW", "r1OO", "blanca")>>> suzuki_gsx =
Moto("Suzuki", "GSX", "negra")
Dado que self es empleado en todos los métodos de instancia,podemos
acceder a los atributos en cualquiera de estos métodos. Dehecho esta es una
de las ventajas de usar self, ya que, cuando lohacemos seguido de un
atributo, tenemos la seguridad de que nosreferimos al mismo y no a una
variable definida en el espacio de
    117/381
    nombres del método en cuestión. Para ilustrar este
comportamiento,añadamos el siguiente nuevo método:
def get_marca(self):marca = "Nueva marca"print(self.marca)
Ahora creemos volvamos a crear un objeto e invoquemos a estemétodo:
>>> honda_cbr = Moto("Honda", "CBR", "roja")>>>
honda_cbr.get_marca()Honda
Métodos de instancia
Este tipo de métodos son aquellos que, en la práctica, definen
lasoperaciones que pueden realizar los objetos. De hecho, nuestroejemplo
anterior (get_marca) es un ejemplo de ello. Habitualmente aeste tipo de
métodos se les llama simplemente métodos, aunque, tal ycomo veremos en
sucesivos apartados, existen otros tipos de métodosque pueden definirse en
una clase.Además de self, un método de instancia puede recibir un número
n de parámetros. Una vez más, volvamos a nuestro ejemplo de la claseMoto
y añadamos un nuevo método que nos permita acelerar:
def acelerar(self, km):print("acelerando {0} km".format(km))
La llamada al nuevo método quedaría de la siguiente forma:
>>> honda_cbr.acelerar(20)acelarando 20 km
Formalmente, un método de instancia es una función que se definedentro de
una clase y cuyo primer argumento es siempre unareferencia a la instancia
que lo invoca.
Variables de clase
    118/381
    Relacionados con los atributos, también existen en Python lasvariables de
clase. Estas se caracterizan porque no forman parte de unainstancia
concreta, sino de la clase en general. Esto implica que puedenser invocados
sin necesidad de hacerlo a través de una instancia. Paradeclararlas bastan
con crear una variable justo después de la definiciónde la clase y fuera de
cualquier método. A continuación, veamos unejemplo que declara la
variable n_ruedas:
class Moto:n_ruedas = 2def __init__(self, marca, modelo, color):self.marca
= marcaself.modelo = modeloself.color = color
Así pues, podemos invocar a la variable de clase directamente:
>>> Moto.n_ruedas2
Además, una instancia también puede hacer uso de la variable declase:
>>> m = Moto()>>> m.n_ruedas2
En realidad, estas variables de clase de Python son parecidas a
lasdeclaradas en Java o C++ a través de la palabra clave static. Sinembargo,
y a diferencia de estos lenguajes, las variables de clasepueden ser
modificadas. Esto se debe a cómo Python trata lavisibilidad de
componentes, aspecto del que nos ocuparemospróximamente.
Propiedades
Las variables de instancia, también llamados atributos, definen unaserie de
características que poseen los objetos. Como hemos vistoanteriormente, se
declaran haciendo uso de la referencia a la instanciaa través de self. Sin
embargo, Python nos ofrece la posibilidad deutilizar un método alternativo
que resulta especialmente útil cuando
    119/381
    estos atributos requieren de un procesamiento inicial en el momentode ser
accedidos. Para implementar este mecanismo, Python empleaun decorador
llamado property. Este decorador o decorator es,básicamente, un
modificador que permite ejecutar una versiónmodificada o decorada de una
función o método concreto. En elsiguiente capítulo entraremos en detalle en
la explicación de sufuncionamiento. De momento y para entender cómo
funciona eldecorador property, nos bastará con la descripción recién
realizada.Supongamos que tenemos una clase que representa un círculo
ydeseamos tener un atributo que se refiera al área del mismo. Dado queesta
área puede ser calculada en función del radio del círculo, parecelógico
utilizar property para llevar a cabo el procesamiento requerido.Teniendo
esto en cuenta, nuestra clase quedaría de la siguiente forma:
from math import piclass Circle:def __init__(self, radio):self.radio =
radio@propertydef area(self):return pi * (self.radio ** 2)
Dada la anterior definición, podemos acceder directamente a área, tal y
como lo haríamos con cualquier variable de instancia. El siguienteejemplo
muestra cómo hacerlo:
>>> c = Circle(25)>>> print(c.area)1661.9025137490005
Alternativamente, en lugar de emplear el decorator, también esposible
definir un método en la clase y luego emplear la función property() para
convertirlo en un atributo. Si optamos por estemecanismo, el código
equivalente al método decorado sería elsiguiente:
def area (self) :return pi * (self.radio ** 2)area = property(area)
Ya que la declaración y uso del decorator es más sencilla y fácil deleer,
recomendamos su utilización en detrimento de la mencionadafunción
property().
    120/381
    Seguro que los programadores de Java están habituados a utilizarmétodos
setters y getters para acceder a atributos de clase. En Java yotros lenguajes
similares, se emplea una técnica diferente a la que seusa en Python.
Normalmente, sobre todo en Java, los atributos deinstancia se declaran con
el modificador de visibilidad private y seemplea un método para acceder al
atributo y otro para modificarlo. Alos del primer tipo se les llama getter y a
los del segundo setter. Habitualmente, se emplea la nomenclatura
get<Nombre_atributo> y set<Nombre_atributo>. Por ejemplo, tendríamos
un método getRadio() y otro llamado setRadio(). Nuestro decorador en
Python, de momento, solo nos sirve paraacceder al atributo en cuestión,
pero no para modificarlo. De hecho, alejecutar la siguiente sentencia,
obtendremos un error:
>>> c.area = 23Traceback (most recent cali last) :File
"Dropbox\libro_python\code\circle.py", line 17,
in<module>print(c.area)File "Dropbox\libro_python\code\circle.py", line
10, in areareturn self.__areaAttributeError: 'Circle' object has no attribute
'_Circle area'
Para poder llevar a cabo esta acción, Python permite utilizar otrosmétodos
disponibles a través del decorado property. En nuestroejemplo estamos
calculando el área y devolviendo su valordirectamente a través del
decorador, pero, para ilustrar el uso de setters y getters en Python, nos
centraremos en el atributo radio denuestra clase Circulo. Haremos esto,
simplemente porque tiene mássentido hacerlo con el radio que no es un
campo calculable. Volviendoa nuestro objetivo, para modificar el atributo
radio haremos una seriede cambios en nuestra clase original. En primer
lugar eliminaremos elconstructor de la misma. Después, añadiremos el
siguiente método,donde, hemos creado un atributo privado empleando el
doble guiónbajo (__:
@propertydef radius(self) :return self.__radio
Seguidamente, escribiremos el método que se encargará de lamodificación
de nuestro nuevo atributo de clase. El código es este:
    121/381
    @radio.setterdef radio(self, radio):self. radio = radio
Ahora veremos cómo emplear nuestra clase con estos cambios,para ello
simplemente instanciaremos un nuevo objeto ymodificaremos su valor:
>>> circulo = Circulo()>>> circulo.radio = 23>>> print(circulo.radio)23
El lector se habrá dado cuenta de que hemos utilizado el conceptode
atributo privado, lo que implica que estamos trabajando con unmodificador
de visibilidad. Para entender cómo realmente funciona lamisma en Python,
pasaremos al siguiente apartado.
Visibilidad
Uno de los aspectos principales en los que difiere la orientación aobjetos de
Python con respecto a otros lenguajes, como Java y PHP5,es en el concepto
de visibilidad de componentes de una clase. EnPython no contamos con
modificadores como public o private ysimplemente, todos los métodos y
atributos pueden considerarsecomo public. Es decir, realmente no se puede
impedir el acceso a unobjeto o atributo desde la instancia de una clase
concreta. Pero ¿esposible de alguna forma indicar que un atributo o método
solo debeser invocado desde la misma clase? Esto correspondería a utilizar
elmodificador private en lenguajes como PHP5 y Java. La respuesta a
lapregunta es sí, pero debemos tener en cuenta que hablamos de debe yno
de puede. De esta forma, los programadores de Python utilizan unasencilla
convención: Si un atributo debe ser privado, basta conanteponer un único
guión bajo (underscore). Sin embargo, paraPython, esto no significa nada,
simplemente es una convención entreprogramadores.No perdamos de vista
que lo comentado sobre la visibilidad esaplicable, tanto a variables como a
métodos. Así pues, si utilizamos elunderscore para declarar un método
como privado, veremos cómo eso
    122/381
    no impide su acceso. Trabajemos con un sencillo ejemplo, declarandouna
clase y su correspondiente método:
class Test:def _privado(self) :print("Método privado")
Ahora pasemos a crear una instancia y observaremos cómo lallamada al
método en cuestión es válida:
>>> t = Test()>>> t._privadoMétodo privado
Por otro lado, en el apartado anterior hemos visto cómo el dobleguión bajo
(__) ha sido empleado para declarar un atributo privado deinstancia.
Realmente, si el atributo ha sido declarado de esta forma,Python previene el
acceso desde la instancia. Observemos la siguienteclase que declara un
atributo de esta forma y comprobemos cómoPython no nos deja acceder al
mismo:
class Privado:def __ini__(self):self.__atributo = 1>>> p = Privado>>>
p.__atributoTraceback (most recent call last):File "<stdin>", line 1, in
<module>AttributeError: 'Privado' object has no attribute '__atributo'
Sin embargo, para acceder a este atributo y dado que Python enrealidad no
entiende el concepto de visibilidad como tal, el lenguajeemplea un
mecanismo técnicamente denominado name mangling. Este consiste en
convertir cualquier atributo declarado con doble guiónen simple guión
seguido del nombre de la clase y luego doble guiónbajo para acceso directo
al atributo. Así pues y continuando connuestra clase Privado, la forma de
acceder a nuestro atributo sería lasiguiente:
>>> p_Privado__atributo12
Algunos prefieren no emplear la denominación privado parareferirse a este
tipo de atributos. En su lugar, prefieren llamarlos pseudoprivados.
    123/381
    La técnica del name mangling se diseñó en Python para asegurarque una
clase hija no sobrescribe accidentalmente los métodos y/oatributos de su
clase padre. Esta es la verdadera razón de su existencia,no la de poder
utilizar un modificador de visibilidad. Acabamos deintroducir el concepto
de hija y padre para referirnos a dos tipos declases diferentes. Estos
conceptos se engloban dentro del ámbito de la herencia, de la que nos
ocuparemos detenidamente en apartadosposteriores. El código que viene a
continuación ilustra cómo evitar lamencionada sobrescritura del atributo
test:
class Padre:def __init__(self):self.__test = "Padre"class Hijo:def
__init__(self):self.__test = "Hijo"
Sin aplicar la técnica del name mangling, en el ejemplo anterior, elatributo
test de la clase padre sería sobrescrito por el de la clase hija yotra clase que
heredara de la padre se vería afectada.
Métodos de clase
Ya hemos aprendido sobre atributos y métodos de instancia. PeroPython
también permite crear métodos de clase. Estos se caracterizanporque
pueden ser invocados directamente sobre la clase, sinnecesidad de crear
ninguna instancia.Dado que no vamos a utilizar un objeto, no es necesario
emplear elargumento self como referencia en los métodos de clase. En su
lugar, síque debemos contar con otro parámetro similar que referenciará a
laclase en cuestión. Por convención, al igual que se emplea self para
losmétodos de instancia, cls es el nombre que suele ser empleado
comoreferencia en los métodos de clase. Además, esto implica que
elprogramador no tiene que pasar como parámetro la mencionadareferencia,
ya que Python lo hace automáticamente. Eso sí, esresponsabilidad del
programador utilizar el parámetro formal, paradicha referencia, en la
definición del método. Esto es válido tanto para self como para cls.Por otro
lado y al igual que en el caso de los métodos de instancia,
    124/381
    se pueden utilizar parámetros adicionales para el método de clase. Loque sí
que es importante que nunca olvidemos la referencia cls en sudefinición. El
resto de parámetros son opcionales.Los métodos de clase requieren el uso
de un decorador definidopor Python, su nombre es classmethod y funciona
de forma similar acomo lo hace el comentado property. Gracias al
decorator, solodebemos definir el método con el argumento referencia cls y
escribirsu funcionalidad. Sirva como ejemplo el siguiente código:
class Test:def __init__(self):self.x = 8@classmethoddef metodo_clase(cls,
param1):print("Parámetro: {0}".format(param1))
Para poner en práctica el código anterior, primero invocaremosdirectamente
al método de clase:
>>> Test.metodo_clase(6)Parámetro: 6
También es fácil comprobar que el método de instancia puede serinvocado,
creando previamente un objeto de la clase en cuestión:
>>> t = Test ()>>> print(t.x)8
Es interesante saber que los métodos de clase también pueden serinvocados
a través de una instancia de la misma. Es tan sencillo comoinvocarlo como
si de un método de instancia se tratase. Siguiendo conel ejemplo anterior, la
siguiente sentencia es válida y produce elresultado que podemos apreciar:
>>> t.metodo_clase(5)Párametro: 5
Lógicamente, si un método de clase puede ser invocado desde unainstancia,
también podemos crear un objeto e invocar directamente almétodo en
cuestión en la misma línea de código:
>>> Test().metodo_clase(3)Párametro: 3
    125/381
    El comportamiento anteriormente explicado tiene sentido, ya que,en
realidad, Python no tiene modificadores de visibilidad como tales.Es por
ello, que un método de clase es también un método deinstancia. La
diferencia es que un método de instancia no puede sercreado si esta no
existe.Lenguajes como Java y C++ emplean la palabra clave static
paradeclarar métodos de clase. Además, en estos lenguajes, no es
posibleutilizar la referencia a la instancia, llamada en ambos casos this.
Sinembargo, estos no pueden ser invocados desde una instancia, adiferencia
de Python.Otros lenguajes como Ruby y Smalltalk no tienen métodos
estáticos, pero sí de clase. Esto implica que estos métodos sí que
tienenacceso a los datos de la instancia. Python cuenta tanto con métodosde
clase como con métodos estáticos. De estos últimos nosocuparemos a
continuación.
Métodos estáticos
Otro de los tipos de métodos que puede contener una clase enPython son los
llamados estáticos. La principal diferencia con respectoa los métodos de
clase es que los estáticos no necesitan ningúnargumento como referencia, ni
a la instancia, ni a la clase.Lógicamente, al no tener esta referencia, un
método estático no puedeacceder a ningún atributo de la clase.De la misma
forma que los métodos de clase en Python usan eldecorador classmethod,
para los estáticos contamos con otrodecorador llamado staticmethod. La
definición de un método estáticorequiere del uso de este decorador, que
debe aparecer antes de ladefinición del mismo.Para comprobar cómo
funcionan los métodos estáticos, añadamosel siguiente método a nuestra
clase Test:
@staticmethoddef metodo_estatico(valor):print("Valor: {0}".format(valor))
Seguidamente, invoquémoslo directamente desde una instanciadeterminada
de la clase:
    126/381
    >>> t = Test()>>> t.metodo_ estático (33)Valor: 33
Echando un vistazo al código anterior, podemos apreciar que ladiferencia de
un método estático y otro de instancia es, además deluso del decorado en
cuestión, que como primer parámetro no estamosusando self para
referenciar a la instancia. Pero este hecho tiene otraimplicación, tal y como
hemos comentado previamente. Se trata deque no es posible acceder desde
el mismo a un atributo de clase. Asípues si cambiamos el código del
método anterior por el siguiente, seproduciría una excepción en tiempo de
ejecución:
@staticmethoddef metodo_estatico(valor):print(self.x)
El error en cuestión, producido por la ejecución del método estáticodesde
una instancia, sería el siguiente:
NameError: global name self is not defined
Es lógico que se produzca el error, no olvidemos que self es solouna
convención para referenciar a la instancia de clase. Dado que elmétodo es
estático y no existe tal referencia en el mismo, no es posibleutilizar ningún
atributo de instancia en su interior. NameError es unaexcepción predefinida
por Python y que es lanzada como consecuenciade intentar acceder al
atributo.Algunos programadores prefieren crear una función en lugar de
unmétodo estático. Se puede llamar a la función y utilizar su
resultadointeractuando posteriormente con la instancia de la clase.
Sinembargo, otros prefieren emplear los métodos estáticos,argumentando
que, si la funcionalidad está estrechamente relacionadacon la clase, se
mantiene y cumple el principio de abstracción (verreferencias),
comúnmente utilizado en la programación orientada aobjetos.
Métodos especiales
Python pone a nuestra disposición una serie de métodos especiales
    127/381
    que, por defecto, contendrán todas las clases que creemos. Enrealidad,
cualquier clase que creemos será hija de una clase especialintegrada
llamada object. De esta forma, dado que esta clase cuentapor defecto con
estos métodos especiales, ambos estarán disponiblesen nuestros nuevos
objetos. Es más, podemos sobrescribir estosmétodos reemplazando su
funcionalidad.Cuando creamos una nueva clase esta hereda directamente de
object, por lo que no es necesario indicar esto explícitamente alinstanciar un
objeto de una clase concreta.Los métodos especiales se caracterizan
principalmente porqueutilizan dos caracteres underscore al principio y al
final del nombre delmétodo.
CREACIÓN E INICIALIZACIÓN
El principal de estos métodos especiales lo hemos presentado enapartados
anteriores, se trata de __init__(). Tal y como hemoscomentado previamente,
este todo será el encargado de realizar lastareas de inicialización cuando la
instancia de una clase determinada escreada. Necesita utilizar como
parámetro formal una referencia (self) ala instancia y, adicionalmente,
pueden contener otros parámetros.Por otro lado, y relacionado con
__init__() , encontramos a __new__() .En muchos lenguajes la operación de
creación e inicialización deinstancia es atómica; sin embargo, en Python
esto es diferente. Primerose invoca a __new__() para crear la instancia
propiamente dicha yposteriormente se llama a __init__() para llevar a cabo
las operacionesque sean requeridas para inicializar la misma. En la práctica,
sueleutilizarse __init__() como constructor de instancia, de la misma
formaque en Java, por ejemplo, se emplea un método que tiene el
mismonombre que la clase que lo contiene. Sin embargo, gracias a
estemecanismo de Python, podemos tener más control sobre lasoperaciones
de creación e inicialización de objetos. Dado quecualquier clase los
contendrá al heredar de object, estos métodospodrán ser sobrescritos con la
funcionalidad que deseemos.A diferencia de __init__() , __new__() no
requiere de self, en su lugarrecibe una referencia a la clase que lo está
invocando. Realmente, eseste un método estático que siempre devuelve un
objeto.
    128/381
    El método __new__() fue diseñado para permitir la personalizaciónen la
creación de instancias de clases que heredan de aquellas que soninmutables.
Recordemos que para Python algunos tipos integradosson inmutables,
como, por ejemplo, los strings y las tuplas. Es importante tener en cuenta
que el método __new__() solo llamaráautomáticamente a __init__() si el
primero devuelve una instancia. Espráctica habitual realizar ciertas
funciones de personalización dentrode __new__ y luego llamar al método
del mismo nombre de la clasepadre. De esta forma, nos aseguraremos que
nuestro __init__() seráejecutado.Con el objetivo de comprender cómo
realmente funcionan losmétodos de creación de instancias e inicialización
de las mismas,vamos a crear una clase que contenga ambos métodos:
class Ini :def __new__(cls):print("new")return super(Test,
cls).__new__(cls)def __init__(self):print("init")
Ahora crearemos una instancia y observaremos el resultadoproducido que
nos indicará el orden en el que ambos métodos hansido ejecutados:
>>> obj = Ini()newinit
¿Qué pasaría si el método __new__() de la clase anterior nodevolviera una
instancia? La respuesta es sencilla: nuestro método __init__() nunca sería
ejecutado. Realizar esta prueba es bastantesencillo, basta con borrar la
última línea, la que contiene la sentencia return del método __new__ . Al
volver a crear la instancia,comprobaremos cómo solo obtenemos como
salida la cadena de texto"init".No olvidemos que cuando trabajamos con
__new__() y este recibeparámetros adicionales, estos mismos deben constar
como parámetrosformales en el método __init__() . Sirva como ejemplo el
siguientecódigo:
def __new__(cls, x):return super(Test, cls).__new__(cls)
    129/381
    def __init__(self, x):self.x = x
La invocación a la creación de un objeto de la clase que contendrálos
módulos anteriores podría ser de la siguiente forma:
>>> t = Test("param")
DESTRUCTOR
Al igual que otros lenguajes de programación, Python cuenta conun método
especial que puede ejecutar acciones cuando el objeto va aser destruido. A
diferencia de lenguajes como C++, Python incorporaun recolector de basura
(garbage collector) que se encarga de llamar aeste destructor y liberar
memoria cuando el objeto no es necesario.Este proceso es transparente y no
es necesario invocar al destructorpara liberar memoria. Sin embargo, este
método destructor puederealizar acciones adicionales si lo sobrescribimos
en nuestras clases. Sunombre es __del__() y, habitualmente, nunca se le
llama explícitamente.Debemos tener en cuenta que la función integrada
del() no llamadirectamente al destructor de un objeto, sino que esta sirve
paradecrementar el contador de referencias al objeto. Por otro lado,
elmétodo especial __del__() es invocado cuando el contador dereferencias
llega a cero.Volviendo a nuestra clase ejemplo anterior (Ini) podemos
añadirle elsiguiente método, para posteriormente crear una nueva instancia
yobservar cómo el destructor es llamado automáticamente por elintérprete.
A continuación, el código del método en cuestión:
def __del__(self):print("del")
En lugar de utilizar directamente el intérprete a través de la consolade
comandos, vamos a crear un fichero que contenga nuestra clasecompleta,
incluyendo el destructor. Seguidamente invocaremos alintérprete de Python
para que ejecute nuestro script y comprobaremoscómo la salida nos muestra
la cadena del como consecuencia de laejecución del destructor por parte del
recolector de basura. Al terminarla ejecución del script y no ser necesario el
objeto, el intérprete invocaal recolector de basura, que, a su vez, llama
directamente al
    130/381
    constructor. Sin embargo, esta situación varía si empleamos la consolade
comandos y creamos la instancia desde la misma, ya que eldestructor no
será ejecutado inmediatamente. La explicación es trivial:el intérprete no
invocará al recolector hasta que no sea necesario, si lohiciera antes de
tiempo, perderíamos la referencia a nuestro objeto.Cuando ejecutamos el
script, el intérprete detectada que se haproducido la finalización del mismo,
que ya no es necesaria lamemoria, puesto que el objeto no va a ser utilizado
y que puedeproceder a la llamada al recolector de basura.
REPRESENTACIÓN Y FORMATOS
En algunas ocasiones puede ser útil obtener una representación deun objeto,
que nos sirva, por ejemplo, para volver a crear un objetocon los mismos
valores que el original. La representación de un objetopuede obtenerse a
través de la función integrada repr(). Por ejemplo, sideclaramos un string
con un valor determinado e invocamos a lamencionada función,
conseguiremos el valor de la cadena. De hecho,en el caso de un string este
es el único valor que necesitamos pararecrear el objeto. Sin embargo, la
situación cambia cuando creamosnuestras propias clases. En este caso,
deberemos emplear el métodoespecial __repr__() , el cual será invocado
directamente cuando se llamaa la función repr(). Pensemos en una clase que
representa a un coche,donde sus atributos principales son la marca y el
modelo. Dado queestos son los únicos valores que necesitamos para recrear
el objeto,serán los que utilizaremos en nuestro método __repr__() , tal y
comomuestra el siguiente ejemplo:
class Coche:def __init__(marca="Porsche", modelo="911")self.marca =
marcaself.modelo = modelodef __repr__(self):return("{0}-
{1}".format(self.marca, self.modelo))
Al crear un objeto e invocar a la mencionada función derepresentación,
obtendremos el siguiente resultado:
>>> c = Coche()>>> print(repr(c))
    131/381
    Con la información devuelta por la función repr podríamos crear unnuevo
objeto con los mismos valores que el original:
>>> arr = repr(c).split("-")>>> d = Coche(arr[0], arr[1])
El método especial __repr__() siempre debe devolver un string, encaso
contrario se lanzará una excepción de tipo TypeError. Habitualmente, esta
representación de un objeto se emplea parapoder depurar código y
encontrar posibles errores.Relacionado con __repr__() encontramos otro
método especialdenominado __str__() , el cual es invocado cuando se llama
a lasfunciones integradas str() y print(), pasando como argumento unobjeto.
El método __str__() puede ser considerado también unarepresentación del
objeto en cuestión, la diferencia con __repr__() radica en que el primero
debe devolver una cadena de texto que nossirva para identificar y
representar al objeto de una forma sencilla yconcisa. De esta forma, la
información que devuelve suele ser una líneade texto descriptiva o una
cadena similar. Al igual que __repr__() , elmétodo __str__ debe devolver
siempre un string. Para nuestra clase deejemplo Coche, bastará con añadir
el siguiente código:
def __str__(self):return("{0} -> {1}".format(self.marca, self.modelo)
Si llamamos a la función print(), apreciaremos el resultado:
>>> print(d)"Porsche->911"
En nuestros ejemplos para la clase Coche no existe muchadiferencia entre
las cadenas que devuelven ambos métodos derepresentación; sin embargo,
si la clase es muy compleja, sí que es másfácil establecer diferencias entre
los resultados devueltos. No obstante,esto siempre queda a criterio del
programador.Similar a __str__ también existe el método especial
__bytes()__ quedevuelve también una representación del objeto utilizando
el tipopredefinido bytes. Este método será ejecutado cuando llamamos a
lafunción integrada bytes(), pasando como argumento un objeto.Por último,
__format__() es otro método especial utilizado paraobtener una
representación en un formato determinado de un objeto.
    132/381
    Es decir, podemos indicar, valores como, por ejemplo, la alineación dela
cadena de texto producida. Esto nos ayudará a crear una cadena detexto
convenientemente formateada. La función integrada format() esla
responsable de llamar a este método especial, y al igual que losotros
métodos de representación, requiere pasar la instancia de laclase en
cuestión.
COMPARACIONES
Comparar tipos sencillos es fácil. Por ejemplo, pensemos en dosnúmeros.
Establecer si uno es mayor que otro es trivial. Igual ocurrepara otras
operaciones similares como son la igualdad, lacomprobación de si es igual
o mayor que y la desigualdad. Sinembargo, este hecho cambia cuando
debemos aplicar estasoperaciones a objetos de nuestras propias clases. Ello
se debe a que elintérprete, a priori, no sabe cómo realizar estas operaciones.
Parasolventar este problema, Python cuenta con una serie de
métodosespeciales que nos ayudarán a escribir nuestra propia lógica
aplicable acada operación de comparación concreta. Bastará con
implementar elmétodo que necesitemos sobrescribiendo el original ofrecido
por ellenguaje para esta situación. Concretamente, contamos con
lossiguientes métodos especiales de comparación:
__It__() : Menor que.
__le__() : Menor o igual que. __gt__() : Mayor que. __ge__() : Mayor o
igual que. __eq__() : Igual a. __ne__(): Distinto de.
Cada uno de estos métodos será invocado en función del
operadorequivalente que empleemos en la comparación. Supongamos
quedeseamos saber qué modelo de coche es más moderno. Porsimplicidad,
nos basaremos en el atributo que representa la marca ytendremos en cuenta
que este solo puede ser un número. Cuantomayor es el número, más
moderno será el coche en cuestión. En base a
    133/381
    esta simple afirmación, escribiremos el código del método que seencargará
de realizar la comprobación:
def __gt__(self, objeto):if int(self.modelo) > int(objeto.modelo):return
Truereturn False
Si lo añadimos a nuestra clase Coche y creamos dos instancias,podremos
emplear el operador > para llevar a cabo nuestra prueba:
>>> modelo_911 = Coche("Porsche", 911)>>> modelo_924 =
Coche("Porsche", 924)>>> if modelo_924 > modelo_911:... print("El {0}
es un modelosuperior".format(modelo_924.modelo))...El modelo 924 es un
modelo superior
De forma análoga podemos emplear el resto de métodosespeciales de
comparación. Es importante tener en mente que estosmétodos deben
devolver True o False. Sin embargo, esto es solo unaconvención, ya que, en
realidad, pueden devolver cualquier valor.
HASH Y BOOL
Los dos últimos métodos especiales que nos quedan por describirson
__hash__() y_ __bool__() . El primero de ellos se ejecuta al invocar a
lafunción integrada hash() y debe devolver un número entero que sirvepara
identificar de forma unívoca a cada instancia de la misma clase.De esta
forma, es fácil comparar si dos objetos son el mismo, ya quedeben tener el
mismo valor devuelto por hash(). A través del métodoespecial __hash__() ,
podemos realizar operaciones que nos seannecesarias y devolver después el
correspondiente valor. Por defectotodos los objetos de cualquier clase
cuentan con los métodos __eq__() y __hash__() y siempre dos instancias
serán diferentes a no ser que secomparen consigo mismas. A continuación,
veamos un ejemplo deinvocación al método __hash__() desde dos
instancias diferentes:
>>> class TestHash:... pass...>>> t = TestHash()
    134/381
    >>> t.__hash__()39175112>>> hash(t)39175112>>> x = TestHash()>>>
x__hash__()2448448>>> hash(x)2448448>>> x == xTrue>>> t == xFalse
Por otro lado, la función bool() será la encargada de llamar almétodo
especial __bool__ cuando esta recibe como argumento lainstancia de una
clase determinada. Por defecto, si no implementamosel mencionado método
en nuestra clase, la llamada a bool() siempredevolverá True. Si
implementamos __bool__() en nuestra clase, estedebe devolver un valor de
tipo booleano, es decir, en la práctica, solopodremos devolver True o False.
En el siguiente ejemplo,sobrescribiremos el método especial para que
siempre devuelva False, cambiando así el resultado que se obtiene por
defecto cuando estemétodo no está implementado:
>>> class TestBool:... def __bool__():... return False...>>> t =
TestBool()>>> t.__bool__()False>>> bool(t)False>>> if t: print("Es
falso")...Es falso
    135/381
    HERENCIA
El concepto de herencia es uno de los más importantes en laprogramación
orientada a objetos. En apartados anteriores de estecapítulo hemos
adelantado la base en la que se fundamente esteconcepto. En términos
generales, se trata de establecer una relaciónentre dos tipos de clases donde
las instancias de una de ellas tengandirectamente acceso a los atributos y
métodos declarados en la otra.Para ello, debemos contar con una principal
que contendrá lasdeclaraciones e implementaciones. A esta la llamaremos
padre,superclase o principal. La otra, será la clase hija o secundaria. La
herencia presenta la clara ventaja de la reutilización de código,además nos
permite establecer relaciones y escribir menos líneas decódigo, ya que no es
necesario, por ejemplo, volver a declarar eimplementar métodos.Python
implementa la herencia basándose en los espacios denombres, de tal forma
que, cuando una instancia de una clase hijahace uso de un método o
atributo, el intérprete busca primero en ladefinición de la misma y si no
encuentra correspondencias accede alespacio de nombres de la clase padre.
Técnicamente, Python construyeun árbol en memoria que le permite
localizar correspondencias entrelos diferentes espacios de nombres de
clases que hacen uso de laherencia.Habitualmente, los modificadores de
visibilidad como public, prívateo protected, empleados por lenguajes como
Java y C++, estándirectamente relacionados con la herencia. Por ejemplo,
un métodoprivado es heredable, pero solo invocable a través de una
instancia dela clase que lo implementa. En Python, tal y como hemos
comentadoanteriormente, los modificadores de visibilidad, como tal, no
existen,ya que cualquier atributo o método es accesible.Lenguajes como
C++, Java y PHP5 soportan la herencia yestablecen diferentes sintaxis para
declararla. Python no es unaexcepción y también permite el uso de esta
técnica. Además, adiferencia, por ejemplo de Java, Python soporta dos tipos
de herencia:la simple y la múltiple. De ambos nos ocuparemos en los
siguientes
    136/381
    apartados.
Simple
La herencia simple consiste en que una clase hereda únicamente deotra.
Como hemos comentado previamente, la relación de herenciahace posible
utilizar, desde la instancia, los atributos de la clase padre.En Python, al
definir una clase, indicaremos entre paréntesis de la claseque hereda.
Comenzaremos definiendo nuestra clase padre:
class Padre:def __init__(self):self.x = 8print("Constructor clase padre")def
metodo(self):print("Ejecutando método de clase padre")
Crear una clase que herede de la que acabamos de definir es biensencillo:
class Hija:def met_hija(self):print("Método clase hija")
Ahora procederemos a crear una instancia de la clase hija y acomprobar
cómo es posible invocar al método definido en su padre:
>>> h = Hija()Constructor clase padre>>> h.método()Ejecutando método
clase padre
Seguro que el lector se ha dado cuenta de que el método __init__() de clase
padre ha sido invocado directamente al invocar la instancia dela clase hija.
Esto se debe a que el método constructor es el primero enser invocado al
crear la instancia y cómo este existe en la clase padre,entonces se ejecuta
directamente. Pero ¿qué ocurre si creamos unmétodo constructor en la clase
hija? Sencillamente, este será invocadoen lugar de llamar al de padre. Es
decir, habremos sobrescrito elconstructor original. De hecho, esto puede ser
muy útil cuandonecesitamos nuestro propio constructor en lugar de llamar
al de padre.Si modificamos nuestra clase Hija, añadimos el siguiente
método yvolvemos a crear una instancia, podremos apreciar el resultado:
    137/381
    def __init__(self):print("Constructor hija")>> z = Hija()Constructor hija
Pero no solo los métodos son heredables, también lo son losatributos. Así
pues, la siguiente sentencia es válida:
>>> h.x7
Obviamente, varias clases pueden heredar de otra en común, esdecir, una
clase padre puede tener varias hijas. En la práctica,podríamos definir un
clase Vehículo, que será la padre y otras dos hijas,llamadas Coche y Moto.
De hecho, la relación que vamos a establecerentre ellas será de
especialización, ya que un coche y una moto son untipo de vehículo
determinado. A continuación, mostramos el códigonecesario para ello:
class Vehiculo:n_ruedas = 2def __init__(self, marca, modelo):self.marca =
marcaself.modelo = modelodef acelerar(self):passdef frenar(self):passclass
Moto(Vehiculo):passclass Coche(Vehiculo):pass
En este punto podemos crear dos instancias de las dos clases hija
ymodificar el atributo de clase:
>>>>>>>>>>>>2
c = Coche("Porsche", "944")c.n_ruedas = 4m = Moto("Honda",
"Goldwin")m.n_ruedas
Como las motos y los coches tienen en común las operaciones
deaceleración y frenado, parece lógico que definamos métodos, en laclase
padre, para representar dichas operaciones. Por otro lado,cualquier clase
que herede de Vehiculo, también contendrá estasoperaciones, con
independencia del número de ruedas que tenga.
    138/381
    Múltiple
Como hemos comentado previamente, Python soporta la herenciamúltiple,
del mismo modo que C++. Otros lenguajes como Java y Rubyno la
soportan, pero sí que implementan técnicas para conseguir lamisma
funcionalidad. En el caso de Java, contamos con las clasesabstractas y las
interfaces, y en Ruby tenemos los mixins. La herencia múltiple es similar en
comportamiento a la sencilla, conla diferencia que una clase hija tiene uno o
más clases padre. EnPython, basta con separar con comas los nombres de
las clases en ladefinición de la misma. Pensemos en un ejemplo de la vida
real paraimplementar la herencia múltiple. Por ejemplo, una clase genérica
sería Persona, otra Personal y la hija sería Mantemiento. De esta forma,
unapersona que trabajara en una empresa determinada como personal
demantenimiento, podría representarse a través de una clase de lasiguiente
forma:
class Mantenimiento(Persona, Personal):pass
De esta forma, desde la clase Mantenimiento, tendríamos acceso atodos los
atributos y métodos declarados, tanto en Persona, como en Personal. La
herencia múltiple presenta el conocido como problema deldiamante. Este
problema surge cuando dos clases heredan de otratercera y, además una
cuarta clase tiene como padre a las dos últimas.La primera clase padre es
llamada A y las clases B y C heredan de ella,a su vez la clase D tiene como
padres a B y C . En esta situación, si unainstancia de la clase D llama a un
método definido en A, ¿lo heredarádesde la clase B o desde la clase C ?.
Cada lenguaje de programaciónutiliza un algoritmo para tomar esta
decisión. En el caso de Python, setoma como referencia que todas las clases
descienden de la clasepadre object. Además, se crea una lista de clases que
se buscan dederecha a Izquierda y de abajo arriba, posteriormente se
eliminantodas las apariciones de una clase repetida menos la última. De
estaforma queda establecido un orden.La figura geométrica que representa
la relación entre clasesdefinida anteriormente tiene forma de diamante, de
ahí su nombre.Podemos apreciar esta relación en la figura.
    139/381
    Teniendo en cuenta esta figura, Python crearía la lista de clases D, B,A, C,
A. Después eliminaría la primera A, quedan el orden establecidoen D, B, C,
A.
    140/381
    Fig. 4-1 El problema del diamante
    141/381
    Dadas las ambigüedades que pueden surgir de la utilización de laherencia
múltiple, son muchos los programadores que decidenemplearla lo mínimo
posible, ya que, dependiendo de la complejidaddel diagrama de herencia,
puede ser muy complicado establecer suorden y se pueden producir errores
no deseados en tiempo deejecución. Por otro lado, si mantenemos una
relación sencilla, laherencia múltiple es un útil aliado a la hora de
representar objetos ysituaciones de la vida real.
    142/381
    POLIMORFISMO
En el ámbito de la orientación a objetos el polimorfismo hacereferencia a la
habilidad que tienen los objetos de diferentes clases aresponder a métodos
con el mismo nombre, pero conimplementaciones diferentes. Si nos fijamos
en el mundo real, estasituación suele darse con frecuencia. Por ejemplo,
pensemos en unaserpiente y en un pájaro. Obviamente, ambos pueden
desplazarse,pero la manera en la que lo hacen es distinta. Al modelar
estasituación, definiremos dos clases que contienen el método desplazar,
siendo su implementación diferente en ambos casos:
class Pajaro:def desplazar(self):print("Volar")
class Serpiente:def desplazar(self):print("Reptar")
A continuación, definiremos una función adicional que se encarguede
recibir como argumento la instancia de una de las dos clases y deinvocar al
método del mismo nombre. Dado que ambas clasescontienen el mismo
método, la llamada funcionará sin problema, peroel resultado será diferente:
>>> def mover(animal):... animal.desplazar()>>> p = Pajaro()>>> s =
Serpiente()>>> p.desplazar()Volar>>> s.desplazar()Reptar>>>
mover(p)Volar>>> mover(s)Reptar
Como hemos podido comprobar, en Python la utilización delpolimorfismo
es bien sencilla. Ello se debe a que no es un lenguajefuertemente tipado.
Otros lenguajes que sí lo son utilizan técnicas para
    143/381
    permitir y facilitar el uso del polimorfismo. Por ejemplo, Java cuentacon las
clases abstractas, que permiten definir un método sinimplementarlo. Otras
clases pueden heredar de una clase abstracta eimplementar el método en
cuestión de forma diferente.
    144/381
    INTROSPECCIÓN
La instrospección o inspección interna es la habilidad que tiene
uncomponente, como una instancia o un método, para examinar
laspropiedades de un objeto externo y tomar decisiones sobre lasacciones
que él mismo puede llevar a cabo, basándose en lainformación conseguida a
través de la examinación.Python posee diferentes herramientas para facilitar
la introspección,lo que puede resultar muy útil para conocer qué acciones es
capaz derealizar un objeto determinado. Una de las más populares de
estasherramientas es la función integrada dir() que nos devuelve todos
losmétodos que pueden ser invocados para una instancia concreta. Deesta
forma, sin necesidad de invocar ningún método, tenemosinformación sobre
un objeto. Por ejemplo, definamos una clase con unpar de métodos y
lancemos la función dir() sobre una instanciaconcreta:
>>> class Intro:def __init__(self, val):self.x = valdef
primero(self):print("Primero")def segundo(self):print("Segundo")...>>> i =
Intro("Valor")>>> dir(i)['__class__', '__delattr__', __dict__', '__doc__',
__eq__','__format__', '__ge__', '__getattribute__', '__gt__',
'__hash__','__init__', '__le__', '__lt__', '__module__', '__ne__',
'__new__','__reduce__', '__reduce_ex__', '__repr__',
'__setattr__','__sizeof__', '__str__', '__subclasshook__',
'__weakref__','primero', 'segundo', 'x']
Como el lector habrá descubierto, la salida de la sentencia dir(i) devuelve
una lista de strings con el nombre de cada uno de losmétodos que pueden
ser invocados. Los tres últimos son los métodosde instancia y el atributo
que hemos creado, mientras que el resto sonmétodos heredados de la clase
predefinida object. Además de dir(), otras dos prácticas funciones de
introspección son
    145/381
    isinstance() y hasattr(). La primera de ellas nos sirve para comprobar siuna
variable es instancia de una clase. En caso afirmativo devolverá True y en
otro caso False. Continuemos con el ejemplo de la claseanteriormente
definida y probemos este método:
>>> isinstance(i, Intro)True
Por otro lado, hashattr() devuelve True si una instancia contiene unatributo.
Esta función recibe como primer parámetro la variable querepresenta la
instancia y como segundo el atributo por el quedeseamos preguntar. Dada
nuestra clase anterior, escribiremos elsiguiente código para comprobar su
funcionamiento:
>>> if (i,Instancia:>>> if (i,Atributo z
'x'): print("Instancia: i. Clase: Intro. Atributo: x")i. Clase: Intro. Atributo:
x'z'): print("Atributo z no existe")no existe
Si utilizamos la herencia, las funciones de introspección puedensernos muy
útiles, además nos permiten descubrir cómo los métodos yatributos son
heredados. Si creamos una nueva clase que herede de laanterior, pero con
un nuevo método, al llamar a dir() veremos cómo losatributos y métodos
están disponibles directamente:
>>> class Hijo(Intro):def tercero(self):print("Tercero")...>>> h = Hijo()>>>
dir(h)['__class__', '__delattr__', '__dict__', '__doc__', '__eq__',__format__',
'__ge__', '__getattribute__', '__gt__', '__hash__','__init__', '__le__', '__lt__',
'__module__', '__ne__', '__new__','__reduce__', '__reduce_ex__',
'__repr__', '__setattr__','__sizeof__', '__str__', '__subclasshook__',
'__weakref__,'primero', 'segundo', 'tercero', 'x']
De igual forma, podemos emplear la función hasattr() paracomprobar si el
atributo x existe también en la clase h:
>>> if hasattr(h, 'x'): print("h puede acceder a x")h puede acceder a x
    146/381
    Obviamente, aunque las clases Intro e Hijo estén relacionadas
sondiferentes. Sin embargo, la función isinstance() nos devolverá True si
    147/381
    pasamos como primer argumento una instancia de cualquier de ellas ycomo
segundo el nombre de una de las dos clases:
>>> if isinstance(h, Intro): print("h es instancia de Intro")h es instancia de
Intro
Entonces, ¿cómo podemos averiguar si una Instancia pertenece a laclase
padre o hija? Fácil, basta con emplear la función Integrada type(), tal y
como muestra el siguiente ejemplo:
>>> type(i)<class '__main__.Intro'>>>> type(h)<class '__main__.Hija'>
Otro método relacionado con la introspección es callable(), el cualnos
devuelve True si un objeto puede ser llamado y False en casocontrario. Por
defecto, todas las funciones y métodos de objetospueden ser llamados, pero
no las variables. Gracias a este métodopodemos averiguar, por ejemplo, si
una determinada variable es unafunción o no. Como ejemplo, echemos un
vistazo al siguiente código:
>>> cad = "Cadena">>> callable(cad)False>>> def fun():...
return("fun")>>> callabe(fun)True
Siguiendo la misma explicación, una instancia de objeto devolverá False
cuando invocamos a callable():
>>> callable(i)False
Echando un vistazo al resultado de la ejecución de la sentenciaprevia dir(h),
descubriremos métodos que pueden ser llamados através de una instancia y
que también sirven para la instrospecclón.Nos referimos a
__getattribute__() y __setattr__() . El primero nos sirvepara obtener el
valor de un atributo a través de su nombre, el segundorealiza la operación
inversa, es decir, recibe como parámetro el nombredel atributo y el valor
que va a serle fijado. Habitualmente, estosmétodos no son necesarios, ya
que podemos acceder directamente aun atributo de instancia directamente
usando el nombre de la variable
    148/381
    de instancia, seguido de un punto y del nombre del atributo encuestión. Sin
embargo, los métodos __getattribute__() y __setattr__() pueden ser muy
útiles, cuando, por ejemplo, necesitamos leer losatributos de una lista o
diccionario. Supongamos que tenemos unaclase con tres atributos de
instancia definidos y deseamos fijar su valorempleando para ello un
diccionario. Comenzaremos definiendo unanueva clase:
class Test:def__init__(self):self.x = 3self.y = 4self.z = 5
Ahora crearemos un diccionario y una instancia de clase:
>>> attrs = {"x": 1, "y": 2, "z": 3}>>> t = Test()
Seguidamente, pasaremos a asignar los valores del diccionario anuestros
atributos de clase:
>>> for k, v in attrs.items():... t.__setattr__(k, v)
Para comprobar que los atributos han sido fijados
correctamente,emplearemos la función __getattribute__() , tal y como
muestra elsiguiente código:
>>>......y =x =z =
for k in attrs.keys():print("{0}={1}".format(k, t.__getattribute__(k))
213
Debemos tener en cuenta que attrs es un diccionario y como tal esuna
estructura de datos no ordenada, por lo cual la salida del orden delos
atributos puede diferir durante sucesivas ejecuciones de laiteración.
    149/381
    PROGRAMACIÓN AVANZADA
INTRODUCCIÓN
En este capítulo nos adentraremos en una serie de aspectosavanzados que
Python pone a nuestra disposición. Los conceptosexplicados en este
capítulo, aun no siendo imprescindibles para elaprendizaje del lenguaje, sí
que son muy interesantes de cara a tenerun amplio conocimiento del mismo
y a poder escribir código de formaelegante. Asimismo, nos ayudarán a sacar
mayor provecho dellenguaje.En concreto, en este capítulo, comenzaremos
descubriendo qué sony cómo utilizar los iterators y generators. Después
pasaremos a explicarla técnica del empleo de closures y continuaremos
centrándonos en losútiles decorators. Dado que la programación funcional
va ganandoadeptos día a día, también hemos creído conveniente dedicar
unapartado a la misma. Finalmente, serán las expresiones regulares y
laordenación eficiente de elementos los apartados que cerrarán elpresente
capítulo.
    150/381
    ITERATORS Y GENERATORS
Python cuenta con dos mecanismos específicos que nos permiteniterar
sobre un conjunto de datos: los iterators y los generators.
Iterators
Hasta el momento hemos visto una serie de ejemplos que nos hanmostrado
cómo iterar sobre una serie de objetos, habitualmenteempleando un bucle
for y a través del operador in. Para refrescar cómopuede llevarse a cabo una
iteración, basta con echar un vistazo alsiguiente código:
>>> lista = [1, 2, 3]>>> for ele in lista:... print(ele)...123
En realidad, Python nos permite iterar directamente sobre ciertosobjetos,
como son las listas y los diccionarios, incluso es posible iterarsobre un
string, tal y como podemos ver en el siguiente código:
>>> for letra in "hola":... print(letra)...hola
Este mecanismo de iteración sobre objetos es bastante potente enPython y
puede sernos muy útil para realizar tareas que son máscomplejas en otros
lenguajes, como, por ejemplo, la lectura de laslíneas que forman un fichero
de texto. Esta operación en Python esmuy sencilla gracias a que un fichero
es un objeto iterable, al igual quelo son las listas, los diccionarios y los
strings, tal y como hemos
    151/381
    comentado previamente. Aunque contamos con un capítulo dedicadoa cómo
trabajar con distintos tipos de ficheros, adelantaremos unsencillo ejemplo
de código que nos enseña cómo leer todas las líneasde un fichero:
>>> for linea in open("fich.txt"):... print(linea)
En realidad, la iteración es posible en Python gracias a que elintérprete y el
lenguaje disponen de una técnica que hace posible suimplementación y
ejecución a través de un determinado protocolo. Asípues, a bajo nivel,
cuando empleamos la sentencia for, lo querealmente ocurre es que se realiza
una llamada a un iterator determinado que se encarga de realizar la función
de iteración sobre elobjeto concreto.En general, cualquier tipo de objeto en
Python que implementeunos métodos especiales, que forman parte del
mencionadoprotocolo, es un iterator. En concreto, para construir un objeto
de estetipo, deberemos implementar al menos dos métodos: __iter__() y
__next__() . El primero de ellos es empleado para devolver unareferenciaa
la Instancia de la clase que ¡mplementa el iterator, mientras que elsegundo
debe implementar la funcionalidad que será ejecutadacuando se lleva a cabo
la iteración sobre cada elemento. Supongamosque necesitamos Iterar sobre
un valor que será pasado comoparámetro, para Imprimir todos los valores
desde ese mismo hastallegar a cero, es decir, se trata de implementar una
sencilla cuentaatrás. Nuestra clase iterator quedaría de la siguiente forma:
class CuentaAtras(object):def __init__(self, start):elf.count = startdef
__iter__(self):return selfdef __next__(self):if self.count <= 0:raise
StopIterationr = self.countself.count -= 1return r
El constructor de nuestra clase recibirá el valor inicial y lo asignará auna
variable de instancia llamada count. Al ejecutar el método __next__()
comprobaremos el valor de dicha variable y si es igual o
    152/381
    inferior a cero deberemos detener la iteración. Para ello, contamos conla
excepción StopIteration, que será lanzada justo en ese momento. Siesta
condición no se cumple, disminuiremos el valor de la variable deInstancia y
devolveremos el valor de la misma antes de que se lleve acabo el
decremento. Una vez que tenemos nuestro objeto podemosemplearlo junto
a un bucle for y al operador in, tal y como muestra elsiguiente ejemplo:
>>> for ele in CuentaAtras(5):... print(ele)...54321
De igual forma, podríamos trabajar con más de un argumento. Estesería el
caso, por ejemplo, de necesitar iterar sobre un rango denúmeros. Veamos
cómo hacerlo construyendo un nuevo iterator:
class Rango:def __init__(self, low, high):self.current = lowself.high =
highdef __iter__(self):return selfdef __next__(self):if self.current >
self.high:raise StopIterationself.current += 1return self.current - 1
La invocación a nuestro iterator sería como sigue:
>>> for ele in Rango(5,9)... print(ele)...56789
FUNCIONES INTEGRADAS
    153/381
    A diferencia de las versiones 2.x de Python, la serie 3 de estelenguaje
cuenta con una serie de funciones integradas que devuelvenobjetos iterators
en lugar de listas. Entre estas funciones encontramosa range(), map(), zip()
y filter(). Por ejemplo, map() aplica una funcióndeterminada a cada uno de
los valores que se le indiquen comoparámetro. En Python 3, la llamada a
esta función nos devolverá unobjeto de tipo map que es un iterator. A
continuación, el código deejemplo que ilustra este hecho:
>>> map(abs, (-1, 2, -3)<map object at 0x00000000026484E0>
Si deseamos obtener una lista a aplicar la función map(), tal y comoocurre
en Python 2.x, deberemos invocar explícitamente a list(), comomuestra el
siguiente ejemplo:
>>> list(map(abs, (-1, 2, -3))[1, 2, 3]
Nótese que la función abs() calcula el valor absoluto de un valordado que
recibe como parámetro.Similar comportamiento encontramos en la función
zip() quecombina valores de los argumentos que recibe. Echemos un vistazo
alsiguiente código para comprobar su funcionalidad:
>>> list(zip (("123", "abc")))[("1", "a"), ("2", "b"), ("3", "c")]
Relacionados con los iterators encontramos a los generators, de losque nos
ocuparemos en el siguiente apartado.
Generators
Si tuviéramos que definir qué es un generator podríamos decir quees una
función que devuelve una secuencia de resultados en lugar deun único
valor. La relación entre los iterators y los generators es muyestrecha, ya
que en la mayoría de las ocasiones, en lugar de crear unobjeto iterator,
podemos crear una simple función que lleve a cabo lamisma funcionalidad.
De esta forma conseguimos minimizar las líneasde código y hacemos que el
código sea más legible. Para comprobar
    154/381
    este hecho, vamos a reescribir nuestro primer ejemplo de iterator
empleando una simple función que hará de generator:
def cuenta_atras(ele):while ele > 0:yield nn -= 1
Si ahora realizamos la siguiente llamada, observaremos cómo elresultado es
idéntico a emplear el iterator al que hemos llamado CuentaAtras :
>>> for ele in cuenta_atras(5):... print(ele)
Como el lector habrá podido observar, en nuestro nuevo ejemplohemos
introducido el uso una nueva función llamada yield(). Esta es laencargada
de detener la ejecución de nuestra función, producir unnuevo resultado y
reanudar la ejecución de la función. Así pues, cadavez que se alcanza yield
se procede a ejecutar el código que aparecedentro de nuestro bucle for,
cuando esta acción termina, continúa laejecución de cuenta_atras().
Internamente, el intérprete de Python creaun objeto iterator cada vez que se
llama a la función generator. Un generator no tiene por qué ser
forzosamente una función,también puede ser una expresión. De hecho,
cuando explicamos lacomprensión de listas, en el capítulo 2, vimos un
ejemplo de unaexpresión que es un generator. Un ejemplo similar es el
siguientecódigo donde, a través de una expresión, construimos un objeto
generator :
>>> lista = [1, 2, 3]>>> (7*ele for ele in lista)<generator object <genexpr>
at 0x00000000022AD750>
Si asignamos nuestro nuevo objeto a una variable, podremosemplear la
misma para realizar iteraciones, tal y como demuestran lassiguientes
sentencias:
>>> gen = (7*ele for ele in lista)>>> for ele in gen:... print(ele)...71421
    155/381
    A diferencia de otros objetos, la iteración sobre un generator solo sepuede
hacer una única vez. Si deseamos hacerla más veces, deberemosvolver a
crear el generator primero. Esto no ocurre, por ejemplo, conlas listas. Una
vez definida es posible iterar n veces sobre la misma sinnecesidad de volver
a declarar. Teniendo en cuenta este hecho ytomando como base nuestro
último ejemplo de código, si volvemos aejecutar el bucle for,
comprobaremos cómo no se imprime ningúnnúmero por la salida estándar.
    156/381
    CLOSURES
En Python es posible anidar una función dentro de otra. Unafunción no es
más que un objeto del lenguaje, que, en realidad, es unasentencia ejecutable
y como tal, puede aparecer en cualquier lugar delscript. Cuando se anidan
funciones debe ser tenido en cuenta elespacio de nombres de las mismas, ya
que cualquier variable definidaen la función que contiene a otra puede ser
accedida desde la funciónanidada. El siguiente ejemplo muestra este hecho:
>>> def principal():... x = 8... def contenida():... print(x)... contenida()...>>>
principal()8
El ejemplo de código anterior muestra cómo la variable x esaccesible a
través de la función contenida, que a su vez se encuentraanidada dentro de
principal. Estas reglas de acceso del espacio denombres se mantienen
incluso si la función principal devuelve lafunción contenida como resultado
de su ejecución. Si modificamos elcódigo anterior y lo sustituimos por el
siguiente, comprobaremoscómo x es accesible:
def principal():x = 7def anidada():print(x)return anidada
Ahora es la función principal la que va a devolvernos comoresultado otra
función, que, en este caso, es la función anidada quecontiene. Dado que una
función queda definida por su nombre y elintérprete pasa a referenciar el
espacio de memoria que necesita através del nombre, es posible devolver
una función como si de unavariable cualquiera se tratase. De hecho, es
simplemente eso, un objeto que Python referencia a través de un
identificador. Teniendo esto en
    157/381
    mente, podemos invocar a la función principal() y asignar su resultadoa una
variable:
>>> res = principal()
Si ahora invocamos a la función a través de la nueva variable,observaremos
cómo la variable x es accesible desde la función anidada:
>>> res ()7
Debe ser tenido en cuenta que ya no podemos invocardirectamente a
principal(), sino que debemos hacerlo a través de lanueva variable res. En
realidad, al devolver una función, aunque elámbito de principal() ya no se
encuentra en memoria sí que es posibleacceder a la funcionalidad que la
misma contiene, dado que su interiorha sido creada una función y devuelta
como resultado de la ejecuciónde principal(). Es decir, esto es lo que ocurre
si invocamos ahora a principal():
>>> principal()<function anidada at 0x0000000002577748>
A esta técnica se la conoce como closure o factory function. Estaúltima
nomenclatura hace referencia a que una clase es capaz de crearotra
diferente, es decir, funciona como una factoría de funciones.En términos
generales, un closure es una técnica de programaciónque permite que una
función pueda acceder a variables que, a priori,están fuera de su ámbito de
acceso. Son muchos los lenguajes deprogramación que soportan esta
técnica, como por ejemplo Ruby, queutiliza el concepto de blocks para
aplicarla.Los closures suelen ser empleados cuando es necesario disponer
deuna serie de acciones que debe llevarse a cabo como respuesta a unevento
que maneja otra función y que ocurre en tiempo de ejecución.Por ejemplo,
pensemos en una aplicación que cuenta con una interfazde usuario.
Habitualmente, estas funciones disponen de manejadoresde eventos, es
decir, de funciones o métodos que responden a ciertotipo de eventos, como
puede ser el pulsar un botón o hacer clic sobreuna caja de texto.
Supongamos que tenemos un evento que seencarga de controlar y responder
cuando una caja de texto pierde el
    158/381
    enfoque. Además, necesitamos realizar una serie de acciones enfunción del
texto que la caja contenga. Dado que no sabemos a prioriel texto que
contendrá, ya que este cambiará en tiempo de ejecución,necesitamos otra
función que, dentro del manejador del evento, seacapaz de realizar ciertas
acciones en función del contenido del texto dela caja.Aunque hemos
descrito un sencillo ejemplo de closure, obviamente,las funciones que
forman parte del mismo pueden recibir y trabajarcon parámetros. Veamos
un sencillo ejemplo donde sumaremos elvalor de los argumentos que
reciben tanto la función contenedoracomo la anidada:
>>>............>>>>>>21>>>23
def contenedora(y):def anidada(z):return y + zreturn anidada
fun = contenedora (9)fun(12)
fun(14)
En este punto, si creamos una nueva variable pasando un valordiferente a
contenedora(), apreciaremos cómo el resultado varía:
>>> otr = contenedora(1)>>> otr(2)3
Además, podemos seguir invocando, tanto a fun(), como a otr(), lasveces
que deseemos. Si comparamos estos ejemplos de código con elcaso de la
interfaz de usuario, anteriormente comentado,comprobaremos que existe
una analogía entre ellos. En concreto, lafunción contenedora() sería la
responsable de responder al evento,mientras que anidada() se encargaría de
procesar el valor del campode texto y devolver un resultado diferente en
función del mencionadovalor.
    159/381
    DECORATORS
En el capítulo anterior, hemos descubierto cómo utilizar un decorator, para
por ejemplo, indicar que un método es de clase. Eneste apartado
describiremos qué es un decorator, cómo se utilizan ycómo implementar los
nuestros propios.
Patrón decorator, macros y Python decorators
Antes de describir qué es un decorator en Python, convieneentender las
diferencias y similitudes entre tres conceptos diferentes: elpatrón decorator,
las macros y los decorators de Python.Aquellos desarrolladores
acostumbrados a implementar patrones dediseño, seguro que conocen y
utilizan el patrón conocido como decorator. Básicamente, este patrón nos
permite modificardinámicamente el comportamiento de un objeto. En la
práctica, el decorator pattern puede ser considerado como una alternativa
adeclarar una clase que hereda de otra, es decir, a la herencia simple. Enlos
lenguajes compilados, como C++, el cambio de comportamiento sehace en
tiempo de ejecución, no en tiempo de compilación.Por otro lado, las macros
son utilizadas en programación paramodificar una serle de elementos del
lenguaje. Los programadores deC estarán acostumbrados a trabajar con
macros de preprocesador, Javay C# emplean las annotations, que es una
técnica muy similar a lasmencionadas macros.Los decorators de Python no
deben ser confundidos con el patrónde diseño decorator, ya que su
funcionalidad está más cerca de lasmacros. De esta forma, un decorator o
decorador de Python nospermite inyectar código para modificar el
comportamiento de la¡mplementaclón original de un método, función o
clase. Por ejemplo,supongamos que deseamos realizar una serle de
operaciones antes ydespués de ejecutar una determinada función. Para este
caso podemosescribir funciones implementadas por decoradores y aplicar
estosdirectamente a nuestra función. Además, esto presenta la ventaja de
    160/381
    que las funciones implementadas pueden ser utilizadas comodecoradores en
otras funciones de nuestro programa, consiguiendo asíreutilizar nuestro
código eficientemente.
Declaración y funcionamiento
Cuando deseamos aplicar un decorador, por ejemplo, a unafunción, basta
con escribir el nombre del mismo, precedido de laarroba (@), en la línea
justamente anterior a la que declarara lafunción. Un sencillo ejemplo que
nos permite aplicar el decorador firstDecorator a una función llamada
función, sería el siguiente:
@firstDecoratordef funcion():print("Ejecutando la función")
Básicamente, la arroba es un operador que indica que un objetofunción
debe ser pasado a través de otra función, asignando elresultado a la función
original. Esta sintaxis es más elegante que utilizarla misma funcionalidad
realizando diferentes llamadas a las funciones.Así pues, el ejemplo anterior
es similar a declarar una nueva función yrealizar una llamada empleando el
resultado obtenido previamente:
def firstDecorator(obj):passres = firstDecorator(funcion)
En realidad, los decoradores de Python siguen la idea de aplicarcódigo a
otro código, como hacen las macros, pero a través de unaconstrucción y
sintaxis dada directamente por el lenguaje.Los decorators ofrecen bastante
flexibilidad, ya que cualquierobjeto, como una clase o función de Python
puede ser utilizada comodecorador sobre otro objeto, siendo habitualmente
este último unafunción.Para entender en la práctica cómo funcionan los
decorados, noscentraremos en aplicar los mismos empleando clases y
funciones. En elsiguiente apartado, comenzaremos por las clases.
    161/381
    Decorators en clases
Para ilustrar el funcionamiento de la aplicación de decoradoresutilizando
clases, vamos a partir de un ejemplo, donde declararemosuna clase que
posteriormente será utilizada como decorador en unafunción.En primer
lugar, vamos a declarar una clase en la queimplementaremos y
sobrescribiremos los métodos __init__() y __new__() :
class Decorador(object):def __init__(self, f):self.f = fdef
__call__(self):print("inicio", self.f.__name__)self.f()print("fin",
self.f.__name__)
El siguiente paso será definir una función a la que aplicaremosnuestra clase
como decorador:
@Decoradordef funcion():print("soy función")
Si ahora procedemos a llamar a nuestra función, podremosobservar el
siguiente resultado que nos lanza el intérprete:
>>> funcion()inicio funcionsoy funciónfin funcion
La explicación al resultado obtenido puede explicarse fácilmente.Cuando
llamamos a la función, se procede a modificar sucomportamiento original
aplicando el decorador. Así pues, dado queeste es una clase, pasa a
ejecutarse el constructor de la misma, querecibe como parámetro la función
invocada. El constructorsimplemente asigna la misma a un atributo de clase
llamado f .Posteriormente, se llama automáticamente al método __call__()
queimprime el primer mensaje en la salida estándar y después invoca a
lafunción original a través del atributo de clase definido previamente.Dado
que esta función originalmente imprime el mensaje soy función,
    162/381
    esto es simplemente lo que ocurre. Continuando con la ejecución de
__call__() , se imprime el último mensaje. Tal y como habremosobservado,
__name__ es un atributo que nos permite acceder alnombre de la función,
en este caso. De esta forma, estamosaprovechando las funcionalidades de
introspección que Python nosofrece. Queda demostrado que el
comportamiento original de lafunción llamada funcion ha sido modificado
gracias al decorador quehemos creado previamente y aplicado automática y
posteriormente.Por convención, el nombre del decorador suele comenzar
porminúscula. Sin embargo, en nuestro ejemplo hemos utilizado
lamayúscula. Esto se debe a que los nombres de las clases, también
porconvención, comienzan por mayúscula. Si se desea puede optarse
porcambiar la nomenclatura de nuestro ejemplo, renombrando el nombrede
la clase para que la primera letra comience con minúscula. De estaforma, el
nombre del decorador también deberá empezar porminúscula. Lo
importante es que mantengamos la correspondenciaentre nombres, en
cualquier otro caso el intérprete lanzará un errorsintáctico.
Funciones como decorators
Si en el apartado anterior hemos descubierto cómo aplicar clasescomo
decoradores a una función, en éste nos ocuparemos de crearuna función
como decorador que será aplicado a otra.Siguiendo con el ejemplo anterior,
implementaremos unafuncionalidad similar pero con funciones en lugar de
con instancias declases.Lo primero que deberemos tener en cuenta es que
vamos a trabajarcon un closure. Este concepto hace referencia a que una
función puedeser evaluada en un contexto que puede, a su vez, uno o
máscomponente dependiendo de otro entorno diferente. En nuestro
caso,tendremos una función declarada dentro de otra, devolviendo
laprincipal el resultado de la ejecución de la función contenida en lamisma.
Aunque parece un poco confuso, es mejor entenderlo echandoun vistazo al
siguiente código:
def principal(f):
    163/381
    def nueva():print("ini", f.__name__)f()print("fin", f.__name__)return nueva
El siguiente paso es definir una nueva función a la que llamaremos
para_decorar. Será esta nueva función a la que aplicaremos comodecorador
la función principal():
©principaldef decorada() :print("decorada")
Ahora procederemos a invocar a la función decorada() yobservaremos la
salida dada por el intérprete del lenguaje:
>>> decorada()ini decoradadecoradafin decorada
Cuando invocamos a decorada(), se evalúa el decorador y se invocaa la
función principal(). Esta simplemente define otra función cuyoresultado es
devuelto al finalizar la ejecución de principal(). Dado quedentro de nueva()
se invoca a la función decorada(), el mensaje"decorada" es impreso entre
"ini decorada" y "fin decorada".Debemos tener en cuenta que el objeto que
hace de decoradordebe recibir como parámetro del objeto que lo invoca. En
este caso,cuando decimos objeto nos referimos a uno interno de Python,
comopuede ser una clase o una función. No se trata de un objeto en
elsentido de instancia de una clase. Así pues, en el ejemplo del
apartadoanterior, era el constructor ( __init__() ) de la clase el encargado
derecibir la función como parámetro. En el ejemplo del presenteapartado, es
la misma función ( principal() ) la que recibe comoparámetro la otra
función ( (decorada() ) pasada como tal.
Utilizando parámetros
Hasta el momento hemos utilizado los decoradores de Python sintener en
cuenta el paso de parámetros. Sin embargo, es posible
    164/381
    invocar a una función decorada pasando a su vez un númerodeterminado de
parámetros. Es más, se pueden dar dos casosdiferentes, uno donde solo la
función decorada recibe parámetros yotro donde es el decorador el que
admite ciertos parámetros. En esteapartado veremos ambos casos,
comencemos pues, por el primero deellos.
DECORADOR SIN PARÁMETROS
Volviendo a nuestro ejemplo inicial en el que el objeto decoradorera una
clase, podemos modificarlo para añadirle parámetros en lafunción decorada.
Para ello, bastará con cambiar el método __call__() ,añadiendo un nuevo
parámetro, que en este caso será *argumentos. Tal y como vimos en el
capítulo 3, vamos a emplear la técnica paradesempaquetado de argumentos.
Así pues, podremos recibir n parámetros. Pasando directamente al código,
nuestra clase quedaríareescrita de la siguiente forma:
class Decorador(object):def init (self, f):self.f = fdef call (self,
*argumentos):print("inicio",
self.f.__name__)self.f(*argumentos)print("fin", self.f.__name__)
Por otro lado, la función decorada va a recibir, por ejemplo, tresparámetros
diferentes. De esta forma, el nuevo código para la funcióndecorada sería el
siguiente:
@Decoradordef funcion(p1, p2, p3):print("soy función con argumentos",
p1, p2, p3)
La invocación a la función decorada con tres parámetros diferentesy su
correspondiente resultado sería como sigue:
>>> funcion("uno", "dos", "tres")inicio funcionsoy función con argumentos
uno dos tresfin funcion
Como habremos podido comprobar, este paso de parámetros es
    165/381
    sencillo. Simplemente hemos tenido en cuenta los mismos al igual queen
una función convencional.
DECORADOR CON PARÁMETROS
Otro caso diferente al anteriormente tratado es cuando eldecorador contiene
diferentes parámetros durante su invocación. Esdecir, al utilizar el
decorador es posible también que este puedatrabajar con diferentes
argumentos.Para ilustrar el paso de parámetros en el decorator vamos a
trabajarcon funciones. Tendremos dos principales: el decorador y la
decorada.Esta última contendrá a su vez otras dos anidadas que nos
ayudarán atratar con los argumentos del decorador y de la función
decorada.Comencemos por la función decorada, que contendrá el
siguientecódigo:
@decorator_args(1, 2, 3)def fun(a, b):print ("fun args:", a, b)
En nuestro ejemplo, la función decorada recibirá dos parámetros,siendo su
funcionamiento original la impresión de los mismos por lasalida
estándar.Por otro lado, declararemos la función decorador empleando
elsiguiente código:
def decorator_args(arg1, arg2, arg3):def wrap(f):print( "ini wrap()")def
wrapped_f(*args):print( "ini wrapped_f")print( "decorator args:", arg1,
arg2, arg3)f(*args)print( "fin wrapped_f")return wrapped_freturn wrap
En este punto, estamos en condiciones de proceder a la invocaciónde la
función decorada con diferentes valores pasados comoparámetros, siendo la
salida la que aparece a continuación:
>>> fun('p1", "p2")ini wrap()
    166/381
    ini wrapped_fdecorator args: 1 2 3fun args: p1 p2fin wrapped_f
En la salida anterior comprobaremos cómo los parámetros queutiliza el
decorador y la función decorada son diferentes.Si estamos trabajando con el
intérprete, al terminar de definir lafunción decorada, veremos cómo,
automáticamente, el intérpretellama al decorador. Posteriormente, podemos
invocar a la funcióndecorada con sus parámetros. Este sería el resultado
utilizandodirectamente el intérprete de Python:
>>> @decorator_args(1, 2, 3)... def fun(a, b) :... print ("fun args:", a, b)...ini
wrap()
A pesar de haber definido y aplicado un único decorador porfunción,
también es posible aplicar n decoradores sobre la misma. Deesta forma
podemos ganar en reutilización de código y aumentar elnúmero de
operaciones realizadas que sustituyen a la funcionalidadimplementada en la
función original.Hasta aquí nuestro recorrido por los decorators. A pesar de
habermostrado sencillos ejemplos para ilustrar el funcionamiento básico
delos mismos, la flexibilidad que nos ofrecen es significativa a la vez
quepotente. Sin duda, los decorators son uno de los aspectos
másinteresantes que nos ofrece Python.
    167/381
    PROGRAMACIÓN FUNCIONAL
Tanto la orientación a objetos como la programación procedural,son dos de
los paradigmas más utilizados por los programadores dePython. Sin
embargo, también es posible emplear la programación funcional con
Python. Básicamente, este paradigma se fundamenta enel empleo de
funciones aritméticas sin tener en cuenta cambios deestado. En este tipo de
programación el valor generado por unafunción depende exclusivamente de
los argumentos que recibe y nodel estado del resto del programa cuando la
misma es invocada. Unprograma escrito utilizando este paradigma
únicamente contienefunciones, pero no en el sentido de las funciones de la
programaciónprocedural, sino en el sentido matemático de expresión.
Otracaracterística de la programación funcional es que no utiliza
laasignación de variables. Tampoco se emplean construccionessecuenciales
como la iteración, por el contrario se hace un usointensivo de la
recursión.Para aplicar la programación funcional con Python contamos
convarias técnicas y recursos como son: las funciones lambda, los iterators
y generators, la comprehensión de listas y una serie de funcionesintegradas.
Dado que solo nos queda ocuparnos de estas últimas,dedicaremos las
siguientes líneas a este propósito.En concreto, entre las funciones integradas
que Python pone anuestra disposición para escribir código utilizando
programaciónfuncional, destacan map(), filter() y reduce(). La primera de
ellas permiteaplicar una función sobre un conjunto de elementos. Por
ejemplo,supongamos que tenemos una lista con varias cadenas de texto
ynecesitamos generar una nueva lista con las mismas cadenas pero
enminúsculas. Haremos uso del método lower(), junto con map() y
lafunción list() para obtener una lista como resultado. Para ilustrarnuestro
ejemplo, construiremos una pequeña función que hará de wrapper sobre el
mencionado método lower():
>>> def lower(t) : return t.lower()...>>> texto = ["HOLA", "mUNdO",
"AdiOS"]>>> list(map(lower, texto))
    168/381
    ["hola", "mundo", "adios"]
El mismo ejemplo podría haber sido realizado utilizando lacomprehensión
de listas:
>>> [cad.lower() for cad in texto]
Efectivamente, la función map() implementa la misma funcionalidadque las
expresiones que producen generators, que es, en realidad, loque hemos
hecho con la sentencia anterior de código.Por otro lado, la función filter()
devuelve un iterator sobre todos loselementos de una secuencia que
cumplen una determinada condición.Para comprobar esta condición se suele
emplear un predicado que es,básicamente, una función que solo devuelve el
valor True cuando secumple una condición. Por ejemplo, supongamos que
necesitamossolo los valores pares de una lista determinada. En primer
lugarconstruiremos la función con nuestro predicado :
def par(val):return (val%2) == 0
Seguidamente pasamos a utilizar directamente la función filter(), junto con
list(), sobre una determinada lista:
>>> lista = [0, 3, 5, 6, 8, 9]>>> list(filter(par, lista))[0, 6, 8]
Por último, reduce() aplica una función tomando como argumentoslos
valores correlativos de una secuencia, devolviendo un únicoresultado. Un
ejemplo sencillo sería definir una función que suma losvalores pasados
como parámetros. A través de reduce() podemos irsumando los valores de
una lista de forma secuencial. Nuestra funciónquedaría como sigue:
def sumar(x, y):return x+y
Después podríamos invocar a reduce() sobre una lista con contienevalores
del 1 al 4, donde sumar() se encargaría de realizar las
siguientesoperaciones:
1+2=3+3=6+4=10
    169/381
    Los números en negrita representan los elementos de la lista y elcódigo
completo para realizar la operación sería el siguiente:
>>> from functools import reduce>>> lista = [1, 2, 3, 4]>>> reduce(sumar,
lista)10
Seguro que el lector ha observado cómo hemos importado lafunción
reduce() de un módulo llamado functools. Este es un móduloIntegrado en
Python3 y que ofrece varios métodos para generar iterators y que, por lo
tanto, pueden utilizarse para la programaciónfuncional. En la versiones 2.x
de Python la función reduce() existía comotal en la librería estándar, no
como parte del mencionado módulo.
    170/381
    EXPRESIONES REGULARES
Las expresiones regulares definen una serie de patrones que seutilizan para
trabajar con cadenas de texto. Podemos considerar a lasmismas como un
lenguaje específico diseñado para localizar textobasándonos en un
determinado patrón previamente definido.Básicamente, las expresiones
regulares son utilizadas con dospropósitos a menudo relacionados: la
búsqueda y reemplazo de texto.Habitualmente, a las expresiones regulares
simplemente se lasllama regex y son muchos los programadores que
prefieren utilizar estetérmino. Además, como nomenclatura, algunos
desarrolladoresemplean el prefijo regex para designar variables que
representan a unaexpresión regular.Al igual que la mayoría de los lenguajes
de programación, Pythonincluye un completo soporte para las expresiones
regulares. Estelenguaje dispone de un módulo específico, llamado re, que
permiterealizar búsquedas, sustituciones y obtener parte de una cadena
detexto empleando expresiones regulares como patrones.El módulo re de
Python utiliza las reglas definidas en el modelo NFA tradicional (autómata
finito no determinista), el cual se basa en lacomparación de cada elemento
de la expresión regular con la cadenade texto de entrada. Este modelo se
encarga de mantener unseguimiento de las posiciones que referencian a la
selección entre dosopciones dentro de la expresión regular. Si una de estas
opciones falla,se busca la última entre las más recientes cuya posición ha
sidoguardada. Para más detalles sobre este modelo podemos consultar
lapágina (ver referencias) de Wikipedia al respecto.
Patrones y metacaracteres
Para componer una expresión regular, debemos emplear una seriede
patrones que son considerados como básicos. La tabla 5-1 muestralos más
utilizados dentro de esta categoría y el significado asociados aellos.
    171/381
    Patrón Significado
 Cualquier carácter excepto el que representa una nueva línea
\n Nueva línea
\r Retorno de carro
\t Tabulador horizontal
\w Cualquier carácter que represente un número o letra en
minúscula
\W Cualquier carácter que represente un número o letra en
mayúscula
\s Espacio en blanco (nueva línea, retorno de carro, espacio y
cualquier tipo de tabulador)
\S Cualquier carácter que no es un espacio en blanco
\d Número entre 0 y 9 Inclusive
\D Cualquier carácter que no es un número
    172/381
    ^ Inicio de cadena
$ Fin de cadena
\ Escape para caracteres especiales
[] Rango. Cualquier carácter que se encuentre entre los corchetes
^[] Cualquier carácter que no se encuentre entre los corchetes
\b Separación entre un número y/o letra
Tabla 5-1. Patrones básicos para definir expresiones regulares en Python
Por otro lado, cualquier carácter se representa a sí mismo, teniendoen
cuenta que algunos constituyen por sí mismo un patrón básico. Porejemplo,
el carácter $ indica el final de una cadena; por lo tanto, nopodemos
emplearlo, cuando, por ejemplo, buscamos este carácter talcual en una
cadena de texto. Si necesitamos buscar el carácter comotal, podemos
emplear el patrón básico de escape ( \ ). De esta forma,para busca el
carácter $ deberemos construir una expresión regularque utilice la secuencia
\$ .Aparte de los patrones básicos, también debemos conocer cuálesson los
caracteres que representan las repeticiones que pueden darse.
    173/381
    Supongamos que estamos buscando en una cadena de texto unpatrón que
corresponde a un determinado número de veces queaparece en la misma un
carácter. Para ello, recurriremos a losmetacaracteres de repetición, los
cuales aparecen explicados en latabla 5-2.
Metacarácter Significado
+ Una o más veces
* Cero o más veces
? Cero o una vez
{n} El carácter se repite n veces
Tabla 5-2. Metacarácteres de repetición en Python
Una vez que conocemos los patrones básicos y los metacaracteresde
repetición, podemos comenzar a definir sencillas expresionesregulares. Por
ejemplo, la siguiente expresión regular representa a queun número puede
aparecer una o más veces:
[\d]+
En los siguientes apartados nos centraremos en la funcionalidadque Python
ponemos a nuestra disposición para realizar búsquedas ysustituciones de
texto basándonos en expresiones regulares.
Búsquedas
    174/381
    La función básica de la que dispone el módulo re de Python es search().
Esta función recibe como argumento una expresión regular,representada por
un patrón, y una cadena donde realizar la búsqueda.El objetivo es buscar
una cadena dentro de otra, quedando la cadenaque deseamos buscar
definida por una expresión regular. Nuestroprimer ejemplo consistirá en
buscar la letra o en la cadena de texto hola. El siguiente código muestra
cómo hacerlo:
>>> import re>>> re.search(r"o", "hola")
    175/381
    <_sre.SRE_Match object at 0x00000000021A15E0>
Efectivamente, la simple cadena de texto "o" es por sí misma unaexpresión
regular. Nótese que nuestra cadena va precedida por la letra"r", esto le
indica al intérprete que se trata de un expresión regular. Porotro lado, el
segundo argumento de search() es la cadena dondevamos a buscar.Si
reescribimos la última línea de código asignado el resultado auna variable,
podremos acceder a los diferentes métodos del objetoque representa los
resultados obtenidos al realizar la búsqueda. Porejemplo, el método group()
nos devolverá la subcadena que coincidecon nuestro patrón de búsqueda.
Veamos un sencillo ejemplo:
>>> m = re.search(r"o", "hola")>>> m.group()"o"
Como habremos podido comprobar, la cadena devuelta es elpropio carácter
que buscamos. Pasemos a un ejemplo un poco máscomplicado, supongamos
que necesitamos encontrar cualquiersubcadena que contenga justamente
tres números seguidos. En estecaso buscaremos nuestro patrón en tres
cadenas de texto diferentes:>>> m = re.search(r"\d\d\d",
"hola402adios").group()
402>>> m = re.search(r"\d\d\d", "986hola").group()986>>> m =
re.search(r"\d\d\d", "adios123").group()123
Si pensamos utilizar la misma expresión regular para búsquedas
endiferentes cadenas de texto, es aconsejable compilar la expresiónregular.
Esto se puede hacer directamente a través de la función compile. Gracias a
ello ganamos en eficiencia y simplificamos nuestrocódigo. A continuación,
aplicaremos esta función tomando como baselos ejemplos anteriores:
>>>>>>402>>>986>>>123
patron = re.compile("\d\d\d")patron. search ("hola402adios").group()
patron.search("986hola")
patron.search("adios123")
    176/381
    ¿Qué ocurre si el patrón no coincide con ninguna parte de lacadena donde
se realiza la búsqueda? Sencillo, el método nosdevolverá None:
>>> m = re.search(r"\d\d\d", "98x65adios")>>> if m is None: print("No
existen coincidencias")
Obsérvese, que en este último caso, no hemos utilizadodirectamente el
método group(), dado que el resultado es None, siaplicáramos directamente
la llamada, obtendríamos un error en tiempode ejecución. Es decir, se
produciría una excepción.En realidad, group() es capaz de agrupar
resultados. De esta forma,podemos utilizar paréntesis en nuestra expresión
regular con elobjetivo de agrupar la subcadenas encontradas. Gracias a ello,
esposible tener más control sobre el resultado. Supongamos quenecesitamos
buscar un patrón que representa una serie de númerosseguidos de un guión
y de otro conjunto de letras. Además, queremosagruparlo en dos grupos,
uno para los números y otro para las letras.Nuestra expresión regular
quedaría de la siguiente forma:
>>> regex = re.compile(r"(\d+)-([A-Za-z]+")
Ahora pasaremos a buscar en una cadena concreta:
>>> m = regex.search("23-cDb")
La variable m representa el resultado de nuestra búsqueda. Dadoque la
cadena cumple el patrón, podremos llamar directamente almétodo group():
>>> m.group(1)23>>> m.group(2) cDb
Por otro lado, si llamamos a group() utilizando el índice 0,obtendremos la
cadena encontrada completa:
>>> m.group(O)23-cDb
Hasta ahora no hemos tenido en cuenta el principio y fin decadena,
simplemente estamos buscando un patrón en toda la cadena.Observemos las
siguientes sentencias y su resultado:
    177/381
    >>> re.search(r" ^hola$", "adiosholaadios")>>> re.search(r"hola",
"adiosholaadios")<_sre.SRE_Match object at 0x00000000021A15E0>
En el primer caso, no encontramos coincidencias, ya que, estamosindicando
que la cadena debe comenzar y terminar con los caracteresque se encuentra
entre el ¡nielo ( ^ ) y el fin ( $ ).Otra de las funciones de búsqueda con las
que cuenta el módulo re es findall(), que puede ser utilizada junto con los
grupos para obteneruna lista de tuplas que representa todas las
coincidencias encontradas.El caso más sencillo sería utilizar findall() sin
emplear grupos. Porejemplo, supongamos que necesitamos una lista con
todos los valoresde una cadena de texto que cumplen el patrón, que nuestro
caso serácuando aparezca exactamente tres números seguidos. Para
ello,podemos emplear el código que viene a continuación:
>>> re.findall(r"\d{3}", "345abcd78ghx678")[345, 678]
Si combinamos la función anterior con grupos, el resultado será unalista de
tuplas, tal y como hemos avanzado previamente:
>> re.findall(r"([a-z]+)-(\d+)", "345abcd-78ghx-678")[(abcd", "78"),
("ghx", "678"]
En el ejemplo anterior hemos podido observar cómo los valores dela lista
contienen una tupla con cada uno de los valores de la cadenaque coinciden
con los grupos del patrón.
Sustituciones
La principal función que nos ofrece el módulo re para realizarreemplazos de
texto en una determinada cadena, empleando para elloexpresiones
regulares, es sub. Como argumentos, esta función recibecuatro argumentos
diferentes. El primero de ellos es una expresiónregular que indica el patrón
que será buscado. El segundo argumentoes la cadena de texto que se
utilizará como reemplazo de lacoincidencia indicada por el primer
argumento. El tercero de losargumentos es la cadena de texto donde se
llevará a cabo elreemplazo. El cuarto y último argumento es opcional e
indica el
    178/381
    número de ocurrencias que deben ser reemplazadas, por defecto
sereemplazarán todas las que se encuentren. La función sub() devuelve
elresultado de aplicar el reemplazado, quedan sin modificar losargumentos
recibidos por la misma.Supongamos que contamos con una cadena de texto
que contieneletras y números y deseamos reemplazar todos los números
quecontenga por guiones. El siguiente código nos muestra cómo hacerlo:
>>> re.sub(r"\d", "-", "345abcdef987")---abcdef---
Si en el código anterior utilizáramos el cuarto parámetro que aceptala
función sub(), por ejemplo usando el valor 2, solo se reemplazaríanlos tres
primeros números, tal y como podemos apreciar en elsiguiente ejemplo:
>>> re.sub(r"\d", "-", "345abcdef987", 3)---abcdef987
Por otro lado, sub() también no permite utilizar grupos y sustituircada uno
de ellos por un valor diferente. Un ejemplo de ello podría serel que ilustra el
siguiente código:
>>> re.sub(r"(\w+)@(\w+)", r"\1@123", "abc@efg")abc@123
En el ejemplo anterior los caracteres \1 del segundo parámetroindican que
el primer grupo del patrón debe conservarse, siendosustituido el segundo
grupo por el valor indicado después de \1. Sisustituimos el segundo
parámetro indicando un 2 en lugar de un 1, elresultado sería diferente:
>>> re.sub(r"(\w+)@(\w+)", r"\2@123", "abc@efg")efg@123
Tanto para las búsquedas como para las sustituciones, Python 3puede
trabajar directamente con caracteres Unicode. No olvidemos,que por
defecto, todos los strings son de este tipo en esta versión delintérprete del
lenguaje. De esta forma, la siguiente sentencia escompletamente válida y
funcional:
>>> re.sub(r"ñ", "n", "niño")niño
    179/381
    Separaciones
Interesante es la también función split() ofrecida por el módulo
paraexpresiones regulares de Python. Básicamente esta función sirve
paraobtener una lista donde cada elemento es el resultado de utilizar
comocriterio de separación el patrón dado por la expresión regular.
Estafuncionalidad es más fácil de entender utilizando un ejemplo:
>>> cad = "Uno<a>Dos<b>Tres<c>Cuatro"["Uno", "Dos", "Tres",
"Cuatro"]
Tal y como el lector habrá podido deducir, esta funcionalidad essimilar al
método del mismo nombre de los strings, con la diferenciaque el incluido
en el módulo re admite expresiones regulares.
Modificadores
Cuando compilamos una expresión regular utilizando la función compile()
podemos emplear una serie de modificadores que alteran elcomportamiento
del patrón. Estos modificadores son muy útiles, porejemplo, para cuando
deseamos ignorar mayúsculas y minúsculas(case insensitive). En concreto,
los modificadores más comunes son lossiguientes:
IGNORECASE: No tiene en cuenta mayúsculas ni minúsculas.DOTALL:
Permite que el metacarácter punto (.) tenga encuenta las líneas en
blanco.MULTILINE: Sirve para que se puedan utilizar los caracteres
deinicio ( ^ ) y fin ( $ ) de línea en una cadena que contiene más deuna
línea.
A continuación, veamos un ejemplo de sustitución de cadena
conindependencia de si son mayúsculas o minúsculas:
>>> regex = re.compile(r"abc", re.IGNORECASE)>>>
regex.search("124AbC")AbC
    180/381
    Para ver cómo funcionan las búsquedas utilizando el modificador
MULTILINE, buscaremos un determinado patrón entre una cadena detexto
que incluye el carácter salto de carro (\n). En realidad, estacadena estará
compuesta por varias líneas, tal y como ocurre, porejemplo, cuando leemos
de un fichero, donde cada línea es unacadena en sí misma. El código
mostrado a continuación nos enseñacómo usar el mencionado modificador
en estos casos:
>>> regex = re.compile(r"^Text: (\d+)$", re. MULTILINE)>>> cad = "Text:
34\nText: 35\nText: aa\nText: 24\n">>> regex.search(cad).group(1)Text: 34
Patrones para comprobaciones cotidianas
En este apartado vamos a mostrar una serie de expresionesregulares que
suelen ser empleadas asiduamente para realizarcomprobaciones y
validaciones. Por ejemplo, es común en muchasaplicaciones web
comprobar si una determinada cadena de textointroducida por el usuario es,
sintácticamente, una cuenta de correoelectrónico. La tabla 5-3 muestra un
conjunto de este tipo deexpresiones regulares.
Expresión regular Funcionalidad
^d{1,6}$ Números desde el 0 hasta el
999999
^#([a-fA-FO-9]){3}(([a-fA-FO-9])
{3})?$
Código hexadecimal para HTML
^\d\d/\d\d/\d\d\d\d$ Fecha en formato dd/mm/yyyy
    181/381
    ^.*\// UNIX path
^([0-9afA-F]{2}:){5}[0-9afA-F]{2}$ Dirección MAC
Cuenta de
    182/381
    ^[0-9a-zA-Z]([-.\w] * [0-9a-zA-Z_+]) * @([0-9a- zA-Z][-\w] * [0-9a-zAZ]\.)+[a-zA-Z{2,9}$
correoelectrónico
Tabla 5-3. Expresiones regulares útiles para validaciones
* (https?):\/\/([0-9a-zA-Z][-\w+] [0-9a-zA- Z]\.)+[a-zA-Z] *
{2,9})(:\d{1,4})?([-\w/\#~:?+=&%@~] )
HTTP
URL
^(\d|[01]?\d\d|2[0-4]\d|25[0-5])\.(\d|[01]?\d\d|2[0- 4]\d|25[0-
5])\.(\d|[0-1]?\d\d|2[0-4]\d|25[0-5])\.(\d[01]? \d\d|2[0-4\d|25[0-
5])$
Dirección
IPv4
    183/381
    ORDENACIÓN DE DATOS
En el presente apartado nos centraremos en aprender cómoordenar datos. La
función más sencilla que Python nos ofrece es lallamada sorted(), que
básicamente, recibe como argumento una lista ydevuelve otra con los
elementos de la original ya ordenados. Enprincipio, es el intérprete quien
decide los criterios de ordenación,siendo esto trivial cuando los elementos
de la lista son números ocaracteres. Observemos el siguiente ejemplo,
donde ordenaremos unalista de números:
>>> lista = [5, 1, 9, 8, 3]>>> sorted(lista)[1, 3, 5, 8, 9]
De forma análoga, podemos ordenar una serie de caracteres ocadenas de
texto:
>>> cads = ["ca", "dc", "cb", "ab", "db"]>>> sorted(cads)["ab", "ca", "cb",
"db", "dc"]
Python también tiene en cuenta el orden si las cadenas de textoutilizan
mayúsculas y minúsculas, teniendo preferencia las primerassobre las
segundas, tal y como podemos comprobar en el siguientecódigo:
>>> cads = ["ca", "Cc", "cb", "aB", "db"]>>> sorted(cads)["Cc", "aB",
"ca", "cb", "db"]
Adicional y optativamente, la función sorted() puede utilizar unsegundo
parámetro para indicar si el orden debe hacerse ascedente
odescendentemente. Este parámetro se llama reverse y por defecto suvalor
es False. Así pues, para ordenar nuestra primera lista en ordendescendente,
basta con ejecutar la siguiente sentencia:
>>> sorted(lista, reverse=True)[9, 8, 5, 3, 1]
En caso de que deseemos indicar cuál será el criterio de ordenación
    184/381
    que debe emplear la función sort(), podremos hacerlo gracias alparámetro
key que acepta esta función. El valor de este parámetrodeberá ser una
función, que bien, podemos implementar nosotrosmismos o una ya
integrada en el intérprete. Por ejemplo, supongamosque deseamos ordenar
una lista en función del número de elementosque contienen. En este caso,
basta con utilizar la función integrada len(), tal y como muestra el siguiente
ejemplo:
>>> lista = ["abc", "de", "fghij"]>>> sorted(lista)["abc", "de", "fghij"]>>>
sorted(lista, key=len)["de", "abc", "fghij"]
Método itemgetter()
Para ordenaciones más complejas puede resultar muy útil emplearel método
itemgetter() del módulo integrado operator. Este métodopermite generar un
objeto que utilizará el valor pasado comoparámetro para devolver el
elemento que ocupa la posición que indicael mencionado valor cuando el
objeto es invocado pasando comoparámetro una serie de elementos. En
realidad, es más fácil de ver conun sencillo ejemplo de código que entender
la explicación queacabamos de ofrecer:
>>> from operator import itemgetter>>> fun = itemgetter(2)>>> fun( [1, 2,
3, 4])3
El valor devuelto es 3 porque este es el valor que ocupa la posicióndos en la
lista pasado como parámetro, recordemos que el índice delos elementos de
una lista comienza por cero y no por uno.Si combinamos la función sorted()
junto con el método itemgetter(), podemos realizar complejas ordenaciones.
Supongamos que contamoscon una lista de tuplas, donde cada uno de los
elementos de cadatupla es un número y una cadena de texto. Dada esta
estructura dedatos, necesitamos ordenar teniendo en cuenta el segundo
valor de latupla, que en nuestro caso será un número. Veamos cómo hacerlo
através del ejemplo que viene a continuación:
    185/381
    >>> lista = [("Madrid", 4), ("Barcelona", 1), ("Sevilla", 5),("Valencia", 3)]
[("Madrid", 4), ("Barcelona", 1), ("Sevilla", 5), ("Valencia", 3)]>>>
sorted(lista, key=itemgetter(1))[("Barcelona", 1), ("Valencia", 3),
("Madrid", 4), ("Sevilla", 5)]
Tal y como podemos apreciar, al ordenar se ha tenido en cuenta elsegundo
valor de cada tupla, es por ello que hemos utilizado el valor 1 como
parámetro para el método itemgetter(). No olvidemos que en lastuplas los
índices también comienzan por cero, al igual que en laslistas.El método
itemgetter() puede recibir más de un argumento.imaginemos que tenemos
una lista similar a la anterior que queremosordenar, primero teniendo en
cuenta el segundo valor y luego elprimero de cada una de las tuplas que
forma la lista. Bastaría con pasarun argumento adicional al mencionado
método, tal y como muestra elsiguiente ejemplo:
>>> lista = [("a", 2), ("c", 2), ("b", 3), ("d", 3), ("z", 1),("a", 1), ("d", 1)]>>>
sorted(lista, key=itemgetter(0,1))[("a", 1), ("a", 2), ("b", 3), ("c", 2), ("d",
1), ("d", 3), ("z", 1) ]
De forma análoga, podemos invertir el orden de los parámetrospara
conseguir una ordenación diferente:
>>> sorted(lista, key=itemgetter(1,0))[("a", 1), ("d", 1), ("z", 1), ("a", 2),
("c", 2), ("b", 3), ("d",3)]
Funciones lambda
Una técnica habitual para ordenar a través de la función sorted() esemplear
funciones lambda() como valor para el parámetro key. Graciasa esta técnica
es fácil ordenar, por ejemplo, una lista que contiene unaserie de objetos con
unos valores determinados. Vamos a trabajar conuna clase que representará
al empleado de una empresa,posteriormente construiremos una lista de
objetos Empleado yfinalmente la ordenaremos en función del atributo que
corresponde asus apellidos. Comenzamos declarando nuestra clase:
    186/381
    class Empleado:def __init__(self, apellidos, puesto, edad):self.apellidos =
apellidosself.puesto = puestoself.edad = edaddef __repr__(self):return
repr((self.apellidos, self.puesto, self.edad))
Llegamos al momento de crear nuestra lista con diferentes objetosde la
clase Empleado :
>>> empleados = [Empleado("Fernández", "Administración", 25),...
Empleado("Dominguez", "Finanzas", 38),... Empleado("Amaro",
"Contabilidad", 21)]...>>> print(sorted(empleados, key=lambda
empleado:empleado.apellidos))[("Amaro", "Contabilidad", 21),
("Dominguez", "Finanzas", 38),("Fernández", "Administración", 25)]
De forma análoga podemos emplear otro atributo como criterio
deordenación, por ejemplo, la edad:
>>> print(sorted(empleados, key=lambda empleado:empleado.apellidos) )
[("Amaro", "Contabilidad", 21), ("Fernández", "Administración",25)m
("Dominguez", "Finanzas", 38)]
    187/381
    FICHEROS
INTRODUCCIÓN
Los sistemas operativos almacenan los datos de forma estructuradaen unas
unidades básicas de almacenamiento a las que llamamos ficheros o
archivos. Tarde o temprano, los programadores necesitantrabajar con
ficheros, ya sea para leer datos de los mismos o paracrearlos. Es por ello
que los lenguajes de programación suelen incluirlibrerías que contienen
funciones para el tratamiento de ficheros. EnPython contamos con una serie
de funciones básicas, incluidas en sulibrería estándar, para leer y escribir
datos en ficheros. A estasfunciones dedicaremos el primer apartado del
presente capítulo.Una vez que aprendamos todo sobre lo básico sobre el
manejo deficheros en Python, pasaremos a adentrarnos en el concepto de
señalización, el cual está directamente relacionado con los
ficheros.Descubriremos qué herramientas nos ofrece el lenguaje y qué
módulosde la librería estándar pueden ser empleados para serializar datos
enficheros. Además, continuaremos haciendo un repaso a los tresprincipales
formatos utilizados para serializar: XML, JSON y YAML.Estos formatos no
solo se usan para serializar, sino que también suelenser empleados para
guardar información estructurada y para elintercambio de la misma. Por
ejemplo, son muchas las aplicacionesque utilizan el formato YAML para
guardar información sobre unadeterminada configuración.Dado que el
formato CSV es uno de los más sencillos y popularespara guardar
información estructurada en ficheros, dedicaremos unapartado específico a
ver cómo puede Python manejar este tipo deficheros. Aplicaciones como
MS Office y LibreOffice pueden exportar eimportar datos en formato CSV,
de forma que necesitamos desarrollaruna aplicación que trabaje con este
formato y que pueda intercambiardatos con este tipo de programas,
podremos emplear Python comolenguaje de programación.Python cuenta
con un módulo específico que permite leer ficherosen formato INI. Este es
muy popular en los sistemas Windows y suelen
    188/381
    ser empleado para guardar información relativa a una
determinadaconfiguración. También en este capítulo aprenderemos lo
básico parapoder trabajar con ficheros del mencionado tipo.Hoy en día es
muy común trabajar con ficheros comprimidos quenos permiten almacenar
más información en menos espacio. Formatoscomo ZIP, RAR o gzip se han
convertido en formatos de facto y lamayoría de los sistemas operativos
permiten utilizar herramientas paracomprimir o descomprimir ficheros.
Teniendo en cuenta estos factores,parece útil disponer de funcionalidades
que permitan trabajar con estetipo de ficheros desde un lenguaje de
programación. En Pythoncontamos con varios módulos de su librería
estándar que nos facilitanel trabajo, de ellas nos ocuparemos en el último
apartado de estecapítulo.
    189/381
    OPERACIONES BÁSICAS
Python incorpora en su librería estándar una estructura de datosespecífica
para trabajar con ficheros. Básicamente, esta estructura esun stream que
referencia al fichero físico del sistema operativo. Dadoque el intérprete de
Python es multiplataforma, el manejo interno y abajo nivel que se realiza
del fichero es transparente para elprogramador. Entre las operaciones que
Python permite realizar conficheros encontramos las más básicas que son:
apertura, creación,lectura y escritura de datos.
Apertura y creación
La principal función que debemos conocer cuando trabajamos conficheros
se llama open() y se encuentra integrada en la librería estándardel lenguaje.
Esta función devuelve un stream que nos permitirá operardirectamente
sobre el fichero en cuestión. Entre los argumentos queutiliza la mencionada
función, dos son los más importantes. El primerode ellos es una cadena de
texto que referencia la ruta (path) delsistema de ficheros. El otro parámetro
referencia el modo en el que vaa ser abierto el fichero. Es importante tener
en cuenta que Pythondiferencia entre dos tipos de ficheros, los de texto y
los binarios. Estadiferenciación no existe como tal en los sistemas
operativos, ya queson tratados de la misma forma a bajo nivel. Sin
embargo, a través del modo podemos indicarle a Python que un fichero es
un tipo u otro. Deesta forma, si el modo del fichero es texto, los datos leídos
seránconsiderados como un string, mientras que si el modo es binario,
losdatos serán tratados como bytes. Antes de comenzar a ver un ejemplo de
código, abriremos nuestroeditor de textos favorito, añadiremos una serie de
líneas de texto y losalvaremos, por ejemplo, con el nombre fichero.txt.
Seguidamente,ejecutaremos el intérprete de Python desde la interfaz de
comandos yescribiremos la siguiente línea:
    190/381
    >>> fich = open("fichero.txt")
En nuestro ejemplo, hemos supuesto que el fichero creado seencuentra en la
misma ruta desde la que hemos lanzado el intérprete,de no ser así es
necesario indicar el path absoluto o relativo al fichero.Por otro lado, no
hemos indicado ningún modo, ya que, por defecto,Python emplea el valor r
para ficheros de texto. Antes de continuar,debemos tener en cuenta que los
sistemas operativos suelen empleardiferentes caracteres como separadores
entre los directorios queforman parte de una ruta del sistema de ficheros.
Por ejemplo,supongamos que nuestro fichero se encuentra en un
directoriollamado pruebas. Si estamos trabajando en Windows, nuestra
línea decódigo para abrir el fichero sería la siguiente:
>>> fich = open("pruebas\fichero.txt")
Sin embargo, para realizar la misma operación en Linuxnecesitaríamos esta
otra línea de código:
>>> fich = open("pruebas/fichero.txt")
Dado que Python es multiplataforma, es deseable que el mismocódigo
funcione con independencia del sistema operativo que loejecuta, pero, en
nuestro ejemplo, este código es distinto. ¿Cómoresolver este problema? Es
sencillo, para ello Python nos facilita unafunción que se encuentra integrada
en el módulo os de su libreríaestándar. Se trata de join() y se encarga de unir
varias cadenas de textoempleando el separador adecuado para cada sistema
operativo. Deesta forma, sería más práctico escribir el código anterior para
laapertura del fichero de la siguiente forma:
>>> from os import path>>> ruta_fich = path.join("pruebas",
"fichero.txt")>>> fich = open(ruta_fich)
Por otro lado y como complemento a la función join(), tambiénexiste la
variable sep, que también pertenece al módulo os. Estarepresenta el carácter
propiamente dicho que cada sistema operativoemplea para la separación
entre directorios y ficheros.Respecto al valor que se puede indicar para el
modo de aperturadel fichero, Python nos permite utilizar un valor
    191/381
    determinado enfunción de la operación (lectura, escritura, añadir y
lectura/escritura) y
    192/381
    otro para señalar si el fichero es de texto o binario. Obviamente,ambos tipos
de valores se pueden combinar, para, por ejemplo, indicarque deseamos
abrir un fichero binario para solo lectura. En concreto, elvalor "r" significa
"solo lectura"; con "w" abriremos el fichero parapoder escribir en él; para
añadir datos al final de un fichero yaexistente emplearemos "a" y, por
último, si vamos a leer y escribir en elfichero, basta con utilizar "+". Por
otro lado, con "b" señalaremos queel fichero es binario y con "t" que es de
texto. Como hemoscomentado previamente, por defecto, la función open()
interpreta quevamos a abrir un fichero de texto como solo lectura. Tanto el
valorpara realizar una operación como para indicar el tipo de fichero
debeindicarse como argumentos del parámetro mode. Así pues, para abrirun
fichero binario para lectura y escritura basta con ejecutar elsiguiente
comando:
>>> open("data.bin", "b+")
Además de los mencionados parámetros de la función open(), existen otros
que podemos utilizar. En concreto, contamos con cincomás. El primero de
ellos es buffering y permite controlar el tamaño del buffer que el intérprete
utiliza para leer o escribir en el fichero. Elsegundo parámetro es encoding y
permite indicar el tipo decodificación de caracteres que deseamos emplear
para nuestro fichero.Otro de los parámetros es errors que indica cómo
manejar erroresdebidos a problemas derivados de la utilización de la
codificación decaracteres. Para controlar cómo tratar los saltos de línea
contamos con newline. Por último, closefd es True y si cambiamos su valor
a False eldescriptor de fichero permanecerá abierto aunque
explícitamenteinvoquemos al método close() para cerrar el fichero.Hasta el
momento hemos hablado de abrir ficheros, utilizando paraello diferentes
parámetros. Pero ¿cómo podemos crear un fichero ennuestro sistema de
archivos desde Python? Basta con aplicar el modode escritura y pasar el
nombre del nuevo fichero, tal y como muestra elsiguiente ejemplo:
>>> f_nuevo = open(nuevo.txt", "w")
Debemos tener en cuenta que, pasando el valor w sobre un ficheroque ya
existe, este será sobrescrito, borrando su contenido.Ahora que ya sabemos
cómo abrir y crear un fichero, es hora de
    193/381
    aprender cómo leer y escribir datos.
Lectura y escritura
La operación básica de escritura en un fichero se hace en Python através del
método write(). Si estamos trabajando con un fichero detexto, pasaremos
una cadena de texto a este método como argumentoprincipal. Supongamos
que vamos a crear un nuevo fichero de textoañadiendo una serie de líneas,
bastaría con ejecutar las siguienteslíneas de código:
>>>>>>14>>>14>>>
fich = open(texto.txt", "w")fich.write("Primera línea\n")
fich.write("Segunda línea\n")
fich.close()
El carácter "\n" sirve para indicar que deseamos añadir un retornode carro,
es decir, crear una nueva línea. Después de realizar lasoperaciones de
escritura, debemos cerrar el fichero para que elintérprete vuelque el
contenido al fichero físico y se pueda tambiéncerrar su descriptor. Si
necesitamos volcar texto antes de cerrar elfichero, podemos hacer uso del
método flush() y, posteriormente,podemos seguir empleando write(), sin
olvidar finalmente invocar a close(). En nuestro ejemplo, observaremos
cómo, después de cadaoperación de escritura, aparece un número. Este
indica el número debytes que han sido escritos en el
fichero.Adicionalmente, el método writelines() escribe una serie de
líneasleyéndolas desde una lista. Por ejemplo, si deseamos emplear
estemétodo en lugar de varias llamadas a write(), podemos sustituir
elcódigo anteriormente mostrado por este otro:
>>> lineas = ["Primera línea\n", "Segunda línea\n"]>>>
fich.writelines(lineas)
Ahora que ya tenemos texto en nuestro fichero, es hora de pasar ala
operación inversa a la escritura, hablamos de la lectura desde elfichero. Para
    194/381
    ello, Python nos ofrece tres métodos diferentes. Elprimero de ellos es
read(), el cual lee el contenido de todo el fichero y
    195/381
    devuelve un única cadena de texto. El segundo en cuestión es readline() que
se ocupa de leer línea a línea del fichero. Por último,contamos con
readlines() que devuelve una lista donde cada elementocorresponde a cada
línea que contiene el fichero. Los siguientesejemplos muestran cómo
utilizar estos métodos y su resultado sobre elfichero que hemos creado
previamente:
>>> fich = open(texto.txt", "w")>>> fich.read()'Primera línea\nSegunda
línea\n'>>> fich.seek(O)0>>> fich.readlines(fich)['Primera línea\n',
'Segunda línea\n']>>> fich.seek(O)0>>> fich.readline()'Primera línea\n'
Seguro que al lector no le ha pasado desapercibido el uso de unnuevo
método. Efectivamente, seek() es otro de los métodos quepodemos usar
sobre el objeto de Python que maneja ficheros y quesirve para posicionar el
puntero de avance sobre un puntodeterminado. En nuestro ejemplo, y dado
que deseamos volver alprincipio del fichero, hemos empleado el valor 0.
Debemos tener encuenta que cuando se hace una operación de escritura,
Python manejaun puntero para saber en qué posición deberá ser escrita la
siguientelínea con el objetivo de no sobrescribir nada. Sin embargo,
estepuntero avanza de forma automática y el método seek() sirve parasituar
el mencionado puntero en una determinada posición del fichero.Para leer
todas las líneas de un fichero no es necesario emplear seek() tal y como
muestra el ejemplo anterior de código. Existe unmétodo más sencillo
basado en emplear un iterator para ello:
>>> for line in open "texto.txt"):print(line)...Primera líneaSegunda línea
Además, también es práctica habitual emplear with para leer todaslas líneas
de un fichero:
>>> with open(texto.txt") as fich:
    196/381
    ... print (fich.read())...Primera líneaSegunda línea
Hasta el momento, nuestros ejemplos se han referido en exclusiva aficheros
de texto, pero, obviamente, es posible crear, abrir, leer yescribir en ficheros
binarios. La forma de llevar a cabo estasoperaciones es similar a como
hemos visto previamente para losficheros de texto. Por ejemplo, podemos
crear un fichero binario quesolo contiene una secuencia de bytes, en
concreto, vamos a escribir enel fichero tres bytes representados por tres
números en hexadecimal. Elcódigo necesario para ello sería el siguiente:
>>> fich = open("test.bin", "bw")<_io.BufferedWriter name='test.bin'>>>>
fich.write(b"\x33\xFA\x1E")3>>> fich.close()
El número 3 que el intérprete devuelve al escribir nuestra secuenciade bytes
corresponde, efectivamente, a los tres bytes que hemosescrito en el fichero.
Recordemos, que cada número en formatohexadecimal ocupa justamente un
byte. Para leer el contenido queacabamos de escribir, volveremos a abrir el
fichero, esta vez en modode solo lectura:
>>> fich = open("test.bin", "br")>>> fich.read(3)b'\x33\xFA\x1E'
En este caso, el parámetro pasado al método read() indica elnúmero de
bytes que deseamos leer. Si hubiéramos utilizado 1 enlugar de 3 , entonces,
el resultado hubiera sido b'\x33'. El fichero quehemos creado puede ser
leído por un editor que soporte la lectura yedición de este tipo de
ficheros.El método seek() suele ser utilizado con frecuencia para
desplazarsepor un fichero binario, a diferencia de los de texto, que
habitualmentesuelen ser leídos línea a línea. El mencionado método soporta
dostipos de parámetros diferentes, el primero de ellos indica el número
debytes que debe moverse el puntero interno de señalización del ficheroy, el
segundo, marca el punto de referencia desde el que debe sermovido dicho
puntero. Este segundo parámetro puede tomar tres
    197/381
    valores: 0 (comienzo del fichero), 1 (posición actual) y 2 (fin de
fichero).Por defecto, si no se indica este parámetro, el valor es 0. De esta
forma,para leer el último byte de nuestro fichero, emplearíamos las
siguientessentencias:
>>> fich.seek(-1, 2)2>>> fich.read(1)b'\xle'
Cuando se emplea como punto de referencia el final del fichero, sedeben
indicar valores negativos para el desplazamiento, tal y comohabremos
podido comprobar en el ejemplo anterior de código.Relacionado con la
creación, lectura y escritura de ficheros binariosse encuentra el concepto de
serialización de objetos del que nosocuparemos en el siguiente apartado.
    198/381
    SERIALIZACIÓN
La serialización es un proceso mediante el cual una estructura dedatos es
codificada de un modo específico para su almacenamiento,que puede ser
físicamente en un fichero, una base de datos o un buffer de memoria.
Habitualmente, la serialización se lleva a cabo utilizandoinstancias de
objetos, aunque son muchos los lenguajes deprogramación que permiten
aplicar este proceso a cualquier estructurade datos que cumpla ciertas
condiciones. El propósito de este proceso,además del almacenamiento
propiamente dicho, suele ser eltransporte de datos a través de una red o,
simplemente para crear unacopia exacta de un objeto determinado. En
computación distribuida esmuy común señalizar objetos para su transporte
entre diferentesmáquinas, también es habitual emplear la serialización en
protocoloscomo CORBA (Common Object Request Broker Architecture),
quepermite invocar a métodos y funciones escritos en un
lenguajedeterminado desde otro diferente. En general, el término de
serialización es conocido como marshalling en el ámbito de lasciencias de
la computación.Diferentes lenguajes de programación emplean
diferentesalgoritmos para señalizar. Python soporta la serialización de
objetos, através de dos módulos diferentes de su librería estándar: pickle y
marshal. Dado que los algoritmos empleados por estos módulos
sondiferentes, es necesario escoger uno de ellos a la hora de señalizardatos
en Python. Al proceso de convertir un objeto cualquiera en unconjunto de
bytes es llamado en Python pickling, siendo el procesoinverso llamado
unpickling. Es por ello, que habitualmente, el módulo pickle es el más
utilizado para la serialización de objetos en Python.Debemos tener en
cuenta que el módulo marshal señaliza de unaforma que no es compatible
entre Python 2.x y Python 3; sin embargo, pickle nos garantiza la
portabilidad del código entre versiones delintérprete. Por otro lado, marshal
no puede ser utilizado para señalizarlas clases propias definidas por el
programador, mientras que esto síque es posible con pickle. Dado que el
formato en el que sonseñalizados los datos, a través de pickle, es específico
de Python, no es
    199/381
    posible deserializar aquellos objetos señalizados con este móduloempleando
otros lenguajes de programación.La versión 3 de Python incluye cuatro
protocolos diferentes con losque puede trabajar pickle. Cada uno de ellos es
identificado por unnúmero entre 0 y 3. El primero de ellos es compatible
con versionesanteriores a Python 3 y serializa utilizando un formato humanredeable. El segundo de los protocolos emplea un formato binario y
escompatible con las primeras versiones de Python. El tercero,identificado
por el número 2, fue incluido por primera vez en Python2.3 y es mucho más
eficiente que sus antecesores. Por último,contamos con uno nuevo incluido
en Python 3.0, que no puede serutilizado por la serie 2.x de Python para la
deserialización y que esactualmente el recomendado para la serie 3.x del
lenguaje. Además, elmencionado módulo incluye dos constantes diferentes:
HIGHEST_PROTOCOL, que se identifica a la versión más alta disponibley
DEFAULT_PROTOCOL, que actualmente está asociada al protocolo
3.Respecto a los tipos de datos que pueden ser señalizados enPython con
pickle, debemos tener en cuenta que son los siguientes:
Aquellos que toman los valores True, False y None.
Números enteros, complejos y reales.Cadenas de texto, bytes y listas de
bytes.Funciones definidas en el nivel superior de un módulo.Funciones
integradas en el intérprete que residan en el nivelsuperior de un
módulo.Tuplas, listas, conjuntos y diccionarios que contenganelementos
que pertenezcan a los tipos anteriores.Clases definidas por el programador
que contenganelementos que pertenezcan a los tipos anteriores.
Como complemento a pickle, Python pone a nuestra disposiciónotro
módulo llamado pickletools, que contiene una serie deherramientas para
analizar los datos en el formato generado alserializar con pickle.
    200/381
    Ejemplo práctico
Básicamente, para serializar objetos en Python, a través del módulo pickle,
emplearemos dos funciones diferentes: dump() para serializar y load() para
la operación inversa. Para nuestro ejemplo práctico vamos acrear una clase,
similar a la del capítulo anterior que representaba a unempleado. En este
caso vamos a definir una clase que representa a unalumno, tal y como
muestra el siguiente código:
>>> class Alumno:... def __init__(self, nombre_completo, titulación,
edad):... self.apellidos = nombre_completo['apellidos']... self.nombre =
nombre_completo['nombre']... self . titulación = titulación... self.edad =
edad... def __repr__(self):... return repr(self.nombre, self.apellidos,
self.titulación)
Como el lector habrá podido adivinar, vamos a emplear cadenas detexto,
enteros y un diccionario como tipos de datos que contendrá laInstancia de
nuestra clase. Dado que todos cumplen con lascondiciones para señalizar,
no tendremos problema para llevar a caboeste proceso. Antes de ello,
crearemos una Instancia que será laseñalizada y, posteriormente,
deserializada:
>>> nombre_completo = {"apellidos": "Rodríguez",
"nombre":"Lucas"}>>> alumno = Alumno(nombre_completo, "Grado en
Derecho", 21)
El siguiente paso será llevar a cabo la serialización,
previamenteimportaremos el módulo pickle y posteriormente Invocaremos a
lafunción dump(). Los datos de nuestra Instancia serán señalizados en
unfichero binario llamado alumnos.bin. Efectivamente, los ficherosbinarlos
suelen ser los empleados para llevar a cabo este proceso, deahí la relación
entre este tipo de ficheros y la serialización, tal y comohabíamos avanzado
en el apartado anterior del presente capítulo. Encuanto al código, el
siguiente nos muestra cómo crear el fichero yaplicar dump():
>>> import pickle>>> with open(alumnos.bin", "wb") as
fich:pickle.dump(alumno, fich)
    201/381
    Para llevar a cabo el proceso inverso, es decir, la deseríalízacíón,
esnecesario invocar al método load(). Así pues, y siguiendo con
nuestroejemplo, para leer los datos que acabamos de señalizar, basta
conejecutar las siguientes líneas de código:
>>> with open(alumnos.bin", "rb") as fich:... pickle.load(fich)Lucas
Rodríguez Grado en Derecho 21
Es importante tener en cuenta que cuando se emplea la función load() para
deseríalízar los datos de una clase, esta debe ser accesibleen el mismo
ámbito. En nuestro ejemplo y dado que hemos utilizado laconsola de
comandos del intérprete, cumplimos con esta condición.
    202/381
    FICHEROS XML, JSON Y YAML
Con la serialización se encuentran relacionados una serie deformatos de
ficheros que suelen ser utilizados para guardar datos enun determinado
formato y, que pueden compartirse entre diferentesmáquinas y tratarse en
distintos lenguajes de programación. Entreestos formatos, destacan tres:
XML, JSON y YAML. En este apartadodescubriremos cómo podemos
trabajar con estos formatos desdePython. Comenzamos por uno de los más
populares, el XML.
XML
Estas tres siglas vienen de eXtensible Markup Language yreferencian a un
lenguaje de etiquetas desarrollado por el WC3 (WorldWide Web
Consortium). En realidad, este lenguaje no es más que unaadaptación del
original SGML (Standard Generalized Markup Language) y fue diseñado
con el objetivo de tener una herramienta que fueracapaz de ayudar a definir
lenguajes de marcas y etiquetas, adaptados adiferentes necesidades.
Además, también puede ser utilizado como unestándar para el intercambio
de datos estructurados. De hecho, es estauna de las aplicaciones más
usuales del formato XML. Dado que esposible definir de forma
personalizada una estructura concreta dedatos, estos pueden ser
almacenados en un simple fichero de texto,haciendo el mismo totalmente
portable entre máquinas y lenguajes deprogramación. En la práctica,
podemos almacenar en un fichero XMLdesde información relativa a una
configuración específica, hasta datosque habitualmente podrían
almacenarse en una base de datos. Es más,comúnmente el formato XML es
empleado para serializar datos yexisten multitud de librerías que permiten,
dada una definición de unobjeto, volcarlo directamente a un fichero en este
formato. En laactualidad, XML es un formato estándar de facto, empleado
pormultitud de aplicaciones web y de escritorio.Los datos en formato XML
pueden ser accedidos a través de dosinterfaces diferentes: DOM (Document
Object Model) y SAX (Simple API
    203/381
    for XML). La primera de ellas se basa en utilizar y tratar los datosbasándose
en una estructura de árbol, donde se definen nodos y unajerarquía que
permite acceder a los diferentes elementos que formanparte de los datos
contenidos en XML. Por otro lado, SAX estáorientado a eventos y permite
trabajar en un sección del documentoXML en lugar de hacerlo en todo el
conjunto, tal y como necesitaDOM.Son muchos los lenguajes de
programación que ofrecenherramientas para trabajar con XML y Python se
encuentra entre ellos.En concreto, el módulo xml de la librería estándar
pone a disposiciónde los programadores diferentes analizadores sintácticos
para leer yescribir información en formato XML Tres son los submódulos
quepermiten interactuar con XML de formas diferentes. El primero de
elloses xml.sax.handler que utiliza el módulo SAX para
analizarsintácticamente ( parsear ). El segundo se denomina
xml.dom.minidom que ofrece una implementación ligera para el modelo
DOM. El últimode los tres es xml.etree.ElementTree y básicamente ofrece
la mismafuncionalidad que xml.dom, solo que empleando una forma
específicade Python para parsear el formato.La estructura de un documento
XML puede ser muy compleja; sinembargo, para nuestros ejemplos
prácticos definiremos un sencillodocumento y veremos cómo puede ser
accedido empleando los tresmódulos diferentes que pone Python a nuestra
disposición. El lectorinteresado en profundizar en todas y cada una de las
opcionesexistentes de estos módulos, puede echar un vistazo a
ladocumentación oficial de Python al respecto (ver
referencias).Comenzamos definiendo un documento XML con el contenido
querepresenta a la información de un varios álbumes musicales de unartista
determinado:
<albums><interprete>U2</interprete><titulo>Achtung Baby</titulo>
<titulo>Zooropa</titulo><titulo>The Joshua Tree</titulo>
<genero>rock</genero></albums>
Una vez definida la estructura, podemos guardar el contenido en unnuevo
fichero llamado albums.xml. Seguidamente, veremos cómo
    204/381
    mostrar la información relativa a los diferentes títulos que
contiene,empleando para ello los diferentes módulos de Python para
análisis deXML. Comenzamos por ElementTree :
>>> from xml.etree.ElementTree import parse>>> xml_doc =
parse('albums.xml')>>> for ele in xml_doc.findall('titulo'):...
print(ele.text)...Achtung BabyZooropaThe Joshua Tree
La función parse() se encarga de cargar y la estructura del fichero
enmemoria y prepararla para poder recorrerla y acceder a su información.El
método findall() localiza todas las etiquetas que contiene el
nombreindicado, y, finalmente, para acceder a la información de los títulos
encuestión, utilizamos text que es un atributo que contiene el valor
encuestión.De funcionalidad análoga al ejemplo anterior tenemos el
siguientecódigo que emplea las herramientas de minidom para acceder
eimprimir los títulos de nuestro fichero XML:
>>> from xml.dom.minidom import parse, Node>>> xmltree =
parse('albums.xml')>>> for nodo in
xmltree.getElementsByTagName('titulo'):... for nodo_hijo in
nodo.childNodes:... if nodo_hijo.nodeType == Node.TEXT_NODE:...
print(nodo_hijo.data)...Achtung BabyZooropaThe Joshua Tree
Observando el código anterior, descubriremos cómo pasando elparámetro
titulo al al método getElementsByTagName() podemosdeclarar un bucle
para recorrer uno a uno todos los nodos quecontiene que cumplen con la
condición especificada. Además,anidamos otro bucle para recorrer todos los
hijos de estos nodos.Dentro de este último bucle for debemos comprobar si
el nodo es detipo texto, en cuyo caso accederemos al atributo data que
contiene elvalor que buscamos. La constante TEXT_NODE representa el
tipo textoque contiene una etiqueta determinada. En nuestro caso,
correspondeal título en cuestión de cada disco.
    205/381
    El último de nuestros ejemplos muestra cómo emplear el métodoSAX para
recorrer nuestro fichero e imprimir el título de cada álbum.Para procesar el
fichero vamos a crear una clase que se encargará deresponder a los
diferentes eventos que se producen cuando se utilizael parser de SAX. Así
pues, nuestra clase estará definida por el siguientecódigo:
import xml.sax.handlerclass
AlbumSaxHandler(xml.sax.handler.ContentHandler):def
__init__(self):self.in_title = False
def startElement(self, name, attributes):if name == 'titulo':self.in title =
Truedef characters(self, data):if self.in_title:print(data)
def endElement(self, name):if name == 'titulo':self.in_title = False
La clase AlbumSaxHandler hereda de una clase incluida en elmódulo que
Python incluye para trabajar con SAX. Los métodos startElement() y
endElement() son los encargados de llevar a cabo unaserie de acciones
cuando se detecta el principio y el fin,respectivamente, de cada etiqueta del
fichero XML. Por otro lado, characters() comprobará si el atributo de clase
in title es True, en cuyocaso imprimirá el contenido del elemento
referenciado por <titulo>. Una vez que tenemos nuestra clase, solo nos
queda invocar al parser, para ello necesitaremos el siguiente código:
>>> import xml.sax>>> parser = xml.sax.make_parser()>>> sax_handler =
AlbumSaxHandler()>>> parser.setContentHandler(sax_handler)>>>
parser.parse('albums.xml')Achtung BabyZooropaThe Joshua Tree
La elección de la técnica y módulo de Python para utilizar a la horade
parsear y trabajar con XML's depende de varios factores. Dos de losmás
importantes son el tamaño del fichero y el tipo de estructura quepresenta.
Estos tienen gran impacto sobre la capacidad de
    206/381
    procesamiento y memoria requerida para su análisis.
JSON
El formato JSON se ha convertido en uno de los más populares parala
serialización e intercambio de datos, sobre todo en aplicaciones webque
utilizan AJAX. Recordemos que esta técnica permite intercambiardatos
entre el navegador cliente y el servidor sin necesidad de recargarla página.
JSON son las siglas en inglés de JavaScript Object Notation yfue definido
dentro de uno de los estándares (ECMA- 262) en los queestá basado el
lenguaje JavaScript. Es en este lenguaje donde, a travésde una simple
función ( eval()), podemos crear un objeto directamentea través de una
cadena de texto en formato JSON. Este factor hacontribuido
significativamente a que sean muchos los servicios webque utilizan JSON
para intercambiar datos a través de AJAX, ya que elanálisis y
procesamiento de datos es muy rápido. Incluso, son muchaslas que han
sustituido el XML por el JSON a la hora de intercambiardatos entre cliente
y servidor.Para estructurar la información que contiene el formato, se
utilizanlas llaves ({}) y se definen pares de nombres y valor separados
entreellos por dos puntos. De esta forma, un sencillo ejemplo en
formatoJSON, para almacenar la información de un empleado, sería
elsiguiente:
{"apellidos": "Fernández Rojas", "nombre": "José Luis","departamento":
"Finanzas", "ciudad": "Madrid"}
JSON puede trabajar con varios tipos de datos como valores; enconcreto,
admite cadenas de caracteres, números, booleanos, arrays y null. La
mayoría de los modernos lenguajes de programación incluyenAPI y/o
librerías que permiten trabajar con datos en formato JSON. EnPython
disponemos de un módulo llamado json que forma parte de lalibrería
estándar del lenguaje. Básicamente, este método cuenta condos funciones
principales, una para codificar y otra para descodificar. Elobjetivo de ambas
funciones es traducir entre una cadena de texto conformato JSON y objetos
de Python. Obviamente, utilizando las
    207/381
    funciones de ficheros de Python podemos leer y escribir ficheros
quecontengan datos en este formato. No obstante, debemos tener encuenta
que las funciones contenidas en json no permiten trabajardirectamente con
ficheros, sino con cadenas de texto.Volviendo a nuestro ejemplo de fichero
XML, los mismos datospueden ser codificados en formato JSON de la
siguiente forma:
{"albums": {"titulos": ["Achtung Baby", "Zooropa", "The JoshuaTree"],
"genero": "Rock"}}
La anterior cadena puede ser salvada en un fichero llamado albums.json,
siendo este el que utilizaremos para nuestros posterioresejemplos.Para leer
los datos de nuestro nuevo fichero, basta con ejecutar lassiguientes líneas de
código:
>>>>>>>>>>>>>>>
import jsonfich = open('albums.json')line = fich.readline()fich.close()data =
json.loads(line)
La variable data contendrá un diccionario de Python que secorresponde con
la estructura de nuestro fichero JSON. Así pues, paraobtener los títulos de
nuestros álbumes basta con ejecutar:
>>> data['albums'] ['titulos']['Achtung Baby', 'Zooropa', 'The Joshua Tree']
Realizar el paso inverso es sencillo, partiendo del nuevo diccionario data,
podemos directamente invocar a dumps(), pasando comoargumento nuestro
diccionario, de esta forma obtendremos unacadena de texto JSON, que
posteriormente, puede ser guardada en unfichero. El siguiente código
realizaría todas estas operaciones,incluyendo la modificación de la cadena
origina, añadiendo un nuevotítulo:
>>>>>>>>>>>>>>>
data['albums'] ['titulos'].append("Rattle and Hum")cad =
json.dumps(data)new_fich = open('albums_new.json',
    208/381
    'w')new_fich.writeline(cad)new_fich.close()
Si abrimos el nuevo fichero albums_new.json y echamos un vistazo a
    209/381
    su contenido, encontraremos el siguiente texto en formato JSON:
{"albums": {"títulos": ["Achtung Baby", "Zooropa", "The JoshuaTree",
"Rattle and Hum"], "genero": "Rock"}}
Además del módulo json, también existen otros módulos paratrabajar con
JSON. Uno de ellos es simplejson (ver referencias), que noforma parte de la
librería estándar pero que puede ser instaladofácilmente. En el capítulo
instalación y distribución de software nosocuparemos de cómo instalar
módulos adicionales que pueden serutilizados por nuestros propios
programas.
YAML
El último de los formatos de ficheros relacionados con laserialización de
datos es YAML, acrónimo de YAML Ain't MarkupLanguage. Este formato
fue diseñado con el objetivo de disponer de unformato de texto para
serializar datos que fuese sencillo y fácil de leerpara las personas. A pesar
de no ser tan popular como JSON y XML, esinteresante conocer este
formato, ya que puede ser procesadorápidamente y resulta muy fácil de leer.
El conocido framework web Ruby on Rails emplea el formato YAML para
definir determinadosficheros de configuración, como es, por ejemplo, el
que declara losdatos para la conexión a las diferentes bases de datos de
cadaentorno.Básicamente, YAML utiliza pares clave: valor para almacenar
yestructurar los datos. Además, la indentaclón es empleada para
separardatos que pertenecen a otros de una jerarquía superior. Un ejemplo
deuna cadena YAML podría ser la siguiente, donde se definen tresentornos
diferentes y cada uno de ellos cuenta con una dirección IP ypuerto
diferentes:
desarrollo:IP: 127.0.0.1puerto: 8000staging:IP: 192.168.1.2puerto:
8002producción:IP: 192.168.1.2
    210/381
    puerto: 8003
Python no dispone de ningún módulo en su librería estándar paratrabajar
con ficheros YAML; sin embargo, existen varias librerías thirdparty para
ello. Una de las más populares es PyYAML (ver referencias).Su instalación
puede ser llevada a cabo directamente a través de sucódigo fuente, el cual
puede ser descargado desde la correspondientepágina web (ver referencias).
En cuanto descarguemos el fichero zip con el código fuente, pasaremos a
descomprimirlo. Seguidamente,desde la línea de comandos y desde el
directorio creado aldescomprimir, ejecutaremos el siguiente comando:
python setup.py install
En cuanto termine la ejecución del comando anterior, tendremosdisponible
un nuevo módulo para Python llamado yaml. Esto implicaque, desde el
intérprete de Python, podemos invocar al nuevo módulocon la siguiente
sentencia:
>>> import yaml
Para la parte práctica partiremos de la cadena YAML que hemosdefinido
previamente, creando un nuevo fichero denominado servidores.yml. De
forma similar al módulo json, yaml cuenta con dosfunciones principales,
una para leer una cadena de texto a través de unfichero y transformarla en
un diccionario de Python, y otra que realizala operación Inversa, es decir,
obtiene una cadena de texto a partir deun diccionario de Python. La primera
de ellas se denomina load() y lasegunda dump(). Así pues, para leer nuestro
fichero YAML, basta conejecutar la siguiente línea de código:
>>> data = yaml.load(open('servidores.yml'))
La variable data contiene ahora un diccionario donde las claves son
desarrollo, staging y producción. Además, cada una de estas claves
daacceso a otro diccionario cuyas claves son IP y puerto que da a su
vezacceso a los valores de nuestro fichero. Dada esta estructura, para,
porejemplo, acceder al puerto del entorno de producción, basta conejecutar
la siguiente línea:
    211/381
    >>> data['producción'] ['puerto']8003
    212/381
    Por otro lado, la estructura puede ser modificada y crear con ella unnuevo
fichero YAML. Supongamos que necesitamos añadir un nuevoentorno
llamado pruebas, junto con los datos de un nuevo servidor. Enprimer lugar,
crearíamos un nuevo diccionario y lo asignaríamos a data, tal y como
muestra el código a continuación:
>>> data['pruebas'] = {'IP': '192.168.2.8', 'puerto': 8004}
Seguidamente, invocamos a la función dump pasando comoargumento
nuestro diccionario original:
>>> yaml.dump(data)'desarrollo: {IP: 192.168.2.8, puerto:
8004}\nproduccion: {IP:192.168.1.2, puerto: 8003}\npruebas: {IP:
192.168.1.8, puerto:8004}\n'\nstaging: {iP: 192.168.1.2, puerto: 8002}\n'
Ahora podemos guardar nuestra cadena YAML en un nuevo fichero:
>>> fich = open('new_servidores', 'w')>>> fich.write(yaml.dump(data))>>>
fich.close()
    213/381
    FICHEROS CSV
El formato CSV (Comma Separated Values) es uno de los máscomunes y
sencillos para almacenar una serle de valores como si deuna tabla se tratara.
Cada fila se representa por una línea diferente,mientras que los valores que
forman una columna aparecen separadospor un carácter concreto. El más
común de los caracteres empleadospara esta separación es la coma, de ahí el
nombre del formato. Sinembargo, es habitual encontrar otros caracteres
como el signo deldólar ( $ ) o el punto y coma ( ; ). Gracias al formato CSV
es posibleguardar una serle de datos, representados por una tabla, en un
simplefichero de texto. Además, software para trabajar con hojas de
cálculo,como, por ejemplo, Microsoft Excel, nos permiten importar y
exportardatos en este formato.Dentro de su librería estándar, Python
incorpora un móduloespecífico para trabajar con ficheros CSV, que nos
permite, tanto leerdatos, como escribirlos. Son dos los métodos básicos que
nosposibilitan realizar estar operaciones: reader() y writer(). El primero
deellos sirve para leer los datos contenidos en un fichero CSV, mientrasque
el segundo nos ayudará a la escritura.Supongamos que tenemos un sencillo
fichero CSV, llamado empleados.csv, que contiene las siguientes líneas:
Martínez,Juan,Administración,BarcelonaLópez,María,Finanzas.ValenciaRo
dríguez,Manuel.Ventas,GranadaRojas,Ana,Dirección,Madrid
Cada línea de nuestro fichero identifica a un empleado, donde, elprimer
valor es el primer apellido, el segundo es el departamentodonde trabaja y
por último, el tercer valor es la ciudad en la que seencuentra. Para leer este
fichero y mostrar la información indicando elnombre de cada campo y su
valor asociado, bastaría con ejecutar elsiguiente código:
>>> import csv>>> with open('empleados.csv') as f:... reader =
csv.reader(f)... for row in reader:
    214/381
    ... print("Apellido: {O}; Nombre: {1}; Departamento: {2};Ciudad:
{3}".format(row[0], row[1], row[2], row[3]))...Apellido: Martínez;
Nombre: Juan; Departamento: Administración;Ciudad: BarcelonaApellido:
López; Nombre: María; Departamento: Finanzas; Ciudad:ValenciaApellido:
Rodríguez; Nombre: Manuel; Departamento: Ventas;
Ciudad:GranadaApellido: Rojas; Nombre: Ana; Departamento: Dirección;
Ciudad:Madrid
Efectivamente, el método reader() es de tipo iterable y nos daacceso a
todas las filas del fichero, es por ello, que en nuestro ejemplo,utilizamos un
bucle for para iterar sobre el objeto devuelto. Por otrolado, cada elemento
iterable es una lista que contiene tantoselementos como valores separados
por el carácter de separación tengacada línea de nuestro fichero. En caso de
que empleemos un carácterde separación diferente de la coma, deberemos
indicarlo a través delparámetro delimiter. Si cambiamos nuestro fichero
empleados.csv yreemplazamos la coma por, por ejemplo, el símbolo del
dólar,tendríamos que emplear delimiter de la siguiente forma:
>>> reader = csv.reader(f, delimiter='$')
La operación de escritura en ficheros de tipo CSV se lleva a cabo através de
los métodos writer() y writerow(). Este último escribedirectamente una fila
en el fichero y como argumento debemos pasaruna lista donde cada
elemento representa cada valor de la columnacorrespondiente a cada fila.
Volvamos a tomar un par de líneas denuestro ejemplo anterior y creemos un
nuevo fichero a partir de ellas:
>>> fich = open('nuevo.csv', 'w')>>> fich_w = csv.writer(fich,
delimiter='$')>>> empleados = [['Martínez', 'Juan',
'Administración','Barcelona'], ['López', 'María', 'Finanzas', 'Valencia']]>>>
for empleado in empleados:... fich_w.writerow(empleado)...>>> fich.close()
En esta ocasión hemos creado un nuevo fichero (nuevo.csv) quecontiene
solo los datos de un par de empleados; además, el carácterde separación es
el símbolo del dólar. Si abrimos el fichero reciéncreado por código,
veremos que tiene el siguiente contenido:
    215/381
    Martinez$Juan$Administracion$BarcelonaLopez$Maria$Finanzas$Valenci
a
Finalizamos en este punto nuestro recorrido por el tratamiento deficheros
CSV con Python. El lector interesado en descubrir más sobre elmódulo csv
puede echar un vistazo a la página web oficial del mismo(ver referencias).
    216/381
    ANALIZADOR DE FICHEROS DE CONFIGURACIÓN
En los sistemas Windows es muy popular el formato INI, quepermite
almacenar una serie de datos en formato propiedad = valor. Además, se
pueden agrupar los datos por secciones, siendorepresentada cada una de
ellas por un nombre entre corchetes ( [] ). Elregistro de Windows emplea un
formato ligeramente diferente al INI,pero inspirado claramente en el
mismo.Estos tipos de ficheros no son exclusivos de Windows, ya que
sonmuchas las aplicaciones que las utilizan, sobre todo, para guardardatos
relativos a cierta configuración. Por ejemplo, supongamos quetenemos que
guardar la información de una serie de servidores,incluyendo el usuario y
puerto por defecto empleados para laconexión. Podríamos utilizar una
sección por servidor y dospropiedades diferentes, una para el usuario y otra
para el puerto. Elfichero INI, por ejemplo, conf.ini, sería como sigue:
[server1.dominio1]user = firstport = 22
[server2,dominio2]user = secondport = 88
En lugar de guardar datos de configuración en una base de datos ode
tenerlos directamente en el código (hardcoded), los ficheros INI sonmuy
prácticos para almacenar y actualizar valores sin necesidad decambiar el
código fuente o de acceder a una base de datos.Para el manejo de estos
ficheros INI, Python pone a nuestradisposición un módulo llamado
configparser, que forma parte de sulibrería estándar. Este módulo incorpora
una clase base denominada ConfigParser que nos permite tanto leer como
crear ficheros delmencionado tipo.Nuestra práctica va a comenzar
importando el módulo y cargandoel fichero que hemos creado previamente:
>>> from configparser import ConfigParser>>> config = ConfigParser()
    217/381
    >>> config.read("config.ini")
Ahora podemos, por ejemplo, obtener una lista con todas lassecciones de
nuestro fichero:
>>> config.sections()['server1.dominio1', 'server2.dominio2']
Una vez que tenemos nuestra instancia config de la clase ConfigParser,
podemos acceder a los valores de cada sección. Para ello,bastará con tener
en cuenta que cada nombre de sección es la clave deun diccionario que, a su
vez, nos proporciona acceso a otro diccionariodonde cada clave es el valor
de la propiedad. Al acceder a estasegunda clave dispondremos del valor
correspondiente de lacorrespondiente propiedad de la sección. De esta
forma, para accederal valor port de la segunda sección de nuestro fichero de
ejemplo,bastará con ejecutar la siguiente sentencia:
>>> config = ['server2.dominio2']['port']88
Análogamente, si deseamos obtener el valor de la propiedad user de la
primera sección del fichero, emplearíamos el siguiente código:
>>> config['server1.dominio1'] ['user']first
Además de leer las propiedades, obviamente, también podemosescribir
otras nuevas. Supongamos que tenemos que añadir una nuevasección con
una serie de propiedades y valores diferentes. El primerpaso sería crear un
nuevo diccionario vacío y posteriormente añadirlelas nuevas propiedades y
valores, tal y como muestran las siguienteslíneas:
>>> config['server3.dominio3'] = {}>>> config['server3.dominio3']
['protocol'] = ’ssh'>>> config['server3.dominio3'] ['timeout'] = '30'
Seguidamente, deberemos abrir el fichero original y escribir en él através
del método write() que pertenece a la clase ConfigParser.
>>> with open ('config.ini', 'w') as fich:... config.write(fich)...
    218/381
    Si ahora abrimos nuestro fichero INI con cualquier editor de
textos,comprobaremos cómo contiene las siguientes líneas:
[server1.dominio1]ser = firstport = 22
[server2.dominio2]user = secondport = 88
[server3.dominio3]protocol = sshtimeout = 30
Por otro lado, y dado que cada sección está representada por undiccionario,
también es posible obtener un listado con las propiedadesque tiene una
determinada sección. El siguiente código nos muestralas propiedades para
la sección que hemos creado anteriormente:
>>> for propiedad in config['server3.dominio3']:...
print(propiedad)...protocoltimeout
Aunque hemos trabajado con una estructura básica de ficheros INI,estos
pueden contener otra ligeramente diferente. Para averiguarcómo está
definida esta estructura, aconsejamos visitar la página oficialde la
documentación de Python al respecto (ver referencias).
COMPRESIÓN Y DESCOMPRESIÓN DE FICHEROS
Python nos ofrece la opción de trabajar con distintos formatos
decompresión para archivos. Es decir, es posible crear ficheroscomprimidos
que contengan uno o varios ficheros a su vez, ydescomprimir un archivo
para extraer su contenido. En concreto, lalibrería estándar de Python cuenta
con cuatro módulos diferentes quepermiten trabajar con los formatos
estándar de compresión ZIP (.zip), tarball (.tar), gzip (.gz) y bunzip (.bz2).
A continuación, veremos unaserie de ejemplos para comprimir y
descomprimir ficheros utilizandocada uno de estos formatos.
    219/381
    Formato ZIP
El nombre del módulo de la librería estándar que nos permitetrabajar con
este formato se llama zipfile. En concreto, podemos crear,leer, escribir,
añadir y listar el contenido de un fichero con esteformato. Respecto a la
encriptación, aún no es posible crear un ficheroencriptado y, aunque la
desencriptación es posible, no esrecomendable, dado que el proceso
requiere de mucho tiempo deprocesamiento. El módulo zipfile también
soporta la creación deficheros ZIP de tamaño superior a 4 GB.Dentro del
mencionado módulo, la principal clase es Zipfile yencapsula la mayoría de
operaciones que pueden ser llevadas a cabosobre los ficheros ZIP.
Adicionalmente, existe otra clase llamada PyZipFile, muy similar a la
anteriormente mencionada, pero con unaserie de mejoras sobre ella. Por
otro lado, cuando invocamos a losmétodos de Zipfile, que nos dan
información sobre el contenido de unfichero comprimido, estos nos
devuelven como resultado un objeto detipo ZipInfo, el cual encapsula la
respuesta ofrecida por los métodos encuestión.Comenzaremos nuestra
práctica creando un fichero ZIP quecontendrá una serie de ficheros.
Podemos elegir varios ficheros que yatengamos en nuestro disco, tanto
binarios como de texto. El siguientecódigo muestra cómo crear el fichero
first.zip con tres ficherosdiferentes:
>>> import zipfile>>> with zipfile.ZipFile('first.zip', 'w') as fzip:...
fzip.write('empleados.csv')... fzip.write('fich.txt')fzip.write('test.dat')
Si ahora abrimos el recién creado fichero first.zip con unaherramienta como
WinZip, Winrar o similar, comprobaremos cómo,efectivamente el fichero
comprimido contiene los tres ficheros quehemos elegido y comprimido
desde nuestro código Python. Además,dado que es posible, listar el
contenido desde Python, será estenuestro siguiente paso, para el que
emplearemos el siguiente código:
>>> fzip = zipfile.ZipFile('first.zip')>>> fzip.printdir()
    220/381
    Filenameempleados.csvfich.txttest.dat
Modified2012-01-07 21:47:142012-02-07 22:32:022012-03-07 20:24:54
Size13391
En este último ejemplo de código, el método Zipfile solo recibe unúnico
argumento, el nombre del fichero, ya que, por defecto, el ficherose abre en
modo de solo lectura. Sin embargo, cuando hemosinvocado al mismo
método para crear el fichero, el valor w ha sidopasado como parámetro para
indicar que el fichero debe ser creado.Este modo es similar al que utiliza la
función open(), tal y como hemosaprendido previamente.Relacionado con
el método printdir(), encontramos a namelist(), elcual nos devuelve una
lista que contiene un elemento por ficherocontenido en el ZIP original.
Podemos invocarlo directamente:
>>> fzip.namelist()['empleados.csv', 'fich.txt', 'test.dat']
La operación inversa a la compresión es la descompresión y, paraello, la
clase Zipfile cuenta con los métodos extract() y extractall(). Elprimero de
ellos extrae solo uno de los ficheros que se encuentrancomprimidos,
mientras que el segundo se encarga de extraer todo elcontenido del fichero
ZIP. Por ejemplo, si ejecutamos el siguientecódigo, comprobaremos cómo
se crean los tres ficheros de los queconsta el fichero ZIP que hemos creado
previamente:
>>> fzip.extractall()(path="..")
El paramétro path sirve para indicar en qué directorio del sistemade
ficheros queremos que sean descomprimidos los ficheros. Ennuestro
ejemplo, hemos elegido el directorio padre desde el que seestá ejecutando la
consola del intérprete de Python. Adicionalmente, siel fichero hubiera sido
creado con una password, podemos indicar lamisma a través del parámetro
pwd. Como hemos mencionado previamente, una instancia de la clase
ZipInfo es devuelta por los métodos que nos ayudan a obtenerinformación
del fichero comprimido; en concreto, dichos métodos son infolist() y
getinfo(). Ambos devuelven el mismo tipo de información, ladiferencia
    221/381
    reside en que el primer método lo hace sobre todos y cadauno de los
ficheros que forman parte del ZIP y el segundo necesita
    222/381
    recibir como parámetro uno de los ficheros para devolver lainformación
relativa al mismo. Entre la información que puede serobtenida desde la
clase ZipInfo, destacamos el nombre de cada fichero,la fecha y hora de la
última modificación, el tipo de compresión y eltamaño que ocupa cada
fichero antes y después de la compresión.Sirvan como ejemplo las
siguientes líneas de código que nos ofreceninformación relativa al nombre,
fecha de última modificación y tamañode cada fichero comprimido:
>>> info = fzip.infolist()>>> for arch in info:... print(arch.filename,
arch.date_time,arch.compress_size)...empleados.csv (2012, 2, 7, 21, 47, 14)
133fich.txt (2012, 2, 8, 18, 48, 26) 9test.dat (2012, 2, 8, 18, 51, 6) 1
Respecto a la fecha y hora, en realidad, el valor devuelto es unatupla de seis
elementos que indican año, mes, día del mes, hora,minutos y segundos.Para
más información sobre otros métodos ofrecidos por el módulo zipfile,
aconsejamos visitar la documentación oficial del mismo (verreferencias).
Formato gzip
El módulo gzip de Python permite comprimir y descomprimirficheros tal y
como lo hacen las herramientas gzip y gunzip. Ambosprogramas pertenecen
a GNU y son muy populares en el mundoUNIX/Linux. De hecho, la gran
mayoría de los sistemas operativosbasados en uno u otro incluyen ambas
herramientas.Técnicamente, el módulo gzip es una simple interfaz sobre
lasmencionadas herramientas de GNU, ya que, en realidad, el módulo zlib
es el que proporciona la compresión real de datos.La principal clase de gzip
es GzipFile y simula la mayoría de losmétodos disponibles para el tipo de
dato fichero de Python. De estaforma, los mismos valores para abrir y crear
un fichero que emplea lafunción open(), son aplicables al constructor de la
mencionada clase.
    223/381
    Para crear un fichero comprimido a partir de un fichero original,basta con
abrir el fichero original, invocar al método open() del módulo gzip, y
posteriormente invocar al método writelines(), tal y comomuestra el
siguiente código:
>>> import gzip>>> with open('fich.dat', ' rb') as f_original:with
gzip.open('fich.txt.gz', ’wb') as fich:... fich.writelines(f_original)...
Es importante tener en cuenta que el módulo gzip solo trabaja condatos en
binario, es decir, que las cadenas de texto no estánsoportadas a través de los
métodos de lectura y escritura. Esta es larazón por la cual nuestro ejemplo
ha utilizado el valor b para marcar eltipo de modo.Si necesitamos crear un
fichero gz a partir de una serie de datosbinarios, también podemos hacerlo,
en este caso, a través del método write():
>>> datos_binarios = b"Este string es binario">>> with
gzip.open('nuevo.gz', 'wb') as fich:... fich.write(datos binarios)...
Por otro lado, el módulo gzip de Python también nos ofrece laopción de
comprimir cadenas de texto, sin necesidad de trabajar conficheros. De este
modo, el siguiente código nos permitiría disponer deuna cadena
comprimida:
>>> datos_binarios = b"Comprimiendo cadena">>> comprimidos =
gzip.compress(datos_binarios)
El método que realiza la operación contraria a la compresión sedenomina
decompress() y funciona de forma inversa a como lo hace compress().
Formato bz2
Para trabajar con ficheros en formato bunzip, Python nos ofrece elmódulo
llamado bz¡p2, que, básicamente, cuenta con la clase BZ2File.
    224/381
    Esta representa a un fichero con este tipo de compresión y dispone
demétodos para crear, leer, comprimir y descomprimir datos. Desde elpunto
de vista técnico, este módulo es una interfaz a la librería decompresión bz2.
A pesar de que este formato no es tan popular comoel tarball y el ZIP, cada
vez son más los que lo emplean, gracias a quees capaz de ajustar bastante el
tamaño final de los ficheroscomprimidos.La compresión y descompresión
de datos se puede realizarfácilmente a través de los métodos compress() y
decompress(), respectivamente. Por ejemplo, para comprimir una cadena
binaria:
>>> import bz2>>> cad = b"Cadena binaria">>> cad_comprimida =
bz2.compress(cad)
La operación inversa, es decir, la descompresión, se realizaría deforma
similar:
>>> cad_descomprimida = bz2.decompress(cad_comprimida)
De forma similar a la que hemos visto previamente en los ejemplosde gzip,
podemos crear un fichero comprimido en formato bz2, a partirde uno
original sin compresión. Para ello, observemos el siguientecódigo:
>>> with open('fich.dat', 'rb') as f_original:... with
bz2.BZ2File('fich.dat.bz2', 'wb') as fich:... fich.writelines(f_original)
Nótese que, a diferencia de gzip, el módulo bz2 dispone delmétodo open() a
través de la clase BZ2File.
Formato tarball
El formato tar es uno de los más populares para compresión dearchivos en
sistemas UNIX. En la actualidad, la gran mayoría de lasdistribuciones de
Linux Incorporan por defecto utilidades para trabajarcon este formato. El
mismo hecho puede ser aplicado para Mac OS X,mientras que en Windows,
el formato más popular sigue siendo el ZIP.Es común encontrar ficheros tar
a los que, además, se les ha
    225/381
    añadido compresión empleando gzip o bz2. En realidad, tar nocomprime
por sí mismo, simplemente agrupa ficheros. Los ficheros queutilizan tar
junto a gzip o bz2 son llamados tarball y pueden tener laextensión tar.gz o
tar.bz2. El módulo de Python que permite trabajar con tarballs se llama
tarfile y permite comprimir, descomprimir y listar el contenido de estetipo
de ficheros. La clase base del módulo se denomina Tarfile yfunciona
deforma similar a Zipfile. Para crear un fichero tarball a partir de una serie
de ficheros,podemos emplear el siguiente conjunto de sentencias:
>>>>>>>>>>>>>>>>>>
import tarfileftar = tarfile.open('first.tar.gz',
'w:gz')ftar.add('empleados.csv')ftar.add('fich.txt')ftar.add('test.dat')ftar.close(
)
Si abrimos el nuevo fichero first.tar.gz, veremos que efectivamentecontiene
los ficheros que hemos añadido. Por ejemplo, paracomprobar que la
operación se ha realizado correctamente, en lainterfaz de comandos de
Linux podemos ejecutar:
$ tar -zvtf first.tar.gz
Como el lector habrá podido observar, el método open() ha pasadocomo
parámetro, además del nombre del nuevo fichero, el valor w:gz, el cual
indica que el fichero debe crearse utilizando gzip paracomprimir. Si solo
indicáramos el valor w, simplemente se crearía unfichero tar, pero sin
compresión. Para evitar errores, es convenienteque los ficheros que no
utilicen compresión lleven la extensión tar enlugar de tar.gz. Al igual que
ZipFile, la clase TarFile cuenta con los métodos extract() y extractall() que
nos permite extraer todo el contenido del tarball o deun fichero concreto
que forma parte del mismo. Si deseamos extraertodos los ficheros basta con
ejecutar:
>>> ftar = tarfile.open('first.tar.gz', 'r:gz')>>> ftar.extractall()
En caso de necesitar listar el contenido del tarball, basta con invocaral
método list():
    226/381
    >>> ftar.list()rw-rw-rw- 0/0 133 2012-01-07 21:47:14rw-rw-rw- 0/0 9
2012-02-07 22:32:0rw-rw-rw- 0/0 1 2012-03-07 20:24:54
empleados.csvfich.txttest.dat
Efectivamente, la primera columna de la salida de list() muestrainformación
relativa a los permisos que existen sobre el fichero. Esta esuna de las
habilidades de los tarballs, ya que permiten agrupar ycomprimir una serie
de ficheros manteniendo la jerarquía de permisosque existe sobre
ellos.Relacionado con el método anteriormente comentado,
tambiéndisponemos de otro denominado getnames(), que nos devuelve
unalista con el nombre de los ficheros que pertenecen al tarball.
Siguiendocon nuestro ejemplo, podemos invocarlo tal y como muestra
lasiguiente línea de código:
>>> ftar.getnames()['empleados.csv', 'fich.txt', 'test.dat']
Hasta aquí todo lo relativo a los ficheros y a su tratamiento. Elsiguiente
capítulo lo dedicaremos a otro aspecto relacionado con elalmacenamiento
de datos: las bases de datos.
    227/381
    BASES DE DATOS
INTRODUCCIÓN
No cabe duda de que las bases de datos juegan un papel muyimportante en
el mundo del software. Muchas de las aplicacionescorporativas que se
ejecutan diariamente en todo el mundo seríaninconcebibles sin el uso de
base de datos. Incluso los servicios web queusamos a diario cuentan con
una base de datos para almacenar lainformación que
utilizan.Tradicionalmente, las bases de datos han sido
identificadasdirectamente con un tipo específico, las llamadas bases de
datosrelacionales o RDBMS ( Relational Database Management System).
Enrealidad, estas denominaciones se refieren al motor empleado
paragestionar la obtención y almacenamiento de información en
ficherosfísicos en disco duro. Sin embargo, existen varios tipos de bases
dedatos, además de las relacionales, como son, por ejemplo, lasorientadas a
objetos y las NoSQL. Estas últimas han ganado granpopularidad en los
últimos años y motores como Cassandra y MongoDB son utilizados por
grandes empresas para manejar grancantidad de información.En este
capítulo nos centraremos en los dos tipos de bases dedatos que más se
utilizan en la actualidad: las relacionales y las NoSQL.Descubriremos cómo
interactuar, desde Python, con varios de losmotores más populares de cada
uno de estos tipos. En concreto,trabajaremos con MySQL, PostgreSQL,
Oracle y SQLite3, en lo que a losrelacionales se refiere, y con Cassandra,
Redls y MongoDB en el casode los NoSQL.Entre las principales
operaciones, aprenderemos a conectarnos ydesconectarnos de una base de
datos, realizar operaciones deconsultar e inserción y a mostrar resultados
obtenidos comoconsecuencia de una operación de consulta.Por otro lado,
dentro de la categoría de las bases de datosrelacionales, existen una serie de
componentes a los que se les conocecomo ORM (Object-Relational
Mapping), que, básicamente, permiten
    228/381
    interactuar con una base de datos con independencia de cuál sea sumotor.
Esto permite desarrollar código portable, ya que, en lugar deutilizar el
propio lenguaje SQL, se utilizan una serie de objetos ymétodos que actúan
como wrappers. Además, los ORM permitenestablecer una relación directa
entre objetos de nuestro programa ytablas de una base de datos. Ello se
logra a través de la técnicaconocida como mapping. Incluso, las relaciones
establecidas entrediferentes clases se corresponden a relaciones entre tablas
a través de,por ejemplo, claves foráneas. En Python, dos son los módulos
máspopulares que se emplean como ORM: SQLObject y SQLAchemy.
Decómo trabajar con ambos nos ocuparemos también en este
capítulo.Suponemos que el lector está familiarizado con el lenguaje SQL y,
almenos, tiene la experiencia básica de trabajar con bases de
datosrelacionales. Igual suposición haremos para las bases de datos
NoSQL.Ello se debe a que no explicaremos fundamentos de ninguno
deambos tipos de bases de datos, sino que nos centraremos en
cómointeractuar con ellas desde Python. Además, para ejecutar y
comprobarlos ejemplos de código de este capítulo, recomendamos
quepreviamente se lleve a cabo la instalación, en la máquina local, de
cadamotor de base de datos con los que vamos a trabajar. Si esto no
esposible, otra opción podría ser la de acceder a una máquina quecuenta
previamente con cada motor de base de datos previamenteinstalado y
configurado. Para consultar información al respecto,aconsejamos echar un
vistazo a los enlaces referenciados en lasreferencias para el presente
capítulo.
    229/381
    RELACIONALES
Previamente a descubrir cómo interactuar con los gestores de basesde datos
relacionales anteriormente mencionados, describiremos unaserie de datos
que pueden ser almacenados en una tabla y en unabase de datos manejada
por cualquiera de estos gestores.Posteriormente, utilizaremos el ejemplo de
tabla descrito en esteapartado como base para trabajar e interactuar con
Python.Para nuestro ejemplo utilizaremos una entidad que nos
permitarepresentar una serie de países que almacenen información sobre
supoblación, en millones de habitantes, el continente al que pertenece yla
moneda que se utiliza en el mismo. Así pues, necesitaremos crearuna tabla
llamada países que cuenta con cuatro atributos principales (nombre,
habitantes, moneda y continente ), más uno adicional (id) quees un número
y que nos servirá para identificar de forma unívoca acada país. De esta
forma, nuestra tabla contendrá varios países, tal ycomo representamos a
continuación:
ID País Continente Habitantes Moneda
1 España Europa 47 Euro
2 Alemania Europa 82 Euro
3 Canadá América 34 Dólar canadiense
4 China Asia 1340 Yuan
5 Brasil América 204 Real
    230/381
    Tabla 7-1. Tabla con información sobre países
Antes de continuar, es conveniente crear una nueva base de datos ala que
llamaremos prueba. Seguidamente, crearemos una tabla, en lanueva base de
datos, utilizando para ello la siguiente sentencia SQL:
CREATE TABLE países('id' INT(11) NOT NULL
AUTOINCREMENT,'nombre' VARCHAR(1OO) NOT NULL,'continente'
VARCHAR(20) NOT NULL,'habitantes' INT(11) NOT NULL,
    231/381
    'moneda' VARCHAR(30) NOT NULL,PRIMARY KEY ('id’))
Debemos tener en cuenta que la anterior sentencia SQL puedevariar en
función del gestor de base de datos. Basta con comprobar lasintaxis para
asegurarnos de que el campo 'id' será la clave de nuestratabla y de que los
tipos de cada uno de los otros campos coincidencon los indicados por la
nuestra sentencia SQL.Ahora que ya tenemos tanto la nueva base de datos,
como la tablade ejemplo de países, podemos pasar a los siguientes
apartadosdonde aprenderemos a conectarnos a la base de datos,
insertarregistros en la nueva tabla y a extraer datos de la misma.
MySQL
La interacción de Python con MySQL puede ser llevada a cabo através de
un módulo llamado MySQLdb. Dado que este no forma partede la librería
estándar, necesitaremos instalarlo en nuestro sistema.Esta instalación puede
ser llevada a cabo a través del gestor pip , delque nos ocuparemos en el
capítulo 9 Instalación y distribución depaquetes. De momento, daremos por
supuesto que tenemos instaladoeste gestor de paquetes para Python. Así
pues, para la instalación de MySQLdb, bastará con ejecutar el siguiente
comando desde unaterminal:
pip install MySQLdb
Una vez que tengamos nuestro módulo instalado, podemosinvocarlo como
si de un módulo de la librería estándar se tratara. Deesta forma, dentro del
intérprete de Python ejecutaremos:
>>> import MySQLdb
Ya estamos listos para ¡nteractuar con el gestor de base de datos.Nuestra
primera operación será obtener una conexión, para ellolanzaremos la
siguiente sentencia:
>>> con = MySQLdb.connect('localhost', 'usuario', 'password','prueba')
    232/381
    La función connect() recibe como parámetros el nombre de lamáquina que
está ejecutando el gestor de base de datos, el usuariopara conectarnos a
nuestra base de datos, su contraseña y el nombrede la base de datos en
cuestión. Si el servidor de base de datos correen nuestra máquina local, el
valor localhost debe ser el indicado. Porotro lado, usuario es el valor de
ejemplo que hemos elegido para elnombre del usuario en cuestión y
password el valor para su contraseña.Obviamente, debemos sustituir estos
valores por los reales de nuestrabase de datos. Como respuesta a la
invocación de connect() obtendremos un objeto que representa una
conexión a la base dedatos, a partir de la cual podremos realizar diversas
operaciones.Tanto para ejecutar una sentencia SQL para actualizar, Insertar
oborrar datos, como para consultar datos, necesitaremos un objetoespecífico
llamado cursor. Así pues, antes de realizar cualquiera deestas operaciones,
procedemos a la obtención del mismo:
>>> cursor = con.cursor()
Ahora que ya disponemos del objeto cursor, podemos, por ejemplo,insertar
un registro:
>>> sql = 'INSERT INTO(nombre, continente, habitantes,
moneda)VALUES ('España', 'Europa', 47, 'Euro')'>>> cursor.execute(sql)
Los demás países de nuestro ejemplo pueden ser insertados de lamisma
forma, cambiando, lógicamente, la sentencia SQL. Elprocedimiento para
ejecutar sentencias SQL de tipo UPDATE o DELETE es similar, basta con
escribir la sentencia en cuestión e invocar a execute(). Sin embargo, si
empleamos una sentencia de tipo SELECT deberemos manejar el resultado
devuelto a través de la llamada amétodos adicionales. Supongamos, por
ejemplo, que necesitamosobtener todos los países de Europa y mostrar toda
la informaciónrelativa a los mismos. El código necesario para ello sería el
siguiente:
>>>>>>>>>>>>......(1,(2,
sql = 'SELECT * FROM paises WHERE
continente='Europa''cursor.execute(sql)rows = cursor.fetchall()for row in
    233/381
    rows:print(row)
'España', 'Europa', 47, 'Euro')'Alemania', 'Europa, 82, 'Euro')
    234/381
    El método fetchall() extrae todos los registros que cumplen con lacondición
especificada, si asignamos su resultado a la variable rows, podemos iterar y
obtener la información de todos los registros. Enrealidad, obtendremos una
tupla donde cada uno de los valores secorresponden con el de los campos de
la tabla. Así pues, siquisiéramos obtener solamente el valor correspondiente
a los millonesde habitantes de cada país, bastaría sustituir la sentencia
print(row) denuestro bucle for, por la siguiente:
print(row[3])
Dado que el número índice para acceder a cada campo no es muyintuitivo,
es posible emplear un tipo especial de cursor que nospermitirá utilizar
como índice el nombre de cada campo de losregistros de la tabla. Para ello,
tendremos que emplear el método cursor pasando un parámetro específico,
tal y como muestra lasiguiente sentencia:
>>> cursor = con.cursor(MySQLdb.cursors.DictCursor)
Seguidamente, invocaremos al método fetchall(), tal y como hemoshecho
previamente y cambiaremos nuestro bucle for por el siguiente,donde solo
vamos a imprimir el nombre de país y su moneda:
>>> for row in rows:... print('País: {o}. Moneda:
{1}'.format(row['nombre'],row['moneda']))...País: España. Moneda:
EuroPaís: Alemania. Moneda: Euro
Otro método interesante que podemos invocar desde el objeto cursor es
fetchone(), el cual solo obtiene un registro. Este método esespecialmente
útil cuando necesitamos obtener la información relativaa un único registro.
Por ejemplo, supongamos que debemos leer lamoneda de China. Dado que
solo puede haber un país con esenombre, ejecutaríamos:
>>>>>>>>>>>>(4,
sql = 'SELECT * FROM países WHERE nombre='China''cursor =
con.cursor()cursor.execute(sql)cursor.fetchone()'China', 'Asia', 1340, 'Yuan')
    235/381
    Si el método fetchone() es lanzado sobre un cursor que devuelvemás de un
resultado, solo el primero será obtenido por este método,quedando el resto
de registros inaccesibles a través del cursor encuestión.Las transacciones
también pueden ser manejadas por el módulo MySQLdb, siempre y cuando
nuestro gestor de MySQL estéconfigurado para este propósito. Las
operaciones básicas sobretransacciones que puede lanzar el módulo en
cuestión, son commit y rollback. La primera de ellas realizaría las
operaciones que forman latransacción, mientras que la segunda desharía
todas si se producealgún error. Habitualmente, se suele utilizar un bloque
try/except paraejecutar transacciones, tal y como muestra el siguiente
ejemplo:
>> try:... cursor.execute(sql)... cursor.commit()... except MySQLdb.Error:...
con.rollback()...
Cuando una sentencia SQL en la que solo cambian los valores seejecuta
repetidamente, por cuestiones de eficiencia, es convenienteutilizar los
llamados prepared statements. Un típico ejemplo es cuandodebemos lanzar
varias sentencias SELECT donde solo cambia el valorde un cierto campo
del WHERE. Eso es bastante habitual enaplicaciones web, donde diferentes
usuarios simultáneamente accedena una determinada página que lanza una
consulta. Además, en estetipo de aplicaciones los prepared statement evitan
un posible ataquepor inyección SQL. En la práctica, basta con parametrizar
los valoresque cambian en la sentencia SQL y pasarlos como un
argumentoadicional. En el caso de MySQLdb podemos hacerlo utilizando
unatupla con los valores en cuestión, pasado como segundo parámetrodel
método execute(). El siguiente ejemplo de código muestra cómoemplear un
prepared statement para realizar una consulta donde solocambia el
continente:
>>> sql = 'SELECT moneda, nombre FROM países WHERE
continente=%s'>>> cursor.execute(sql, ('Europa'))>>> cursor.execute(sql,
('Asia'))
No olvidemos que es importante cerrar la conexión a la base dedatos en
cuanto finalicemos nuestro trabajo con ella. En caso contrario,
    236/381
    estaremos consumiendo recursos innecesariamente y el rendimientode la
aplicación bajará considerablemente. Para cerrar una conexiónque
previamente tenemos abierta, basta con invocar al método close(), en
nuestro ejemplo ejecutaríamos la siguiente sentencia:
>>> con.close()
El módulo MySQLdb incluye otras clases y métodos que tambiénpueden ser
utilizadas para interactuar con MySQL. Una vez que hemosaprendido los
fundamentos de utilización de ese módulo, dejamos allector como ejercicio
que eche un vistazo a la documentación de estemódulo para complementar
lo expuesto en este apartado.
PostgreSQL
El módulo más popular para trabajar con bases de datosPostgreSQL es
psycopg2 y, al igual que en el caso de MySQLdb, este noforma parte de la
librería estándar de Python, por lo que hay queinstalarlo para poder ser
utilizado. Contando con que tenemos pip instalado, bastaría con ejecutar
desde la línea de comandos:
pip install psycopg2
En cuanto lo tengamos instalado, podremos cargarlo desde elintérprete de
Python:
>>> import psycopg2
La conexión a una base de datos es sencilla y similar a como hemosvisto
previamente para MySQLdb, basta con invocar al método connect() pasando
como parámetros de conexión el nombre delservidor donde se está
ejecutando la base de datos, el nombre de labase de datos en cuestión, el
usuario y su contraseña. Como ejemplo,echemos un vistazo a la siguiente
sentencia:
>>> con = psycopg2.connect('dbname=paises
user=usuariopassword=password host=localhost')
    237/381
    Para lanzar una sentencia SQL, también necesitamos contar con elobjeto
cursor, así pues, obtendremos una instancia ejecutando:
    238/381
    >>> cursor = con.cursor()
El método execute() es el encargado de lanzar las sentencias SQL,sirva
como ejemplo una actualización del número de habitantes deBrasil:
>>> sql = 'UPDATE países SET habitantes=205 WHERE
nombre='Brasil''>>> cursor.execute(sql)
Para leer la información obtenida a través de sentencias SELECT, contamos
con dos métodos principales homónimos a los de MySQLdb:fetchone() y
fetchall(). Su funcionamiento es indéntico al mencionadomódulo de
MySQL. Adicionalmente, el método fetchmany() puede serutilizado para
obtener un determinado número de filas, basta conpasar como argumento el
número de las que necesitamos, tal y comomuestra el siguiente ejemplo:
>>> sql = 'SELECT nombre FROM países WHERE habitantes > 80'>>>
cursor.execute(sql)>>> cursor.fetchmany(2)('Alemania, 'Europa', 87,
'Euro')'('China', 'Asia', 1340, ’Yuan)'
Otros métodos comunes a MySQLdb que también existen en psycopg2 son
close(), commit() y rollback(), teniendo estos análogafuncionalidad.
Además, este módulo para interactuar con PostgreSQLofrece la opción de
trabajar con prepared statement de la misma formaque MySQLdb, es decir,
parametrlzando con una tupla los valores de lasentencia SQLEl lector
interesado puede ampliar su conocimiento sobre psycopg2 consultando la
documentación oficial (ver referencias) que puede serencontrada en el sitio
web del módulo.
Oracle
En el mundo empresarial Oracle es uno de los gestores de bases dedatos
relacionales más utilizados. Este gestor es conocido por sucapacidad de
procesamiento, en especial cuando trata con elevadosconjuntos de datos. A
diferencia de MySQL y PostgreSQL, Oracle no es open source y debe ser
utilizado bajo previo pago de la
    239/381
    correspondiente licencia de software.Desde Python también podemos
trabajar con bases de datosgestionadas por Oracle, para ello contamos con
el módulo llamado cxOracle. Dado que este no existe en la librería estándar,
también debeser Instalado manualmente. Esta operación puede ser
realizada, comohemos visto en casos anteriores, a través del gestor de
paquetes pip:
pip install cx_Oracle
Para importar el módulo desde el intérprete o desde un script,escribiremos:
import cx_Oracle
La conexión a la base de datos se realiza a través de una cadena detexto con
los datos necesarios para la conexión. Esta cadena debe serpasada a la
función connect(), tal y como muestra el siguiente ejemplo:
>>> cad_con = 'usuario/password@localhost/paises'>>> con =
cx_Oracle.connect(cad_con)
A partir de que obtengamos nuestro objeto de conexión y, deforma similar a
como hemos aprendido en MySQLdb y psycopg2, necesitamos un cursor
sobre el que ejecutar nuestras sentencias SQL. Elmétodo para obtener el
cursor es similar al de los módulosanteriormente mencionados y el código
para ello sería el siguiente:
>>> cursor = con.cursor()
Para obtener una lista de tuplas donde cada elemento representaun registro
de la tabla, contamos con el método fetchAII(), el cualpuede ser invocado
directamente sobre el cursor. Por otro lado, si solonecesitamos un número
determinado de registros, el método fetchmany() podrá ayudarnos. Este
método debe pasar comoparámetro el número de filas que deseamos
obtener; para ello,fijaremos el valor de numRows, tal y como muestra el
siguienteejemplo:
>>> rows = cursor.fetchmany(numRows=3)
    240/381
    A la hora de utilizar prepared statements, el módulo cxOracle lohace de
forma diferente a MySQLdb y psycopg2, ya que emplea unmétodo llamado
prepare() que recibe la consulta y se vale de execute()
    241/381
    para pasar los parámetros necesarios. De esta forma, el siguienteejemplo
muestra cómo parametrizar una sentencia SELECT:
>>>>>>>>>>>>
cursor = con.cursor ()sql = 'SELECT * FROM países WHERE moneda=
:moneda')cursor.prepare(sql)cursor.execute(None, {'moneda': 'Euro'})
En el caso de cx_0rade se emplean diccionarios para las
consultasparametrizadas, donde cada clave corresponde a la variable que va
aser parametrizada y su valor se corresponde con el que va a ser pasadopara
ejecutar la sentencia SQL.Oracle permite trabajar con procedimientos
almacenados que suelenestar escritos en un lenguaje propio llamado
PI/SQL. Este tipo decomponentes permiten ganar en eficiencia y es habitual
que las basesde datos Oracle hagan uso de ellos, sobre todo para
complicadasconsultas y operaciones. Desde Python podemos invocar a
estosprocedimientos almacenados gracias al método callfunc() del objeto
cursor. Supongamos que nuestra base de datos cuenta con uno deestos
procedimientos al que hemos llamado miproc. Este debe recibircomo
argumento el valor para una variable, llamada x, que es de tipoentero. La
invocación a través de cx_Oracle sería como sigue:
>>> cursor = con.cursor()>>> res = cursor.callfunc('miproc',
cx_Oracle.NUMBER, ('x', 33) )
A continuación, nos ocuparemos de los ORM más popularescompatibles
con Python 3.
SQLite3
SQLite3 es un gestor de bases de datos relacionales caracterizadopor su
motor, el cual se encuentra contenido en una librería de C.Gracias a este
hecho, no es necesaria la instalación y configuración delgestor, basta con
instalar la correspondiente librería en el sistema. Unade sus principales
ventajas es que ocupa menos de 300 KB, lo que lahace muy portable,
especialmente para dispositivos que cuentan conrecursos hardware
    242/381
    limitados. Por otro lado, una base de datos SQLite3está compuesta por un
único fichero binario.
    243/381
    A diferencia de los casos anteriormente explicados para Oracle,MysQL y
PostgreSQL, para conectarnos a SQLite3 no necesitaremosningún módulo
adicional, debido a que Python incluye uno en sulibrería estándar. Así pues,
para su utilización, bastará con importarlodirectamente, tal y como muestra
la siguiente sentencia:
>>> import sqlite3
Para establecer la conexión a nuestra base de datos, lanzaremos elsiguiente
comando:
>>> con = sqlite3.connect('países.db')
De forma análoga a los ejemplos anteriores, para la ejecución desentencias
SQL necesitaremos valernos de un cursor, la obtención delmismo se realiza
así:
>>> cursor = con.cursor()
En cuanto tengamos el cursor, podremos, por ejemplo, consultarnuestra
tabla países, empleando para ello el método execute():
>>> cursor.execute('select * from paises')>>> for pais in cursor:...
print(pais)
Si lanzamos una sentencia que provoca un cambio en la base dedatos, por
ejemplo, una sentencia de tipo insert o delete, deberemosinvocar al método
commit() para que sea efectiva. El siguientefragmento de código muestra
cómo hacerlo:
>>> query = 'INSERT INTO pais(nombre, continente, habitantes,moneda)
VALUES ('Japón, 'Asia', 127, 'Yen)'>>> cursor.execute(query)>>>
cursor.commit()
Una vez finalizado el recorrido por los gestores de bases de datos,llega el
turno de los ORM, de los que nos ocuparemos en el siguienteapartado.
ORM
    244/381
    Dos son los ORM más utilizados en Python: SQLAlchemy y SQLObject.
Ambos son similares en funcionalidad y, básicamente,ofrecen una serie de
técnicas para interacturar con una base de datoscon independencia del
gestor. Es decir, el código escrito que utiliza unORM para trabajar con una
base de datos puede ejecutarse en MySQL,PostgreSQL, Oracle y cualquier
otro motor soportado por el ORM encuestión. Además, los ORM
proporcionan una interfaz basada enobjetos, donde sus tablas son
representadas por clases, los registrospor instancias de estas clases y los
campos de las tablas por losatributos de instancia. Así pues, es muy sencillo
establecer unacorrespondencia directa en los componentes de una base de
datos yuna serie de objetos de Python.Otra de las ventajas de emplear un
ORM en las aplicaciones Pythonque necesitan interactuar con una base de
datos, es que no esnecesario escribir código SQL, ya que los ORM
proporcionan una seriede métodos que hacen de interfaz a diferentes
sentencias SQL. Sinembargo, son muchos los ORM que también permiten
escribirdirectamente código SQL para trabajar con la base de datos.
Algunosprogramadores prefieren emplear esta técnica para tener un mayor
decontrol sobre el SQL que es ejecutado.En la actualidad, los ORM son
empleados por diferentes lenguajes ytecnologías y su uso se ha
popularizado y, podríamos decir estandarizado, gracias a que son muchos
los modernos frameworks web, que, o bien integran uno propio, o bien
ofrecen facilidades paraintegrar el que el programador
desee.Comenzaremos centrándonos en SQLAlchemy, uno de los primerosy
más populares ORM para Python.
SQLAlchemy
La primera versión de este ORM fue liberada en 2006 yrápidamente se
convirtió en uno de los más aceptados por lacomunidad de desarrolladores
de Python. Es open source y sedistribuye bajo la MIT license. Entre las
funcionales de SQLAlchemy encontramos las que nospermiten conectarnos
a una base de datos para insertar, leer, modificary borrar datos. También es
posible crear diferentes clases
    245/381
    estableciendo relaciones entre ellas que luego serán traducidas en labase de
datos empleando claves foráneas. Además, las operaciones join se realizan
automáticamente por el ORM cuando es necesarioextraer información de
diversas tablas que están relacionadas entre sí. SQLAlchemy soporta
diferentes gestores de bases de datosrelacionales, como son, por ejemplo,
MySQL, PostgreSQL, SQLite, Oracle y MS SQL Server. Para los ejemplos
que veremos en este apartado noscentraremos en MySQL, aunque,
lógicamente, son totalmenteextrapolares a otro gestor. Básicamente, lo
único dependiente delgestor en sí, será la cadena de conexión que
empleemos en la función create_engine(), tal y como veremos más
adelante.A pesar de que SQLAlchemy incluye una amplia funcionalidad,
eneste apartado nos centraremos en descubrir aquellas funcionalidadesque
son consideradas como básicas. Si el lector está interesado enexplotar todo
lo que este módulo tiene que ofrecernos,recomendamos consultar la
documentación oficial del mismo (verreferencias).Es necesario instalar
SQLAlchemy en nuestro equipo, ya que estemódulo no forma parte de la
librería estándar de Python. Tal y comohemos aprendido previamente, este
módulo puede ser instaladodirectamente a través de pip ; para ello, basta
con ejecutar el siguientecomando desde un terminal:
pip install sqlalchemy
El primer paso para poder comenzar a utilizar nuestro nuevomódulo será
importarlo, de lo que se encargará la siguiente sentencia:
>>> import sqlalchemy
Por simplicidad y, dado que vamos a emplear varias clases de SQLAlchemy,
en nuestro ejemplo comenzaremos cargando aquellasque necesitamos. Para
ello, bastará con ejecutar las siguientessentencias:
>>> from sqlalchemy.ext.declarative import declarative_base>>> from
sqlalchemy import Column, Integer, String, create_engine
Seguidamente, estableceremos una conexión con nuestra base dedatos.
Como hemos comentado previamente, utilizaremos MySQL como ejemplo:
    246/381
    >>> cad_con = 'mysql://usuario:password@localhost/prueba'>>> engine =
create_engine(cad_con)
Las clases que vayan a ser creadas para representar a las tablas dela base de
datos deben heredar de una clase propia de SQLAlchemy, llamada Base.
Esta debe ser obtenida a través de la llamada a unafunción concreta:
>>> Base = declarative_base()
SQLAlchemy requiere que cada clase, que vaya a declararse paraque se
corresponda con una determinada tabla, tenga al menos dosmétodos
principales. El primero de ellos será un constructor querecibirá una serie de
parámetros que deben corresponder con losvalores de un registro
determinado. Este constructor se debe encargarde asignar estos valores a
cada uno de los atributos de clase. Por otrolado, el segundo método en
cuestión será utilizado para identificar acada instancia de clase. Con
sobrescribir el método especial __repr__() ,será suficiente. Teniendo en
cuenta estos requisitos, nuestra clasequedaría de la siguiente forma:
class Pais(Base):__tablename__ = 'país'id = Column(Integer,
primary_key=True)nombre = Column(String(100))continente =
Column(String(20))habitantes = Column(Integer)moneda =
Column(String(30))
def __init__(self, nombre, continente, habitantes, moneda):self.nombre =
nombreself.continente = continenteself.habitantes = habitantesself.moneda
= moneda
def __repr__(self):return '<Pais('%s')>' % (self.nombre)
Ahora es el momento de crear la tabla físicamente en la base dedatos, a
partir de la definición de nuestra clase Pais. Esta acción essencilla y puede
ser llevada a cabo a través de la siguiente sentencia:
>>> Base.metadata.create_all(engine)
    247/381
    Conectándonos a la base de datos podremos comprobar que,efectivamente,
la nueva tabla ha sido creada. Sin embargo, las
    248/381
    operaciones para interactuar con ella deben ser ejecutadas a través deuna
instancia específica de la clase Session. Esta debe ser creadaempleando el
siguiente código:
>>>>>>>>>>>>
Session = sessionmaker(bind=engine)Session =
sessionmaker()Session.configure(bind=engine)session = Session()
Dado que nuestra nueva tabla no contiene ningún registro, elsiguiente paso
será añadir uno nuevo:
>>> p = Pais('Tailandia', 'Asia', 65, 'Baht')>>> session.add(p)>>>
session.commit()
Justo después de lanzar la última sentencia, se ejecutará elcomando INSERT
que insertará nuestro nuevo registro. Podemoscomprobar que,
efectivamente, ha sido así, lanzando una consulta querecorra toda la tabla y
muestre el nombre del país, tal y como muestrael siguiente código:
>>> rows = session.query(Pais, Pais.nombre) .all ()>>> for row in rows:...
print(row.nombre)...Tailandia
SQLAlchemy incluye varios métodos, llamados filters, para
realizarconsultas que contengan una cláusula WHERE. Por ejemplo,
sideseamos obtener todos los países cuya moneda es el euro, podemoslanzar
la siguiente sentencia:
>>> res = session.query(Pais).filter(Pais.moneda == 'Euro')
Para la ordenación podemos emplear el método order_by, porejemplo, para
obtener todos los países ordenados por su id:
>>> session.query(Pais).order_by(Pais.id)
La actualización de un valor de un determinado campo de una tablaes muy
sencilla, basta con asignar un nuevo valor al correspondienteatributo de
    249/381
    instancia e invocar al método commit(), tal y como muestrael siguiente
código:
>>> p.habitantes = 67
    250/381
    >>> session.commit()
Una vez aprendido lo básico sobre la utilización de SQLAlchemy, eshora de
comenzar a descubrir SQLObject.
SQLOBJECT
Al igual que SQLAlchemy, SQLObject es otro de los más popularesORM
disponibles para Python. Se distribuye bajo la licencia libre LGPLy también
puede ser instalado a través del gestor de módulos pip . Apesar de que
incluye las mismas funcionalidades básicas que SQLAlchemy, este último
cuenta con un mayor número de clases,métodos y funciones que aportan
mayor funcionalidad. Sin embargo, SQLObject es perfectamente utilizable
para la gran mayoría deaplicaciones que requieren de un ORM rápido y
sencillo de utilizar.La primera versión liberada de SQLObject fue en mayo
2002 y, en laactualidad, la sintaxis y la forma de realizar la correspondencia
entretablas y objetos son muy similares a la de Active Record, el
popularORM empleado por el framework web Ruby on Rails. Si el lector
tieneexperiencia previa con este ORM, seguro que no tiene
ningunadificultad en familiarizarse rápidamente con SQLObject. Por otro
lado,es este el módulo elegido por Turbogears, conocido Framework
webpara Python, como ORM por defecto. SQLObject puede trabajar con
diferentes gestores de bases dedatos relacionales, como MySQL,
PostgreSQL, MS SQL Server, SQLite y Sybase. Al igual que en el caso de
SQLAlchemy vamos a trabajar conuna base de datos MySQL como
ejemplo.Nuestra práctica comienza instalando el correspondiente móduloen
nuestro sistema:
pip install sqlobject
A partir de la correcta instalación del módulo podemos importarlocomo tal,
como muestra la siguiente sentencia:
>>> import sqlobject
Al igual que hemos hecho con SQLAlchemy, comenzaremoscreando nuestra
clase que será la que represente a, la ya popular, tabla paises. La definición
    251/381
    de la clase en cuestión es como sigue:
    252/381
    >>> class Pais(sqlobject.SQLObject):... nombre =
sqlobject.StringCol(length=100)... continente =
sqlobject.StringCol(length=20)... habitantes = sqlobject.IntCol()... moneda
= sqlobject.StringCol(length=30)
El siguiente paso será establecer una conexión con nuestra base dedatos
pruebas ; para ello, nos apoyaremos en el método connectionForURI() y
asignaremos el valor que nos devuelva el mismo auna variable llamada
processConnection. Con esta asignación nosaseguramos de que todas las
clases que definamos interactuarán conla conexión previamente establecida.
El código para realizar estasoperaciones es el siguiente:
>>> db_cad = 'mysql://cruceros:cruceros@localhost/cruceros'>>> con =
sqlobject.connectionForURI(db_cad)>>>
sqlobject.sqlhub.processConnection = con
Ahora estamos ya listos para crear la tabla pais partiendodirectamente de la
clase anteriormente definida:
>>> Pais.createTable()
Si consultamos la base de datos pruebas, veremos cómo tenemos lanueva
tabla pais, que, por el momento, está vacía. Así pues, pasaremosa crear un
nuevo país en la misma a través de una nueva instancia denuestra clase:
>>>
Pais(nombre='Francia',continente='Europa',moneda='Euro',habitantes=66)
Simplemente, ejecutando la sentencia anterior, ya tendremos unnuevo
registro en nuestra tabla. Para comprobar este hecho,utilizaremos el método
get() que recibirá como argumento el valor 1, indicando que deseamos
obtener aquel cuyo id coincide con dichovalor:
>>> p = Pais.get(1)>>> p.nombreFrancia
Si ahora deseamos modificar uno de los campos del registro reciéncreado,
bastará con invocar al método set(), tal y como muestran lassiguientes
    253/381
    líneas:
    254/381
    >>> p.set(habitantes=67)>>> p = Pais.get(1)>>> p.habitantes67
La consulta de registros puede ser llevada fácilmente a cabo graciasal
método selectBy() que puede recibir como argumento el nombre delatributo
y su valor que correspondería con la cláusula WHERE de SQL.Por ejemplo,
para obtener todos los países de Europa, ejecutaríamos:
>>> paises = Pais.selectBy(continente='Europa')>>> from pais in paises:...
print(pais.nombre)...Francia
El módulo SQLObject nos permite representar relaciones 1-N y N-M de
tablas. Además, ofrece una serie de sencillos métodos para obtenervalores
de las tablas relacionadas, haciendo automáticamente cuántasoperaciones
JOIN sean necesarias.Comparado con SQLAlchemy, SQLObject es más
sencillo e intuitivo,aunque sí es cierto que el primero ofrece más
funcionalidades. Laelección de uno u otro dependerá de varios factores,
aunque sibuscamos un ORM que consuma pocos recursos y sea rápido,
SQLObject puede ser una buena elección.
NOSQL
Las bases de datos NoSQL (Not Only SQL) son diferentes a las basesde
datos relacionales tanto en estructura como en el tipo de relacionesque se
establecen y en la forma de interactuar con los datos. Si en lasrelacionales
el lenguaje SQL es el encargado de trabajar directamentecon los datos, en
las NoSQL no se utiliza este tipo de lenguaje.Además, la mayoría de bases
de datos NoSQL no soportan JOINS, yaque no se establecen las típicas
relaciones 1-N y N-M. Frente a las bases de datos relacionales, las de tipo
NoSQL sonfácilmente escalables, ofrecen mínimos tiempos de consulta y
puedentrabajar con grandes volúmenes de datos. Gracias a
estascaracterísticas se han vuelto muy populares para aplicaciones web
dealto tráfico, como son las ofrecidas por empresas como Google,
    255/381
    Facebook o Twitter.Básicamente, una base de datos NoSQL almacena una
serie depares claves:valor y, en vez de hablar de registros , se habla de
documentos. En la actualidad, son tres los gestores de bases de datosNoSQL
más populares: Redis, Cassandra y Redis. De cómo interactuarcon ellos
desde Python nos ocuparemos en esta parte final delpresente capítulo.
Debemos tener en cuenta que el lector estáfamiliarizado con el
funcionamiento de estos gestores y conoce losfundamentos sobre ellos.
Asimismo, vamos a aprender lo básico sobrela conexión y manejo de datos
desde Python contra estos gestores. Siel lector está interesado en
profundizar en el tema, es aconsejableechar un vistazo a la documentación
de los módulos de Python con losque vamos a trabajar para interactuar con
cada uno de losmencionados gestores NoSQL.
Redis
Para trabajar con Redis contamos con varios módulos de Python.Uno de los
más sencillos y fáciles de usar se llama redis y ofrece lasfuncionalidades
básicas para fijar un par clave:valor, para leer registrosy para
borrarlos.Dado que el mencionado módulo para trabajar con Redis
seencuentra disponible a través de pip, su instalación es bastante
sencilla,simplemente deberemos ejecutar el siguiente comando desde
unaterminal:
pip install redis
Si la instalación ha finalizado correctamente, el módulo podrá serimportado
en cualquiera de nuestros scripts. Es más, podemos probardirectamente en
la interfaz de comandos del intérprete de Python:
>>> import redis
Fijar el valor de una clave es tan sencillo como conectarnos a unade las
bases de datos de nuestro servidor Redis, e invocar al método set(). El
primer paso será establecer la conexión, tal y como muestra lasiguiente
sentencia:
    256/381
    >>> con = redis.StrictRedis(host='localhost', port=6379, db=0)
Los tres parámetros pasados al constructor de la clase StrictRedis indican: el
servidor al que vamos a conectarnos, el puerto en el queestá corriendo y el
número que identifica a la base de datos. Si nopasamos ninguno de estos
valores, se utilizarán los que hemosempleado en nuestra línea ejemplo.
Redis permite simplemente fijar pares clave:valor, a diferencia de
MongoDB y Cassandra que soportan el almacenamiento más complejode
valores básandose en la misma estructura. De esta forma, lainserción de un
valor en nuestra base de datos Redis, sería como sigue:
>>> con.set('clave', 'valor')
La recuperación de un valor asociado a una clave, es decir, la lecturade un
registro almacenado en la base de datos, se realiza invocando almétodo
get(). El valor que hemos insertado previamente puede serrecuperado
ejecutando la siguiente sentencia:
>>> con.get('clave')valor
El registro insertado puede ser borrado sencillamente gracias almétodo
delete(). Así pues, la siguiente línea de código eliminará elregistro que
hemos insertado previamente:
>>> con.delete('clave')
El módulo redis soporta trabajar con pools de conexiones, con loque
ganamos en eficiencia a la hora de establecer diferentesconexiones con la
base de datos. La clase ConnectionPool() se encargade realizar la conexión
empleando el pool de conexiones yseleccionando aquella que se encuentre
libre. Las siguientes dos líneasde código son necesarias para utilizar el pool
y obtener una conexiónlibre que podamos emplear para nuestro trabajo:
>>> pool = redis.ConnectionPool()>>> con =
redis.Redis(connection_pool=pool)
    257/381
    Aprendido lo básico para interactuar con una base de datos Redis desde
Python, continuaremos descubriendo cómo realizar operacionessimilares
con MongoDB.
    258/381
    MongoDB
El módulo más popular para trabajar con MongoDB desde Pythones el
llamado PyMongo. Este puede ser instalado fácilmente a través de pip, así
pues basta con invocar a este gestor directamente desde lalínea de
comandos:
pip install pymongo
En cuanto finalice la instalación, podrá ser importado a través de lasiguiente
sentencia:
>>> import pymongo
Para nuestros ejemplos vamos a partir de que tenemos previamentecreada
una base de datos llamada pruebas, conectarnos a la misma esbastante
sencillo. Primero debemos obtener una conexión y despuéspodremos
conectarnos a la base de datos. El código necesario paraello sería el
siguiente:
>>> con = pymongo.Connection()>>> db = con.pruebas
Hemos de tener en cuenta que si no pasamos ningún parámetro ala hora de
realizar la conexión, por defecto se entenderá que deseamosconectarnos a
localhost y al puerto estándar (27017) que emplea MongoDB. En caso
contrario, podemos pasar dos parámetrosdiferentes, uno para el nombre o IP
del servidor y otro para el puertoen cuestión.Los datos son almacenados en
MongoDB empleando el formatoJSON; así pues, podemos crear un simple
documento para representarun país en cuestión:
>>> pais = {'nombre': 'Alemania', 'habitantes': 82, 'continente':'Europa',
'moneda': 'euro'}>>> paises = db.paises>>> paises.insert(pais)
En Python utilizaremos un diccionario para emular el formato JSON,tal y
como nos muestra la primera línea del ejemplo anterior. Una vezque
tenemos nuestro documento, creamos una colección y añadimosdicho
documento a la misma.
    259/381
    Para comprobar que el documento ha sido insertadocorrectamente, basta
con invocar al método find_one() que seencargará de consultar nuestra
colección:
>>> paises.find_one({'nombre': 'Alemania}){'nombre': 'Alemania',
'habitantes': 82, 'continente': 'Europa','moneda': 'euro'}
Si necesitamos hacer una consulta que devuelva más de undocumento,
podemos emplear el método find() e iterar sobre elresultado, ya que en este
caso obtendremos una lista de diccionarios.Por ejemplo, supongamos que
tenemos varios países que tienen eleuro como moneda y queremos extraer
los mismos de la base dedatos. Para ello, bastaría con ejecutar las siguientes
líneas:
>>> p_euro = paises.find({'moneda': 'Euro'})>>> for pin p_euro:... print(p)
{'nombre': 'Alemania', 'habitantes': 82, 'continente': 'Europa','moneda':
'euro'}{'nombre': 'España, 'habitantes': 47, 'continente': 'Europa','moneda':
'euro'}{'nombre': 'Francia', 'habitantes': 67, 'continente': 'Europa','moneda':
'euro'}
Otro de los métodos útiles que nos ofrece PyMongo es count(), elcual nos
devuelve el número de documentos que cumplen ciertacondición. Así pues,
si ejecutamos la siguiente sentencia, obtendremoslos tres países que utilizan
el euro y que se encuentran en nuestra basede datos:
>>> paises.find({'moneda': 'Euro'}).count()3
Ahora que ya hemos aprendido lo básico sobre PyMongo, pasaremos a
ocuparnos de cómo trabajar desde Python con Cassandra.
Cassandra
El gestor de bases de datos Cassandra almacena la información deforma
diferente a como lo hace MongoDB. Cassandra se basa en los
    260/381
    conceptos de keyspace y column familiy para almacenar la informaciónde
forma estructurada. Al fin y al cabo, también se emplea el conceptode clave
y valor, solo que también se permite estructurar lainformación en columnas
y en supercolumnas. A pesar de que existen varios módulos para trabajar
con Cassandra desde Python, uno de los más populares y fáciles de usar es
pycassa. De forma análoga a como hemos instalado otros módulos que
noforman parte de la librería estándar de Python, ejecutaremos desde lalínea
de comandos, la siguiente orden:
pip install pycassa
Para trabajar con el módulo que acabamos de instalar, basta con importarlo
:
>>> import pycassa
La conexión a la base de datos se hará a través de un objetoespecífico que
representa a un pool de conexiones, al que indicaremosel nombre del
keyspace que deseamos utilizar, más los datos deconexión del servidor:
>>> from pycassa.pool import ConnectionPool>>> pool =
ConnectionPool('mainspace')
En caso de que tengamos Cassandra corriendo en un puertodiferente al
estándar o en un servidor que no sea la máquina local( localhost ), podemos
indicar estos valores a través de una lista, tal ycomo muestra la siguiente
línea de código:
>>> pool = ConnectionPool('mainspace', ['192.168.0.3', '8080'])
Una vez que tengamos acceso a nuestro keyspace, es necesarioindicar la
column family a la que deseamos conectarnos. Estaoperación puede ser
llevada a cabo a través de una clase llamada ColumnFamily. Las siguientes
líneas muestran cómo realizar estaoperación:
>>> from pycassa.columnfamily import ColumnFamily>>> col =
ColumnFamily(pool, 'maincolumn')
    261/381
    Ahora ya estamos listos para insertar nuestro primer registro; paraello,
emplearemos el método insert(), cuyo primer argumentoreferencia a la
clave y el segundo es un diccionario con los pares
    262/381
    clave:valor que deseamos insertar. Veamos cómo el siguiente códigonos
servirá para insertar un nuevo país:
>>> col.insert('Alemania', {'habitantes': 82, 'continente':'Europa', 'moneda':
'euro'})
El recién insertado registro puede obtenerse fácilmente gracias almétodo
get(). Las siguientes líneas muestran cómo utilizarlo:
>>> col.get('Alemania'){'habitantes': 82, 'continente': 'Europa', 'moneda':
'euro'}
Como resultado de pasar la clave Alemania, obtenemos undiccionario de
Python con los valores requeridos. Si necesitáramos dosregistros diferentes
en una única sentencia, podríamos hacerlo a travésdel método multiget(), tal
y como muestra el siguiente ejemplo:
>>> col.multiget(['Alemania', 'Francia']){'Alemania': {'habitantes': 82,
'continente': 'Europa', 'moneda':'euro'},'Francia': {'habitantes': 67,
'continente': 'Europa', 'moneda' :'euro'}}
Finalizamos así el capítulo dedicado a las bases de datos.Esperamos que el
lector haya logrado una buena visión de conjuntoque le permita trabajar,
tanto con bases de datos relacionales, comocon bases de datos NoSQL,
desde Python.
    263/381
    INTERNET
INTRODUCCIÓN
En este capítulo nos ocuparemos de aquellos aspectos másrelevantes
relacionados con Python y la programación relativa aservicios de Internet.
Ante el innegable auge de la red de redes, es muyinteresante descubrir
cómo interactuar, a través de la misma,empleando Python como lenguaje de
programación para escribiraplicaciones que puedan hacer uso de servicios
como el correoelectrónico, la web, los web Services o la conexión directa a
través deprotocolos específicos como FTP.Comenzaremos nuestro recorrido
aprendiendo cómo realizarconexiones con servidores accesibles a través de
Internet, empleandodos protocolos ampliamente utilizados, como son
TELNET y FTP.Gracias a utilizar un lenguaje como Python, descubriremos
cómo esposible automatizar muchas tareas que se realizan de forma
manualtrabajando con estos protocolos.Seguiremos el camino ocupándonos
de los servicios web queemplean el protocolo XML-RPC como estándar
para la comunicación eintercambio de información entre el cliente y el
servidor.Al finalizar con los protocolos anteriormente mencionados,
llegarála hora del correo electrónico. Aprenderemos cómo conectarnos
einteractuar con servidores que empleen diferentes protocolos
estándarcomo son IMAP4, POP3 y SMTP.El último apartado del presente
capítulo se ocupará de la web,donde nos centraremos en aprender lo básico
sobre el manejo deprotocolos como CGI y WSGI. Además, descubriremos
quéherramientas podemos emplear para realizar web scraping, una de
lastécnicas más utilizadas en la actualidad para la obtención deinformación
desde sitios web. Por último, llegará el turno de los frameworks web,
también de rabiosa actualidad, gracias al conjunto decomponentes y
herramientas que ofrecen para desarrollar y mantenerfácilmente complejas
aplicaciones web.Es conveniente tener en cuenta que este capítulo no
pretende
    264/381
    realizar un exhaustivo análisis de todos los módulos, herramientas
ycomponentes existentes y relacionados con Internet y disponibles
paraPython. El objetivo es otro, tratándose el mismo de repasar
aquellosaspectos, técnicas y módulos que consideramos más interesantes
parapoder comenzar a desarrollar rápidamente aplicaciones en Python
quepuedan sacar provecho del potencial que nos ofrece Internet.En la
actualidad existe un mayor conjunto de módulos ycomponentes para
interactuar con Internet en la versión 2 de Pythonque en la 3. No obstante,
esta situación está cambiando y son muchosdesarrolladores que se
encuentran trabajando en la migración de suscomponentes. A pesar de ese
hecho, es fácil encontrar módulos quefuncionan correctamente en la última
versión del intérprete de Python.Una muestra de ellos son los que
descubriremos y utilizaremos en elpresente capítulo.A continuación,
comenzamos aprendiendo a trabajar con losprotocolos FTP y TELNET
desde Python.
    265/381
    TELNET Y FTP
Dos de los más utilizados protocolos en Internet para interactuarcon
servidores son FTP y TELNET. Ambos permiten comunicarnos conuna
máquina desde otra a través de una conexión a Internet. Elprotocolo FTP es
ampliamente utilizado tanto para subir como para bajar ficheros. Si alguna
vez hemos usado en servicio de hosting, seguro que hemos utilizado este
protocolo a través de algún clienteque lo soportara. Por otro lado, gracias a
TELNET podemos abrir unterminal y ejecutar comandos como si
físicamente estuviéramossentados enfrente de la máquina remota. Este
protocolo se utilizatípicamente en servidores UNIX, Linux y Mac OS X
Server. Aunque esmenos frecuente, también algunas versiones de Windows
lo soportan.En este apartado descubriremos qué módulos tenemos
disponiblesen Python para poder trabajar con los mencionados protocolos
paracomunicarnos remotamente con otras máquinas configuradas
comoservidores. Comenzamos ocupándonos del protocolo TELNET.
telnetlib
Python cuenta en su librería estándar con un módulo llamado telnetlib, el
cual encapsula la funcionalidad básica para conectarnos ydesconectarnos a
un servidor, enviar comandos y obtener respuestas,todo a través del
protocolo TELNET.El mencionado módulo pone a nuestra disposición una
claseprincipal denominada Telnet. Al crear una instancia de la
mismaobtendremos un objeto que nos permitirá interactuar con el
servidor.El método open() será el encargado de abrir una conexión a
unservidor que acepte el protocolo TELNET. Como parámetros
delconstructor de la mencionada clase podemos pasar tres diferentes,
unoque indique nombre o IP del servidor, otro para especificar el puertode
conexión y un tercero que servirá para fijar un número de segundoscomo
máximo para esperar respuesta. El único de estos parámetrosrequeridos es
el primero, ya que, por defecto, la conexión se realizará
    266/381
    al puerto estándar (23) para TELNET.Antes de comenzar a ver los ejemplos
de código de utilización de telnetlib, si no disponemos de conexión a
ningún servidor medianteTELNET, es fácil instalar un servidor en nuestra
máquina local. Siutilizamos Mac OS X bastará con ejecutar el servicio que
corre unservidor de TELNET en el puerto 21. Para ello, abriremos un
terminal ylanzaremos el siguiente comando:
$ sudo launchctl load -w/System/Library/LaunchDaemons/telnet.plist
La mayoría de las distribuciones de GNU/Linux cuentan conservidores de
TELNET entre sus paquetes binarios. Así pues, porejemplo, en Ubuntu
podemos abrir una consola de comandos y lanzarel siguiente comando:
$ sudo apt-get install telnetd
Para instalar un servidor de TELNET en Fedora lanzaremos elsiguiente
comando:
$ sudo yum install telnet-server
En cuanto tengamos listo nuestro servidor local o los datos deacceso a uno
remoto, podemos comenzar a practicar con el códigoejemplo que veremos a
continuación.La primera operación será conectarnos a nuestro servidor, para
ello,ejecutemos las siguientes sentencias:
>>> from telnetlib import Telnet>>> tel = Telnet('localhost')
En lugar de invocar al método open() para abrir una conexión,vamos
directamente a llamar al método read_until(), el cual seencargará de abrir la
conexión por nosotros y comenzar a leer desde elservidor hasta que este
ofrezca la respuesta que coincida con elargumento pasado al mencionado
método. En nuestro ejemplo y dadoque vamos entrar en la máquina
empleando un login y password correspondientes a un usuario concreto,
vamos pasar como parámetrola cadena binaria "login: " , tal y como
muestra el siguiente código:
    267/381
    >>> tel.read_until(b'login: ')'\r\r\nDarwin/BSD (yoda.local)
(ttysOOO)\r\r\n\r\r\nlogin:'
    268/381
    En cuanto el servidor responda, deberemos mandar el nombre deusuario
para hacer login, para ello contamos con el método write(), elcual se
encarga de mandar una serie de bytes al servidor. Esta será lasentencia que
necesitaremos, donde usuario debe ser reemplazado porel nombre en
cuestión del usuario que va a realizar login en el servidor:
>>> tel.write(b'usuario\n')
A través de la cadena \n indicamos que un retorno de carro debeser lanzado
justo después del nombre de usuario. Ello simularía lapulsación de la tecla
enter si estuviéramos utilizando el comando telnet directamente desde una
terminal. El siguiente paso será esperar a queconteste el servidor
pidiéndonos una contraseña:
>>> tel.read_until(b'Password: ')' usuario\r\nPassword:'
De la misma forma que el nombre de usuario, ahora mandaremosnuestra
contraseña, a través del método write():
>>> tel .write (b'password\n' )
Si todo va bien, el login se realizará correctamente y podremosenviar otros
comandos. Probemos con un sencillo listado delcontenido del directorio del
usuario que se ha conectado al servidor:
>>> tel.write(b'ls\n')
Seguidamente y, previo paso a leer los datos de respuesta enviadospor el
servidor, procederemos realizar el logout del servidor, para ellobastará con
mandar el comando exit, soportado por el protocoloTELNET:
>>> tel.write(b'exit\n')
Para leer la salida mandada desde el servidor contamos con elmétodo
read_all() que nos devolverá todos los datos enviados comorespuesta desde
el servidor, incluyendo, tanto la salida del comando ls, como los que se
obtienen al ejecutar el comando exit:
    269/381
    >>> tel. read_all ()'\r\nLast login: Mon Feb 20 17:10:37 on
ttys003\r\n[arturo@yoda
~\xlb[0;32m]\x1b[0;37m$ls\r\nApplications\t\t\tfile.txt\r\nDesk
    270/381
    top\t\t\t\tftdetect\r\nDocuments\t\t\tftplugin[arturo@yoda~\x1b[0;32m]\x1b[
0;37m $\r [arturo@yoda ~\x1b[0;32m]\x1b[0;37m$ exit\r\nlogout\r\n'
Por último, solo nos queda cerrar la conexión al servidor. Acciónque puede
realizarse gracias al método close(), tal y como muestra lasiguiente línea:
>>> tel.close()
El módulo telnetlib también puede ser utilizado para conectarnos aotros
servicios que corren en máquinas remotas y que aceptan elprotocolo
TELNET. Como ejemplo de estos servicios están losservidores de correo
electrónico que soportan SMTP y POP3, losservidores de caché como
memcached y redis, el servidor de basedatos NoSQL.
ftplib
Sin necesidad de recurrir a ningún módulo fuera de la libreríaestándar de
Python, para el manejo de conexiones a través delprotocolo FTP (File
Transfer Protocol) contamos con el módulo ftplib. Este incluye una clase
principal llamada FTP que se encarga degestionar la conexión con otros
servidores. Entre las acciones que lamisma permite llevar a cabo, destacan
las que nos permiten abrir ycerrar una conexión, autenticarnos, mandar
cualquier comandoaceptado por el protocolo y leer los resultados enviados
comorespuesta por el servidor. Utilizar esta clase desde Python nos
permitiráautomatizar sencillas tareas como la descarga o subida de ficheros
eincluso aquellas más complejas, como pueden ser, por ejemplo, lacreación
y mantenimiento de un servidor mirror. La obtención de una instancia de la
clase principal FTP se realiza através de su constructor, el cual recibe como
parámetros la IP onombre del servidor y el puerto donde realizar la
conexión. Si no sepasa ningún entero para el puerto, se usará el empleado
por defecto yconvención en los servidores. De esta forma, para conectarnos
a unservidor, crearemos previamente una instancia de la mencionada clase:
    271/381
    >>> from ftplib import FTP>>> con = FTP('ftp.miservidor.com')
En cuanto tengamos creada la instancia podremos comenzar ainteractuar
con el servidor. Si necesitamos autenticarnos, invocaremosal método
login(), tal y como muestra la siguiente sentencia:
>>> con.login('usuario', 'password')'230 User usuario logged in.'
Son muchos los servidores FTP que aceptan un usuario especialllamado
anonymous, cuya password coincide con el nombre de usuario.Es práctica
habitual colocar la información pública que ofrecen losservidores FTP bajo
el acceso de este usuario especial. Así pues, paraconectarnos empleando el
mismo bastará con invocar al método login() sin pasar ningún
parámetro.Una vez conectados al servidor mediante FTP, podemos,
porejemplo, echar un vistazo al contenido del directorio al que
hemosaccedido por defecto. Para ello invocaremos al método retrlines(),
elcual obtiene el contenido de un fichero o lista los que forman parte deun
determinado directorio. En nuestro ejemplo, vamos a mandar elcomando
LIST para obtener una cadena de texto con el contenido deldirectorio al que
hemos accedido por defecto al realizar la conexión alservidor:
>>> con.retrlines('LIST')total 234drwxrwsr-x 5 usuario usuario 1520 Feb
16 09:22 .dr-xr-srwt 15 usuario usuario 1520 Feb 16 09:25 ..-rw-rw-rw- 20
usuario usuario 230 Feb 18 10:12 myfile.txt
Subir un fichero a un servidor es sencillo gracias al método storbinary().
Simplemente deberemos abrir el fichero en cuestión einvocar al
mencionado método pasando dos parámetros. El primero deellos será una
cadena de texto que contiene el valor 'STOR', seguidodel nombre que
deseemos darle al fichero remoto. El segundo encuestión será un objeto de
tipo fichero. El siguiente código nosmuestra cómo acceder a un fichero
local llamado prueba.json y subirlocon el nombre test.json:
fich = open ('prueba.json', 'rb')>>> con.storbinary('STOR test.json',
fich)'226 Transfer complete. '
    272/381
    >>> fich.close()
Efectivamente, para abrir nuestro fichero hemos empleado b como flag para
indicar que el fichero, aunque sea de texto, debe ser tratadocomo binario.
De esta forma podremos utilizar su contenido sinproblema a través del
método storbinary(). Análogamente, también es posible descargarnos un
fichero desdeel servidor FTP. Para esta acción contamos con el método
retrbinary(), el cual también recibe dos parámetros: uno para indicar que se
va allevar a cabo la operación de descarga y otro para pasar el objetofichero
donde se almacenará el contenido del fichero descargado.Como ejemplo
podemos descargarnos el fichero que hemos subidopreviamente. En esta
ocasión nos bajaremos el fichero test.json y losalvaremos con el nombre
miprueba.json. El siguiente código nosmuestra cómo hacerlo:
>>> fich = open('miprueba.json', 'wb')>>> con.retrbinary('RETR test.json',
fich.write)'226 Transfer complete.'>>> fich.close()
Es importante cerrar al final el fichero, en caso contrario el ficherono será
creado. Igualmente ocurre en el caso de la subida, hasta queno cerremos el
fichero, no será volcado su contenido al servidorremoto.Para enviar un
comando FTP válido existe el método sendcmd(), querecibe como cadena
de texto el comando en cuestión que seráejecutado. Como resultado
obtendremos la salida de la ejecución delmismo. Por ejemplo, para indicar
que vamos a cambiar a modo binario,podríamos lanzar la siguiente
sentencia:
>>> con.sendcmd('TYPE i')
Otro método interesante con los que cuenta la clase FTP es size() que sirve
para averiguar cuánto ocupa un determinado fichero endisco. La invocación
puede realizarse directamente, pasando comoparámetro el nombre del
fichero en cuestión, tal y como muestra elsiguiente ejemplo:
>>> con.size('myfile.txt')230
    273/381
    Para trabajar con directorios dentro del servidor existen diferentesmétodos,
como son cwd(), pwd(), mkd() y rmd(). El primero de ellossería para
cambiar a un directorio determinado, el cual será fijadocomo actual, para
ello deberemos especificar como parámetro elnombre del directorio en
cuestión. El segundo simplemente nosdevolverá el path del directorio
actual. Por otro lado, mkd() creará unnuevo directorio, mientras que rmd()
servirá para borrar aqueldirectorio que ya no necesitemos.No olvidemos
cerrar la conexión al servidor en cuanto finalicemosnuestro trabajo:
>>> con.quit()
Como habremos podido comprobar la interacción con servidoresFTP es
bastante sencilla desde Python, lo cual nos abre un interesantecampo para
crear scripts o aplicaciones que requieran de dichainteracción.
    274/381
    XML-RPC
El protocolo XML-RPC se basa en la utilización de la invocaciónremota a
funciones o métodos a través de la codificación de los datosnecesarios para
la llamada en el formato XML. Para el transporte dedatos se emplea el
protocolo HTTP, es por ello que XML-RPC es unode los protocolos más
utilizados para la implementación de webServices. En Python 3 contamos
con dos módulos principales para trabajarcon XML-RPC. El primero de
ellos es xmlrpc.server y nos ofrece unaserie de funcionalidades para
desarrollar un servidor que puedainteractuar con un cliente a través del
mencionado protocolo. Por otrolado, el segundo módulo en cuestión es
xmlrpc.client, diseñado paraimplementar clientes que puedan interactuar
con cualquier servidorque sea capaz de entender y trabajar con este
protocolo. Ambosmódulos forman parte de la librería estándar de Python, lo
quesignifica que podemos utilizarlos directamente, sin necesidad derealizar
ninguna instalación adicional.A continuación, veremos una serie de
ejemplos prácticos, tanto decliente, como de servidor, para aprender lo
básico para trabajar conXML-RPC en Python. Comenzamos por el módulo
que se ocupa delservidor.
xmlrpc.server
Para ilustrar el funcionamiento de este módulo, vamos aimplementar un
sencillo servidor que contendrá dos funcionesdiferentes que podrán ser
llamadas desde un cliente a través de,lógicamente, XML-RPC. En realidad,
vamos a desarrollar un sencillo web Service que correrá en nuestra máquina
local. El mismo códigopuede ejecutarse en un servidor accesible a través de
Internet. Es decir,con xmlrpc.server podemos desarrollar cualquier web
Service en Pythoncapaz de interactuar con clientes, escritos o no en el
mismo lenguaje.Nuestro web Service contendrá una función llamada
say_bye(), la
    275/381
    cual recibirá como parámetro una cadena de texto y devolverá unsencillo
mensaje, concatenando la cadena pasada como parámetro.Además, el
mencionado web service también contará con una clase conun método, al
que llamaremos say_hello(), que devolverá el clásico Hola Mundo!. Tanto
la función como el método en cuestión seránaccesibles directamente como
funciones desde un cliente XML-RPC.Dos clases del módulo xmlrpc.server
son fundamentales para laimplementación de un servidor:
SimpleXMLRPCServer y SimpleXMLRPCRequestHandler. Ambas clases
contienen lasfuncionalidades necesarias para crear un servidor que pueda
entenderlas peticiones que se realizan desde un cliente y responder a
lasmismas a través de las funciones previamente definidas.El primer paso
en la implementación de nuestro servidor será crearuna clase que herede de
SimpleXMLRPCRequestHandler y quecontenga una variable indicando la
ruta ( path ) a través del cual seráaccesible nuestro web service. El código
Python para ello sería elsiguiente:
>>> from xmlrpc.server import SimpleXMLRPCServer>>> from
xmlrpc.server import SimpleXMLRPCRequestHandler>>> class
RequestHandler(SimpleXMLRPCRequestHandler):... rpc_paths =
('/mywebservice',)
Seguidamente, crearemos una instancia de la clase SimpleXMLRPCServer a
la que pasaremos dos parámetros diferentes.Uno de ellos será un tupla que
contendrá el nombre o IP del servidormás el puerto donde correrá el web
service. El segundo parámetroindicará cuál será la clase que manejará las
peticiones recibidas. Dadoque estamos trabajando con nuestra máquina, en
local, indicaremoscomo servidor localhost. Como puerto vamos a elegir,
por ejemplo, el8080. La mencionada clase que gestionará las peticiones de
los clientesserá la que hemos definido previamente. Así pues, el código
encuestión para crear la instancia de nuestro web service, es el siguiente:
>>> servidor = SimpleXMLRPCServer(("localhost",
8080),requestHandler=RequestHandler)
Ahora llega el momento de crear nuestra primera función invocabledesde el
cliente. Simplemente definiremos la función y la registraremos empleando
    276/381
    el método register_function(). Si no registramos la función,no será posible
su invocación. No olvidemos este paso, pues es
    277/381
    importante. A continuación, el código necesario que define nuestrafunción
y que la registra en el servidor:
>>> def say_bye(name):... return("Bye, bye {0}".format(name))...>>>
servidor.register_function(say_bye)
El módulo xmrpc.server no solo nos permite definir funciones,también es
posible crear clases con sus correspondientes métodos yhacer estos
accesibles a los clientes que interactúen con el web service. Para ello,
deberemos seguir los mismos pasos que hemos vistopreviamente para las
funciones, es decir, bastará con definir nuestraclase y registrarla. La
principal diferencia es que emplearemos elmétodo register_instance(), en
lugar de register_function(). El código encuestión para ambas operaciones
es el que viene a continuación:
>>> class MySer:... def say_hello(self):... return 'Hola Mundo!'...>>>
servidor.register_instance(MySer())
Con el objetivo de que los clientes puedan tener acceso a lasfunciones que
expone nuestro web service, vamos a invocar a unmétodo determinado, tal y
como muestra la siguiente línea de código:
>>> servidor.register_introspection_functions()
Una vez definidas y registradas la clase y la función de nuestroservidor,
solo nos quedará lanzarlo para que los clientes puedantrabajar con él. Este
paso se lleva a cabo a través de un métodoespecífico llamado
server_forever():
>>> servidor. serve forever()
Ya tenemos nuestro servidor web desarrollado y ejecutándose,desde este
momento, los clientes, escritos en cualquier lenguaje quepermita escribir
clientes que se ajusten a las especificaciones delprotocolo XML-RPC,
podrán conectarse a nuestro servidor y realizar lasllamadas a las funciones
expuestas por el mismo. En el siguienteapartado, vamos a escribir uno de
    278/381
    estos clientes en Python de estaforma, descubriremos cómo el módulo
xmlrpc.client puede ayudarnosa ello.
    279/381
    xmlrpc.client
En Python el módulo xmlrpc.client de su librería estándar nos ofreceuna
serie de funcionalidades para implementar sencillamente clientesque
interactúen con servidores a través de XML-RPC. Como ejemplo,vamos a
desarrollar un cliente que se pueda conectar al servidor quehemos creado en
el apartado anterior y que invoque a sus funcionesdefinidas.La clase
principal que permite establecer una conexión con elservidor se llama
ServerProxy. A través de una instancia de esta clasepodremos,
directamente, invocar a los métodos que nos ofrezca elservidor. Así pues, el
primer paso será realizar la mencionada conexión;sirva el siguiente código
como ejemplo:
>>> from xmlrpc.client import ServerProxy>>> url =
'http://localhost:8080/mywebservice'>>> cli = ServerProxy(url)
Efectivamente, la variable url contiene tanto el nombre y puerto
delservidor, como la ruta que han sido indicados previamente en
nuestroservidor web. Por otro lado, la variable cli será la instancia
queemplearemos para llamar a las funciones del servidor. Antes
decontinuar, invocaremos a un método, accesible a través de
nuestrainstancia de cliente, que nos mostrará las funciones expuestas en
elservidor web:
>>> cli.system.listMethods()['say_bye', 'say_hello',
'system.listMethods','system.methodHelp', 'system.methodSignature']
Como el lector habrá podido comprobar, los dos primeros valoresde la lista
devuelta por el método listMethods() corresponden a lasfunciones que
pueden ser invocadas desde el cliente. El resto devalores hacen referencia a
otras funciones accesibles, por defecto, através de system, que a su vez tiene
acceso desde la instancia denuestro cliente. De esta forma, ya estamos listos
para invocar a lasfunciones de nuestro web service, tal y como muestra el
siguientecódigo:
>>> cli.say_hello()Hola Mundo!
    280/381
    >>> cli.say_bye('Lucas')Bye, bye Lucas
Si en lugar de emplear el intérprete de Python desde la línea decomandos,
creamos un fichero, copiamos el código y lo lanzamosdesde un terminal,
comprobaremos cómo cada petición desde elcliente origina un mensaje de
log en la terminal donde se estáejecutando nuestro servidor. Por ejemplo,
justo después de invocar alservidor desde el cliente, obtendremos una línea
en la terminal, dondeha sido lanzado el servidor, como la siguiente:
localhost - - [20/Feb/2012 11:13:53] "POST /mywebserviceHTTP/1.1"200 -
Tal y como hemos podido comprobar, a través de un sencilloejemplo,
Python pone a nuestra disposición todo lo básico paratrabajar con
servidores y clientes XML-RPC en dos sencillos módulosque, además,
forman parte de su librería estándar. De esta forma, esmuy fácil escribir
tanto clientes, como servidores, ya que toda lafuncionalidad y gestión a bajo
nivel del protocolo, está encapsulada enlas diferentes clases, métodos y
funciones que nos ofrecen losmencionados módulos.
    281/381
    CORREO ELECTRÓNICO
De los servicios utilizados a través de Internet, sin duda el correoelectrónico
es uno de lo más utilizados diariamente por millones depersonas. Para el
envío y recepción de correo electrónico son varios losprotocolos de red
empleados. Entre los más populares se encuentranIMAP4, POP3 y SMTP.
Este último se utiliza para el envío, mientras queIMAP4 y POP3 se
encargan de la recepción.Gracias a una serie de módulos contenidos en la
librería estándarde Python, podemos escribir programas que se encarguen
de trabajarcon los protocolos anteriormente mencionados para enviar y
recibircorreo electrónico. Por ejemplo, podemos desarrollar un sencillo
scriptque se conecte a un servidor SMTP y realice el envío de uno o
varioscorreos. Incluso podríamos escribir nuestra propia aplicación cliente
decorreo, con funcionamiento similar a los populares Thunderbird y
Outlook, para enviar y recibir correo electrónico.En este apartado nos
centraremos en tres módulos específicos quepermiten interactuar con
servidores de IMAP4, SMTP y POP3 desdePython. En concreto nos
ocuparemos de poplib, imaplib y smtplib. Comenzaremos por el primero de
ellos.
pop3
El módulo poplib es el encargado de manejar las operacionesnecesarias,
utilizando el protocolo POP3, para interactuar conservidores de correo
electrónico. Básicamente, cuenta con dos clasesprincipales, POP3 y
POP3_SSL. Ambas implementan la mismafuncionalidad, con la diferencia
de que la segunda permite manejarconexiones al servidor a través del
protocolo seguro SSL. De hecho, POP3_SSL está implementada como una
subclase de POP3. Entre los métodos disponibles en las mencionadas
clasesencontramos los que nos permiten realizar una conexión
ydesconexión al servidor, pasar un usuario y contraseña, listar losmensajes
disponibles para dicho usuario, obtener Información sobre
    282/381
    un buzón y, cómo no, obtener los correos recibidos.Para comenzar a trabajar
con poplib, lo primero que deberemoshacer es establecer una conexión con
el servidor. Ello se lleva a cabofácilmente a través de una instancia de la
clase POP3, que recibirá dosparámetros principales: el nombre o IP del
servidor más el puertodonde se está ejecutando. Por defecto, se utilizará el
puerto 110, quees el empleado por convención por los servidores
POP3.Adicionalmente, se puede emplear un tercer parámetro ( timeout )
paraindicar el número de segundos que se deben esperar para obtener
unarespuesta del servidor, si en esos segundos no obtenemos respuesta,la
conexión será fallida y no será establecida. Las siguientes líneas decódigo
muestran cómo conectarnos a un servidor POP3:
>>> from poplib import P0P3>>> servidor = POP3('miservidor.com')
Si la conexión se ha realizado satisfactoriamente, estaremos encondiciones
de conectarnos al buzón de un determinado usuario. Paraello, emplearemos
dos métodos diferentes, uno para pasar el nombrede usuario y otro para
indicar su contraseña:
>>> servidor.user('nombre_de_usuario')>>>
servidor,pass_('password_de_usuario')
Ahora que ya tenemos acceso al buzón del usuario, obtengamos,por
ejemplo, el número de correos que existen en su buzón. Estaacción es tan
sencilla como invocar al método list(), que nos devolveráuna lista con todos
los correos recibidos, además la longitud delsegundo elemento hará
referencia al número de ellos. El siguientecódigo nos muestra un ejemplo
de utilización del mencionadométodo:
>>> num = len(servidor.list()[1])>>> "El usuario tiene {0}
mensajes".format(num)El usuario tiene 5 mensajes
El método retr() es el encargado de obtener el contenido de losmensajes que
se encuentran en un buzón. Por ejemplo, para imprimirpor pantalla todos
los correos de nuestro usuario ejemplo, podríamosemplear el siguiente
código:
    283/381
    >>> i = 0>>> for i in range(num):
    284/381
    .........
for mensaje in servidor.retr(i+1)[1]:print(mensaje)
En cuanto terminemos de realizar las operaciones que necesitemoscon el
servidor P0P3, deberemos cerrar la conexión al servidor. Estaacción se lleva
a cabo a través del método quit(), tal y como muestra lasiguiente sentencia:
>>> servidor.quit()
En ocasiones es interesante obtener una traza de lo que estáocurriendo al
interactuar con el servidor. Es por ello, que existe elmétodo
set_debuglevel() que imprime por pantalla información sobre laacción que
produce en el servidor cualquier operación que ejecutemosa través de la
clase POP3. El mencionado método para depuraciónadmite como
parámetro un entero que indica el nivel de log quedeseamos sea mostrado.
Cuanto mayor sea este número, mayor será lainformación que
obtenemos.Previamente hemos mencionado que la clase POP3_SSL cuenta
conla misma funcionalidad que POP3. Como ejemplo de utilización de
POP3_SSL vamos a conectarnos al servidor de GMail, que requiere estetipo
de autenticación y, además, fijaremos el nivel de depuración a 2,con el fin
de obtener información ofrecida por el servidor sobre lo queestá ocurriendo
cuando invocamos a los métodos de la clase empleadapara la conexión. El
siguiente código muestra cómo realizar lamencionada conexión y la
información ofrecida por el servidor al pasarnuestro usuario:
>>> from poplib import P0P3_SSL>>> ser = P0P3_SSL('pop.gmail.com',
995)>>> ser.set_debuglevel(2)>>>
ser.user('arturofernandezm@gmail.com')*cmd* 'USER
arturofernandezm@gmail.com'*put* b'USER
arturofernandezm@gmail.com'*get* b'+OK send PASS\r\n'*resp* b'+OK
send PASS'b'+OK send PASS'>>> ser.quit()*cmd* 'QUIT'*put*
b'QUIT'*get* b'+OK Farewell.\r\n'*resp* b'+OK Farewell.'b'+OK
Farewell.'
    285/381
    No olvidemos que GMail emplea como usuario de correo elnombre seguido
de la arroba más el dominio. Sin embargo, otrosservidores de correo
utilizan nombres de usuario que no contienen nila arroba ni el dominio en
cuestión. Además, el puerto empleado porGMail no es el estándar, sino el
995, es por ello que hemos indicado elmismo al crear nuestra instancia de la
clase POP3_SSL. Ahora que hemos aprendido lo básico para conectarnos y
acceder ala información de un determinado usuario en un servidor de
correoelectrónico compatible con POP3, es el momento de pasar a ver
cómotrabajar con otro protocolo diferente que nos permita enviar correos,en
lugar de tener acceso a los recibidos.
smtp
El protocolo de red para enviar correos más utilizado es SMTP( Simple
Mail Transfer Protocol). Python cuenta en su librería estándarcon el
módulo llamado smtplib, el cual permite conectarse a cualquierservidor que
cuente con un servidor de correo, que emplee elmencionado protocolo para
enviar correos electrónicos.De forma similar a poplib, el módulo smtplib
cuenta con dos clasesprincipales, una para realizar la conexión no segura y
otra para emplearel protocolo seguro SSL. Los nombres de ambas clases
son,respectivamente, SMTP y SMTP_SSL. La utilización de una u otra
clasedependerá del servidor al que vayamos a conectarnos. Entre
losmétodos ofrecidos por ambas clases encontramos los que nospermiten
conectarnos y desconectarnos de un servidor, pasar usuario ycontraseña, y
por supuesto, realizar el envío de un correo electrónico.Para conectarnos a
un servidor SMTP basta con crear una instanciade la mencionada clase
SMTP o SMTP_SSL, en función de si el servidortrabaja con SSL o no.
Como parámetros deberemos pasar el nombre oIP del servidor y el puerto
de conexión, siendo obligatorio únicamenteel primer parámetro, ya que, por
defecto, si no se indica ningún valor,se empleará el puerto estándar para
SMTP, que es el 25. El siguientecódigo establece la conexión a un servidor
determinado:
>>> from smtplib import SMTP>>> servidor.SMTP('miservidor.com')
    286/381
    Una vez que tenemos la conexión realizada, si el servidor así lorequiere,
será necesario pasar el nombre de usuario y contraseña delusuario en
cuestión que va a realizar el envío del correo electrónico.Para ello, basta
con invocar al método login(), tal y como podemosobservar en la siguiente
sentencia:
>>> servidor.login('usuario', 'password')
En este momento, estamos en condiciones de realizar el envío delcorreo
electrónico en cuestión. El método que nos permitirá hacerlo sellama
sendmail() y requiere de varios parámetros para su invocación. Elprimero
de ellos hace referencia a la dirección desde la que se realizaráel envío.
Como segundo parámetro debe indicarse la dirección deldestinatario. El
tercer parámetro será el cuerpo del correo en cuestión.Opcionalmente se
puede indicar también el asunto del mensaje comootro parámetro adicional.
Así pues, para nuestro ejemplo vamos acrear tres variables diferentes antes
de invocar al método que realiza elenvío del correo electrónico propiamente
dicho:
>>>>>>>>>>>>
email_from = 'miusuario@miservidor.com'email_to =
'destinatario@miservidor.com'email_body = 'Mensaje de
prueba'servidor.sendmail(email_from, email_to, email_body)
En caso de que el método sendmail() falle, se lanzará una
excepcióndiferente en función del error producido. Para estos casos, el
módulo smtplib cuenta con cuatro excepciones diferentes que son:
SMTPRecipientRefused, SMTPHeloError, SMTPSenderRefused y
SMTPError. Capturando estas excepciones podremos indicarle alusuario
qué tipo de error ha ocurrido y actuar en consecuencia.Al igual que hemos
visto previamente en el caso de poplib, encuanto finalicemos la interacción
con el servidor de SMTP deberemoscerrar la conexión que hemos creado al
principio. Bastará con invocaral método quit(), tal y como muestra la
siguiente sentencia:
>>> servidor.quit()
    287/381
    Otro método análogo al set debuglevel() de poplib es el homónimoque
existe en smtplib. La invocación debe realizarse a través de lainstancia
creada de la clase SMTP o SMTP SSL Por otro lado, el método starttls
permite emplear TLS ( Transport
    288/381
    Layer Security) para aquellos servidores que lo requieran.Adicionalmente,
smtplib cuenta también con un método llamado helo() que permite ejecutar
el comando del mismo nombre en el servidor.
imap4
IMAP son las siglas de Internet Message Access Protocol, unprotocolo para
el acceso a servidores de correo, consulta de buzones ydescarga de
mensajes. IMAP4 hace referencia a la versión 4 delmencionado protocolo,
siendo esta la más reciente y sustituyendo a laversión 3, ampliamente
utilizada durante muchos años.Junto con POP3, IMAP4 es uno de los dos
protocolos más utilizadosen la actualidad para obtener correo electrónico
desde un servidor.Además, la mayoría de clientes de correo son compatibles
con ambosprotocolos. Por otro lado, servidores que ofrecen correo
electrónico deforma gratuita, como GMail, sorportan ambos protocolos
además de,obviamente, la interfaz web.En Python el módulo que ofrece la
interacción con servidoresIMAP4 se llama imaplib y cuenta con tres clases
principales: IMAP4,IMAP4_SSL y IMAP4_stream. Las dos primeras son
análogas, con laexcepción de que la segunda permite el acceso a través de
SSL. Latercera en cuestión soporta la utilización de la entrada y
salidaestándar como descriptores de ficheros, permitiendo la ejecución
decomandos y salida de resultados empleando las mismas.De forma similar
a como hemos aprendido previamente, en el casode POP3 y SMTP, imaplib
ofrece métodos para conectarnos a unservidor, autenticarnos e interactuar
con el mismo. La conexión alservidor es sencilla, basta con crear una
instancia de la clase IMAP4, taly como podemos apreciar en el siguiente
ejemplo:
>>> from imaplib import IMAP4>>> con = IMAP4()
A diferencia de las clases SMTP y POP3, al constructor de IMAP4 nole
hemos pasado ningún parámetro. En este caso y por defecto, elservidor
utilizado será localhost y el puerto en cuestión será el 143,siendo este el que
usan por convención los servidores. Obviamente,
    289/381
    podemos pasar al mencionado constructor diferentes valores paraestos
parámetros, siendo el primero el servidor y el segundo el puertodel
mismo.La autenticación se lleva a cabo a través del método login(),
quenecesita el usuario y contraseña como parámetros:
>>> con.login('usuario', 'password')
Una vez establecida la conexión y realizada la autenticación, en casode que
ello sea necesario, estaremos en disposición de interactuar conel servidor.
Los mensajes de un determinado buzón pueden ser leídosa través del
método search(), que se utiliza para buscar mensajes quecumplan un
criterio dado. Antes de invocar a este método, debemosseleccionar el buzón
que va a ser leído. Esta acción se ejecuta gracias almétodo select(), que
recibe como parámetros el nombre del buzón yun flag para indicar si el
acceso será de solo lectura o no. Si elmencionado método no recibe ningún
parámetro, el buzón del usuariopreviamente autenticado será el utilizado.
Así pues bastará con invocara este método para seguir con nuestro ejemplo:
>>> con.select()
Ahora que tenemos seleccionado nuestro buzón, podemos obtener,por
ejemplo, todos los mensajes que existen en el mismo. Esto es tansencillo
como ejecutar la siguiente sentencia:
>>> tipo, datos = con.search(None, 'ALL')
El anterior método devolverá una tupla donde el segundo elementode la
misma es una lista que contendrá los datos referentes a losmensajes
encontrados y que cumplen con el criterio de búsqueda.Como parámetros
del método search(), hemos utilizado None paraindicar que vamos a
emplear el conjunto de caracteres por defecto delservidor y la cadena de
texto ALL para indicar que deseamos recuperartodos los mensajes del
buzón. En caso, por ejemplo, de necesitar todoslos mensajes enviados desde
una dirección concreta, bastaría conpasar un par de parámetros adicionales.
Supongamos que deseamosobtener todos los mensajes recibidos y que han
sido enviados por lacuenta prueba@miservidor.com . En este caso,
realizaríamos la siguientellamada:
    290/381
    >>> tip, data = con.search(None, ’FROM','"prueba@miservidor.com"')
Volviendo a nuestra llamada anterior, cuando esta sea ejecutada ytengamos
los valores tipo y datos, podremos escribir un bucle para, porejemplo,
imprimir por pantalla todos los mensajes obtenidos delbuzón. Sirva para
ello el siguiente código:
>>> for mensaje in datos [0] .split():typ, data = con.fetch(mensaje,
'(RFC822)')print('Mensaje {O}: {1}'.format(mensaje, data[0][1]))...
Efectivamente, el método fetch() es el responsable de obtener lainformación
de cada mensaje, ya que search() nos devuelve lainformación que debemos
procesar. Como primer argumento de fetch() hemos pasado, precisamente,
información recogida gracias a search(). Por otro lado, el segundo
argumento hace referencia alestándar RFC822 que define el formato de los
mensajes que debentener los correos electrónicos.No olvidemos que, al
finalizar nuestra interacción con el servidorIMAP4, deberemos
desconectarnos del mismo, siendo esto posiblegracias al método close().
Además, si nos hemos autenticadopreviamente, deberemos hacer logout.
Dado que así lo hemos hechoen nuestro ejemplo, para finalizar nuestro
trabajo con imaplib invocaremos a ambos métodos:
>>> con.close()>>> con.logout()
Aparte de las funcionalidades básicas que hemos comentado, imaplib pone
a nuestra disposición otras más avanzadas, como, porejemplo, la copia de
un mensaje a un determinado buzón, el borradocompleto de un buzón, el
listado de los nombres de todos los buzonesque cumplen una determinada
condición o la posibilidad de trabajarcon ACL (Access Control List). En
contraste con POP3, la interacción con IMAP4 desde Python esmás
compleja. Ello se debe a que, de por sí, el protocolo IMAP4 utilizaun
acceso diferente a los buzones y que, además, ofrece la posibilidadde
ejecutar distintas acciones.
    291/381
    WEB
La web es uno de los servicios que más peso tiene en Internet, juntocon el
correo electrónico. Los sitios web ya se cuentan por millones ycada vez se
encuentran más tipos de aplicaciones que ofrecen sufuncionalidad a través
de la web. Tanto es así que el desarrollo deaplicaciones web es uno de los
campos a los que más profesionales sededican dentro del ámbito de la
ingeniería del software. Python es unabuena opción a la hora de elegir un
lenguaje de programación paradesarrollar aplicaciones web, tal y como
podremos comprobar acontinuación. Una de las principales razones para
ello es la cantidad demódulos, tanto dentro como fuera de la librería
estándar, que existenpara interactuar con la web, ya sea, a través de
protocolos como CGI yWSGI, o empleando modernos frameworks web.En
este apartado también aprenderemos a aplicar la técnica del webscraping
para acceder, de forma automática, a la información queofrecen los sitios
web.
CGI
Durante largos años, el estándar CGI (Common Gateway Interface) fue la
técnica más utilizada para generar páginas web dinámicas. Estatécnica
consiste en ejecutar un programa en el servidor que se encargade construir
una página web como respuesta a una petición que serealiza desde un
cliente web, siendo el más habitual de estos unnavegador. El lenguaje de
programación Perl fue uno de los másutilizados para desarrollar estos
programas de servidor a los que se lesllamaba Scripts CGI. La forma de
procesar la petición y generar larespuesta se realiza basándose en una serie
de reglas que define elestándar CGI (RFC 3875). Floy en día, aún se utiliza
el CGI para construirsitios web dinámicos, aunque de forma minoritaria.
Ello es debido aque existen otras opciones que ofrecen ventajas
significativasrelacionadas con la seguridad y la eficiencia. Incluso se han
llegado adesarrollar soluciones específicas para algunos lenguajes y
tecnologías,
    292/381
    como es el caso de los servlets de Java, Rack para Ruby, mod_php paraPHP
y WSGI para Python.En ocasiones puede ser interesante escribir un script
CGI para, porejemplo, dotar de una interfaz web a un programa que ya
tenemosdesarrollado. Supongamos que tenemos un script escrito en
Pythonque se encarga de analizar una serie de ficheros de log y
mostrarinformación sobre los mismos. Adaptarlo para que pueda mostrar
elresultado a través de una página web es fácil gracias a CGI. Además,
nonecesitaremos montar ningún servidor de aplicaciones ni
ningunacompleja arquitectura de servidores web. Bastará con que el script
seejecute en cualquier servidor web que pueda ejecutar Scripts CGI. En
lapráctica, la mayoría de los modernos servidores web tienen estacapacidad,
entre ellos Apache, nginx y lighttpd. En la librería estándar de Python
encontramos dos módulos quenos permitirán desarrollar scripts CGI, nos
referimos a cgitb y a cgi. Ambos módulos nos ofrecen funcionalidades para
analizar la peticiónenviada desde el cliente, generar contenido en formato
HTML, leervalores de variables pasadas por el método GET y procesar
formularios.Para realizar pruebas es conveniente contar con un servidor
webcapaz de interpretar y gestionar CGI. No vamos a entrar en detallecómo
configurar el servidor y daremos por hecho que el lector poseelos
conocimientos básicos sobre el funcionamiento de CGI. Losusuarios de
Mac OS X lo tienen sencillo, ya que, por defecto, Apacheestá instalado y
configurado para ejecutar CGI. Los scripts CGI debenresidir en el directorio
/Library/WebServer/CGI-Executables/. Como ejemplo, vamos a escribir un
sencillo script que lanzará unmensaje de bienvenida, leyendo el nombre de
una persona quepasaremos como parámetro, a través del método GET de
HTTP. Lalectura de este tipo de variables y de aquellas enviadas por el
métodoPOST, como ocurre habitualmente con los formularios, se lleva a
caboa través de la clase FieldStorage(), la cual pertenece al módulo cgi.
Acontinuación, reproducimos el código completo de nuestro primer CGIen
Python:
#!/usr/local/bin/python3import cgitbimport cgi
cgitb.enable()
    293/381
    vars = cgi.FieldStorage()nombre = vars.getvalue('nombre')
print("Content-type: text/html")print()print("<html>")print("
<body>")print("<h2>Bienvenid@ {0}</h2>".format(nombre))print("
</body>")print("</html>")
Para probar nuestro script, deberemos copiar el código en unnuevo fichero
al que llamaremos, por ejemplo, hola.py. Luegopasaremos a alojarlo en el
directorio determinado del servidor webdonde deben residir los CGI's según
la configuración del mismo. Lospermisos de ejecución del fichero deben
estar activos, en otro casoobtendremos un error al realizar la petición desde
el navegador. Encuanto estemos listo para la prueba, abriremos un
navegador y nosdirigiremos a la siguiente URL:
http://localhost/cgi-bin/hola.py?nombre=Lucas
Si el script se ejecuta correctamente, veremos un mensaje como elque
muestra la figura:
Figura. Página web generada por el script CGI
En la URL hemos pasado una variable llamada nombre con el valor Lucas;
para recoger su valor, hemos creado una instancia de la clase
FieldStorage(). El método getvalue() de la mencionada clase nos daacceso a
la variable pasada por GET.Por otro lado, la primera línea de nuestro script
es necesaria paraindicar dónde se encuentra el intérprete de Python que
debe utilizarsepara procesar el código del script en cuestión. El método
enable() se
    294/381
    ejecuta para llevar a cabo una serie de inicializaciones necesarias
paratrabajar con CGI. Con la primera sentencia print comienza a
generarsela salida HTML que será enviada al navegador. Es por ello, que
loprimero que hacemos es fijar el tipo de contenido, en este caso
será,obviamente, HTML. También es posible indicar, por ejemplo, que
lasalida será texto o json, útil para responder a llamadas realizadas
víaAJAX. Justamente después, necesitamos una llamada a print()
sinparámetros para indicar que comienza a generarse lo que será elcódigo
HTML propiamente dicho. A partir de este punto construimosla página
HTML de salida, empleando para ello una serie de etiquetasHTML y el
valor de la variable nombre que hemos recogido gracias a laclase
FieldStorage().
WSGI
Originalmente los frameworks para desarrollar aplicaciones web enPython
presentaban la restricción de que cada uno de ellos necesitabaun
determinado servidor web que implementara la interfaz necesariapara la
comunicación entre ambos. De esta forma, por ejemplo, si unframework
había sido desarrollado utilizando mod_python comointerfaz, el servidor
web Apache era requerido para la ejecución deaplicaciones. Con objetivo
de eliminar este requisito restrictivo sedesarrolló WSGI (Web Server
Gateway Interface), una interfaz de bajonivel que permite la comunicación
entre diferentes frameworks yservidores web, haciendo portables las
aplicaciones entre amboscomponentes.La interfaz WSGI define dos
componentes diferenciados, uno es el servidor o gateway y otro es la
aplicación o framework que la sirve. El gateway se encarga de gestionar el
intercambio de información entreel cliente y la aplicación propiamente
dicha.Para trabajar con WSGI, Python pone a nuestra disposición elmódulo
wsgiref de su librería estándar, el cual incluye funcionalidadespara procesar
variables de entorno, procesar peticiones y generarrespuestas para un
cliente web.Como ejemplo de utilización del módulo wsgiref, vamos a
construirun sencillo servidor web capaz de manejar peticiones WSGI y
    295/381
    responder a las mismas generando contenido que pueda ser leído porun
navegador web. Nuestro sencillo servidor devolverá el famosomensaje Hola
Mundo! cuando un navegador invoque a una URLdeterminada a la que
responderá nuestro servidor. Antes de comenzary para probar nuestro
servidor, todo el código que vamos a irmostrando y explicando debe
salvarse en un fichero, el cuallanzaremos por la línea de comandos. La
primera línea de código seencargará de importar la función necesaria para
crear el mencionadoservidor web:
from wsgiref.simple_server import make_server
Justo después definiremos una función que responderá a la URLraíz de
nuestro servidor, devolviendo el mencionado mensaje:
def simple_app(environ, start_response):status = '200 OK'header =
[('Content-type', 'text/html; charset=utf-8')]start_response(status, header)ret
= b"<html><body><h2>Hola Mundo</h2></body></html>"return [(ret)]
La variable status devolverá el valor 200, que es el código HTTPestándar
para indicar que la respuesta se ha generado correctamente.Por otro lado, la
variable header Indicará que vamos a generarcontenido HTML, siendo el
conjunto de caracteres elegido el UTF-8.Por último, devolveremos el
código HTML que generará en el cliente lapágina web de respuesta.
Observemos cómo deberemos devolver unalista que contenga una tupla, en
nuestro caso, con un único valor.Ya solo nos queda crear nuestro servidor
web y hacer que seejecute. Estas acciones son llevadas a cabo por la
función make_server() y por el método serve_forever(), respectivamente.
Además, vamos alanzar un mensaje que indique que el servidor ha sido
iniciado. Elcódigo necesario para todo ello sería el siguiente:
if __name__ == '__main__':httpd = make_server('', 8080,
simple_app)print("Lanzando servidor en puerto
8080...")httpd.serve_forever()
Ahora solo nos queda invocar a nuestro script por línea decomandos, en
cuanto sea lanzado apreciaremos que obtendremos unmensaje como el
siguiente:
    296/381
    $python servidor_wsgi.pyLanzando servidor en puerto 8080...
Si abrimos nuestro navegador y nos conectamos a la URL
http://localhost:8080 , obtendremos como respuesta una sencilla páginacon
el mencionado Hola Mundo! Volviendo a la terminal donde hemoslanzado
nuestro servidor, comprobaremos que aparece una nuevalínea:
localhost - - [21/Feb/2012 12:59:21] "GET / HTTP/1.1" 200 45
La anterior línea es similar a la lanzada por el servidor web Apache,la cual
habitualmente se refleja en el fichero access_log. Tal y como el lector habrá
podido comprobar, con muy pocas líneasde código, Python nos ofrece todo
lo necesario para construir unservidor WSGI capaz de responder a
peticiones HTTP. Diferentesframeworks se basan en el módulo wsgiref para
desarrollar sus propiosservidores, ajustando así como manejar peticiones y
respuestas yofreciendo, por ejemplo, capas adicionales de middleware.
Web scraping
El web scraping es la técnica mediante la cual se extraen datos deuna
determinada página web. Habitualmente se utiliza el web scraping para
simular el comportamiento de una persona en la navegación deun sitio web,
a través de un programa. De esta forma, se automatiza laconexión a un
determinado sitio web, la navegación sobre el mismo yla final extracción de
la información que contiene. Los robots que sededican al indexado de la
web, conocidos como bots, hacen uso deesta técnica para poder leer el
contenido de los sitios web. Otrosejemplos de aplicaciones que emplean el
scraping son loscomparadores de precios y aquellas que utilizan datos
ofrecidos porsitios de terceros para integrarlos con sus propios sitios
web.Para realizar el web scraping necesitamos llevar a cabo varias
tareascomo la conexión a un sitio web, la autenticación de diferentes tipos,
lagestión de cookies, la navegación a través de distintos enlaces incluidosen
el propio sitio web y el análisis de la estructura HTML para obtenerla
información que contiene. Para esta última fase necesitaremos leer
    297/381
    un documento HTML y ser capaces de extraer información analizandolas
distintas etiquetas que contiene. Existe un módulo para Python quepodemos
emplear para ello, su nombre es Ixml. Por otro lado, el restode acciones
comentadas que forman parte del scraping pueden serllevadas a cabo por un
módulo de la librería estándar de Pythondenominado urllib.request. De
ambos módulos nos ocuparemos acontinuación.
URLLIB.REQUEST
El módulo urllib.request ofrece la funcionalidad necesaria para
abrirconexiones HTTP y HTTPS, obteniendo el resultado ofrecido por
unservidor web a través de estos protocolos. Dado que en este
procesopuede ser necesario realizar diferentes tipos de autenticación,
gestiónde cookies y redirecciones, urllib.request pone a nuestra disposición
unconjunto de clases y funciones para realizar estas acciones de formafácil
y eficiente.La principal función que nos servirá para conectarnos a una
URLdeterminada es urlopen(), la cual recibirá la URL en cuestión
comoparámetro. Supongamos que vamos a hacer web scraping para
obtenerla previsión del tiempo para la ciudad de Madrid para los
próximoscinco días. El servicio Weather de Yahoo! ofrece esta
informacióndirectamente en una página web concreta, cuya URL es
http://weather.yahoo.com/spain/madrid/madrid-766273/?unit=c . Asípues,
para conseguir esta información de forma automática,deberemos
conectarnos a la mencionada URL, leer su contenido yanalizarlo. El primer
paso lo vamos a realizar empleando lamencionada función urlopen(),
mientras que del segundo se encargaráel módulo Ixml, tal y como veremos
más adelante. De esta forma,procederemos a importar el correspondiente
módulo y a invocar a lafunción en cuestión:
>>> import urllib.request>>> url =
'http://weather.yahoo.com/spain/madrid/madrid- 766273/?unit=c'>>>
pagina = urllib.request.urlopen(url)
Seguidamente, cargaremos el contenido leído en una cadena detexto para
que su posterior análisis sea más sencillo. Para ello, basta
    298/381
    con invocar al método open() del objeto HTTPResponse, que a su vezfue
devuelto por la función urlopen():
>>> contenido = pagina.read()
Si en este momento hacemos un print de la variable contenido,
observaremos cómo tendremos todo el código HTML que forma lapágina
web a la que hemos accedido por la URL en cuestión.En caso de que la
URL a la que nos conectemos requiera deautenticación básica HTTP,
podemos emplear la clase HTTPBasicAuthHandler(), la cual se encargará
de gestionar laautenticación. La siguiente línea muestra cómo instanciar
lamencionada clase:
>>> auth = urllib.request.HTTPBasicAuthHandler()
En cuanto tengamos la instancia, el siguiente paso será añadir lainformación
relativa al usuario y contraseña. Ello puede realizarseempleando el método
add_password(), tal y como muestran lassiguientes líneas:
>>> auth.add_password(user='usuario', passwd= 'password')
Después invocaremos a un par de métodos para gestionar laautenticación de
forma transparente y llamar a la URL en cuestión de lamisma forma que si
esta no necesitara autenticación:
>>> opener = urllib.request.build_opener(auth)>>>
urllib.request.install_opener(opener)>>> pagina =
urllib.request.urlopen('http://localhost/login/')
En ocasiones es útil pasar una cabecera (header) en la solicitud deuna
página web. Por ejemplo, para indicar que deseamos utilizar undeterminado
User-Agent. Esto es práctico cuando necesitamoscomunicarle al servidor
que nuestra petición simula ser un navegadorconcreto. A través del método
addheaders(), es posible enviar unacabecera HTTP determinada.
Supongamos que vamos a pedir unapágina web pero queremos
identificarnos como si fuéramos elnavegador web Safari, ejecutado desde la
    299/381
    versión 10.6.8 de Mac OS X.El código Python necesario para ello sería el
siguiente:
>>> opener = urllib.request.build_opener()>>> opener.addheaders([('Useragent',
    300/381
    Mozilla/5.0 (Macintosh; U; Intel Mac
OS X 10_6_8) ')])>>> opener.open(url)
Efectivamente, al método addheaders() debemos pasarle una listaque
contenga tantas tuplas como valores diferentes admitidos por unacabecera
HTTP necesitemos.En nuestro ejemplo, simplemente hemos indicado un
único valor, eldel mencionado User-Agent. Cuando navegamos por un sitio
web es común hacer uso de las cookies para guardar información única de
cada sesión que semantiene abierta por el navegador web. Dado este hecho,
al emplearel web scraping, es habitual tener que pasar la información de
lascookies entre diferentes páginas web del mismo sitio web. Esto es fácilen
Python gracias a la clase HTTPCookieProcessor, que realiza el trabajopor
nosotros de forma transparente. Esta clase acepta en suconstructor, como
parámetro, un objeto de tipo CookieJar, cuya clasese encuentra definida en
el módulo http.cookiejar , también de lalibrería estándar de Python. Si la
petición a una URL determinadarequiere del manejo de cookies, podemos
utilizar el siguiente códigopara realizar la gestión:
>>>>>>>>>>>>>>>
import http.cookiejarcookie = http.cookiejar.CookieJar()handler =
urllib.request.HTTPCookieProcessor(cookie)opener =
urllib.request.build_opener(handler)opener.open(url)
Tal y como habremos podido comprobar, el módulo urllib.request ofrece lo
básico para realizar conexiones y gestiones de cookies,cabeceras HTTP y
autenticación. Siendo todas estas operaciones laparte inicial del web
scraping, nos queda averiguar cómo extraemos losdatos del código HTML.
De ello nos ocuparemos en el siguienteapartado, que describe el
funcionamiento básico del módulo Ixml.
LXML
A pesar de que su nombre puede confundir, el módulo Ixml permiteanalizar,
tanto ficheros XML como HTML. Desde el punto de vistatécnico, este
    301/381
    módulo es un binding para Python de las populareslibrerías libxml2 y
libxslt, escritas ambas en el lenguaje de
    302/381
    programación C. Haciendo uso de la estructura ElementTree y
lascorrespondientes clases y funciones, es posible manejar con soltura
laestructura de un documento XML y HTML, lo cual resultará muypráctico
para el web scraping. Básicamente, la estructura ElementTree proporciona
acceso a los elementos del documento como si lasetiquetas y nodos del
mismo estuvieran estructurados en forma deárbol. En la actualidad son
muchos los componentes software que sevalen de este tipo de estructura
para el análisis de documentosestructurados SGML, como lo son el formato
XML y el HTML.Dado que lxml no forma parte de la librería estándar de
Python,deberemos acudir a un gestor de paquetes, por ejemplo pip, para
suinstalación. Así pues nos bastará con ejecutar la siguiente orden desdela
línea de comandos:
pip install lxml
Las funcionalidades ofrecidas por el módulo lxml son extensas yentre ellas
se encuentran las que nos permiten utilizar XPath, XSLT ydiferentes tipos
de parsers, como son html5lib y BeautifulSoup. Sinembargo, dado que
estamos tratando el tema del web scraping, noscentraremos en la parte
específica del análisis de HTML ofrecido por lxml. En concreto, las clases y
métodos para este propósito seencuentran en el módulo lxml.html. Para
ilustrar de forma sencilla elfuncionamiento del mismo, nos centraremos en
el ejemplo queempezamos en el apartado anterior. De esta forma,
partiremos de queya tenemos volcado el contenido de la página web del
tiempo,ofrecida por Yahoo!, en una variable a la que hemos llamado pagina.
Teniendo este hecho en cuenta, procederemos a buscar la informaciónque
necesitamos. Esta se encuentra en el interior de un elemento <tr> cuyo
atributo class tiene el valor fiveday-temps. Partiendo de este dato,bastará
con invocar al método find_class(), el cual nos devolverá unobjeto que
representa al elemento HTML en cuestión. Además,llamaremos a la función
fromstring() que se encargará de leer unacadena de texto y formar un objeto
que cumpla con la estructura ElementTree. Finalmente, solo nos quedará
emplear el método text_content() para obtener el contenido del elemento
HTML quebuscamos dentro de la página web en cuestión. El código
requeridopara todas estas operaciones es el siguiente:
    303/381
    >>> from lxml.html import fromstring>>> doc = fromstring(pagina)>>>
ele = doc.find_class('fiveday-temps')>>> ele [0] .text_content()'High:
12\xb0 Low: -3\xbOHigh: 14\xb0 Low: -3\xbOHigh: 17\xb0
Low:-3\xbOHigh: 17\xb0 Low: -2\xbOHigh: 17\xb0 Low: 1\xb0'
Otro práctico método para acceder al contenido de un determinadonodo de
un documento HTML es get_element_id(), el cual se basa en labúsqueda
del atributo id de los elementos HTML.Además de acceder a la información
de un documento HTML,también podemos modificar su estructura. De ello
se encargan dosmétodos diferentes, drop_tree() y drop_tag(), que permiten
borrar unelemento y todos sus hijos y borrar un elemento manteniendo
sushijos y el texto que el mismo contiene, respectivamente.Para el trabajo
con enlaces y formularios presentes en undocumento HTML, el módulo
lxml.html ofrece una serie de fundonesadicionales que facilitan en gran
medida el trabajo. Ejemplos de ellosson aquellas funciones que nos
permiten acceder a todos loselementos de un formulario, las que nos
devuelven el tipo de elemento input o las que nos permiten iterar
directamente por todos los enlacespresentes en el documento.El lector
interesado en profundizar en todos los componentes de lxml.html puede
consultar la documentación de referenciacorrespondiente (ver referencias).
Frameworks
Para el desarrollo de aplicaciones web complejas, los frameworks web
ofrecen diversas herramientas y utilidades para agilizar eldesarrollo y
facilitar el mantenimiento. En los últimos años su uso se hapopularizado
enormemente y en la actualidad es fácil encontrarmultitud de ellos para
lenguajes como Java, PHP, Ruby y, cómo no,Python. Uno de los más
conocidos y utilizados para este últimolenguaje es Django, el cual, en el
momento de escribir estas líneas, aúnno es compatible con Python 3. Sin
embargo, contamos con otros quesí lo son, como es el caso de Pyramid y
Pylatte, de los que nosocuparemos en este apartado. Más que hacer un
análisis exhaustivo de
    304/381
    ellos, nos dedicaremos a ver qué puede ofrecernos cada uno en
líneasgenerales.
PYRAMID
El proyecto open source llamado Pylons Project es el responsable
deldesarrollo y mantenimiento del framework web Pyramid. El desarrollode
este componente software nace con la idea de disponer de un framework
web que sea fácil de utilizar, minimalista en cuanto alconjunto de
componentes de los que se compone y que sea rápidopara la ejecución de
aplicaciones web.La primera versión de Pyramid fue liberada allá por
diciembre de2010, siendo la última versión la 1.3 totalmente compatible
con Python3. Como interfaz de comunicación emplea WSGI y se inspira en
otros frameworks como Zope, Pylons y Django. Pyramid se basa en el
patrón de diseño MVC ( Model ViewController ), aunque no lo implementa
de la manera tradicional. Esdecir, no existen clases o componentes que
actúen directamente conun controlador o un modelo. En cambio, una
aplicación construida coneste framework cuenta con un árbol de recursos,
que representa laestructura del sitio web. Adicionalmente existen una serie
de vistas yun conjunto de clases para representar los modelos de la
aplicación.Las vistas son las encargadas de conectar y procesar las
peticionesgeneradas en un cliente para ofrecer una salida basada en
lainformación de los datos representados por los modelos. Sin embargo,y a
diferencia de otros frameworks, Pyramid no ofrece facilidades paraconectar
los datos que existan en una base de datos con las clases quedefinen los
modelos. No obstante, sí que ofrece facilidades, para, porejemplo, integrar
un ORM que realice este trabajo de conexión o mapping. En comparación
con otros frameworks web de tipo full stack, Pyramid es minimalista en el
sentido de que ofrece lo básico parapoder desarrollar aplicaciones web,
siendo el programador elresponsable de añadir módulos adicionales para
implementarfuncionalidades avanzadas, como es el caso, por ejemplo, de
un ORMpara interactuar con una base de datos.Una de las características
principales de Pyramid es que permite,
    305/381
    haciendo gala de su carácter minimalista, la creación de aplicacionesweb
utilizando un único fichero. Lo cual resulta muy cómodo pararealizar
prototipos o para desarrollar pequeñas aplicaciones.Pyramid incluye
herramientas para que los programadores puedandepurar de forma fácil y
rápida sus aplicaciones. Con este objetivo, labarra de herramientas de
depuración (debug toolbar) ofreceinformación como, por ejemplo, sobre las
variables enviadas en cadapetición, la configuración actual del servidor e
información sobre elrendimiento de la aplicación.Gracias a una serie de
componentes, la internacionalización ylocalización de aplicaciones es
posible en Pyramid, utilizando comobase gettext para la generación de
cadenas de texto en diferentesidiomas.Al igual que otros frameworks,
Pyramid permite trabajar consesiones HTTP, definir fácilmente las URL de
las rutas accesibles de laaplicación, servir ficheros estáticos y utilizar
plantillas (templates) parala generación de páginas HTML.A diferencia de
otros, Pyramid ofrece la opción de utilizar eventosdurante el ciclo de vida
de las peticiones que recibe. De esta forma, esposible crear manejadores de
eventos que respondan con unadeterminada acción cuando se produzca un
evento determinado.Incluso es posible definir eventos propios creados por
el programador.Ello nos da gran flexibilidad y control sobre la forma de
responder a lasdiversas peticiones originadas desde un cliente web.La
funcionalidad de Pyramid puede ser extendida gracias a losllamados addons, que son componentes software que permitenutilizar funcionalidades no
definidas originalmente en el framework. Entre estos componentes,
contamos con algunos que permitenintegrar diferentes librerías JavaScript,
emplear JSON o utilizar undeterminado sistema de plantillas.La instalación
de Pyramid puede ser llevada a cabo directamente através del gestor de
paquetes de Python pip. Para ello, bastará conejecutar desde la línea de
comandos la siguiente orden:
pip install pyramid
Durante el proceso de instalación, serán descargados y tambiéninstalados
una serie de paquetes adicionales que Pyramid requierepara su
funcionamiento. Dado que este proceso es transparente, no
    306/381
    será necesario llevar a cabo ninguna acción adicional.En cuanto finalice la
instalación podremos comenzar a escribirnuestra primera aplicación. Como
sencillo ejemplo, vamos a crear unsimple script que responda a una
determinada URL generando elmensaje Hola Mundo!. Para ello,
comenzaremos creando un nuevofichero, al que llamaremos
hola_mundo.py, que contendrá lassiguientes líneas iniciales de código:
from wsgiref.simple_server import make_serverfrom pyramid.config
import Configuratorfrom pyramid.response import Response
Seguidamente, pasaremos a crear una función que será ejecutadacomo
respuesta a una petición y que, simplemente, mostrará elmencionado
mensaje inicial. El código para la función sería el quemostramos a
continuación:
def hola_mundo(request):return Response('Hola Mundo!' %
request.matchdict)
Ahora debemos escribir el código de entrada de nuestra aplicaciónPyramid.
Inicialmente, instanciaremos la clase que lee la configuraciónpara la
aplicación. Dado que estamos trabajando con un ejemplomínimo, no es
necesario crear ninguna configuración adicional. La líneade código que se
encarga de ello será la siguiente:
config = Configurator()
El siguiente paso será establecer la ruta de la URL e indicar quéfunción
debe ejecutarse como resultado de su petición. En nuestrocaso, vamos a
responder a la URL /hola, devolviendo un mensaje através de la función
hola_mundo(), la cual ha sido previamentedefinida. El código en cuestión
necesario para estas acciones es elsiguiente:
config.add_route('hola', '/hola')config.add_view(hola_mundo,
route_name='hola')
Ya solo nos queda crear un servidor WSGI e indicar que nuestraaplicación
debe ser servida por él mismo. Ello es bastante sencillo,siendo este el
    307/381
    código que necesitamos:
app = config.make_wsgi_app()server = make_server('0.0.0.0', 8081, app)
    308/381
    server.serve_forever()
A través del primer y segundo parámetro de la función make_server()
hemos indicado que el servidor identificado por la IP0.0.0.0, es decir,
localhost, debe servir nuestra aplicación Pyramid en elpuerto 8081. Al
invocar a nuestra aplicación desde la interfaz decomandos, quedará
automáticamente accesible a través del navegadorweb. Así pues, al pedir la
URL http://localhost/hola , obtendremos elmensaje Hola Mundo! Una vez
descubierto lo básico sobre Pyramid es hora de continuarcon Pylatte
nuestro recorrido por los frameworks web para Python 3.
PYLATTE
Los frameworks web más populares y utilizados en la actualidadsolo son
compatibles con Python 2.x. Este es el caso de Django, Flask,Turbogears y
web2py. Es por ello que Pylatte nace con la idea dedesarrollar un
framework web específicamente diseñado para serejecutado con Python
3.Pylatte es open source y la primera versión fue liberada en octubrede
2011, es por lo tanto un proyecto bastante joven. Sin embargo, suestado es
estable, con lo que puede ser empleado sin ningúnproblema.Una de las
características principales de Pylatte es que emplea unformato específico
llamado pyl, que contiene tanto código HTML comocódigo Python.
Internamente, un componente de Pylatte se encarga deanalizar y procesar
este tipo de ficheros y producir la correspondientesalida en HTML, la cual
será enviada al cliente web.Para la correspondencia entre URL y las
acciones que deben serllevadas a cabo, Pylatte utiliza ficheros XML donde
se realiza estaconfiguración. Pylatte permite aplicar una serie de filtros, es
decir,acciones que se llevan a cabo previa o posteriormente alprocesamiento
de una petición. Su configuración también se realiza através de ficheros
XML.Dado que Pylatte no incluye un ORM como tal, sino una serie
defacilidades para interactuar con la base de datos. De momento,
soloMySQL está soportado, siendo necesaria la instalación del módulo
MySQLdb para trabajar con el mencionado gestor de bases de datos
    309/381
    relacionales.Componentes incluidos el framework facilitan la obtención
deparámetros recibidos por GET y POST, así como el uso de
sesionesHTTP. Pylatte también incluye un servidor web para poder servir
lasaplicaciones que desarrollemos empleando este framework.La
instalación de Pylatte se puede realizar a partir de su códigofuente, el cual
se encuentra disponible en la página web de descargasdel proyecto (ver
referencias).En comparación con otros frameworks web de Python,
Pylatteofrece menos componentes para facilitar el desarrollo web, siendo
losmismos demasiado básicos. Por otro lado, no implementa como tal
elpatrón MVC y permite la mezcla de código Python y HTML en elmismo
fichero, lo que, en términos generales, no es aconsejable. Porotro lado, es
un framework totalmente pensado para trabajar conPython 3, lo que supone
una ventaja significativa con respecto a suscompetidores.La elección de un
determinado framework web no es una tareasencilla, influyendo en la
decisión diversos factores técnicos yhumanos. Es conveniente tener en
cuenta aspectos como quécomponentes necesitamos teniendo en cuenta la
funcionalidad de laaplicación, el rendimiento en tipo de ejecución o cómo
es la curva deaprendizaje. Animamos al lector a investigar sobre otros
frameworksweb para Python con el objetivo de elegir aquel que más se
adapte asus necesidades.
    310/381
    INSTALACIÓN YDISTRIBUCIÓN DE PAQUETES
INTRODUCCIÓN
En capítulos anteriores nos hemos valido de ciertos módulos queno están
presentes en la librería estándar de Python, para tener accesoa determinadas
funcionalidades. Para emplear estos módulos,simplemente los hemos
instalado a través de una herramienta llamadapip. Pero ¿qué es esta
herramienta? ¿Cómo podemos instalarla yutilizarla? La primera parte de
este capítulo se ocupará de los métodosde instalación de módulos, donde
obtendremos respuestas a laspreguntas anteriores y aprenderemos lo básico
para poder instalar ytrabajar con módulos desarrollados por otras personas y
que noforman parte de la librería estándar de Python. En
concreto,aprenderemos a utilizar el método estándar, proporcionado por
elmódulo distutils y a trabajar con dos gestores de paquetes: pip y
easy_install. Como desarrolladores, nosotros también podemos crear
nuestrospropios paquetes y distribuirlos para que puedan ser empleados
porotras personas. De hecho, este proceso suele formar parte deldesarrollo
de software. Preparar nuestros programas para que puedanser distribuidos
es una tarea importante que no debe ser pasada poralto. Más aún si vamos a
obtener por ello un beneficio económico, yaque nuestros clientes
necesitarán instalar el software que hemosdesarrollado para ellos. De la
misma forma, en el ámbito del FOSS (Free and Open Source software), este
proceso también es fundamentalsi queremos que otros puedan trabajar con
nuestro software. En lasegunda parte del presente capítulo nos ocuparemos
del proceso dedistribución de software, aprendido a empaquetar nuestros
propiosmódulos.Cuando hablamos sobre la distribución e instalación de
software deterceros en Python, empleamos tanto el término paquete como
módulo. Recordemos que, por convención, un módulo de Python
    311/381
    puede ser un simple script escrito en este lenguaje, mientras que un paquete
es un conjunto de ficheros Python que guardan entre sí unarelación
funcional. Sin embargo, para referirnos a un script de Pythonno solemos
emplear la palabra módulo, sino programa o, simplemente script. Es por
ello que suele ser habitual emplear módulo cuandotécnica y estrictamente
deberíamos decir paquete. Este hecho nos llevaa emplear el término módulo
y paquete indistintamente, tal y comohemos hecho a lo largo de los
diferentes capítulos de este libro.La parte final del capítulo estará dedicada
a los entornos virtuales, los cuales nos permiten tener un control total sobre
los módulos queinstalamos en nuestro sistema. Explicaremos cómo crear
diferentesentornos y veremos cómo ellos nos permiten tener
diferentesversiones del mismo módulo instalados en la misma máquina.
    312/381
    INSTALACIÓN DE PAQUETES
La instalación en nuestro sistema de paquetes Python desarrolladospor
terceros puede ser llevada a cabo, básicamente, de dos formasdiferentes. La
primera de ellas es a través de un gestor, como pip, y laotra es directamente
a partir del código fuente. La utilización de uno uotro método dependerá de
qué método han decidido losdesarrolladores para distribuir su software.
Supongamos quenecesitamos un determinado paquete, ofrecido como open
source ycuyos desarrolladores han decidido distribuirlo
exclusivamenteutilizando el código fuente. En este caso no podremos
recurrir a ungestor de paquetes, ya que los desarrolladores no han ofrecido
laforma de instalarlo a través del mismo. En este caso la única opción
esrealizar la instalación desde el fuente. En otros casos, será posibleutilizar
ambos métodos, quedando a nuestra elección el queprefiramos.A
continuación, describiremos ambos métodos para la instalaciónde módulos
en Python. Comenzamos aprendiendo cómo realizar lainstalación a partir
del código fuente.
Instalación desde el código fuente
Python nos ofrece un módulo en su librería estándar que incluyeuna serie de
herramientas para poder empaquetar y distribuir elsoftware que hemos
desarrollado en este lenguaje. De esta forma, apartir del código fuente y
utilizando estas herramientas es posiblecrear un fichero listo para su
distribución e instalación. Este ficheroestará comprimido y el formato del
mismo dependerá del sistemaoperativo. Por ejemplo, en Mac OS X y Linux
se emplea un tarball, mientras que en Windows es un ZIP. De los detalles
de este procesonos ocuparemos más adelante. El módulo en cuestión se
llama distutils y la utilización del mismo está considerada como la forma
estándar decrear y distribuir paquetes de Python.Si necesitamos instalar un
paquete creado con distutils, bastará con
    313/381
    obtener el fichero en el que está siendo distribuido, descomprimir elmismo
y ejecuta un comando determinado. Por ejemplo, supongamosque nuestra
máquina está corriendo Fedora Linux y que el paqueteque vamos a instalar
viene en un fichero llamado hoia-1.O.tar.gz. Elprimer paso será
descomprimirlo, acción que llevaremos a cabo através de un terminal:
$ tar -xzvf hola-1.0.tar.gz
Seguidamente, accederemos al directorio que ha sido creado
comoconsecuencia de la descompresión del fichero y ejecutaremos un
scriptllamado setup.py, que encontraremos en el nuevo directorio. A
estescript le pasaremos un argumento: install. Ambas acciones
comentadasserán ejecutadas con los siguientes comandos:
$ cd hola-1.0$ python setup.py install
En cuanto lancemos el último comando se procederá a lainstalación
automática del nuevo paquete. Al finalizar el proceso, elmismo quedará
directamente accesible y podremos importarlo tantoen nuestros programas,
como desde la línea de comandos delintérprete de Python. Esto se debe a
que, por defecto, el script setup.py se ha encargado de copiar los ficheros
Python al directorio donde, porconvección, se almacenan los paquetes de
terceros. Este directoriovariará en función del sistema operativo. Por
ejemplo, en Windowsestará en C:\Python3.2/Lib/site- packages. En los
sistemas Linux, elmenionado directorio suele estar en
/usr/lib/python/lib/site-packages. Debemos tener en cuenta que esta ruta
puede variar en función de laversión de Python que tengamos instalada o de
si hemos escogidopara la instalación un directorio diferente al que se
emplea pordefecto. En cualquier caso, el directorio site-packages suele
encontrarsecomo subdirectorio de lib. El script setup.py no solo se encarga
de copiar los ficheros aldirectorio comentado anteriormente, sino que
también realiza otrasoperaciones, como, por ejemplo, la compilación de
código. Pensemosen el caso de un paquete que incluye código C o C++ que
es invocadodesde Python. Dado que estamos distribuyendo el código fuente
yC/C++ necesita ser compilado, para la instalación del paquete
seránecesario realizar este proceso. De ello se ocupará automática y
    314/381
    transparentemente el script setup.py. Sin embargo, existe unargumento que
puede ser pasado al mencionado script para solocompilar el código fuente,
tal y como muestra el siguiente comando:
$ python setup.py build
No olvidemos que deberemos invocar al mismo script pasando elargumento
install para realizar la instalación, una vez que hayaterminado la
compilación. En caso contrario solo habremos compiladoy el módulo no
será instalado.Si en lugar de realizar la instalación de nuevo módulo en el
path pordefecto preferimos hacerla en otro diferente, podemos pasar un
tercerargumento al script setup.py. En concreto podemos emplear --user,
para realizar la instalación en el directorio home del usuario que
estállevando a cabo la instalación; --home, para un directorio determinado,y
--prefix, utilizado en el caso de Windows, ya que el concepto de home suele
ser exclusivo de sistemas UNIX. Por ejemplo, para que unmódulo quede
instalado en el directorio c:\Temp\Python\Lib\site-packages de una máquina
Windows, ejecutaremos el siguientecomando:
python setup.py install --prefix=\Temp\Python
Puede que en un momento determinado nos interese tener uncontrol sobre
qué tipo de ficheros se instalan en qué directorioscuando invocamos a la
instalación de un método. Para este casocontamos con una serie de
parámetros que también pueden serpasados a setup.py. De esta forma la
personalización del esquema deinstalación es completa. En concreto,
contamos con argumentos paraindicar dónde instalar los módulos puros
escritos en Python; aquellosque son de extensión, escritos en C/C++; todos
los módulos, sindiferencia de tipo; aquellos ficheros que son considerados
scripts y quedeben ser alojados en un directorio accesible a través de la
variable deentorno PATH; los datos puramente dichos y las headers de
losficheros C. Por ejemplo, supongamos que vamos a instalar un
móduloque solo contiene dos ficheros Python y que podrán ser
invocadosdirectamente por línea de comando. Es decir, consideraremos que
son scripts, similares a los de bash y que, por tanto, queremos que
seencuentren, por ejemplo, en el directorio /usr/local/bin/. En este
caso,bastará con ejecutar el siguiente comando:
    315/381
    $ python setup.py --install-scripts=/usr/local/bin/
Otro parámetro interesante, que también acepta setup.py para lainstalación
de módulos que contienen extensiones escritas en C o C++,es -compiler.
Este nos sirve para indicar qué compilador de C/C++deseamos utilizar,
siendo los valores diferentes en función del sistemaoperativo donde estamos
realizando la instalación. Por ejemplo, enWindows podemos usar algunos
diferentes como cygwin, mingw32 y Borland C++. El siguiente comando
sería utilizado, por ejemplo, paraindicar que deseamos compilar solo con
cygwin :
python setup.py build --compiler=cygwin
Obviamente, para utilizar cualquiera de los compiladoresanteriormente
mencionados, deberemos tenerlos instalados en nuestramáquina.Ahora que
hemos aprendido a instalar un paquete a partir de sucódigo fuente, estamos
en condiciones de aprender el segundométodo, del que nos ocuparemos a
continuación.
Gestores de paquetes
Un gestor de paquetes es un software que nos permite realizardiversas
acciones como son, la consulta, la búsqueda y, por supuesto,la instalación
de módulos de Python. Gracias a estos gestorespodemos realizar la
instalación de paquetes con solo un comando.También son prácticos para
descubrir si existen algunos paquetesrelacionados con un criterio específico.
Los usuarios de Linux estánacostumbrados a sistemas similares como son,
por ejemplo, apt-get y yum, los cuales permiten instalar, desinstalar y
buscar, entre otrasoperaciones, software para nuestro sistema operativo.Tal
y como hemos comentado previamente, para Python contamoscon easy
install y pip. Aunque son diferentes, ambos implementan lamisma
funcionalidad básica y, lo que es más importante, los dosutilizan la misma
fuente para instalar los paquetes. ¿De dóndeobtienen estos gestores la
información sobre los paquetes que puedenser instalados? La respuesta a
esta pregunta es sencilla, ambos usancomo origen un servicio denominado
Python Package Index (PyPi). Este
    316/381
    servicio está accesible a través de Internet y es considerado elrepositorio
oficial de paquetes para Python. En otros lenguajes existenservicios
similares como CPAN para Perl y PEAR para PHP. A través delsitio web de
PyPi (ver referencias) podemos acceder a informacióncomo el número total
de paquetes que hay, el listado completo depaquetes o los últimos 40 que
han sido añadidos. Además, en elmismo sitio web, se ofrece información
para aquellos desarrolladoresinteresados en publicar sus propios módulos
para que quedenaccesibles desde el repositorio. Actualmente, más de
19.000 paquetespueden ser instalados a través de PyPi.A continuación,
descubriremos cómo emplear los gestores easy_install y pip para interactuar
con PyPi.
EASY_INSTALL
Existe un módulo para Python llamado distribute que contiene,entre otros
componentes, un script llamado easy_install. Este scriptserá el que
utilicemos para invocar al gestor de paquetes del mismonombre. Dado que
distribute no forma parte de la librería estándar dePython, recurriremos a su
código fuente para realizar la instalación delmismo. Para ello, seguiremos
una serie de sencillos pasos. El primeroserá descargarnos un script que se
encargará de descargar el códigonecesario y de realizar automáticamente la
instalación. Así pues,deberemos descargar el fichero distribute_setup.py
que se encuentraaccesible a través de la página web (ver referencias) de
descargas delmencionado módulo para Python. En sistemas Linux y Mac
OS X eshabitual contar con herramientas como wget y curl, que permiten
ladescarga de ficheros desde la línea de comandos. Si tenemos uno deestos
dos programas instalados, podremos descargarnos elmencionado fichero
empleando, por ejemplo, el siguiente comandoque hace uso de curl :
$ curl -O http://python-distribute.org/distribute_setup.py
En cuanto dispongamos del script de instalación en nuestramáquina,
procederemos a la instalación propiamente dicha del módulo distribute.
Para ello, solo necesitaremos ejecutar el siguiente comandodesde un
terminal:
    317/381
    python distribute_setup.py
Dado que distribute es un módulo empaquetado siguiendo elestándar para
ello propuesto oficialmente por Python, es posiblerealizar la instalación del
mismo directamente a través del tarball ofrecido por los desarrolladores del
módulo en cuestión. Es decir,opcionalmente, en lugar de realizar la
instalación como hemos vistoanteriormente, podemos recurrir al método
estándar. Para ello, enprimer lugar, nos descargaremos el mencionado
tarball. En elmomento de escribir estas líneas, la última versión es la
0.6.24, asípues, será esta la que empleemos para mostrar cómo realizar
lainstalación. Como ejemplo, partiremos de un sistema Linux, aunque
elproceso es prácticamente igual para Mac OS X y Windows.Comenzamos
con la descarga del fichero en cuestión:
$ curl -Ohttp://pypi.python.org/packages/source/d/distribute/distribute0.6.24.tar.gz
Después procederemos a la descompresión del fichero descargado:
$ tar -zxvf distribute-0.6.24.tar.gz
Ahora llega el momento de acceder al nuevo directorio creado einvocar a
setup.py para que comience la instalación:
$ cd distribute-0.6.24$ python setup.py install
Al finalizar la instalación, con independencia del método empleado,ya
tendremos acceso al mencionado módulo y al script easy_install , através
del cual podremos instalar, buscar o consultar paquetes dePython que se
encuentran registrados en PyPi. Para comprobar que lainstalación se ha
realizado correctamente y que, efectivamentetenemos acceso al gestor de
paquetes, comprobaremos la existenciade un fichero llamado
easy_install.py, en el caso de sistemas UNIX o de easy_install.exe, en el
caso de Windows. Si estamos trabajando en esteúltimo sistema operativo y
el directorio raíz de la instalación de Pythones C:\Python3.2, podremos
comprobar cómo existirá un subdirectoriollamado Scripts donde
localizaremos el fichero easy_install.exe. Conindependencia del sistema
    318/381
    operativo que estemos utilizando, esinteresante que el script easy_install
sea accesible a través de la
    319/381
    variable de entorno PATH. Si realizamos esta configuración, tanto enMac
OS X, como en Linux y Windows, tendremos acceso directo a easy_install
desde la línea de comandos. Así pues, nuestra primerainvocación al script
será para echar un vistazo a las opciones que nosofrece. Para realizar esta
acción, por ejemplo en Linux, bastará conlanzar el siguiente comando:
$ easy_install --help
Al ejecutar el comando anterior observaremos cómo obtenemosinformación
sobre las opciones y los argumentos adicionales quepodemos pasar al script
para realizar diferentes acciones.La principal acción que puede ser llevada a
cabo con easy_install es,lógicamente, la instalación de paquetes para
Python. Ello es tansencillo como indicar como argumento el nombre del
paquete encuestión. Por ejemplo, supongamos que deseamos instalar el
paquete lxml. El comando para ello será el siguiente:
$ easy_install lxml
Automáticamente, easy_install se encargará de acceder a PyPi,buscar el
fichero de distribución dado por los desarrolladores, compilarel código
C/C++ -en caso de que sea necesario- y proceder a la copiade los
correspondientes ficheros al directorio site-packages de nuestrointérprete de
Python. Debemos tener en cuenta que PyPi actúa comoregistro de los
paquetes, pero en ocasiones no directamente comorepositorio. Esto
significa que algunos desarrolladores alojan el ficherode distribución de un
módulo en un servidor distinto. Este es el caso,por ejemplo, de lxml.
Durante el proceso de instalación del mismocomprobaremos cómo se van
lanzando una serie de líneas que nosvan informando de qué acción se está
llevando a cabo en cadamomento. Observando esta información,
comprobaremos cómo ladescarga del fichero de distribución de lxml se ha
realizado desde laURL http://lxml.de/files/lxml-2.3.3.tgz. En lugar del
nombre del paquete, easy_install también acepta elpaso como argumento de
una URL que indique dónde se encuentra elfichero de distribución
correspondiente al módulo en cuestión quedeseamos instalar. Esto también
permite instalar paquetes que noestén referenciados por PyPi.Otra
interesante funcionalidad referente a la instalación de
    320/381
    paquetes es la opción de Indicar el número de versión que deseamosinstalar
de un determinado paquete. En este caso, deberemos indicarel número de
versión concreto, tal y como muestra el siguienteejemplo:
$ easy_install lxml==2.3.3
Además de la instalación, también es posible actualizar a la versiónmás
reciente que exista un paquete que tengamos previamenteinstalado. Esta
acción puede ser llevada a cabo empleando la opción —upgrade, tal y como
muestra el siguiente ejemplo:
$ easy_install --upgrade lxml
Cuando ya no necesitemos un paquete podremos borrarlodirectamente a
través de easy_install. Para realizar esta acción, soloserá necesario indicar
el parámetro -m precedido por el nombre delpaquete que deseemos
desinstalar. Por ejemplo, el siguiente comandodesinstalaría el paquete lxml
de nuestro sistema:
$ easy_install -m lxml
Si por defecto deseamos emplear una fuente adicional a PyPi parala
instalación de paquetes, podremos hacerlo a través de un fichero
deconfiguración llamado setup.cfg. Este fichero, por defecto, residirá en
eldirectorio desde donde estamos realizando la instalación.Adicionalmente,
es posible también crear un fichero en el directorio home del usuario, al que
llamaremos pydistutils.cfg. De esta forma,conseguiremos que la
configuración indicada en este último ficherosea utilizada por easy_install,
con independencia del directorio dondesea invocado. Las siguientes líneas,
correspondientes al mencionadofichero de configuración, muestran cómo
indicar que vamos a emplearun servidor adicional para la instalación de
paquetes:
[easy_install]find_links = http://miservidor/python/paquetes/
Hasta aquí todo lo básico para trabajar con easy_install, seguimosnuestro
recorrido por los gestores de paquetes con pip .
    321/381
    PIP
    322/381
    El gestor de paquetes pip nació con la idea de ofrecer unaalternativa a
easy_install, que además, ofreciera funcionalidadesadicionales. Además de
la funcionalidad básica de instalación depaquetes, pip permite
actualizaciones, búsquedas, desinstalaciones eincluso es capaz de mostrar
información sobre todas las versionesespecíficas de cada paquete instalado
en un sistema.Para instalar pip deberemos previamente tener instalado
distribute, si aún no lo hemos hecho, antes de continuar, deberemos realizar
lainstalación de este módulo, tal y como hemos explicado en el
apartadoanterior.La instalación de pip se puede realizar de dos formas
diferentes.Una de ellas es a través del script de instalación ofrecido por
susdesarrolladores. La otra forma es empleando el fichero de distribucióne
invocando al script setup.py. Ambas técnicas son sencillas, tal y
comopodremos comprobar. El primer método requiere de la descarga
delinstalador en cuestión, acción que puede ser realizada directamente
através de herramientas como curl o wget. En este ejemplo, utilizamos wget
desde Fedora Linux:
$ wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py
En cuanto tengamos descargado el script, procederemos a suejecución:
$ sudo python get-pip.py
Para la instalación alternativa necesitaremos el tarball dedistribución, el
cual también puede ser descargado a través de curl owget. En esta ocasión,
vamos a invocar a curl, tal y como muestra elsiguiente comando:
$ curl -O http://pypi.python.org/packages/source/p/pip/pip-1.0.tar.gz
El siguiente paso será la descompresión del tarball que acabamosde
descargar:
$ tar -zxvf pip-1.0.tar.gz
Ahora es el turno de acceder al nuevo directorio y proceder a lainvocación
del script setup.py, tal y como muestran las siguientessentencias:
    323/381
    $ cd pip-1.0$ python setup.py install
La instalación de módulos en sistemas UNIX se realiza, por defecto,en un
determinado directorio en el que solo el usuario root tienepermisos de
escritura. Este hecho implica que para instalar paquetes,tanto con
easy_install, como con pip, deberemos invocar a estosprogramas como el
mencionado usuario root. En sistemas operativoscomo Ubuntu, esto es tan
sencillo como ejecutar los scripts a través delcomando sudo. En otro caso,
es necesario cambiar a root antes deinvocar a cualquiera de estos scripts. En
Windows no tendremos esteproblema, ya que, por defecto, la instalación se
realiza en un directoriodonde el usuario tendrá acceso de escritura.Una vez
que la instalación de pip ha finalizado satisfactoriamente,podremos
ejecutarlo, por ejemplo, para mostrar las opciones que nosofrece. El
siguiente comando se encargará de esta acción:
$ pip --help
Tal y como hemos aprendido en capítulos anteriores, la instalaciónde un
paquete a través de pip es tan sencilla como invocar al comando install,
seguido del nombre del paquete que deseamos instalar. Comoejemplo sirva
el siguiente comando:
$ pip install lxml
La actualización de paquetes a la versión más reciente liberadatambién se
realiza empleando el comando install, pasándole comoparámetro -U, tal y
como muestra el siguiente ejemplo:
$ pip install -U lxml
Al igual que easy_install, pip también admite la instalación desde
undeterminado tarball accesible a través de una URL Para este tipo
deinstalación bastará con pasar la URL en cuestión al comando install. Muy
interesante resulta la opción de poder instalar un paquetedirectamente a
través de un sistema de control de versiones. Enalgunos casos, esto puede
sernos muy útil, para, por ejemplo, realizarinstalaciones desde nuestro
propio sistema de control de versiones opara asegurarnos de obtener el
    324/381
    último código que está siendodesarrollado. El parámetro -e , seguido de la
URL en cuestión, será el
    325/381
    que deberemos pasar al comando install para realizar este tipo
deinstalación. Por ejemplo, para instalar el paquete prueba que seencuentra
en un sistema de control de versiones git, ejecutaríamos elsiguiente
comando:
$ pip -e git://miservidor.com/prueba.git#egg=prueba
A diferencia de easy_install, pip sí que nos permite realizarbúsquedas de
paquetes. Gracias a esta funcionalidad, es posible pasarcomo argumento un
nombre de paquete y pip se encargará deofrecernos una serie de resultados,
consultando para ello PyPi ybuscando todos los paquetes que coinciden con
el criterio debúsqueda. Por ejemplo, supongamos que deseamos buscar
paquetesrelacionados con XML. El siguiente comando será el que
emplearemospara realizar la búsqueda:
$ pip search xml
Por la salida estándar recibiremos un completo listado, indicando,tanto el
nombre del paquete, como una pequeña descripción. Siademás uno de los
paquetes listado coincide con que ya lo tengamosinstalado, se nos informará
de ello, mostrándonos informaciónadicional sobre la versión más reciente
que existe sobre el mismo.Como ejemplo, mostramos unas cuantas líneas
obtenidas al invocar alcomando anterior de búsqueda:
ll -xist
- Extensible HTML/XML generator, cross-platformtemplating language,
Oracle Utilities and various othertoolslxml - Powerful and Pythonic XML
processing library combininglibxml2/libxslt with the ElementTree
API.INSTALLED: 2.3.3LATEST: 2.3beta1Chameleon - Fast HTML/XML
Template Compiler.INSTALLED: 2.7.3 (latest)generateDS - Generate
Python data structures and XML parser fromXschemaarchgenxml - UML to
code generator for Plonelibxml2dom - PyXML-style API for the libxml2
Python bindingsbridge - General purpose XML library forCPython and
IronPythonAmara - Library for XML processing in Python
    326/381
    La desinstalación de módulos puede ser ejecutada fácilmentegracias al
comando uninstall, el cual se encargará de eliminar aquellosficheros
instalados a través de un comando install. Consultar todos los paquetes
instalados en nuestro sistema esposible gracias al comando freeze, el cual
produce un listado con losnombres y versiones de todos ellos. Después del
nombre de cadapaquete aparecerán dos símbolos de igual, seguidos de la
versión encuestión. Por ejemplo, tras invocar al siguiente comando,
obtendremosuna serie de líneas como las que se indican a continuación:
$ pip freezewsgiref==0.1.2wxPython==2.8.8.1wxPythoncommon==2.8.8.1wxaddons==2.8.8.1xattr==0.5zope.deprecation==3.5.0zo
pe.interface==3.8.0
En lugar de pasar diferentes opciones para cada comando, puedeser
interesante emplear un fichero de configuración donde indiquemoslas
opciones, con sus correspondientes valores que necesitemos. Elfichero en
cuestión se llama pip.conf o pip.ini, en el caso de Windows, ysu formato se
corresponde con un INI estándar, donde podemos tenerdiferentes secciones
y donde cada línea indica propiedad igual a valor. El mencionado fichero
puede ser encontrado en un subdirectorio,llamado pip o .pip, dentro del
directorio home del usuario en cuestiónque invoca a pip. En un momento
dado puede ser interesante obtener informaciónsobre la sintaxis de cada uno
de los comandos de pip. Para realizar estaacción, bastará con emplear otro
comando llamado help. Así pues, porejemplo, para consultar la información
sobre el comando search, ejecutaremos la siguiente orden:
$ pip help search
Tanto easy_install como pip son capaces de manejarautomáticamente las
dependencias entre paquetes. Esto implica que, siun determinado paquete
requiere de otros para su funcionamiento,estos serán instalados
automáticamente. Esta funcionalidad es unagran ventaja y nos asegura que
cada paquete será instalado yfuncionará correctamente.
    327/381
    En este punto termina nuestro recorrido por la instalación depaquetes, en el
siguiente apartado trataremos sobre cómo prepararnuestro software para
que pueda ser distribuido e instalado por otrosdesarrolladores o usuarios.
    328/381
    DISTRIBUCIÓN
Para que otros desarrolladores puedan instalar y utilizar nuestrosmódulos de
Python, deberemos preparar estos de una formadeterminada. En la librería
estándar de Python existen un módulo quenos ayudará a esta tarea, llamado
distutils. Gracias al mismo podremos empaquetar nuestros módulos Python
para que otros puedaninstalarlos, por ejemplo, a través de PyPi. El proceso
general para crear un fichero de distribución y ponerlo adisposición de otras
personas consta de los siguientes pasos:
Creación del fichero setup.py con la información sobre elmódulo.Creación
del fichero para distribución, puede ser un tarball o ZIP.Registro del
paquete en PyPi, si deseamos que pueda serindexado.Subida del paquete al
repositorio de PyPi. Este paso esopcional.
A través de un ejemplo básico, vamos a descubrir cómo aplicar lospasos
anteriormente mencionados. Partiremos de la idea de quenuestro paquete
estará compuesto por un solo fichero Python, el cualcontendrá una clase.
Nuestra clase contendrá un constructor y unmétodo para imprimir el
popular Hola Mundo!. En concreto el códigode nuestro hola.py, será el
siguiente:
class Hola:def __init__(self):passdef say_hello(self):print('Hola Mundo!")
Ahora necesitamos crear un directorio al que copiaremos nuestronuevo
fichero. Podemos llamarlo hola y dentro del mismo vamos acrear el fichero
setup.py. El contenido del mismo estará formado por
    329/381
    las siguientes líneas:
from distutils.core import setupsetup(name='hola', version='1.0',
py_modules= [' hola'])
A través de la función setup() vamos a indicar diferente tipo deinformación
sobre el paquete. Hemos elegido la información mínimapara nuestro
ejemplo, indicando, simplemente, el nombre del paquete,la versión y los
módulos Python de los que consta. Esta función aceptaargumentos
opcionales para indicar datos, como el tipo de licencia,una descripción
sobre el paquete, el nombre y correo electrónico delautor y una lista de
clasificadores. Esta lista sirve para indicar qué tipode paquete es, a qué
audiencia va dirigido o para qué versión dePython ha sido desarrollado. En
general esta información va dirigida apoder clasificar el paquete, de forma
que pueda ser fácilmenteindexado por PyPi. Una lista completa de todos los
clasificadoressoportados puede consultarse en la página web (ver
referencias) dePyPi dedicada a ello.El siguiente paso será invocar a un
comando que nos crearádirectamente el fichero de distribución. En
Windows será un ZIP,mientras que en Mac OS X y Linux será un tarball. El
comando encuestión es el siguiente:
$ python setup.py sdist
Si echamos un vistazo a nuestro directorio hola, comprobaremoscómo se
han creado un nuevo directorio, llamado dist, y un fichero conel nombre
MANIFEST. El contenido de este fichero está formado por lassiguientes
líneas, que hacen referencia a una serie de informaciónsobre el paquete:
Metadata-Version: 1.0Name: holaVersión: 1.0Summary:
UNKNOWNHome-page: UNKNOWNAuthor: UNKNOWNAuthor-email:
UNKNOWNLicense: UNKNOWNDescription: UNKNOWNPlatform:
UNKNOWN
En lo que al directorio dist respecta, comprobaremos cómo se ha
    330/381
    creado el fichero de distribución, en nuestro caso, se llama hola-1.O.tar.gz.
Ya estamos preparados para distribuir nuestro paquete hola. Podríamos
hacer el fichero accesible desde un servidor web o FTP, paraque pudiera ser
descargado. Para poder instalarlo, habría que seguir elprocedimiento
estándar que hemos visto previamente, es decir, bajarseel fichero,
descomprimirlo, entrar en el directorio y ejecutar pythonsetup.py install.
Opcionalmente, podemos hacer que PyPi indexe nuestro paquetepara que
pueda ser buscado e instalado a través de gestores como easy_install y pip.
Antes de continuar y dado que PyPi requiere que elnombre y correo
electrónico del autor de cada paquete sea indicado,deberemos modificar
nuestro setup.py para añadir estos datos y volvera generar el tarball. Por
otro lado, PyPi también necesita que el autor,que vaya a registrar y/o subir
su paquete, esté registrado en susistema. Cualquiera puede registrarse a
través de un formulario webdisponible en la correspondiente página web
(ver referencias) de PyPi.En cuanto cumplamos con la formalidad del
registro esteremoslistos para registrar nuestro nuevo paquete. Esto es tan
sencillo conejecutar el siguiente comando y seguir las instrucciones que se
nosirán dando:
$ python setup.py register
Justo después de ejecutar el comando anterior, veremos cómoaparece un
prompt que nos preguntará si deseamos utilizar un usuariode PyPi existente,
crear uno nuevo o abortar el proceso. Daremos porhecho que ya contamos
con un usuario, así pues, elegiremos laprimera opción, introduciremos
nuestro usuario y contraseña.Automáticamente se procederá al registro del
paquete en cuestión.Una vez registrado el paquete, también podemos
subirlo alrepositorio de PyPi, con el objetivo de que pueda ser
descargadodesde el mismo. En este caso, simplemente bastará con ejecutar
elsiguiente comando:
$ python setup.py sdist upload
Llegados a este punto, cualquiera podrá instalar nuestro paquetedesde, por
ejemplo, pip. Si el paquete es instalado, podrá serimportado en cualquier
entorno virtual o sistema donde haya sidoinstalado. De hecho, podemos
comprobar que funciona correctamente
    331/381
    ejecutando las siguientes líneas de código:
>>> import hola>>> h = hola.Hola()>>> h.say_hello()Hola Mundo!
Como el lector habrá podido comprobar, Python nos ofrece, através de su
módulo distutils y del sistema PyPi, un completo sistemapara distribuir
nuestros paquetes.
    332/381
    ENTORNOS VIRTUALES
Hasta el momento hemos aprendido a instalar paquetes en eldirectorio
asignado por defecto para ello y que afectan a todo elsistema. Esto implica
que si tenemos dos paquetes instalados que, a suvez, emplean un tercero,
ambos tendrán que usar la versión instaladade este último. Pero ¿qué ocurre
si actualizamos a una nueva versiónde este tercer paquete? En principio,
uno de los dos que lo utilizanpodría no funcionar correctamente por los
cambios aplicados en lanueva versión. Además, ¿qué pasaría si uno de estos
paquetes sí querequiere de esta nueva versión? Por otro lado, ¿cómo
podríamosutilizar exclusivamente una determinada versión de un módulo
del quedepende otro? ¿Qué pasaría si deseamos instalar una serie de
módulospero no tenemos acceso al directorio por defecto? Estas
preguntassuelen hacérselas muchos administradores de sistemas cuando
tienenque mantener un servidor y diferentes aplicaciones Python que
correnen el mismo. También son muchos los desarrolladores que
seenfrentan a estos escenarios, cuando por ejemplo, necesitan probar siuna
nueva versión de un determinado módulo es compatible con laaplicación ya
desarrollada.Dadas las preguntas anteriores y las situaciones que las
originan,parece conveniente encontrar una solución que nos ayude a
resolver elproblema. Es aquí donde podemos contar con los entornos
virtuales. Existen para Python un conjunto de herramientas que permiten
crearentornos aislados, donde cada uno de ellos es responsable de
suspropios paquetes. Es decir, es posible crear una sandbox en la queinstalar
diferentes módulos que serán independientes, tanto de los queexisten a nivel
de sistema (directorio site-packages), como unos deotros. De esta forma,
por ejemplo, para comprobar si un determinadomódulo es compatible con
nuestra aplicación, bastará con crear unentorno virtual, instalar todos los
paquetes, incluida la nueva versióndel paquete en cuestión. Si la aplicación
funciona correctamente,entonces podremos instalar esta nueva versión en
nuestro entorno deproducción.
    333/381
    virtualenv
Para la gestión de entornos virtuales en Python, necesitamosinstalar un
módulo llamado virtualenv, el cual está accesible a través dePyPi. Ello
implica que podremos instalarlo con un gestor de paquetescomo
easy_install y pip. Por ejemplo, empleando este últimoejecutaríamos el
siguiente comando:
$ pip install virtualenv
Una vez instalado virtualenv, tendremos acceso a un script Pythoncon el
mismo nombre, el cual puede ser invocado directamente. Porejemplo, en
Linux podremos lanzar el siguiente comando:
$ virtualenv -help
Para crear un nuevo entorno virtual, bastará con pasar la ruta deldirectorio
donde queremos que sea creado. El siguiente ejemplo crearáun entorno
virtual en el directorio /tmp/env de un sistema Linux:
$ virtualenv /tmp/env --no-site-packages
Observando la salida del comando anterior descubriremos cómo
lasherramientas proporcionadas por distribute y el gestor de paquetes pip
son instalados también. Si nos fijamos en el directorio que hemospasado
como argumento al comando virtualenv, comprobaremoscómo se han
creado tres subdirectorios diferentes. El primero de elloses bin y contiene
una serie de scripts que podemos utilizar desdenuestro entorno. Entre ellos
encontraremos al mencionado pip y otroscomo actívate, del cual nos
ocuparemos más adelante. Otro de losdirectorios es include, que contendrá
el intérprete de Python y unaserie de ficheros fuente que serán empleados,
en caso de que algunosde los paquetes que instalemos requiera de ello, para
compilar. Elúltimo directorio en cuestión es lib, que contendrá, entre otros
ficherosy directorios, el subdirectorio site-packages. Serán en este
directoriodonde se instalen los paquetes que pertenezcan a nuestro
entornovirtual.Ya tenemos nuestro entorno creado, así que nuestro siguiente
    334/381
    pasoserá activarlo para poder comenzar a trabajar con él. Esta acción
selleva a cabo a través del script activate, que se encuentra en el
    335/381
    directorio bin. En Linux, ejecutaríamos los siguientes comandos paraactivar
nuestro entorno recién creado:
$ cd /tmp/env$ source bin/activate(env)$
La última línea indica que el prompt de la línea de comandos
habrácambiado para mostrar, entre comas, el nombre del entorno que
estáactivado. A partir de este momento, si instalamos un módulo loharemos
solo en el entorno virtual en cuestión, quedando el mismoaccesible
exclusivamente en dicho entorno. Debemos tener en cuentaque esto ha sido
posible gracias a que pasamos el argumento —no-site-packages al crear
nuestro entorno. Si no lo hubiéramos hecho, elentorno virtual tendría acceso
también a los paquetes del sistema. Estoúltimo podría ser interesante en
algunos casos, pero generalmente, sesuele emplear el mencionado
argumento para lograr entornos virtualescompletamente aislados.Al lanzar
el siguiente comando, comprobaremos cómo los paqueteslistados son
diferentes a los que obtendremos lanzando el mismocomando fuera de
nuestro entorno virtual:
(env)$ ./bin/pip freeze
Esto nos demuestra que, efectivamente, los paquetes quedanaislados unos
de otros, quedando nuestro entorno aislado del resto depaquetes Python
instalados en el sistema. Cada vez que creemos unnuevo entorno virtual lo
haremos en un directorio diferente delsistema de ficheros.
virtualenvwrapper
Con el objetivo de facilitar el manejo de virtualenv y los diferentesentornos
virtuales de Pyton que pueden ser instalados en la mismamáquina, se
desarrolló otro paquete conocido como virtualenvwrapper. Como su propio
nombre indica, este paquetecontiene una serie de herramientas que actúan
como wrapper sobre virtualenv, y facilitan su utilización. Entre las
funcionalidades queincluye, encontramos la de crear diferentes entornos
virtuales bajo el
    336/381
    mismo directorio del sistema de ficheros, activar un entorno a travésde un
nombre dado, permitir fácilmente desactivar un entorno paraactivar otro
diferente, borrar un entorno y copiar completamente unentorno con un
nombre distinto.La instalación de virtualenvwrapper es muy sencilla y
puede serllevada a cabo a través de un gestor de paquetes como pip. De
estaforma, bastaría con invocar al siguiente comando para realizar
lainstalación:
$ pip install virtualenvwrapper
Antes de comenzar a trabajar con las herramientas que ofrece
virtualenvwrapper es conveniente modificar el fichero de configuraciónde
la terminal (.bashrc, .profile o similar) para añadir un par de líneasque nos
faciliten la gestión de los entornos. La primera de ellasindicará cuál será el
directorio a partir del que se crearán los diferentesentornos. La segunda
línea se encargará de invocar automáticamenteal shell script
virtualenvwrapper.sh , cada vez que abramos una terminal.Las líneas en
cuestión son las siguientes:
export WORKON_HOME=$HOME/.virtualenvssource
/usr/bin/virtualenvwrapper.sh
En nuestro ejemplo hemos decidido que el subdirectorio .virtualenvs, que
pertenecerá al directorio home de nuestro usuario,será el que albergará los
diferentes entornos que sean creados. Porotro lado, la segunda línea de
configuración hace referencia a la rutadonde, por defecto, existirá el shell
script instalado por virtualenvwrapper. Ahora nos encontramos en
disposición de usar virtualenvwrapper, siendo nuestra primera acción la
creación de un nuevo entornollamado prueba, tal y como muestra el
siguiente comando:
$ mkvirtualenv prueba --no-site-packages
Por defecto, el comando mkvirtualenv se encargará tanto de crearel entorno
prueba como de activarlo. De esta forma, justo después deejecutar el
comando anterior, estaremos en condiciones de comenzar ainstalar paquetes
en nuestro nuevo entorno virtual.Si tenemos varios entornos virtuales,
    337/381
    podemos cambiar de uno aotro directamente a través del comando workon.
Este comando se
    338/381
    encargará tanto de desactivar el entorno actualmente activado, comode
activar el nuevo. Si ningún entorno está activado, pasará a activar
elindicado. Por ejemplo, supongamos que nos encontramos en elentorno
prueba y deseamos pasar a otro denominado test, el comandopara ello sería
el siguiente:
(prueba)$ workon test
Un listado completo de todos los entornos virtuales que tenemosinstalados
en una máquina puede ser obtenido invocando al comando workon, sin
pasar ningún argumento.La desactivación del entorno actualmente activado
se realiza con elcomando deactivate, el cual puede ser utilizado
directamente.
pip y los entornos virtuales
Anteriormente hemos aprendido a consultar los paquetes queexisten, bien
en el sistema, bien en un determinado entorno virtual. Esla opción freeze del
gestor pip la que nos permite realizar esta acción.Además de listar los
paquetes, esta opción puede sernos útil para crearun fichero con todos los
paquetes que tenemos instalados. Bastaríacon redireccionar la salida
estándar a un fichero, tal y como muestra elsiguiente comando:
$ pip freeze > paquetes.txt
Si tenemos el mencionado fichero, será posible crear un nuevoentorno a
partir del mismo. Esto es muy útil cuando, por ejemplo,tenemos diferentes
máquinas de desarrollo y tenemos que mantenerlos mismos paquetes en
ambas. Existe un parámetro adicional quepuede ser pasado al comando
install de pip , que se encargará de leerun fichero de texto e Instalar los
paquetes listados en el mismo.Gracias a este comando, es posible utilizar el
fichero creado con freeze para realizar la instalación automática de los
paquetes, bien en unanueva máquina, bien en un nuevo entorno.
Continuando con nuestroejemplo, la Instalación se realizaría ejecutando el
siguiente comando:
$ pip install -r paquetes.txt
    339/381
    Tanto los gestores de paquetes, como los entornos virtuales,
sonherramientas muy útiles para el desarrollo, administración
ymantenimiento de aplicaciones Python. En la actualidad son muchoslos
desarrolladores y administradores de sistemas que emplean
estasherramientas diariamente en su trabajo.
    340/381
    10
    341/381
    PRUEBAS UNITARIAS
INTRODUCCIÓN
Una de las fases más importantes en el proceso del desarrollo desoftware es
la de pruebas. Con ella pretendemos demostrar que elsoftware desarrollado
cumple con la funcionalidad para la que ha sidodiseñado e
implementado.Existen diferentes tipos de pruebas para el software, como
son, porejemplo, las unitarias, las funcionales y las de integración. El
primertipo se emplea para demostrar que una serie de componentescumplen
su función correctamente. Por otro lado, las de integraciónnos aseguran que
diversos componentes software que interactúanentre sí lo hacen de forma
correcta. Las pruebas funcionales seencargan de comprobar que la
funcionalidad general para la que hasido diseñado un software específico es
correcta. Por ejemplo, unaprueba unitaria se encargaría de comprobar que
una función suma dosvalores de forma correcta. Otra prueba garantizaría
que otra funcióntambién lo hace al llamar a la función de suma. Por último,
una pruebafuncional demostraría que, cuando un usuario introduce dos
valoresempleando la interfaz gráfica, la suma devuelve el valor
esperado.Las pruebas unitarias son muy importantes, ya que
puedenconsiderarse como una unidad mínima y, además, son la base de
lafase de pruebas en el proceso de desarrollo de software. Cada
pruebaunitaria comprobará que cada parte de código (función, método
osimilar) cumple su función por separado, es decir, de
formaindependiente.Una de las principales características de las pruebas
unitarias es queestas deben ser automáticas. Es decir, una vez diseñadas,
podrán serejecutadas directamente a través de un comando o script.
Esto,además, garantiza que las pruebas se pueden repetir sucesivas
veces.Entre las ventajas de emplear pruebas unitarias, destacan el hechode
simplificar la integración de componentes, el aislamiento yacotación de
errores, la facilitación de la refactorización de código y laseparación entre
la interfaz de usuario y los detalles de
    342/381
    implementación.En los últimos años, la amplia aceptación de metodologías
dedesarrollo como TDD ( Test-Driven Development) y BDD
(BehaviorDriven Development), han puesto de manifiesto el interés por
llevar acabo pruebas sobre el software y han demostrado la necesidad de
lasmismas para desarrollar software de calidad.En este capítulo final nos
ocuparemos de las herramientas quepueden ser utilizadas en Python para
escribir y ejecutar pruebasunitarias. Comenzaremos explicando una serie de
conceptos básicos ydespués pasaremos a centrarnos en los más utilizados
frameworks dePython que existen para pruebas.
    343/381
    CONCEPTOS BÁSICOS
Para llevar a cabo las pruebas unitarias, es importante estarfamiliarizado
con una serie de conceptos básicos, de los cuales nosvamos a ocupar a
continuación. El primero de ellos es el llamado fixture, que referencia a
aquellos datos iniciales que vamos a necesitarpara nuestras pruebas. Por
ejemplo, supongamos que vamos a probaruna serie de acciones que tienen
lugar entre un conjunto de usuarios.Para ello, previamente, necesitaremos
unos cuantos usuarios, estosserían nuestros fixtures. Otro ejemplo, sería una
serie de ficheros odirectorios, en caso de que las pruebas requieran de
ellos.Probablemente, el concepto más importante en el ámbito de laspruebas
unitarias sea el de caso de prueba, también conocido como test case. El
caso de prueba representa aquella funcionalidad que va aser probada. Se
trata de comprobar una serie acciones en base a unosdatos de entrada.
Como resultado obtendremos éxito o fallo, lo quedemostrará si las acciones
probadas funcionan correctamente, es decir,tomando unos valores de
entrada conseguimos un determinadoresultado esperado.Dado que una
prueba unitaria puede constar de varios casos deprueba, se define la suite de
pruebas (test suite) como un conjunto decasos de prueba que deben ser
ejecutados de forma agregada paradeterminar el resultado final de una
prueba.Una vez que tenemos preparados nuestros casos de
prueba,incluyendo o no suites de los mismos, y los fixtures, solo nos queda
uncomponente que se encargue de lanzar las pruebas y obtener unresultado.
A este será al que llamamos test runner y también seráresponsable de
ofrecer el resultado de una forma fácil de interpretar.En ocasiones se
utilizan gráficos y otras, simplemente texto; encualquier caso debemos
obtener información sobre el número depruebas ejecutadas y si el resultado
ha sido satisfactorio o no.Para probar una aplicación se suelen crear
diferentes casos deprueba, incluyendo varias suites de prueba. Debe tenerse
en cuentaque cada funcionalidad, considerada como tal, necesitará de su
propiocaso de prueba. Por ejemplo, partamos de un sencillo ejemplo, un
    344/381
    pequeño módulo Python que contiene tres funciones diferentes. Eneste caso
se debería escribir un caso de prueba para cada una de lasfunciones. No
existe un criterio general para establecer unacorrespondencia entre los casos
de prueba y los componentes decódigo. En algunas ocasiones, la elección
de caso de prueba es trivial,como en el ejemplo previo, pero en otras
necesitaremos definir quéfunciones, clases u otros componentes intervienen
en un único caso deprueba. Igual escenario ocurre con las suites de prueba,
quedando acriterio de los programadores la decisión sobre la creación de
lasmismas para probar determinado código.Las pruebas unitarias en Python
pueden ser llevadas a cabo con laayuda de diferentes frameworks, los cuales
ofrecen una serie deherramientas para facilitarnos el trabajo. A
continuación, nosocuparemos de los frameworks más populares.
    345/381
    UNITTEST
Python incorpora un módulo en su librería estándar que permiteescribir y
ejecutar pruebas unitarias, su nombre es unittest y puede serinvocado de la
siguiente forma:
>>> import unittest
Para crear casos de prueba contamos con la clase denominada TestCase,
mientras que para escribir suites tenemos otra llamada TestSuite. La
creación y/o carga de fixtures se realiza a través de unmétodo especial
(setUp) que pertenece a la clase TestCase. Por otrolado, si son necesarias
realizar acciones al terminar cada caso deprueba, por ejemplo, el borrado de
algún dato, estas pueden llevarse acabo gracias al método tearDown(),
también implementado en TestCase(). Tanto setUp(), como tearDown(),
deben ser sobrescritos ennuestra clase de prueba, en caso de que sean
necesarias acciones deinicialización o finalización, respectivamente. Es
importante saber quepara cada método de nuestra clase de prueba, serán
ejecutados setUp() y tearDown(). El primero lo será justo antes de
comenzar la ejecuciónde cada método y el segundo al terminar dicha
ejecución.Así pues, para escribir un script que ejecute una o varias
pruebasunitarias, comenzaremos creando una clase de prueba que herede de
TestCase. Seguidamente, escribiremos una serie de métodos, uno
porfuncionalidad y finalmente, invocaremos a la función main() delmódulo
unittest, que se encargará de ejecutar las pruebas cuandonuestro script sea
invocado. ¿Cómo comprobamos si la prueba tieneéxito o no? Para ello
contamos con varios métodos, de la clase TestCase, que comienza con el
prefijo assert. De esta forma, porejemplo, el método assertEqual() recibirá
dos valores y, en caso de seriguales, devolverá un valor que indicará que el
resultado de la pruebaes válido. Si deseamos comprobar que dos valores
son diferentes,utilizaremos el método assertNoEqual. Otros métodos
similares son assertTrue() y assertFalse() que comprueban si un valor es
True o False, respectivamente. Interesantes son también los métodos
assertIsInstance() y assertNotIsInstance(), útiles para comprobar si un
    346/381
    objeto es instancia o no de una determinada clase.Para ilustrar el
funcionamiento de unittest vamos a escribir unsencillo fichero en Python
que contendrá una función para sumartodos los valores de una lista.
Posteriormente, escribiremos un caso deprueba que comprobará si el valor
obtenido es el esperado, en cuyocaso, la prueba habrá sido positiva. El
código en cuestión para nuestrofichero (lista.py) de ejemplo sería el
siguiente:
def suma(li):total = 0for ele in li:total += elereturn total
El siguiente paso será escribir un nuevo script al que llamaremos
pruebas.py, cuyo contenido será el siguiente:
import unittestimport lista
class ListaTestCase(unittest.TestCase):def setUp(self):self.li = [1, 3, 5, 7]def
test suma(self):total = lista.suma(self.li)self.assertEqual(total, 16)if
__name__ == '__main__':unittest.main()
Por convención, los nombres de los métodos de la clase de
pruebascomenzarán por el prefijo test_, seguido del método o función que
vana probar. En nuestro ejemplo solo tenemos un método llamado
test_suma(). A través del método assertEqual() comprobaremos si elvalor
obtenido al invocar a la función suma() es 16, que es elcorrespondiente a la
suma de todos los valores de nuestra lista deejemplo, cuyo valor hemos
prefijado, como un atributo de instancia, enel método de inicialización
setUp(). Al ejecutar nuestro script por líneade comandos, la función main()
será invocada, procediendo allanzamiento de las pruebas. Ahora solo nos
queda lanzar el script de lasiguiente forma:
python pruebas.py
Dado que el valor es correcto, obtendremos el siguiente resultadopor la
salida estándar:
    347/381
    ..
Ran 1 test in 0.002sOK
La información obtenida hace referencia al número de pruebasejecutadas y
la cadena de texto string significa que la prueba ha sidopasada
correctamente, asegurando así que nuestra función suma() funciona
correctamente.Hasta aquí la funcionalidad básica del módulo unittest, los
lectoresinteresados pueden completar la información recibida con
ladocumentación existente en la página web oficial (ver referencias)
delmencionado módulo.
    348/381
    DOCTEST
Además de unittest, Python nos ofrece en su librería estándar otromódulo
para escribir y realizar pruebas unitarias. Su nombre es doctest y permite
escribir pruebas en línea, es decir, como parte de ladocumentación de una
función y método, podemos escribirdirectamente el caso de prueba. Para
comprobar el funcionamiento deeste módulo vamos a modificar la función
suma() del ejemplo anterior,añadiendo nuestro caso de prueba como parte
del comentarlo de ladocumentación de la función. De esta forma, la nueva
función suma() del módulo lista quedaría como sigue:
def suma(li) :"""Devuelve la suma de todos los elementos de la lista>>>
suma([1, 2, 3])6"""total = 0for ele in li:total += elereturn total
Asimismo, para poder ejecutar el nuevo caso de prueba, seránecesario
añadir un par de líneas, las cuales serán ejecutadas cuandose invoque a
lista.py, directamente desde la línea de comandos. Laslíneas de código en
cuestión serán las siguientes:
if __name__ == '__main__ :import doctestdoctest.testmod()
Si ahora lanzamos el siguiente comando, comenzará la ejecución denuestro
caso de prueba:
python lista.py
Como resultado de la ejecución del comando anterior noobtendremos
ninguna respuesta, ello significará que el test hafuncionado correctamente.
No obstante, si quisiéramos ver un registrode las acciones llevadas a cabo,
podríamos recurrir al parámetro -v , taly como muestra el siguiente ejemplo:
    349/381
    python lista.py -v
Por otro lado, si la prueba fallara, obtendríamos información alrespecto. Si
cambiamos el valor que debe ser obtenido a otrocualquiera, la prueba dará
un resultado erróneo, tal y como podemoscomprobar en la salida que
obtendríamos:
************************************************************
File "lista.py", line 3, in __main__.sumaFailed example:suma([1, 2,
3])Expected:7Got: 6
************************************************************
1 ítems had failures:of 1 in __main__.suma***Test Failed*** 1 failures.
A pesar de que la funcionalidad y flexibilidad ofrecidas por doctest es
bastante limitada con respecto a unittest, el primero es un móduloque nos
permite escribir pruebas unitarias de una forma muy sencilla.En función de
la complejidad que necesitemos podemos emplear unou otro módulo.
    350/381
    OTROS FRAMEWORKS
Aparte de doctest y unittest existen otros frameworks para escribirpruebas
unitarias en Python. Entre ellos, por ejemplo, se encuentran nose, unittest2
y pytest. Ninguno de estos forma parte de la libreríaestándar, por lo que
deben ser instalados, por ejemplo, a través de ungestor de paquetes como
pip .El módulo unittest2 fue desarrollado con la idea de implementar
lasnuevas funcionalidades, que fueron añadidas a unirttest en la versión2.7
de Python, para hacerlo compatible con las versiones de Pythondesde la 2.3
a la 2.6. Además, existe también una versión específica de unittest2 para
Python 3, de forma que aquellos que han escrito suspruebas con unittest,
puedan migrar su código a Python 3 sinproblemas. pytest incorpora una
serie de componentes para escribir complejoscasos de prueba. Entre sus
funciones encontramos aquellas quepermiten obtener información sobre los
tests que tardan más de untiempo determinado, continuar con la ejecución
de todas las pruebasaunque algunas fallen y la opción es escribir plugins
para personalizarnuevas pruebas funcionales que nos sean necesarias.Por
último, nose está basado en unittest solo que se han añadidouna serie de
funcionalidades para lograr que sea más sencillo escribir yejecutar casos de
prueba. Entre ellas se encuentra la opción de pasaruna serie de argumentos,
por línea de comandos, para ejecutar unalista de test concreta o para indicar
un directorio por defecto donde seencuentran todos los scripts de prueba.
    351/381
    AEL ZEN DE PYTHON
TRADUCCIÓN DE "EL ZEN DE PYTHON"
Bonito es mejor que feo.Explícito es mejor que implícito.Simple es mejor
que complejo.Complejo es mejor que complicado.Sencillo es mejor que
anidado.Disperso es mejor que denso.La legibilidad cuenta.Los casos
especiales no son lo suficientemente especiales pararomper las reglas. La
practicidad bate a la pureza.Los errores no deben pasar inadvertidos.A
menos que sean explícitamente silenciados.En caso de ambigüedad,
rechazar la tentación de adivinar.Debe haber una -y preferiblemente únicaforma obvia de hacerlo.Aunque esa única forma no sea obvia en un primer
momento, a noser que seas holandés.Ahora es mejor que nunca.Aunque
nunca es mejor que *ahora* mismo.Si la implementación es difícil de
explicar, es una mala idea.Si la implementación es fácil de explicar, debe
ser una buena idea.Los espacios de nombres son una idea genial -¡hay que
hacer másde esos!
El original puede leerse desde el intérprete interactivo, ejecutando la
    352/381
    siguiente línea:
>>> import this
    353/381
    BCÓDIGO DE BUENASPRÁCTICAS
REGLAS
Es importante estructurar correctamente un proyecto. Crear unaadecuada
jerarquía de directorios y ficheros en función de lasnecesidades de cada
aplicación.Utilizar una guía de estilo para la codificación. PEP 8 (
http://www.python.org/dev/peps/pep-0008/ ) define una completaguía de
estilo, considerada como un estándar para código Python.El módulo pep8
puede comprobar automáticamente si un script escompatible con las reglas
fijadas en PEP 8. Aplicar El Zen de Python. Documentar siempre el código.
Unas simples líneas dedocumentación pueden ahorrar horas de trabajo en el
futuro.Herramientas como Sphinx ( http://sphinx.pocoo.org/ ) y
formatoscomo reStructuredText ( http://docutils.sourceforge.net/rst.html )
pueden ayudarnos mucho.Los controles de versiones son una excelente
herramienta para eldesarrollo de software. Cualquier proyecto debería hacer
uso deuno de ellos.Escribir pruebas unitarias. Las pruebas de integración y
funcionalestambién son recomendables.
    354/381
    REFERENCIAS
CAPÍTULO 1. PRIMEROS PASOS
Sitio web oficial de Python: http://www.python.org/ Página web de
descargas de Python: http://www.python.org/getit/ Página web de descargas
para Python 3.2.2: http://www.python.Org/getit/releases/3.2.2/ Página web
oficial sobre IDLE: http://docs.python.org/library/idle.html Página web
sobre la comunidad de Python: http://www.python.org/community/ Utilidad
py2exe: http://www.py2exe.org Información para configurar Emacs para
desarrollo con Python:http://www.emacswiki.org/emacs/?
action=browse;old¡d=PythonMode;id=PythonProgramminglnEmacsScripts
y plugins de Python para Vim\ http://vim.wikia.eom/wiki/Category:Python
Sitio web de TextMate : http://www.macromates.com Sitio web de gedit:
http://projects.gnome.org/gedit/ Sitio web de Notepad++: http://notepadplus-plus.org/ Sitio web de Eclipse : http://www.eclipse.org/ PyDev: Python
+ Eclipse: http://pydev.org/ Sitio web de NetBeans: http://netbeans.org
Soporte para Python en NetBeans: http://wiki.netbeans.org/Python fríe IDE:
http://eric-ide.python-projects.org PyCharm IDE:
http://www.jetbrains.com/pycharm/ Wingware Python IDE:
http://wingware.com Documentación oficial de IPython:
http://ipython.org/ipython-doc/stable/index.html Documentación y
comandos de pdb: http://docs.python.org/library/pdb.html
    355/381
    Página web dedicada a 2to3, la herramienta para migrar de Python2.x a
Python 3: http://docs.python.org/library/2to3.html Página oficial sobre las
novedades de Python 3: http://docs.python.Org/py3k/whatsnew/3.0.html
    356/381
    CAPÍTULO 3. SENTENCIAS DE CONTROL, MÓDULOS Y
FUNCIONES
Página web oficial de la librería estándar de Python:
http://docs.python.org/py3k/library/index.html
    357/381
    CAPÍTULO 4. ORIENTACIÓN A OBJETOS
Página de Wikipedia sobre orientación a objetos:
http://es.wikipedia.org/wiki/Programacion_orientada_a_objetos Principio
de abstracción: http://es.wikipedia.org/wiki/Abstracción_(informática )
    358/381
    CAPÍTULO 5. PROGRAMACIÓN AVANZADA
Modelo NFA tradicional:
http://en.wikipedia.org/wiki/Nondeterministic_finite_automata
    359/381
    CAPÍTULO 6. FICHEROS
Página oficial sobre el módulo csv:
http://docs.python.org/py3k/library/csv.html Documentación oficial sobre el
módulo zipfile: http://docs.python.org/py3k/library/zipfile.html
Documentación oficial sobre el módulo tarfile:
http://docs.python.org/py3k/library/tarfile.html Documentación oficial
sobre el módulo bz2: http://docs.python.org/py3k/library/bz2.html
Documentación oficial sobre el módulo gzip:
http://docs.python.org/py3k/library/gzip.html Estructura para ficheros INI:
http://docs.python.org/py3k/library/configparser Documentación sobre
analizadores sintácticos de XML y HTML:
http://docs.python.org/py3k/library/markup.html Sitio web de PyYAML:
http://pyyaml.org/wiki/PyYAML URL para la descarga de la versión 3.10
de PyYAML: http://pyyaml.org/download/pyyaml/PyYAML-3.10.zip Sitio
web oficial de JSON: http://www.jsong.org/ Página web sobre el módulo
simplejson: http://pypi.python.org/pypi/simplejson/
    360/381
    CAPÍTULO 7. BASES DE DATOS
Sitio web de MySQL: http://www.mysql.com Documentación de referencia
de MySQLdb: http://mysqlpython.sourceforge.net/MySQLdb.html#mysqldb Sitio web de psycopg2 :
http://initd.org/psycopg/ Documentación oficial de psycopg2:
http://initd.org/psycopg/docs/ Sitio web de PostgreSQL:
http://www.postgresql.org Sitio web de Oracle:
http://www.oracle.com/technetwork/database/enterpriseedition/overview/index.html Sitio web de cx_Oracle: http://cxoracle.sourceforge.net/ Sitio web de SQLAlchemy:
http://www.SQLAlchemy.org Documentación oficial de SQLAlchemy :
http://docs.SQLAlchemy.org/en/latest/index.html Sitio web de SQLObject :
http://www.sqlobject.org Documentación oficial sobre SQLObject :
http://sqlobject.org/SQLObject.html Sitio web de Cassandra :
http://cassandra.apache.org Documentación oficial sobre pycassa :
http://pycassa.github.com/pycassa/api/index.html Sitio web de Redis :
http://redis.io Sitio web de redis-py en github:
https://github.com/andymccurdy/redis-py Sitio web de MongoDB:
http://www.mongodb.org Documentación oficial sobre PyMongo:
http://api.mongodb.org/python/current/
    361/381
    CAPÍTULO 8. INTERNET
Sitio web de WSGI: http://www.wsgi.org Información de referencia del
módulo lxml.html: http://lxml.de/lxmlhtml.html Sitio web de Django:
http://www.djangoproject.com Sitio web oficial de Pyramid:
http://www.pylonsproject.org Sitio web oficial de Pylatte:
http://www.pylatte.org Página web de descarga de Pylatte:
http://www.pylatte.org/subpage/download.html
    362/381
    CAPÍTULO 9. INSTALACIÓN Y DISTRIBUCIÓN DE PAQUETES
Sitio web de PyPi: http://pypi.python.org/pypi Página web de descargas del
módulo distribute: http://python-distribute.org/ Página web de distribute en
PyPi: http://pypi.python.org/pypi/distribute Página web de pip en PyPi:
http://pypi.python.org/pypi/pip Página web de virtualenv en PyPi:
http://pypi.python.org/pypi/virtualenv Página web de virtualenvwrapper en
PyPi: http://pypi.python.org/pypi/virtualenvwrapper Sitio web de
virtualenvwrapper:
http://www.doughellmann.com/projects/virtualenvwrapper/ Listado
completo de clasificadores para setup.py:
http://pypi.python.org/pypi7%3AactionHist_classifiers Formulario de
registro para registrar y subir paquetes a PyPi : http://pypi.python.org/pypi?
%3Aaction=register_form
    363/381
    CAPÍTULO 10. PRUEBAS UNITARIAS
Documentación oficial del módulo unittest: http://
http://docs.python.org/py3k/library/unittest.html Página de PyPi sobre
unittest2: http://pypi.python.org/pypi/unittest2 Sitio web de pytest:
http://pytest.org/latest/ Documentación oficial de nose:
http://readthedocs.org/docs/nose/en/latest/
    364/381
    ÍNDICE ALFABÉTICO
-
__call__() 113, 115__iter__() 105__name__() 113, 114, 116, 210, 245,
246__next__() 21, 105
A
append() 41 array dinámico 40ASCII 33atributos 76, 78, 79, 80, 81, 82, 83,
84, 90, 94, 95, 96, 97, 100, 102, 165,174, 176
B
BDD 242 break 53, 54 bytearray 33, 34 bytecode 14, 67
C
callable() 101 caso de prueba 242, 243, 244, 245, 246Cassandra 163, 164,
180, 181, 184, 256CGI 188, 206, 207, 208 classmethod 84, 86 closure 110,
114 comprensión 21, 44, 45, 49, 108 configparser 153, 255conjuntos
matemáticos 31 CORBA 140 count() 40, 184CPython 6, 232
    365/381
    CSV 6, 134, 150, 151, 152
D
decode() 34decorador 80, 81, 84, 86, 111, 112, 113, 114, 115, 116, 117
decorator 80, 81, 84, 111, 116, 117 del() 42, 48, 89 desempaquetado de
argumentos 61, 115 diccionario 21, 23, 46, 47, 48, 49, 60, 61, 102, 142,
148, 149, 150, 153,154, 183, 185 dir() 99, 100 distutils 221, 223, 233,
235DOM 143,144
E
easy_install 221, 225, 226, 227, 228, 229, 230, 231, 232, 234, 236 encode()
34entornos virtuales 15, 222, 236, 237, 238, 239, 240 estructura de datos
24, 38, 46, 76, 102, 131, 134, 140 excepciones 3, 22, 51, 71, 72, 73, 74,
202expresiones regulares 16, 69, 103, 120, 122, 123, 126, 127, 128
F
fixture 242 flush() 137formato INI 134,152 frameworks web 75, 174, 188,
206, 215, 216, 218, 219FTP 69, 187, 188, 191, 192, 193, 194, 234 función
7, 10, 11, 20, 21, 25, 26, 28, 31, 33, 34, 35, 37, 40, 42, 43, 44, 46,47, 48, 49,
53, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 74,76, 79, 80, 81,
87, 89, 90, 91, 92, 93, 98, 99, 100,101, 102, 105, 106,107, 108,109, 110,
111, 112, 113, 114, 115, 116,117, 118, 119, 120,123, 124, 125, 126,127,
129, 130, 131, 132, 134, 135, 136,142, 145,146, 150, 156, 158, 165,
166,171, 175, 176, 195, 196, 201, 202, 209,211, 214, 217, 218, 223, 225,
234, 241, 244, 245, 247, 251función anónima 62funciones lambda 62, 63,
118, 132
    366/381
    G
garbage collector 6, 89 generators 103, 107, 118, 119Guido van Rossum 2,
3gzip 134, 155, 157, 158, 159, 160, 255
H
hasattr() 100, 101 herencia 5, 73, 76, 83, 94, 95, 97, 98, 100, 111
I
IDLE 8, 9, 10, 12, 13, 14, 253IMAP4 187, 198, 203, 205 index()
40inmutable 32, 38, 39, 57, 64 insert() 42,185 instrospección 99, 102
IPython 17, 18, 254 isinstance() 100, 101 ítems() 21, 47 iterator 21, 104,
105, 106, 107, 119, 138 iterators 103, 106, 107, 118, 120
J
join() 36, 135, 136JSON 133, 143, 146, 147, 148, 183, 217, 255
K
keys() 21, 47, 48, 102
L
len() 40, 42, 48, 130 librería estándar VII, 5, 19, 20, 22, 68, 69, 73, 74,
120, 133, 134, 135, 140,144, 147, 148, 149, 151, 153, 155, 166, 169, 171,
173, 175, 184, 189,191, 194, 197, 198, 201, 206, 209, 211, 213, 214, 221,
223, 226, 233,243, 245, 247, 254 list() 41, 48, 106, 118, 119, 161, 199, 200
    367/381
    lista 21, 22, 26, 30, 36, 40, 41, 42, 43, 44, 45, 48, 49, 53, 56, 57, 58, 63,64,
65, 68, 71, 97, 100, 102,104, 106, 108, 118, 119, 120, 125,126,127, 129,
130, 131, 132, 137, 151,152, 153, 156, 161, 172, 183, 184,192,197, 199,
204, 209, 212, 234, 244, 245, 246, 247 lower() 36, 118, 119 lstrip() 35, 36
M
macros 111, 112 map() 63, 106, 118, 119 marshalling 140 matrices
45método de instancia 79, 85 métodos de clase 84, 85, 86métodos
especiales 87, 92, 93, 105 módulos 6, 17, 22, 24, 51, 65, 67, 69, 70, 89, 133,
134, 140, 144, 148,155,164, 171, 178, 181, 184, 188, 189, 194, 198, 206,
211, 216, 221,222, 224, 225, 226, 230, 232, 233, 234, 236, 257MongoDB
163, 164, 181, 182, 183, 184, 256mutable 33, 40, 46, 57, 58, 64MySQL
163, 166, 168, 169, 170, 171,174, 175, 178, 219, 256 MySQLdb 166, 168,
169, 170, 171, 172, 219, 256
N
name mangling 83, 84 None 24, 65, 124, 141, 172, 204 NoSQL 163, 164,
180, 185, 191número indefinido de parámetros 59
O
objeto iterable 39, 104OOP 23, 75, 76Oracle 163, 171, 172, 173, 174, 175,
231, 256 ORM 164, 172, 174, 175, 178, 180, 216, 219
P
paquetes 11, 12, 17, 51, 65, 70, 166, 171, 189, 214, 217, 221, 222, 223,225,
226, 227, 228, 229, 230, 231, 232, 233, 235, 236, 237, 238, 239,
    368/381
    240, 247, 258paso de parámetros 56, 57, 58, 61, 115, 116pdb 18, 254 pickle
140, 141, 142 pickling 140 pip 17, 166, 170, 171, 175, 178, 181, 182, 184,
214, 217, 221, 222, 225,226, 229, 230, 231, 232, 234, 235, 236, 237, 238,
239, 240, 247, 257 polimorfismo 76, 98, 99 pop() 43, 48POP3 187, 191,
198, 199, 200, 201, 203, 205PostgreSQL 163, 169, 171, 173, 174, 175, 178,
256precedencia de operadores 29 profiler 19programación funcional 5, 51,
103, 118, 120programación orientada a objetos VIII, 5, 23, 73, 87,
94programación procedural VII, 51,118prompt del intérprete 13 property()
81 psycopg2 169, 170, 171, 172, 256 Pylatte 215, 218, 219, 257PyPi 225,
226, 227, 228, 231, 233, 234, 235, 236, 257, 258 Pyramid 215, 216, 217,
218, 257 pytest 247, 258 Python Software Foundation 3,4 PYTHONPATH
68
R
RAR 134RDBMS 163 read() 137, 138, 139, 211 readlines() 137Redis 164,
180, 181, 182, 256 remove() 32, 42, 43 replace() 35 repr() 90, 91 reverse()
44rstrip() 35, 36
    369/381
    S
SAX 143, 144, 145, 146 Scripts 4, 65, 68, 69, 181, 194, 206, 207, 224, 230,
237, 247 seek() 138, 139sentencias de control VIII, 9, 51, 52 serialización
133, 140, 142, 143, 146, 148 setUp() 244, 245sistema operativo 1, 52SMTP
187, 191, 198, 201, 202, 203 sort() 21, 43, 44, 49, 130 sorted() 21, 43, 44,
49, 129, 130, 131, 132 split() 36, 127, 205 SQLAchemy 164 SQLAlchemy
174, 175, 176, 177, 178, 180, 256SQLite3 163, 173 SQLObject 164, 174,
177, 178, 180, 256 staticmethod 86 strings 5, 20, 22, 32, 34, 35, 37, 88,
100, 104, 127 strip() 35, 36
T
TDD 242 tearDown() 244TELNET 187, 188, 189, 191 terminal 5, 11, 12,
166, 175, 181, 188, 189, 190, 198, 210, 223, 226, 238 test runner 243 test
suite 243 tipado dinámico 5, 24, 25TLS 203 tupla 21, 38, 39, 40, 41, 42, 53,
58, 59, 61, 74, 126, 131, 157, 167, 169,171, 195, 204, 209 type() 26, 34,
101
U
Unicode 20, 33, 127 unittest2 247, 258 unpickling 140
    370/381
    upper() 36
V
values() 21, 47, 48variable de entorno PATH 14, 224, 227variables de
instancia 78, 80
W
web scraping 188, 206, 210, 211, 213, 214 write() 137, 154, 158, 190
writelines() 137, 158WSGI 188, 206, 208, 209, 210, 215, 218, 257
X
XML 69, 133, 143, 144, 145, 146, 147, 148, 187, 194, 195, 197, 198,
213,219, 231, 232, 255
Y
YAML 133, 143, 148, 149, 150 yield() 107
Z
ZIP 6, 134, 155, 156, 157, 159, 223, 233, 234
    371/381
    Python 3 al descubierto - Page 372
    372/381
    Ta
bleofContents
page-001
page-002
page-003
page-004
page-005
page-006
page-007
page-008
page-009
page-010
page-011
page-012
page-013
page-014
page-015
page-016
page-017
page-018
page-019
page-020
page-021
page-022
page-023
page-024
page-025
page-026
page-027
page-028
page-029
page-030
page-031
page-032
page-033
page-034
    373/381
    page-035
page-036
page-037
page-038
page-039
page-040
page-041
page-042
page-043
page-044
page-045
page-046
page-047
page-048
page-049
page-050
page-051
page-052
page-053
page-054
page-055
page-056
page-057
page-058
page-059
page-060
page-061
page-062
page-063
page-064
page-065
page-066
page-067
page-068
page-069
page-070
page-071
    374/381
    page-072
page-073
page-074
page-075
page-076
page-077
page-078
page-079
page-080
page-081
page-082
page-083
page-084
page-085
page-086
page-087
page-088
page-089
page-090
page-091
page-092
page-093
page-094
page-095
page-096
page-097
page-098
page-099
page-100
page-101
page-102
page-103
page-104
page-105
page-106
page-107
page-108
    375/381
    page-109
page-110
page-111
page-112
page-113
page-114
page-115
page-116
page-117
page-118
page-119
page-120
page-121
page-122
page-123
page-124
page-125
page-126
page-127
page-128
page-129
page-130
page-131
page-132
page-133
page-134
page-135
page-136
page-137
page-138
page-139
page-140
page-141
page-142
page-143
page-144
page-145
    376/381
    page-146
page-147
page-148
page-149
page-150
page-151
page-152
page-153
page-154
page-155
page-156
page-157
page-158
page-159
page-160
page-161
page-162
page-163
page-164
page-165
page-166
page-167
page-168
page-169
page-170
page-171
page-172
page-173
page-174
page-175
page-176
page-177
page-178
page-179
page-180
page-181
page-182
    377/381
    page-183
page-184
page-185
page-186
page-187
page-188
page-189
page-190
page-191
page-192
page-193
page-194
page-195
page-196
page-197
page-198
page-199
page-200
page-201
page-202
page-203
page-204
page-205
page-206
page-207
page-208
page-209
page-210
page-211
page-212
page-213
page-214
page-215
page-216
page-217
page-218
page-219
    378/381
    page-220
page-221
page-222
page-223
page-224
page-225
page-226
page-227
page-228
page-229
page-230
page-231
page-232
page-233
page-234
page-235
page-236
page-237
page-238
page-239
page-240
page-241
page-242
page-243
page-244
page-245
page-246
page-247
page-248
page-249
page-250
page-251
page-252
page-253
page-254
page-255
page-256
    379/381
    page-257
page-258
page-259
page-260
page-261
page-262
page-263
page-264
page-265
page-266
page-267
page-268
page-269
page-270
page-271
page-272
page-273
page-274
page-275
page-276
page-277
page-278
page-279
page-280
page-281
page-282
page-283
page-284
page-285
page-286
page-287
page-288
page-289
page-290
page-291
page-292
page-293
    380/381
    page-294
page-295
page-296
page-297
page-298
page-299
page-300
page-301
page-302
page-303
page-304
page-305
page-306
page-307
page-308
page-309
page-310
page-311
page-312
page-313
page-314
page-315
page-316
page-317
page-318
page-319
page-320
page-321
page-322
page-323
page-324
page-325
page-326
page-327
    381/381

    Python 3 al descubierto

    • 5. Python 3 al descubierto Arturo Fernández MontoroISBN: 978-84-939450- 4-6 edición original publicada por RC Libros, Madrid, EspañaDerechos reservados © 2012 RC Libros Segunda edición: Alfaomega Grupo Editor, México, septiembre 2013 © 2013 Alfaomega Grupo Editor, S.A. de C.V. Pitágoras 1139, Col. Del Valle, 03100, México D.F. Miembro de la Cámara Nacional de la Industria Editorial MexicanaRegistro No. 2317 Pág. Web: http://www.alfaomega.com.mx E-mail: atencionalcliente@alfaomega.com.mx ISBN: 978-607-707-718-3eISBN: 978-607-62200-8-5 Datos CatalográficosFernández, ArturoPython 3 al descubiertoAlfaomega Grupo Editor, S.A. de C.V.,México ISBN: 978-607-707-718-3eISBN: 978-607-62200-8-5 Formato: 17 x 23 cmPáginas: 276 La transformación a libro electrónico del presente título fue realizada porSextil Online, S.A. de C.V./ Editorial Ink ® 2016.+52 (55) 52 54 38 52 contacto@editorial-ink.com www.editorial-ink.com Derechos reservados: Esta obra es propiedad intelectual de su autor y los derechos de publicación en lenguaespañola han sido legalmente transferidos al editor. Prohibida su reproducción parcial o totalpor cualquier medio sin permiso por escrito del propietario de los derechos del copyright Nota importante: La información contenida en esta obra tiene un fin exclusivamente didáctico y, por lo tanto, noestá previsto su aprovechamiento a nivel profesional o industrial. Las indicaciones técnicas yprogramas incluidos, han sido elaborados con gran cuidado por el autor y reproducidos bajoestrictas normas de control. ALFAOMEGA GRUPO
    • 6. EDITOR, S.A. de C.V. no será jurídicamenteresponsable por: errores u omisiones; daños y perjuicios que se pudieran atribuir al uso de lainformación comprendida en este libro, ni por la utilización indebida que pudiera dársele. Edición autorizada para venta en México y todo el continente americano.
    • 7. Impreso en México. Printed in México. Empresas del grupo:México: Alfaomega Grupo Editor, S.A. de C.V. - Pitágoras 1139, Col. Del Valle, México, D.F. -C.R 03100. Tel.: (52-55) 5575-5022 - Fax: (52-55) 5575-2420 / 2490. Sin costo: 01-800-020-4396Email: atencionalcliente@alfaomega.com.mx Colombia: Alfaomega Colombiana S.A. - Calle 62 No. 20-46, Barrio San Luis, Bogotá,Colombia, Tels.: (57-1) 746 0102 / 210 0415 - E-mail: cliente@alfaomega.com.co Chile: Alfaomega Grupo Editor, S.A. -Av. Providencia 1443. Oficina 24, Santiago, Chile Tel.: (56-2) 2235-4248 - Fax: (56-2) 2235-5786 - E-mail: agechile@alfaomega.cl Argentina: Alfaomega Grupo Editor Argentino, S.A. - Paraguay 1307 PB. Of. 11, C.R 1057,Buenos Aires, Argentina, - Tel./Fax: (54-11) 4811-0887 y 4811 7183 - E-mail: ventas@alfaomegaeditor.com.ar
    • 8. PRÓLOGO En la actualidad Python es uno de los lenguajes de programacióncon mayor proyección. Su facilidad de uso, su librería estándar y lacantidad de librerías adicionales que existen contribuyen a que seanmuchos los desarrolladores de software que optan por su utilizaciónpara llevar a cabo sus proyectos.Python es un lenguaje de propósito general, de alto nivel,interpretado y que admite la aplicación de diferentes paradigmas deprogramación, como son, por ejemplo, la programación procedural,imperativa y la orientación a objetos.La programación científica, la programación de sistemas o lasaplicaciones web son ámbitos en los que habitualmente se empleaPython como lenguaje de programación principal. También puede serempleado para desarrollar aplicaciones de escritorio con interfazgráfica de usuario, integrar componentes escritos en diferenteslenguajes de programación o incluso desarrollar juegos.Dadas sus principales características, Python es un lenguaje idealpara el prototipado. Para diversos tipos de aplicaciones puedenconstruirse rápidamente prototipos, facilitando el desarrollo delmodelo final en otros lenguajes que ofrezcan mayor rendimiento comoes el caso de C y C++. Todo ello sin perder de vista el hecho de quePython puede utilizarse como un lenguaje más de alto nivel.El presente libro no pretende ser un manual de referencia al uso,sino ofrecer una completa visión del lenguaje desde un punto de vistapráctico. Con ella se pretende que el lector consiga familiarizarserápidamente con el lenguaje, aprendiendo sus fundamentos ydescubriendo cómo utilizarlo para desarrollar diferentes tipos deaplicaciones.Los primeros cinco capítulos están dedicados a los aspectos másimportantes del lenguaje. En ellos aprenderemos sobre las estructurasy tipos de datos básicos, sentencias de control, cómo aplicar la
    • 9. programación orientada a objetos y detalles más avanzados sobre ellenguaje. Los siguientes capítulos, que pueden ser considerados comouna segunda parte, están orientados a utilizar Python para desarrollaraplicaciones que interactúen con bases de datos, manejen ficheros yutilicen diversos servicios de Internet. Seguidamente nos centraremosen la instalación y distribución de programas desarrollados en ellenguaje de programación que nos ocupa. Por último, descubriremoscómo diseñar y ejecutar pruebas unitarias, formando parte estas deuna de las fases más importantes en el desarrollo de software.Esperamos que el lector disfrute aprendiendo los fundamentos deeste lenguaje de programación y pueda rápidamente aplicar losconceptos aprendidos a sus propios proyectos.
    • 10. ÍNDICE PRÓLOGO CAPÍTULO 1. PRIMEROS PASOS Introducción ¿Qué es Python? Un poco de historia Principales características InstalaciónWindows Mac OS X Linux Hola Mundo Código fuente y bytecode Herramientas de desarrolloEditores Entornos integrados de desarrollo (IDE) Intérprete interactivo mejorado Depuradores Profiling Novedades en Python 3 CAPÍTULO 2. ESTRUCTURAS Y TIPOS DE DATOS BÁSICOS Introducción Conceptos básicos Tipado dinámico NúmerosEnteros, reales y complejos Sistemas de representación
    • 11. Operadores Funciones matemáticas Conjuntos Cadenas de textoTipos Principales funciones y métodos Operaciones Tuplas Listas Inserciones y borrados Ordenación Comprensión Matrices DiccionariosAcceso, inserciones y borrados ComprensiónOrdenación CAPÍTULO 3. SENTENCIAS DE CONTROL, MÓDULOS YFUNCIONES Introducción Principales sentencias de controlif, else y elif for y while pass y with Funciones Paso de parámetros Valores por defecto y nombres de parámetros Número indefinido de argumentos Desempaquetado de argumentos Funciones con el mismo nombre Funciones lambda Tipos mutables como argumentos por defecto Módulos y paquetesMódulos
    • 12. Funcionamiento de la importación Path de búsqueda Librería estándarPaquetes Comentarios ExcepcionesCapturando excepciones Lanzando excepcionesExcepciones definidas por el usuario Información sobre la excepción CAPÍTULO 4. ORIENTACIÓN A OBJETOS Introducción Clases y objetos Variables de instancia Métodos de instanciaVariables de clase Propiedades Visibilidad Métodos de clase Métodos estáticos Métodos especiales Creación e inicialización Destructor Representación y formatos Comparaciones Hash y bool Herencia Simple Múltiple Polimorfismo Introspección CAPÍTULO 5. PROGRAMACIÓN AVANZADA Introducción Iterators y generators3
    • 13. Iterators Funciones integradas Generators Closures DecoratorsPatrón decorator, macros y Python decorators Declaración y funcionamiento Decorators en clases Funciones como decorators Utilizando parámetros Decorador sin parámetros Decorador con parámetros Programación funcional Expresiones regularesPatrones y metacaracteres Búsquedas Sustituciones SeparacionesModificadores Patrones para comprobaciones cotidianas Ordenación de datos Método itemgetter() Funciones lambda CAPÍTULO 6. FICHEROS Introducción Operaciones básicasApertura y creación Lectura y escritura Serialización Ejemplo práctico Ficheros xml, json y yamlXML JSON YAML
    • 14. Ficheros CSV Analizador de ficheros de configuración Compresión y descompresión de ficheros Formato ZIP Formato gzip Formato bz2 Formato tarball CAPÍTULO 7. BASES DE DATOS Introducción Relacionales MySQL PostgreSQL Oracle SQLite3 ORM Sqlalchemy Sqlobject Nosql Redis MongoDB Cassandra CAPÍTULO 8. INTERNET Introducción TELNET y FTPtelnetlib ftplib XML-RPCxmlrpc.server xmlrpc.client Correo electrónicopop3 smtp imap4 Web
    • 15. CGI WSGI Web scraping urllib.request lxml Frameworks pyramid pylatte CAPÍTULO 9. INSTALACIÓN Y DISTRIBUCIÓN DE PAQUETES Introducción Instalación de paquetesInstalación desde la fuente Gestores de paquetes easy_install pip Distribución Entornos virtuales virtualenv virtualenvwrapper pip y los entornos virtuales CAPÍTULO 10. PRUEBAS UNITARIAS Introducción Conceptos básicos UNITTEST DOCTEST Otros frameworks APÉNDICE A. EL ZED DE PYTHON Traducción de “El zen de Python” APÉNDICE B. CÓDIGO DE BUENAS PRÁCTICAS REGLAS REFERENCIAS
    • 16. ÍNDICE ALFABÉTICO
    • 17. PRIMEROS PASOS INTRODUCCIÓN Este primer capítulo será nuestra primera toma de contacto conPython.Comenzaremos con una sencilla descripción del lenguaje y unaserie de datos que nos ayuden a tener una visión general del mismo.Posteriormente, haremos un breve recorrido a su historia, para pasardespués a examinar sus principales características. Después,realizaremos la primera incursión práctica escribiendo nuestro primercódigo en este lenguaje. Los dos últimos apartados los dedicaremos aver con qué herramientas de desarrollo contamos y cuáles son lasprincipales novedades de Python 3.
    • 18. ¿QUÉ ES PYTHON? Básicamente, Python es un lenguaje de programación de alto nivel, interpretado y multipropósito. En los últimos años su utilización ha idoconstantemente creciendo y en la actualidad es uno de los lenguajesde programación más empleados para el desarrollo de software.Python puede ser utilizado en diversas plataformas y sistemasoperativos, entre los que podemos destacar lo más populares, comoWindows, Mac OS X y Linux. Pero, además, Python también puedefuncionar en smartphones, Nokia desarrolló un intérprete de estelenguaje para su sistema operativo Symbian. ¿Tiene Python un ámbito específico? Algunos lenguajes deprogramación sí que lo tienen. Por ejemplo, PHP fue ideado paradesarrollar aplicaciones web. Sin embargo, este no es el caso dePython. Con este lenguaje podemos desarrollar software paraaplicaciones científicas, para comunicaciones de red, para aplicacionesde escritorio con Interfaz gráfica de usuario (GUI), para crear juegos,para smartphones y por supuesto, para aplicaciones web. Fig. 1-1 Logo de Python Empresas y organizaciones del calibre de Industrial Light & Magic,Walt Disney, la NASA, Google, Yahoo!, Red Hat y Nokia hacen usointensivo de este lenguaje para desarrollar sus productos y servicios.Esto demuestra que Python puede ser utilizado en diversos tipos desectores, con independencia de su actividad empresarial.Entre las principales razones para elegir Python, son muchos losque argumentan que sus principales características lo convierten en unlenguaje muy productivo. Se trata de un lenguaje potente, flexible y
    • 19. con una sintaxis clara y concisa. Además, no requiere dedicar tiempo asu compilación debido a que es interpretado.Python es open source, cualquiera puede contribuir a su desarrollo ydivulgación. Además, no es necesario pagar ninguna licencia paradistribuir software desarrollado con este lenguaje. Hasta su intérpretese distribuye de forma gratuita para diferentes plataformas.La última versión de Python recibe varios nombres, entre ellos, Python 3000 y Py3K, aunque, habitualmente, se le denominasimplemente Python 3. Un poco de historia El origen del lenguaje Python se remonta a principios de losnoventa. Por este tiempo, un investigador holandés llamado Guido vanRossum, que trabajaba en el centro de investigación CWI (CentrumWiskunde & Informática) de Ámsterdam, es asignado a un proyectoque consistía en el desarrollo de un sistema operativo distribuidollamado Amoeba. Por aquel tiempo, el CWI utilizaba un lenguaje deprogramación llamado ABC. En lugar de emplear este lenguaje para elproyecto Amoeba, Guido decide crear uno nuevo que pueda superarlas limitaciones y problemas con los que se había encontrado altrabajar en otros proyectos con ABC. Así pues, es esta la principalmotivación que dio lugar al nacimiento de Python.La primera versión del lenguaje ve la luz en 1991, pero no es hastatres años después cuando decide publicarse la versión 1.0. Inicialmenteel CWI decidió liberar el intérprete del lenguaje bajo una licencia opensource propia, pero en septiembre de 2000 y coincidiendo con lapublicación de la versión 1.6, se toma la decisión de cambiar la licenciapor una que sea compatible con la licencia GPL ( GNU General PublicLicense). Esta nueva licencia se denominará Python Software FoundationLicense y se diferencia de la GPL al ser una licencia no copyleft. Estehecho implica que es posible modificar el código fuente y desarrollarcódigo derivado sin la necesidad de hacerlo open source. Hasta el momento solo se ha liberado tres versiones principales,teniendo cada una de ellas diversas actualizaciones. En lo que respectaa la versión 2, la última en ser liberada fue la 2.7, en julio de 2010. En el
    • 20. momento de escribir estas líneas, la versión 3 cuenta con laactualización 3.2, liberada en febrero de 2011. Ambas versiones, la de 2y 3, son mantenidas por separado. Esto implica que, tanto la 2.7 comola 3.2 se consideran estables pero, lógicamente, correspondientes adiferentes versiones. ¿Por qué mantener ambas versiones y no seguiruna evolución lógica? La respuesta a esta pregunta es fácil deresponder: Entre ambas versiones existen diferencias que las hacenincompatibles. Posteriormente, nos centraremos en este aspecto,comentando las principales diferencias entre ambas y viendo lasnovedades que supone la versión 3 con respecto a su predecesora.Entre las características de las primeras versiones de Python cabedestacar el soporte de la orientación a objetos, el manejo deexcepciones y el soporte de estructuras de datos de alto nivel, como,por ejemplo, las listas y los diccionarios. Además, desde su desarrolloinicial, se tuvo en cuenta que el código escrito en este lenguaje fuerafácil de leer y de aprender, sin que esto suponga renunciar acaracterísticas y funcionalidades avanzadas.Muchos se preguntan el origen del nombre de este lenguaje deprogramación. Guido van Rossum decidió darle este nombre en honora la serie de televisión Monty Python's Flying Circus, de la cual era fan.Esta es una serie cómica protagonizada por el grupo de humoristas Monty Python, famoso por películas como La vida de Brian o El sentidode la vida. Desde el principio de su diseño, se pretendía que Pythonfuera un lenguaje que resultara divertido de utilizar, de ahí que en elnombre influyera la mencionada serie cómica. También resulta curiosoque, tanto en tutoriales, como en ejemplos de código, se suelan utilizarreferencias a los Monty Python. Por ejemplo, en lugar de emplear lostradicionales nombres de variables foo y bar, se suele utilizar spam y egss, en referencia a sketchs de este grupo de cómicos.El desarrollo y promoción de Python se lleva a cabo a través de unaorganización, sin ánimo de lucro, llamada Python Software Foundation, que fue creada en marzo de 2001. Entre las actividades que realiza estaorganización destacan el desarrollo y distribución oficial de Python, lagestión de la propiedad intelectual del código y documentosrealizados, así como la organización de conferencias y eventosdedicados a poner en contacto a todas aquellas personas interesadasen este lenguaje de programación.Python tiene un claro carácter open source y la Python Software
    • 21. Foundation invita, a cualquiera que quiera hacerlo, a contribuir aldesarrollo y promoción de este lenguaje de programación. Aquelloslectores interesados en contribuir pueden echar un vistazo a la páginaoficial dedicada a la comunidad de Python (ver referencias). Principales características No hay duda de que a la hora de elegir un lenguaje es muyimportante conocer sus características. Ver qué nos puede ofrecerresulta determinante para tomar la decisión adecuada. Son muchas lasempresas que se plantean esta cuestión a la hora de elegir un lenguajede programación para un determinado proyecto. Esto también esextrapolable a proyectos open source o aquellos proyectos personalesque requieren del uso de un lenguaje de programación. Ya sabemosque Python es un lenguaje de propósito general, dinámico einterpretado. Sin embargo, Python puede ofrecernos mucho más, tal ycomo descubriremos a continuación.Dos de las principales características del lenguaje Python son, porun lado que es interpretado y, por otro lado, que es multiplataforma. Lo primero significa que no es necesario compilar el código para suejecución, ya que existe un intérprete que se encarga de leer el ficherofuente y ejecutarlo. Gracias a este funcionamiento es posible ejecutarel mismo código en distintas plataformas y sistemas operativos sinnecesidad de cambiar el código fuente, bastará con tener instalado elintérprete. Eso sí, la versión de este intérprete es nativa para cadaplataforma. En este sentido, Python es similar a Perl o a Ruby y difierede otros lenguajes como C++ y Objective-C.Habitualmente, a los programas en Python se les denomina scripts. En realidad, script es el término que se suele emplear para los ficherosde código fuente escritos en Python, pudiendo un programa contarcon uno o más de estos scripts.Los programadores de Python suelen llamar indistintamente coneste nombre tanto al lenguaje como al intérprete del mismo.Deberemos tener esto en cuenta, debido a que es habitual escuchar"voy a instalar Python" o "la versión que tengo instalada de Python esla 3.2". En estos casos se hace referencia directa al intérprete y no al
    • 22. lenguaje.La interacción con el intérprete del lenguaje se puede hacerdirectamente a través de la consola. Tal y como veremosposteriormente, durante la instalación de Python, se instala uncomponente llamado shell o consola que permite ejecutardirectamente código Python a través de una terminal o interfaz decomandos. En lo que respecta a la sintaxis del lenguaje cabe destacar susimplicidad; es decir, gracias a la misma, es sencillo escribir código quesea fácil de leer. Este factor es muy importante, ya que, además defacilitar el aprendizaje del lenguaje, también nos ayuda a que nuestrocódigo sea más fácil de mantener.Python carece de tipos propiamente dichos, es decir, es un lenguajecon tipado dinámico. Los programadores de C++ y Java estánacostumbrados a declarar cada variable de un tipo específico. Esteproceso no es necesario en Python, ya que el tipo de cada variable sefija en el momento de su asignación. Como consecuencia de estehecho, una variable puede cambiar su tipo durante su ciclo de vida sinnecesidad explícita de ser declarado. Dado que puede ser interesanteconsultar el tipo de una variable en un momento dado, Python nosofrece una serie de funciones que nos dan este tipo de información.Además de soportar la orientación a objetos, Python también nospermite utilizar otros paradigmas de programación, como, porejemplo, la programación funcional y la imperativa. En la actualidad,Python es considerado uno de los lenguajes que más facilidadesofrecen para enseñar programación orientada a objetos. A estocontribuyen su sintaxis, los mecanismos de introspección queincorpora y el soporte para la implementación de herencia sencilla ymúltiple.Con respecto a su sintaxis, una de las diferencias más destacableses el uso de la indentación. Diferentes niveles de indentación sonutilizados para marcar las sentencias que corresponden al mismobloque. Por ejemplo, todas las sentencias que deban ser ejecutadasdentro de un bloque if llevarán el mismo nivel de indentación, mientrasque el resto utilizarán un nivel diferente, incluida la sentencia quecontiene la condición o condiciones del mencionado if. Además, cadasentencia no necesita un punto y coma (;), como sí ocurre en lenguajescomo C/C++, PHP y Java. En Python basta con que cada sentencia vaya
    • 23. en una línea diferente. Por otro lado, tampoco se hace uso de las llaves ({}) para indicar el principio y fin de bloque. Tampoco se empleanpalabras clave como begin y end. Simplemente se utilizan los dospuntos (:) para marcar el comienzo de bloque y el cambio deindentación se encarga de indicar el final.Para facilitar la programación, Python incluye una serie deestructuras de datos de alto nivel, como son, por ejemplo, las listas, losdiccionarios, cadenas de texto (strings), tuplas y conjuntos. Por otrolado, su librería estándar incorpora multitud de funciones que puedenser utilizadas en diversos ámbitos, entre ellas podemos mencionar,desde aquellas básicas para manejar strings, hasta las que pueden serusadas en programación criptográfica, pasando por otros de nivelintermedio, como son las que permiten manejar ficheros ZIP, trabajarcon ficheros CSV o realizar comunicaciones de red a través de distintosprotocolos estándar. Todo ello, sin necesidad de instalar libreríasadicionales. Comúnmente, se emplea la frase batteries included pararesaltar este hecho.A diferencia de lenguajes compilados, como C++, en Python existeun recolector de basura (garbage collector). Esto significa que no esnecesario pedir y liberar memoria, de forma explícita, para crear ydestruir objetos. El intérprete lo hará automáticamente cuando seanecesario y el recolector se encargará de gestionar la memoria paraevitar los temidos memory leaks. Otro de los aspectos interesantes del lenguaje es su facilidad parainteractuar con otros lenguajes de programación. Esto es posiblegracias a los módulos y extensiones. ¿Cuándo puede ser útil esto?Supongamos que ya contamos con un programa en C++ que seencarga de realizar, por ejemplo, una serie de complejas operacionesmatemáticas. Por otro lado, estamos realizando un desarrollo enPython y nos damos cuenta que sería interesante contar con lafuncionalidad que nos ofrece el mencionado programa en C++. Enlugar de reescribir este programa en Python, podemos comunicarambos a través de la interfaz que Python incorpora para ello.Existen diversas implementaciones del intérprete de Python, esdecir, el código escrito en Python puede ejecutarse desde diferentessistemas preparados para ello. La implementación más popular es lallamada CPython, escrita en el lenguaje de programación C, aunqueexisten otras como Jython, la cual está desarrollada en el lenguaje Java,
    • 24. e IronPython, que permite la ejecución en la plataforma .NET deMicrosoft. El siguiente apartado lo dedicaremos a la instalación delintérprete de Python implementado en CPython y para la cual existenversiones para diferentes sistemas operativos.
    • 25. INSTALACIÓN A continuación, nos centraremos en la instalación del intérprete dePython y sus herramientas asociadas en las tres familias más popularesde sistemas operativos. Dentro de las mismas y en concreto,explicaremos el proceso de instalación en Windows, Mac OS X y lasprincipales distribuciones de GNU/Linux. Windows Para la instalación de Python en Windows recurriremos al programade instalación ofrecido desde el sitio web oficial (ver referencias) deeste lenguaje de programación. En concreto, accederemos a la páginaprincipal de descargas (ver referencias) y haremos clic sobre el enlaceque referencia a la última versión liberada de Python 3. Dicho enlacenos llevará a una nueva página web donde se nos ofrecen una serie deficheros, tanto binarios, como fuentes, para diferentes sistemasoperativos y arquitecturas de procesador. Antes de continuar esconveniente averiguar si nuestro Windows 7 es de 32 o de 64 bits. Lamayoría de fabricantes de PC instalan la versión de este sistemaoperativo en función del tipo de arquitectura que incorpora elprocesador de la máquina en cuestión. Los actuales PC suelen contarcon procesadores de 64b. Podemos comprobar qué tipo de sistemaoperativo tiene instalado nuestro PC accediendo a la opción de menú Panel de Control > Sistema, apartado Tipo de sistema. Una vez queconocemos este dato, podemos volver a la página web de descargas ybuscar el enlace para el fichero de instalación de Python quecorresponde a Windows y al tipo de arquitectura de nuestro PC. Porejemplo, si contamos con un sistema de 64b, haremos clic sobre Windows x86-64 MSI Installer (3.2.2). Automáticamente comenzará ladescarga del fichero binario apuntado por el enlace, que no es otroque un programa de instalación guiado a través de un asistente o wizard.
    • 26. Figura 1-2. Selección de la instalación de Python para todos losusuarios o solo para el actual Al finalizar la descarga del programa de instalación, haremos dobleclic sobre el mismo para comenzar el proceso de instalaciónpropiamente dicho. El primer cuadro de diálogo (figura 1-2) nospregunta si deseamos realizar la instalación para todos los usuarios delsistema o solamente para el usuario que está ejecutando el asistente.Por defecto aparece seleccionada la primera opción.Pulsando sobre el botón Next accederemos al siguiente paso, elcual nos pide seleccionar el directorio donde serán instalados losficheros (figura 1-3).
    • 27. Figura 1-3. Selección del directorio base para de la instalaciónde Python Avanzamos un paso más y se nos ofrece la personalización de lainstalación, siendo posible elegir qué componentes deseamos instalar(figura 1-4). Salvo que tengamos muy claro cómo hacer esta selección,es recomendable utilizar las opciones marcadas por defecto. Al pulsarsobre el botón Next se procederá a la copia de ficheros al disco duro yal finalizar este proceso veremos un mensaje informándonos de ello.Por último, el asistente nos pide reiniciar el PC para completar lainstalación.Comprobar si la instalación de Python 3 se ha realizadocorrectamente en nuestro Windows es sencillo, basta con acceder almenú de inicio y teclear python en el cuadro de diálogo para buscarprogramas. Como resultado de la búsqueda nos deben aparecer variosprogramas, entre ellos, IDLE (Python GUI) y Python (command line). Elprimero nos da acceso a una interfaz de comandos, en modo gráfico,donde podemos interactuar con el intérprete del lenguaje. El segundonos permite abrir la misma interfaz pero en modo consola, como silanzáramos un comando a través de la interfaz de comandos deWindows invocada a través del comando cmd.
    • 28. Figura 1-4. Personalización de la instalación de Python La interfaz gráfica presenta algunas ventajas funcionales conrespecto a la textual, por ejemplo, el resaltado de sintaxis del código, elautocompletado de palabras clave o la opción de utilizar undepurador. En ambas interfaces de comandos, observaremos cómo enla primera línea aparece el número de versión del intérprete de Pythonque tenemos instalado y que estamos usando.En realidad, IDLE es algo más que una interfaz gráfica parainteractuar con el intérprete de Python, ya que es un sencillo, perofuncional entorno integrado de desarrollo. De ahí, que cuenta concaracterísticas ya comentadas, como la posibilidad de depurar código.También es posible editar ficheros y ejecutarlos directamente. Paramás información sobre las características de este entorno dedesarrollo, recomendamos echar un vistazo a la documentación oficialsobre el mismo (ver referencias).Esta interfaz de comandos del intérprete de Python nos será muyútil para llevar a cabo nuestra primera práctica toma de contacto con ellenguaje. Además, podemos recurrir a ella siempre que lo
    • 29. necesitemos,para, por ejemplo, probar ciertas líneas de código o sentencias de
    • 30. control.Obviamente, además de la mencionada interfaz de comandos, elintérprete de Python ha sido instalado. Esto significa que podemoscrear un fichero de texto con código Python, salvarlo con la extensión .py y ejecutarlo haciendo clic sobre el mismo. Mac OS X El sistema operativo de Apple incluye Python preinstalado de serie.En concreto, la versión Lion (10.7) incorpora la versión 2.7 de Python,mientras que su predecesora, llamada Snow Leopard, cuenta, pordefecto, con la versión 2.6. Sin embargo, para utilizar Python 3 ennuestro Mac deberemos instalarlo. Para ello, basta con recurrir albinario de instalación ofrecido desde la página web de descargas delsitio oficial de Python. Desde esta página se ofrecen dos binariosdiferentes: uno para Mac OS X 10.6 y 10.7, para ordenadores conprocesador Intel, y otro específico para la arquitectura de procesador PPC. Haciendo clic sobre el correspondiente enlace, deberemos elegiren función del sistema que tenga instalado nuestro Mac, se procederáa la descarga de un fichero DMG, el cual podemos ejecutar una vezdescargado. Para ello, bastará con hacer clic sobre el mismo. Seráentonces cuando se abrirá una nueva ventana en Finder que nosmostrará una serie de archivos (figura 1-5). Figura 1-5. Ficheros contenidos en la imagen DMG delinstalador de Python Haciendo doble clic sobre el fichero Python.mpkg se lanzará elasistente que nos guiará en el proceso de instalación. La primeraventana que aparece nos describe los programas que van a serinstalados, nos invita a leer el fichero ReadMe.text y nos propone
    • 31. continuar a través del botón Continue. En el siguiente paso del asistente se nos solicita que indiquemos launidad de disco donde se va a realizar la instalación. Después depulsar el botón para continuar el proceso, el software será instalado enla ubicación seleccionada. Finalmente, aparecerá un mensajeindicándonos que la instalación se ha realizado correctamente.Al abrir una ventana del Finder y acceder a Aplicaciones, observaremos que tenemos una nueva carpeta llamada Python 3.2. Dentro de la misma aparecen varios archivos. Entre ellos IDLE, unfichero HTML de documentación y un script que nos permitirá fijar laversión 3.2 de Python como el intérprete por defecto, sustituyendo asía la versión 2 que Apple incluye por defecto en su sistema operativo. Figura 1-6. Pantalla inicial del asistente para la instalación dePython Aquellos programadores de Mac, acostumbrados a utilizar lainterfaz de comandos, pueden lanzar Terminal y ejecutar el comando python3.2. Este comando invocará al intérprete del lenguaje y nospermitirá utilizar la terminal para interactuar con él.
    • 32. Figura 1-7. Selección del disco para la instalación de Python Linux La mayoría de las distribuciones de GNU/Linux, como, por ejemplo,Ubuntu, Fedora y Debian, incluyen e instalan Python por defecto.Algunas de ellas utilizan la versión 2.6, mientras que otras se decantanpor la 2.7. La instalación de Python 3 en Linux es sencilla, ya que lasmencionadas distribuciones incluyen paquetes binarlos listos para suinstalación. En función de la distribución que estemos utilizando, bastacon emplear una de las herramientas de instalación de software con lasque cuenta específicamente cada una de ellas. Por ejemplo, en Ubuntu11.10 basta con acceder al Centro de Software y realizar una búsquedapor python3. Entre los resultados de la búsqueda, veremos queaparecerá un paquete llamado python3, haciendo doble clic sobre elmismo se procederá a la instalación. Si preferimos utilizar la interfaz decomandos, bastará con lanzar una consola y ejecutar el siguientecomando: $ sudo apt-get install python3 En distribuciones de GNU/Linux basadas en paquetes con formato RPM, como, por ejemplo, Fedora, lanzaremos el siguiente comando,como usuario root, desde una terminal: # yum install python3 Una vez que finalice la instalación, con independencia de ladistribución que estemos utilizando, bastará con acceder a la línea decomandos y lanzar el
    • 33. siguiente comando para comenzar a utilizar la
    • 34. consola interactiva del intérprete de Python: $ python3 Al contrario que en Mac y en Windows, para utilizar IDLE en Linuxdeberemos instalar el correspondiente binario ofrecido por nuestradistribución. El nombre del paquete binario en cuestión se llama idle3en Ubuntu. En el caso de Fedora, será necesario instalar un paquetellamado python3-tools. Sin embargo, el nombre del ejecutable paraambas distribuciones es idle3, lo que significa que, lanzandodirectamente este comando desde la consola podemos disfrutar deeste entorno integrado de desarrollo.Debemos tener en cuenta que la invocación al comando python seguirá lanzando la versión 2 del intérprete. Si deseamos cambiar estecomportamiento, podemos crear un enlace simbólico para que elcomando python apunte directamente a la versión 3. Para ello bastaejecutar, como usuario root, los siguientes comandos: # mv /usr/bin/python /usr/bin/python2# ln -s /usr/bin/python3 /usr/bin/python De esta forma, con el comando python2 estaremos invocando a laversión 2 del intérprete, y python será el encargado de lanzar la versión3. Como el lector habrá podido averiguar, es posible disponer de dosversiones diferentes del intérprete en la misma máquina.
    • 35. HOLA MUNDO La primera toma de contacto práctica con el lenguaje larealizaremos a través del famoso Hola Mundo. Comenzaremoslanzando la interfaz de comandos del intérprete de Python.Dependiendo del sistema operativo que estemos utilizando,accederemos a la mencionada interfaz de forma diferente. Porejemplo, en Linux comenzaremos abriendo una shell y lanzando elcomando python. En Mac OS X procederemos de la misma forma através del programa Terminal. Los usuarios de Windows puedenacceder al menú Inicio y buscar el programa IDLE. Nada más lanzar la interfaz de comandos del intérprete, tambiénllamado intérprete interactivo, comprobaremos que aparece unmensaje inicial con el número de versión del intérprete y una serie deinformación adicional que hace referencia a la plataforma donde estásiendo ejecutado. Justo en la línea siguiente aparece otro mensaje quenos indica de qué forma podemos acceder a la información sobre lalicencia del intérprete. La última línea comienza por los caracteres >>> y nos muestra un cursor parpadeando. Este es el prompt del intérpreteque nos permite interactuar directamente con él. Por ejemplo, sitecleamos copyright y pulsamos enter, veremos cómo se lanzainformación sobre el copyright de Python y después, vuelve a aparecerel prompt, invitándonos a lanzar otro comando o sentencia.A lo largo de este libro, los ejemplos de código que comiencen porlos mencionados caracteres >>> representarán sentencias que puedenser lanzadas directamente en el intérprete. Si debajo de la mismaapareciera otra más, sin los caracteres >>>, esta hará referencia alresultado obtenido como consecuencia de la ejecución en el intérpretede la línea de código correspondiente.Como es tradicional, cuando se está aprendiendo un lenguaje deprogramación, nuestras primeras líneas de código imprimirán enpantalla el mensaje Hola Mundo. Para ello, desde el prompt delintérprete escribiremos el siguiente comando y pulsaremos enter: >>> print ("Hola Mundo")
    • 36. Veremos, entonces, cómo aparece el mencionado mensaje en lasiguiente línea y después volverá a aparecer el prompt del intérprete.Obviamente, no hace falta teclear los caracteres >>>, ya que estosaparecen por defecto y nos indican que el prompt se encuentra enespera y listo para que tecleemos y ejecutemos nuestro código.A pesar de que la interfaz del intérprete es muy práctica y nospuede servir para realizar pruebas, habitualmente, nuestro código seráejecutado desde un fichero de texto. Siguiendo con nuestro ejemplo,crearemos un nuevo fichero con nuestro editor de textos favorito alque añadiremos la misma línea de código que hemos ejecutado desdeel intérprete (sin añadir los caracteres >>>). Lo salvaremos con elnombre hola.py. Efectivamente, la extensión .py es la que se utiliza paralos ficheros de código Python. Seguidamente, los usuarios de Mac OSX y Linux pueden invocar directamente al intérprete desde la shell odesde Terminal: $ python hola.py El resultado aparecerá directamente en la siguiente línea, cuando elcomando finalice su ejecución. Los usuarios de Windows tendrán quehacer un poco de trabajo extra para ejecutar el mismo comando. Estose debe a que, por defecto, el ejecutable del intérprete de Python nose añade a la variable de entorno PATH, como sí ocurre en los sistemasoperativos basados en UNIX. Así pues, para modificar el valor de estavariable, en Windows, accederemos a Panel de control > Sistema >Configuración avanzada del sistema y pulsaremos el botón Variables deentorno... de la pestaña Opciones Avanzadas. Dentro de Variables delsistema, localizaremos la variable Path y haremos clic sobre el botón Editar.... Aparecerá una nueva ventana que nos permite modificar elvalor de la variable, al final de la línea añadiremos el directorio dondese encuentra el ejecutable del intérprete de Python. Por defecto, estedirectorio es C:/Python32. Una vez realizada esta configuración, bastarácon lanzar el comando cmd para poder acceder a la shell del sistema einvocar directamente al comando, igual que en Mac OS X y en Linux.Asimismo, si deseamos utilizar directamente la interfaz de comandosen Windows, sin invocar a IDLE, podemos hacerlo desde la misma cmd, tecleando python.
    • 37. Código fuente y bytecode Hasta ahora solo hemos hablado de los ficheros de código Python,que utilizan la extensión .py. También sabemos que este lenguaje esinterpretado y no compilado. Sin embargo, en realidad, internamenteel intérprete Python se encarga de generar unos ficheros binarios queson los que serán ejecutados. Este proceso se realiza de formatransparente, a partir de los ficheros fuente. Al código generadoautomáticamente se le llama bytecode y utiliza la extensión .pyc. Asípues, al invocar al intérprete de Python, este se encarga de leer elfichero fuente, generar el bytecode correspondiente y ejecutarlo. ¿Porqué se realiza este proceso? Básicamente, por cuestiones de eficiencia.Una vez que el fichero .pyc esté generado, Python no vuelve a leer elfichero fuente, sino que lo ejecuta directamente, con el ahorro detiempo que esto supone. La generación del bytecode es automática yel programador no debe preocuparse por este proceso. El intérprete eslo suficientemente inteligente para volver a generar el bytecodecuando es necesario, habitualmente, cuando el fichero de códigocorrespondiente cambia.Por otro lado, también es posible generar ficheros binarios listospara su ejecución, sin necesidad de contar con el intérprete.Recordemos que los ficheros Python requieren del intérprete para serejecutados. Sin embargo, en ocasiones necesitamos ejecutar nuestrocódigo en máquinas que no disponen de este intérprete. Este casosuele darse en sistemas Windows, ya que, por defecto, tanto Mac OS X,como la mayoría de las distribuciones de GNU/Linux, incorporan dichointérprete. Para salvar este obstáculo contamos con programas como py2exe (ver referencias), que se encarga de ejecutar un binario paraWindows (.exe) a partir de un fichero fuente escrito en Python.
    • 38. HERRAMIENTAS DE DESARROLLO Uno de los factores importantes a tener en cuenta, a la hora deabordar el desarrollo de software, es el conjunto de herramientas conel que podemos contar para realizar el trabajo. Con independencia dela tecnología y el lenguaje, existen diferentes tipos de herramientas dedesarrollo de software, desde un sencillo editor de texto, hastacomplejos depuradores, pasando por entornos integrados dedesarrollo que ofrecen bastantes funcionalidades en un solo programa.Python no es una excepción y cuenta con diferentes herramientas dedesarrollo que nos ayudarán a ser más productivos.Dado que entrar en profundidad, en cada una de las herramientasde desarrollo que podemos utilizar para trabajar con Python, escapa alámbito de este libro, nos centraremos en mencionar y describir las máspopulares. El objetivo es que el lector tenga un punto de referenciasobre las mismas y no se encuentre perdido a la hora de elegir.Por funcionalidad hemos realizado una agrupación en categorías.En concreto, se trata de editores, entornos integrados de desarrollo,depuradores, herramientas de profiling y entornos virtuales. Editores Podemos considerar a los editores de texto como las herramientasbásicas para desarrollar software, ya que nos permiten escribir elcódigo fuente y crear un fichero a partir del mismo.Dentro de este grupo, existen multitud de programas, desde losbásicos como Bloc de Notas, hasta aquellos más complejos como Vimo TextMate. Aunque cualquier editor de texto es válido para escribircódigo, es interesante que este cuente con ciertas funcionalidades quenos hagan el trabajo más fácil. Por ejemplo, el resaltado de sintaxis (syntax highlighting), la búsqueda utilizando expresiones regulares, laautoindentación, la personalización de atajos de teclado o lanavegación de código, resultan muy prácticas a la vez que nos ayudana mejorar la productividad.
    • 39. En la actualidad existen multitud de editores de texto queincorporan otras muchas funcionalidades, además de las mencionadasanteriormente, que nos serán muy válidos para escribir código Python.Algunos son multiplataforma, mientras que otros solo existen para unsistema operativo concreto. Vim y Emacs son los editores más populares en el mundo UNIX y delos cuales podemos encontrar versiones para Mac OS X, Linux yWindows. En realidad, muchos consideran a ambos mucho más que uneditor de texto, ya que ambos se pueden personalizar ampliando susfuncionalidades hasta convertirlos en un moderno entorno integradode desarrollo. En la red existen multitud de recursos (ver referencias)que podemos añadir a ambos editores para convertirlos enherramientas imprescindibles para desarrollar aplicaciones en Python.Aunque Vim y Emacs son muy potentes, deberemos tener en cuentaque ambos tienen una curva de aprendizaje elevada.Muchos desarrolladores que trabajan en Mac OS X estánhabituados a TextMate (ver referencias). Se trata de un potente editorque también cuenta con útiles herramientas para Python. Este editorno es open source y deberemos adquirir una licencia para su uso.Distribuciones de Linux, como Ubuntu y Fedora, instalan pordefecto un sencillo y práctico editor que también podemos utilizarpara Python. Su nombre es gedit y su funcionalidad puede serampliada a través de plugins. Otro editor de código digno de mención es Notepad++. Sedistribuye bajo la licencia GPL, aunque solo existe una versión parasistemas Windows. Entornos integrados de desarrollo (IDE) La evolución natural de los editores de código son los entornosintegrados de desarrollo. Estos amplían la funcionalidad de los editoresañadiendo facilidades para la depuración de código, la creación deproyectos, el auto completado, la búsqueda de referencias en ladocumentación o el marcado de sintaxis errónea. Dos de los máspopulares son Eclipse y NetBeans. Aunque se hicieron populares para eldesarrollo Java, actualmente, ambos soportan Python como lenguaje y
    • 40. ofrecen funcionalidades específicas para él mismo. Entre las ventajasde estos dos IDE caben destacar su carácter open source, la grancomunidad de usuarios con la que cuentan y que existen versionespara distintas plataformas. Por otro lado, la dependencia del runtime de Java y el consumo de recursos hardware son algunas de susdesventajas.Aunque menos conocido, Komodo es otra de las opciones.Desarrollado por la empresa ActiveState, es multiplataforma, noconsume demasiados recursos y ofrece bastantes prácticasfuncionalidades. A diferencia de Eclipse y NetBeans, no es open source y requiere del pago de una licencia para su uso. No obstante, existeuna versión más limitada en funcionalidades, llamada Komodo Edit yque sí es gratuita y open source. En lo que respecta a algunos IDE específicos para Python, son treslos más populares. El primero de ellos es fríe, que está escrito enPython utilizando el toolkit gráfico Qt. La última versión de este IDE esla 5 y requiere de Python 3 para su ejecución. Por otro lado tenemos a PyCharm, desarrollado por la empresa JetBrains y caracterizado portener un amplio soporte para el desarrollo para Django, el popularframework web de Python. Wingware es el tercero de este grupo yentre sus características cabe destacar el soporte para populares toolkits y frameworks para Python, como son, Zope, PyQt, PyGTK, Django y wxPython. Intérprete interactivo mejorado A pesar de que el intérprete interactivo estándar de Python es muypráctico para ejecutar código escrito en este lenguaje sin necesidad decrear fichero, tiene algunas carencias. Por ejemplo, no es posible usarel tabulador para autocompletar código, no numera las líneas decódigo que se van escribiendo, no contiene una ayuda interactiva y nopermite la introspección dinámica de objetos. Con el objetivo dedisponer de una herramienta, similar al intérprete interactivo estándar,pero que pudiera suplir las carencias de este, se desarrolló IPython. Esta herramienta puede ser utilizada como sustituta del mencionadointérprete, el cual está incluido en la instalación estándar de Python.
    • 41. La instalación de IPython puede realizarse como si de un módulo dePython más se tratara, siendo, pues, posible su utilización en diferentessistemas operativos. Recomendamos leer el capítulo 9 (Instalación ydistribución de módulos) para realizar la instalación a través del gestorde paquetes pip.IPython puede facilitarnos en gran medida el trabajo de desarrollo yes recomendable su utilización como intérprete interactivo, sobre todopara aquellos programadores avanzados de Python.Para más información sobre las características, método deinstalación y documentación en general sobre IPython, podemos visitarla página web (ver referencias) que existe a tal efecto. Depuradores La acción de depurar código es de gran ayuda a la hora de resolver bugs. Dentro del proceso de desarrollo de software es una de las tareasmás habituales llevadas a cabo por los programadores.¿En qué consiste la depuración? Básicamente se trata de seguirpaso a paso la ejecución de un programa o una parte del mismo.Contar con una herramienta automática que nos ayude a ello, resultaimprescindible. Al igual que para otros lenguajes, para Pythoncontamos con la herramienta llamada pdb que soporta la fijación de breakpoints, el avance paso a paso, la evaluación de expresiones yvariables y el listado del código actual en ejecución. Esta utilidadpuede ser invocada directamente desde la interfaz del intérprete dePython o a través del ejecutable python. El funcionamiento básico de pdb es sencillo. Podemos comenzarpor fijar un breakpoint en un punto determinado de nuestro códigofuente. Esto se realiza a través de dos sencillas líneas de código: import pdbpdb.set_trace() Al lanzar pdb y llegar al punto donde hemos puesto el breakpoint,entrará en marcha el depurador, parando la ejecución del programa yesperando, a través del prompt, para que introduzcamos un comandoque nos permita, por ejemplo, evaluar una variable o continuar la
    • 42. ejecución del programa paso a paso. El lanzamiento de pdb paranuestro script de ejemplo se haría de la siguiente forma: $ python -m pdb hola.py Para una referencia completa sobre los comandos que puedenlanzarse desde el prompt ofrecido por pdb, recomendamos visitar lapágina web oficial (ver referencias) de este depurador. Profiling En ingeniería de software, un profiler es un programa que mide elrendimiento de la ejecución de otro programa, ofreciendo una serie deestadísticas sobre dicho rendimiento. Este tipo de herramientas es muyútil para mejorar un determinado programa, debido a que lainformación que nos proporciona es difícil obtenerla de otra manera.Además, en ocasiones se da la circunstancia de que durante eldesarrollo es muy complicado predecir que partes de una aplicacióncontribuirán a bajar su rendimiento. Para averiguar cuáles son lassecciones o componentes de código, tendremos que esperar al tiempode ejecución y es aquí donde los profilers realizan su trabajo.Dentro de la librería estándar de Python contamos con tres profilers diferentes: cProfile, profile y hotshot. El primero de ellos fue introducidoen la versión 2.5 y es el más recomendado, tanto por su facilidad deuso, como por la información que nos ofrece. Por otro lado, profile estáescrito en Python, es más lento que cProfile y además su funcionalidadestá limitada a este. El uso de hotshot no es aconsejable paraprincipiantes, dado que es experimental, además hemos de tener encuenta que será eliminado en futuras versiones del intérprete.El uso básico de cProfile es bastante sencillo, bastará con invocar alintérprete de Python pasando un parámetro específico, seguido delprograma que deseamos comprobar. Por ejemplo, hagámoslo connuestro primer programa: $ python -m cProfile hola.py Como salida de la ejecución del comando anterior, obtendremos losiguiente:
    • 43. Hola Mundo8 function calls in 0.000 secondsOrdered by: standard namencalls tottime percall cumtime percall filename:lineno(function)2 0.000 0.000 0.000 0.000 cp850.py:18(encode)1 0.000 0.000 0.000 0.000 hola.py:1(<module>)2 0.000 0.000 0.000 0.000 {built-inmethod charmap_encode}1 0.000 0.000 0.000 0.000 {built-inmethod exec}1 0.000 0.000 0.000 0.000 {built-inmethod print}1 0.000 0.000 0.000 0.000 {method 'disable' of'_lsprof.Profiler' objects} Dado que nuestro programa ejemplo es muy sencillo, noobtendremos valiosa información, pero sí que nos servirá paradescubrir cómo funcionan este tipo de herramientas.Otra herramienta que podemos utilizar para hacer profiling es elmódulo timeit, el cual nos permite medir el tiempo que tarde enejecutarse una serie de líneas de código. Esta herramienta forma partede la librería estándar de Python, lo que significa que no tenemos querealizar ninguna instalación adicional.
    • 44. NOVEDADES EN PYTHON 3 La última versión de Python trae consigo una serie de clarasnovedades y diferencias con respecto a la serie 2.x. Aquellosprogramadores de Python 2, que deseen migrar sus aplicaciones paraque funcionen en la versión 3, deberán tener en cuenta estasdiferencias. A continuación, resumiremos las más significativas, loslectores no familiarizados con Python pueden pasar por alto esteapartado y saltar hacia el siguiente capítulo.En lo que respecta a los strings, el cambio más significativo es queen la versión 3 todos son Unicode. Como consecuencia de ello, se hasuprimido la función unicode(). Además, el operador %, utilizado parala concatenación de strings, ha sido reemplazado por la nueva función format(). Así pues, por ejemplo, la siguiente sentencia en Python 2: >>> cad = "%s %s" % (cadl, cad2) Pasa a ser de esta forma en Python 3: >>> cad = "{o} {1}".format(cadl, cad2) Otra nueva función introducida en Python 3 es print(), siendo ahoranecesario utilizar paréntesis cuando la invocamos. Igualmente ocurrecon la función exec(), utilizada para ejecutar código a través de unobjeto. Relacionada con esta funcionalidad, en Python 3 ha sidoeliminada execfile(). Para simular su funcionamiento, deberemos leerun fichero línea a línea y ejecutar exec() para cada una de ellas.En Python 2.x la operación aritmética para realizar la división exactadebe hacerse entre dos números reales, utilizando para ello eloperador /. Sin embargo, en la nueva versión de Python esta operaciónpuede hacerse directamente con números enteros. Para la divisiónentera utilizaremos el operador //. Veamos unos ejemplos al respecto.La siguiente sentencia nos devolverá el número real 3.5 en Python 2.x: >>> 7.0 / 2.0 La misma operación puede realizarse en Python 3:
    • 45. >>> 7 / 2
    • 46. Por otro lado, para la división entera, en Python 3, ejecutaríamos elsiguiente comando, siendo el resultado 3: >>> 7 // 2 La representación de números en octal (base 8) ha sido tambiéncambiada en la nueva versión de Python. Ahora se debe poner la letrao justo detrás del 0 y antes del número que va a ser representado. Esdecir, la siguiente expresión ha dejado de ser válida en Python 3: >>> x = 077 En su lugar debe emplear esta otra: >>> x = 0o77 Si implementamos clases iterator, deberemos escribir un método __next __ (), esto implica que no podremos utilizar el método next() denuestra clase. Así pues, en Python 3, invocaremos directamente almencionado método pasando como argumento la clase iterator. Con respecto a los diccionarios, la forma de iterar entre sus claves yvalores ha cambiado. Ahora las funciones iterkeys(), iteritems() y itervalues() no son necesarias, en su lugar emplearemos las funciones keys(), ítems() y values(), respectivamente. Para comprobar si una clavese encuentra en un diccionario, en lugar de invocar a la función has_key(), bastará con preguntar directamente a través del operador if: >>> if mykey in mydict: print("Clave en diccionario") Si trabajamos con comprensión de listas y dentro de ellas usamostuplas, estas deberán, en Python 3, ir entre paréntesis. Además, lafunción sorted() devuelve directamente una lista, sin necesidad deconvertir su argumento a este tipo de dato. Para emplear esta funciónde ordenación deberemos tener en cuenta que la lista o tupla debecontener elementos del mismo tipo. En Python 3 la función sorted() y elmétodo sort() devolverán una excepción si los elementos que van a serordenados son de diferentes tipos.La librería estándar ha reemplazado los nombres de algunosmódulos, lo que significa
    • 47. que debemos tenerlo en cuenta a la hora deutilizar la sentencia import. Por ejemplo, el módulo Cookie ha sidorenombrado a http.Cookies . Otro ejemplo es httplib que ahora se
    • 48. encuentra dentro de http y se llama client (import http.Client) .Las excepciones que capturan un objeto, en Python 3, requieren dela palabra clave as. De esta forma, escribiremos: try: myfun()except ValueError as myerror:print(err) En relación también con las excepciones, para invocar a raise conargumentos necesitaremos paréntesis en la llamada. Además, los strings no pueden ser usados como excepciones. Si necesitamos deesta funcionalidad, podemos escribir: raise Exception("Ha ocurrido un error") La nueva versión del lenguaje no solo nos permite desempaquetar diccionarios, también podemos hacerlo con conjuntos. Por ejemplo, lasiguiente sentencia nos devuelve el valor 1 para la variable a y una listacon los valores 2 y 3 para la variable b: a, *b = (1, 2, 3) Para migrar nuestro código de una versión a otra, existe unaherramienta llamada 2to3 (ver referencias). Gracias a ella,automáticamente podemos obtener una versión de nuestro códigocompatible con Python 3. Si bien es cierto, que esta herramienta no esperfecta, es recomendable repasar el código generadoautomáticamente para asegurarnos que el proceso se ha realizadocorrectamente. 2to3.py es un script escrito en Python y que sedistribuye junto al intérprete del lenguaje. Por ejemplo, en Windowspodemos localizarlo en el subdirectorio Tools\Scripts, que se encuentradentro del directorio donde, por defecto, fue instalado el intérprete.Hasta aquí las novedades y diferencias más interesantes entreversiones de este lenguaje. Si estamos interesados en obtener unacompleta referencia de todas las novedades de Python 3, podemosechar un vistazo a la página oficial dedicada a este efecto (verreferencias).
    • 49. ESTRUCTURAS Y TIPOS DE DATOSBÁSICOS INTRODUCCIÓN Realizada una primera toma de contacto con Python en el capítuloanterior, dedicaremos el presente a descubrir cuáles son las estructurasde datos y tipos básicos con los que cuenta este lenguaje.Comenzaremos describiendo y explicando una serie de conceptosbásicos propios de este lenguaje y que serán empleados a lo largo dellibro. Posteriormente, pasaremos a centrarnos en los tipos básicos,como son, los números y las cadenas de texto. Después, llegará elturno de las estructuras de datos como las tuplas, las listas, losconjuntos y los diccionarios.
    • 50. CONCEPTOS BÁSICOS Uno de los conceptos básicos y principales en Python es el objeto. Básicamente, podemos definirlo como un componente que se aloja enmemoria y que tiene asociados una serie de valores y operaciones quepueden ser realizadas con él. En realidad, los datos que manejamos enel lenguaje cobran vida gracias a estos objetos. De momento, estamoshablando desde un punto de vista bastante general; es decir, nodebemos asociar este objeto al concepto del mismo nombre que seemplea en programación orientada a objetos (OOP). De hecho, un objeto en Python puede ser una cadena de texto, un número real, undiccionario o un objeto propiamente dicho, según el paradigma OOP,creado a partir de una clase determinada. En otros lenguajes deprogramación se emplea el término estructura de datos para referirse al objeto. Podemos considerar ambos términos como equivalentes.Habitualmente, un programa en Python puede contener varioscomponentes. El lenguaje nos ofrece cinco tipos de estoscomponentes claramente diferenciados. El primero de ellos es el objeto, tal y como lo hemos definido previamente. Por otro ladotenemos las expresiones, entendidas como una combinación devalores, constantes, variables, operadores y funciones que sonaplicadas siguiendo una serle de reglas. Estas expresiones se suelenagrupar formando sentencias, consideradas estas como las unidadesmínimas ejecutables de un programa. Por último, tenemos los módulos que nos ayudan a formar grupos de diferentes sentencias.Para facilitarnos la programación, Python cuenta con una serie de objetos integrados (built-in). Entre las ventajas que nos ofrecen, cabendestacar, el ahorro de tiempo, al no ser necesario construir estasestructuras de datos de forma manual, la facilidad para crear complejasestructuras basadas en ellos y el alto rendimiento y mínimo consumode memoria en tiempo de ejecución. En concreto, contamos connúmeros, cadenas de texto, booleanos, listas, diccionarios, tuplas,conjuntos y ficheros. Además, contamos con un tipo de objeto especialllamado None que se emplea para asignar un valor nulo. A lo largo deeste capítulo describiremos cada uno de ellos, a excepción de los
    • 51. ficheros, de los que se ocupa el capítulo 7.Antes de comenzar a descubrir los objetos built-in de Python, esconveniente explicar qué es y cómo funciona el tipado dinámico, delque nos ocuparemos en el siguiente apartado. Tipado dinámico Los programadores de lenguajes como Java y C++ estánacostumbrados a definir cada variable de un tipo determinado.Igualmente ocurre con los objetos, que deben estar asociados a unaclase determinada cuando son creados. Sin embargo, Python notrabaja de la misma forma, ya que, al declarar una variable, no sepuede indicar su tipo. En tiempo de ejecución, el tipo será asignado ala variable, empleando una técnica conocida como tipado dinámico. ¿Cómo es esto posible, cómo diferencia el intérprete entrediferentes tipos y estructuras de datos? La respuesta a estas preguntashay que buscarla en el funcionamiento interno que el intérprete realizade la memoria. Cuando se asigna a una variable un valor, el intérprete,en tiempo de ejecución, realiza un proceso que consiste en variospasos. En primer lugar se crea un objeto en memoria que representaráel valor asignado. Seguidamente se comprueba si existe la variable, sino es así se crea una referencia que enlaza la nueva variable con elobjeto. Si por el contrario ya existe la variable, entonces, se cambia lareferencia hacia el objeto creado. Tanto las variables, como los objetos,se almacenan en diferentes zonas de memoria.A bajo nivel, las variables se guardan en una tabla de sistema dondese indica a qué objeto referencia cada una de ellas. Los objetos sontrozos de memoria con el suficiente espacio para albergar el valor querepresentan. Por último, las referencias son punteros que enlazanobjetos con variables. De esta forma, una variable referencia a unobjeto en un determinado momento de tiempo. ¿Cuál es laconsecuencia directa de este hecho? Es sencillo, en Python los tiposestán asociados a objetos y no a variables. Lógicamente, los objetosconocen de qué tipo son, pero no las variables. Esta es la forma con laque Python consigue implementar el tipado dinámico.Internamente, el intérprete de Python utiliza un contador de las
    • 52. referencias que se van asignando entre objetos y variables. En funciónde un algoritmo determinado, cuando estás van cambiando y ya noson necesarias, el recolector de basura se encargará de marcar comodisponible el espacio de memoria ocupado por un objeto que hadejado de ser referenciado.Gracias al tipado dinámico podemos, en el mismo bloque decódigo, asignar diferentes tipos de datos a la misma variable, siendo elintérprete en tiempo de ejecución el que se encargará de crear losobjetos y referencias que sean necesarios.Para clarificar el proceso previamente explicado, nos ayudaremosde un ejemplo práctico. En primer lugar asignaremos el valor numérico 8 a la variable x y seguidamente asignaremos a una nueva variable y elvalor de x: >>> x = 8>>> y = x Después de la ejecución de las sentencias anteriores, en memoriatendríamos una situación como la que muestra la figura 2-1. Fig. 2-1 Variables y valor asignado
    • 53. Posteriormente ejecutamos una nueva sentencia como la siguiente:
    • 54. >>> x = "test" Los cambios efectuados en memoria pueden apreciarse en la figura2-2, donde comprobaremos cómo ahora las variables tienen distintovalor puesto que apuntan a diferentes objetos. Fig. 2-1 Cambio de valores Sin embargo, para algunos tipos de objetos, Python realiza laasignación entre objetos y variables de forma diferente a la que hemosexplicado previamente. Un ejemplo de este caso es cuando se cambiael valor de un elemento dentro de una lista. Supongamos quedefinimos una lista (un simple array o vector) con una serie de valorespredeterminados y después, asignamos esta nueva variable a otradiferente llamada lista_2: >>> lista_1 = [9, 8, 7]>>> lista_2 = lista_1
    • 55. Ahora modificamos el segundo elemento de la primera lista,ejecutando la siguiente sentencia: >>> lista_1[2] = 5
    • 56. Como resultado, ambas listas habrán sido modificadas y su valorserá el mismo. ¿Por qué se da esta situación? Simplemente, porque nohemos cambiado el objeto, sino un componente del mismo. De estaforma, Python realiza el cambio sobre la marcha, sin necesidad decrear un nuevo objeto y asignar las correspondientes referencias entrevariables. Como consecuencia de ello, se ahorra tiempo deprocesamiento y memoria cuando el programa es ejecutado por elintérprete.Una función que nos puede resultar muy útil para ver de qué tipoes una variable es type(). Como argumento recibe el nombre de lavariable en cuestión y devuelve el tipo precedido de la palabra clave class. Gracias a esta función y debido a que una variable puede tomardistintos tipos durante su ejecución, podremos saber a qué tipopertenece en cada momento de la ejecución del código. Veamos unsencillo ejemplo, a través de las siguientes sentencias y el resultadodevuelto por el intérprete: >>> z = 35>>> type(z)<class 'int'>>>> z = "ahora es una cadena de texto" <class 'str'>
    • 57. NÚMEROS Como en cualquier lenguaje de programación, en Python, larepresentación y el manejo de números se hacen prácticamenteimprescindibles. Para trabajar con ellos, Python cuenta con una seriede tipos y operaciones integradas, de ambos nos ocuparemos en elpresente apartado. Enteros, reales y complejos Respecto a los tipos de números soportados por Python, contamoscon que es posible trabajar con números enteros, reales y complejos.Además, estos pueden ser representados en decimal, binario, octal yhexadecimal, tal y como veremos más adelante.De forma práctica, la asignación de un número entero a unavariable se puede hacer a través de una sentencia como esta: >>> num_entero = 8 Por supuesto, en el contexto de los números enteros, la siguienteexpresión también es válida: >>> num_negativo = -78 Por otro lado, un número real se asignaría de la siguiente forma: >>> num_real = 4.5 En lo que respecta a los números complejos, aquellos formados poruna parte real y otra imaginaria, la asignación sería la siguiente: >>> num_complejo = 3.2 + 7j Siendo también válida la siguiente expresión: >>> num_complex = 5J + 3 Como el lector habrá deducido, en los números complejos, la parte
    • 58. imaginaria aparece representada por la letra j, siendo también posibleemplear la misma letra en mayúscula.Python 2.x distingue entre dos tipos de enteros en función deltamaño del valor que representan. Concretamente, tenemos los tipos int y long. En Python 3 esta situación ha cambiado y ambos han sidointegrados en un único tipo int. Los valores para números reales que podemos utilizar en Python 3tienen un amplio rango, gracias a que el lenguaje emplea para surepresentación un bit para el signo (positivo o negativo), 11 para elexponente y 52 para la mantisa. Esto también implica que se utiliza laprecisión doble. Recordemos que en algunos lenguajes deprogramación se emplean dos tipos de datos para los reales, quevarían en función de la representación de su precisión. Es el caso de C,que cuenta con el tipo float y double. En Python no existe estadistinción y podemos considerar que los números reales representadosequivalen al tipo double de C.Además de expresar un número real tal y como hemos vistopreviamente, también es posible hacerlo utilizando notación científica.Simplemente necesitamos añadir la letra e, que representa elexponente, seguida del valor para él mismo. Teniendo esto en cuenta,la siguiente expresión sería válida para representar al número 0.5*10 -7 : >>> num_real = 0.5e-7 Desde un punto de vista escrito los booleanos no son propiamentenúmeros; sin embargo, estos solo pueden tomar dos valoresdiferentes: True (verdadero) o False (falso). Dada esta circunstancia,parece lógico utilizar un tipo entero que necesite menos espacio enmemoria que el original, ya que, solo necesitamos dos números: 0 y 1.En realidad, aunque Python cuenta con el tipo integrado bool, este noes más que una versión personalizada del tipo int. Si necesitamos trabajar con números reales que tengan unaprecisión determinada, por ejemplo, dos cifras decimales, podemosutilizar la clase Decimal. Esta viene integrada en la librería básica queofrece el intérprete e incluye una serie de funciones, para, por ejemplo,crear un número real con precisión a través de un variable de tipo float
    • 59. Sistemas de representación Tal y como hemos adelantado previamente, Python puederepresentar los números enteros en los sistemas decimal, octal, binarloy hexadecimal. La representación decimal es la empleada comúnmentey no es necesario indicar nada más que el número en cuestión. Sinembargo, para el resto de sistemas es necesario que el número seaprecedido de uno o dos caracteres concretos. Por ejemplo, pararepresentar un número en binario nos basta con anteponer loscaracteres Ob. De esta forma, el número 7 se representaría en binariode la siguiente forma: >>> num_binario = Ob111 Por otro lado, para el sistema octal, necesitamos los caracteres Oo,seguidos del número en cuestión. Así pues, el número entero 8quedaría representado utilizando la siguiente sentencia: >>> num_octal = Oo1O En lo que respecta al sistema hexadeclmal, el carácter necesario,tras el número 0, es la letra x. Un ejemplo sería la representación delnúmero 255 a través de la siguiente sentencia: >>> num_hex = Oxff Operadores Obviamente, para trabajar con números, no solo necesitamosrepresentarlos a través de diferentes tipos, sino también es importanterealizar operaciones con ellos. Python cuenta con diversos operadorespara aplicar diferentes operaciones numéricas. Dentro del grupo de lasaritméticas, contamos con las básicas suma, división entera y real,multiplicación y resta. En cuanto a las operaciones de bajo nivel y entrebits, existen tanto las operaciones NOT y NOR, como XOR y AND.También contamos con operadores para comprobar la igualdad ydesigualdad y para realizar operaciones lógicas como AND y OR.Como en otros lenguajes de programación, en Python también
    • 60. existe la precedencia de operadores, lo que deberemos tener encuenta a la hora de escribir expresiones que utilicen varios de ellos. Sinolvidar que los paréntesis pueden ser usados para marcar lapreferencia entre unas operaciones y otras dentro de la mismaexpresión.La tabla 2-1 resume los principales operadores y operacionesnuméricas a las que hacen referencia, siendo o y b dos variablesnuméricas. Expresión con operador Operación a + b Suma a - b Resta a * b Multiplicación a % b Resto a / b División real a // b División entera a ** b Potencia a | b OR (bit)
    • 61. ^ a b XOR (bit) a & b AND (bit) a == b Igualdad a != b Desigualdad a or b OR (lógica) a and b AND (lógica) not a Negación (lógica) Tabla 2-1. Principales operaciones y operadores numéricos Funciones matemáticas A parte de las operaciones numéricas básicas, anteriormentemencionadas, Python nos permite aplicar otras muchas funciones
    • 62. matemáticas. Entre ellas, tenemos algunas como el valor absoluto, laraíz cuadrada, el cálculo del valor máximo y mínimo de una lista o elredondo para números reales. Incluso es posible trabajar conoperaciones trigonométricas como el seno, coseno y tangente. Lamayoría de estas operaciones se encuentran disponibles a través de unmódulo (ver definición en capítulo 3) llamado math. Por ejemplo, elvalor absoluto del número -47,67 puede ser calculado de la siguienteforma: >>> abs(-47,67) Para algunas operaciones necesitaremos importar el mencionadomódulo math, sirva como ejemplo el siguiente código para calcular laraíz cuadrada del número 169: >>> import math>>> math.sqrt(169) Otras interesantes operaciones que podemos hacer con números esel cambio de base. Por ejemplo, para pasar de decimal a binario o deoctal a hexadecimal. Para ello, Python cuenta con las funciones int(),hex(), oct() y bin(). La siguiente sentencia muestra cómo obtener enhexadecimal el valor del entero 16: >>> hex(16)'0x10' Si lo que necesitamos es el valor octal, por ejemplo, del número 8,bastará con lanzar la siguiente sentencia: >>> oct(8)'Oo1O' Debemos tener en cuenta que las funciones de cambio de baseadmiten como argumentos cualquier representación numéricaadmitida por Python. Esto quiere decir, que la siguiente expresióntambién sería válida: >>> bin(Oxfe)'Ob1111111O' Conjuntos
    • 63. Definir y operar con conjuntos matemáticos también es posible enPython. La función para crear un conjunto se llama set() y acepta comoargumentos una serie de valores pasados entre comas, como si setratara de una cadena de texto. Por ejemplo, la siguiente línea decódigo define un conjunto tres números diferentes: >>> conjunto = set ('846') Un conjunto también puede ser definido empleando llaves ({}) yseparando los elementos por comas. Así pues, la siguiente definiciónes análoga a la sentencia anterior: >>> conjunto = {8, 4, 6} Operaciones como unión, intersección, creación de subconjuntos ydiferencia están disponibles para conjuntos en Python. Algunasoperaciones se pueden hacer directamente a través de operadores obien, llamando al método en cuestión de cada instancia creada. Unejemplo de ello es la operación intersección. Creemos un nuevoconjunto, utilicemos el operador & y observemos el resultado: >>> conjunto_2 = set('785')>>> conjunto & conjunto_2('8') Si en su lugar ejecutamos la siguiente sentencia, veremos que elresultado es el mismo: >>> conjunto.intersection(conjunto_2) A través de los métodos add() y remove() podemos añadir y borrarelementos de un conjunto.Si creamos un conjunto con valores repetidos, estos seránautomáticamente eliminados, es decir, no formarán parte del conjunto: >>> duplicados = {2, 3, 6, 7, 6, 8, 2, 1}>>> duplicados {1, 3, 2, 7, 6, 8}
    • 64. CADENAS DE TEXTO No cabe duda de que, a parte de los números, las cadenas de texto( strings ) son otro de los tipos de datos más utilizados enprogramación. El intérprete de Python integra este tipo de datos,además de una extensa serie de funciones para interactuar condiferentes cadenas de texto.En algunos lenguajes de programación, como en C, las cadenas detexto no son un tipo integrado como tal en el lenguaje. Esto implica unpoco de trabajo extra a la hora de realizar operaciones como laconcatenación. Sin embargo, esto no ocurre en Python, lo que hacemucho más sencillo definir y operar con este tipo de dato.Básicamente, una cadena de texto o string es un conjuntoinmutable y ordenado de caracteres. Para su representación ydefinición se pueden utilizar tanto comillas dobles ("), como simples (').Por ejemplo, en Python, la siguiente sentencia crearía una nuevavariable de tipo string: >>> cadena = "esto es una cadena de texto" Si necesitamos declarar un string que contenga más de una línea,podemos hacerlo utilizando comillas triples en lugar de dobles osimples: >>> cad_multiple = """Esta cadena de texto... tiene más de una línea. En concreto, cuenta ... con tres líneas diferentes""" Tipos Por defecto, en Python 3, todas las cadenas de texto son Unicode. Sihemos trabajado con versiones anteriores del lenguaje deberemostener en mente este hecho, ya que, por defecto, antes se empleabaASCII. Así pues, cualquier string declarado en Python seráautomáticamente de tipo Unicode.
    • 65. Otra de las novedades de Python 3 con referencia a las cadenas detexto es el tipo de estas que soporta. En concreto son tres las incluidasen esta versión: Unicode, byte y bytearray. El tipo byte solo admitecaracteres en codificación ASCII y, al igual que los de tipo Unicode, soninmutables. Por otro lado, el tipo bytearray es una versión mutable deltipo byte. Para declarar un string de tipo byte, basta con anteponer la letra b antes de las comillas: >>> cad = b"cadena de tipo byte">>> type(cad)<class 'bytes'> La declaración de un tipo bytearray debe hacerse utilizando lafunción integrada que nos ofrece el intérprete. Además, esimprescindible indicar el tipo de codificación que deseamos emplear.El siguiente ejemplo utiliza la codificación de caracteres latin1 paracrear un string de este tipo: >>> lat = bytearray("España", 'latin1') Observemos el siguiente ejemplo y veamos la diferencia al empleardiferentes tipos de codificaciones para el mismo string: >>> print(lat)bytearray(b'Esp\xfla')>>> bytearray("España", "utf16")bytearray(b'\xff\xfeE\xOOs\xOOp\xOOa\xOO\xf1\xOOa\xOO') Para las cadenas de texto declaradas por defecto, internamente,Python emplea el tipo denominado str. Podemos comprobarlosencillamente declarando una cadena de texto y preguntando a lafunción type(): >>> cadena = "comprobando el tipo str">>> type(cadena)<class 'str'> Realizar conversión entre los distintos tipos de strings es posiblegracias a dos tipos de funciones llamadas encode() y decode(). Laprimera de ellas se utiliza para transformar un tipo str en un tipo byte. Veamos cómo hacerlo en el siguiente ejemplo: >>> cad = "es de tipo str"
    • 66. >>> cad. encode()b'es de tipo str' La función decode() realiza el paso inverso, es decir, convierte unstring byte a otro de tipo str. Como ejemplo, ejecutaremos lassiguientes sentencias: >>> cad = b"es de tipo byte">>> cad.decode()'es de tipo byte' Como el lector habrá podido deducir, cada una de estas funcionessolo se encuentra definida para cada tipo. Esto significa que decode() no existe para el tipo str y que encode() no funciona para el tipo byte. Alternativamente, la función encode() admite como parámetro untipo de codificación específico. Si este tipo es indicado, el intérpreteutilizará el número de bytes necesarios para su representación enmemoria, en función de cada codificación. Recordemos que, para surepresentación interna, cada tipo de codificación de caracteresrequiere de un determinado número de bytes. Principales funciones y métodos Para trabajar con strings Python pone a nuestra disposición unaserie de funciones y métodos. Las primeras pueden ser invocadasdirectamente y reciben como argumento una cadena. Por otro lado,una vez que tenemos declarado el string, podemos invocar adiferentes métodos con los que cuenta este tipo de dato.Una de las funciones más comunes que podemos utilizar sobrestrings es el cálculo del número de caracteres que contiene. Elsiguiente ejemplo nos muestra cómo hacerlo: >>> cad = "Cadena de texto de ejemplo">>> len(cad)26 Otro ejemplo de función que puede ser invocada, sin necesidad dedeclarar una variable de tipo string, es print(). En el capítulo anteriormostramos cómo emplearla para imprimir una cadena de texto por lasalida estándar.
    • 67. Respecto a los métodos con los que cuentan los objetos de tipostring, Python incorpora varios de ellos para poder llevar a cabofuncionalidades básicas relacionadas con cadenas de texto. Entre ellas,contamos con métodos para buscar una subcadena dentro de otra,para reemplazar subcadenas, para borrar espacios en blanco, parapasar de mayúsculas a minúsculas, y viceversa.La función find() devuelve el índice correspondiente al primercarácter de la cadena original que coincide con el buscado: >>> cad ="xyza">>> cad.find("y")1 Si el carácter buscado no existe en la cadena, find() devolverá -1.Para reemplazar una serie de caracteres por otros, contamos con elmétodo replace(). En el siguiente ejemplo, sustituiremos la subcadena"Hola" por "Adiós": >>> cad = "Hola Mundo">>> cad.replace("Hola", "Adiós")'Adiós Mundo' Obsérvese que replace() no altera el valor de la variable sobre elque se ejecuta. Así pues, en nuestro ejemplo, el valor de la variable cad seguirá siendo "Hola Mundo". Los métodos strip(), lstrip() y rstrip() nos ayudarán a eliminar todoslos espacios en blanco, solo los que aparecen a la izquierda y solo losque se encuentran a la derecha, respectivamente: >>> cad = " cadena con espacios en blanco ">>> cad.strip()"cadena con espacios en blanco">>> cad.lstrip()"cadena con espacios en blanco ">>> cad.rstrip()" cadena con espacios en blanco" El método upper() convierte todos los caracteres de una cadena detexto a mayúsculas, mientras que lower() lo hace a minúsculas. Veamosun sencillo ejemplo: >>> cad2 = cad.upper()>>> print(cad2)"CADENA CON ESPACIOS EN BLANCO">>> print(cad3.lower())
    • 68. " cadena con espacios en blanco " Relacionados con upper() y lower() encontramos otro métodollamado capitalize(), el cual solo convierte el primer carácter de unstring a mayúsculas: >>> cad = "un ejemplo">>> cad.capitalize()'Un ejemplo' En ocasiones puede ser muy útil dividir una cadena de textobasándonos en un carácter que aparece repetidamente en ella. Estafuncionalidad es la que nos ofrece split(). Supongamos que tenemosun cadena con varios valores separadas por ; y que necesitamos unalista donde cada valor se corresponda con los que aparecendelimitados por el mencionado carácter. El siguiente ejemplo nosmuestra cómo hacerlo: >>> cad = "primer valor;segundo;tercer valor">>> cad.split(";")['primer valor', 'segundo', 'tercer valor'] join() es un método que devuelve una cadena de texto donde losvalores de la cadena original que llama al método aparecen separadospor un carácter pasado como argumento: >>> "abc".join(',')'a,b,c' Operaciones El operador + nos permite concatenar dos strings, el resultadopuede ser almacenado en una nueva variable: >>> cad_concat = "Hola" + " Mundo!">>> print(cad_concat)Hola Mundo! También es posible concatenar una variable de tipo string con unacadena o concatenar directamente dos variables. Sin embargo,también podemos prescindir del operador + para concatenar strings. Aveces, la expresión es más fácil de leer si empleamos el método
    • 69. format(). Este método admite emplear, dentro de la cadena de texto,los caracteres {}, entre los que irá, número o el nombre de una variable.Como argumentos del método pueden pasarse variables que seránsustituidas, en tiempo de ejecución, por los marcadores {}, indicados enla cadena de texto. Por ejemplo, veamos cómo las siguientesexpresiones son equivalentes y devuelven el mismo resultado: >>> "Hola " + cad2 + ". Otra " + cad3>>> "Hola {O}. Otra {1}".format(cad2, cad3)>>> "Hola {cad2}. Otra {cad3}".format(cad2=cad2,cad3=cad3) La concatenación entre strings y números también es posible,siendo para ello necesario el uso de funciones como int() y str(). Elsiguiente ejemplo es un caso sencillo de cómo utilizar la función str(): >>> num = 3>>> "Número: " + str(num) Interesante resulta el uso del operador * aplicado a cadenas detexto, ya que nos permite repetir un string n veces. Supongamos quedeseamos repetir la cadena "Hola Mundo" cuatro veces. Para ello,bastará con lanzar la siguiente sentencia: >>> print("Hola Mundo" * 4) Gracias al operador in podemos averiguar si un determinadocarácter se encuentra o no en una cadena de texto. Al aplicar eloperador, como resultado, obtendremos True o False, en función de siel valor se encuentra o no en la cadena. Comprobémoslo en elsiguiente ejemplo: >>> cad = "Nueva cadena de texto">>> "x" in cadFalse Un string es inmutable en Python, pero podemos acceder, a travésde índices, a cada carácter que forma parte de la cadena: >>> cad = "Cadenas">>> print(cad[2])d Los comentados índices también nos pueden ayudar a obtenersubcadenas de texto basadas en la original. Utilizando la variable cad
    • 70. del ejemplo anterior podemos imprimir solo los tres primeroscaracteres: >>> print(cad[:3])Cad En el ejemplo anterior el operador : nos ha ayudado a nuestropropósito. Dado que delante del operador no hemos puesto ningúnnúmero, estamos indicando que vamos a utilizar el primer carácter dela cadena. Detrás del operador añadimos el número del índice de lacadena de texto que será utilizado como último valor. Así pues,obtendremos los tres primeros caracteres. Los índices negativostambién funcionan, simplemente indican que se empieza contar desdeel último carácter. La siguiente sentencia devolverá el valor a : >>> cad[-2] A continuación, veamos un ejemplo donde utilizamos un númerodespués del mencionado operador: >>> cad [3:]'enas'
    • 71. TUPLAS En Python una tupla es una estructura de datos que representa unacolección de objetos, pudiendo estos ser de distintos tipos.Internamente, para representar una tupla, Python utiliza un array deobjetos que almacena referencias hacia otros objetos.Para declarar una tupla se utilizan paréntesis, entre los cuales debensepararse por comas los elementos que van a formar parte de ella. Enel siguiente ejemplo, crearemos una tupla con tres valores, cada unode un tipo diferente: >>> t = (1, 'a', 3.5) Los elementos de una tupla son accesibles a través del índice queocupan en la misma, exactamente igual que en un array: >>> t [1]'a' Debemos tener en cuenta que las tuplas son un tipo de datoinmutable, esto significa que no es posible asignar directamente unvalor a través del índice.A diferencia de otros lenguajes de programación, en Python esposible declarar una tupla añadiendo una coma al final del últimoelemento: >>> t = (1, 3, 'c', ) Dado que una tupla puede almacenar distintos tipos de objetos, esposible anidar diferentes tuplas; veamos un sencillo ejemplo de ello: >>> t = (1, ('a', 3), 5.6) Una de las peculiaridades de las tuplas es que es un objeto iterable ;es decir, con un sencillo bucle for podemos recorrer fácilmente todossus elementos: >>> for ele in t:... print(ele)...1
    • 72. ('a', 3)5.6 Concatenar dos tuplas es sencillo, se puede hacer directamente através del operador + . Otros de los operadores que se pueden utilizares * , que sirve para crear una nueva tupla donde los elementos de laoriginal se repiten n veces. Observemos el siguiente ejemplo y elresultado obtenido: >>> (’r', 2) * 3>>> ('r', 2, 'r', 2, 'r', 2) Los principales métodos que incluyen las tupas son index() y count() El primero de ellos recibe como parámetro un valor y devuelve elíndice de la posición que ocupa en la tupla. Veamos el siguienteejemplo: >>> t = (1, 3, 7)>>> t.index(3)1 El método count() sirve para obtener el número de ocurrencias deun elemento en una tupla: >>> t = (1, 3, 1, 5, 1,)>>> t.count(1)3 Sobre las tuplas también podemos usar la función integrada len(), que nos devolverá el número de elementos de la misma. Obviamente,deberemos pasar la variable tupla como argumento de la mencionadafunción.
    • 73. LISTAS Básicamente, una lista es una colección ordenada de objetos,similar al array dinámico empleado en otros lenguajes deprogramación. Puede contener distintos tipos de objetos, es mutable yPython nos ofrece una serie de funciones y métodos integrados pararealizar diferentes tipos de operaciones.Para definir una lista se utilizan corchetes ([]) entre los cualespueden aparecer diferentes valores separados por comas. Esto significaque ambas declaraciones son válidas: >>> lista = []>>> li = [2, 'a' , 4] Al igual que las tuplas, las listas son también iterables, así pues,podemos recorrer sus elementos empleando un bucle: >>> for ele in li:... print(ele)...2'a'4 A diferencia de las tuplas, los elementos de las listas pueden serreemplazados accediendo directamente a través del índice que ocupanen la lista. De este modo, para cambiar el segundo elemento denuestra lista li, bastaría como ejecutar la siguiente sentencia: >>> 1i [ 1] = ' b' Obviamente, los valores de las listas pueden ser accedidosutilizando el valor del índice que ocupan en la misma: >>> li [2]4 Podemos comprobar si un determinado valor existe en una lista através del operado in, que devuelve True en caso afirmativo y False encaso contrario: >>> 'a' in li
    • 74. True Existen dos funciones integradas que relacionan las listas con lastuplas: list() y tuple(). La primera toma como argumento una tupla ydevuelve una lista. En cambio, tuple() devuelve una tupla al recibircomo argumento una lista. Por ejemplo, la siguiente sentencia nosdevolverá una tupla: >>> tuple(li)(2, 'a', 4) Operaciones como la suma (+) y la multiplicación (*) tambiénpueden ser aplicadas sobre listas. Su funcionamiento es exactamenteigual que en las tuplas. Inserciones y borrados Para añadir un nuevo elemento a una lista contamos con el método append(). Como parámetro hemos de pasar el valor que deseamosañadir y este será insertado automáticamente al final de la lista.Volviendo a nuestra lista ejemplo de tres elementos, uno nuevoquedaría insertado a través de la siguiente sentencia: >>> li.append('nuevo') Nótese que, para añadir un nuevo elemento, no es posible utilizarun índice superior al número de elementos que contenga la lista. Lasiguiente sentencia lanza un error: >>> li [4] = 23Traceback (most recent call last):File "<stdin>", line 1, in <module>IndexError: list assignment index out of range Sin embargo, el método insert() sirve para añadir un nuevoelemento especificando el índice. Si pasamos como índice un valorsuperior al número de elementos de la lista, el valor en cuestión seráinsertado al final de la misma, sin tener en cuenta el índice pasadocomo argumento. De este modo, las siguientes sentencias produciránel mismo resultado, siendo 'c' el nuevo elemento que será insertado enla lista li:
    • 75. >>> li.insert(3, 'c')>>> li.insert(12, 'c') Por el contrario, podemos insertar un elemento en una posicióndeterminada cuyo índice sea menor al número de valores de la lista.Por ejemplo, para insertar un nuevo elemento en la primera posiciónde nuestra lista li, bastaría con ejecutar la siguiente sentencia: >>> li.insert(0, 'd')>>> li>>> ['d’, 2, 'a', 4] Si lo que necesitamos es borrar un elemento de una lista, podemoshacerlo gracias a la función del(), que recibe como argumento la listajunto al índice que referencia al elemento que deseamos eliminar. Lasiguiente sentencia ejemplo borra el valor 2 de nuestra lista li: >>> del(li[1]) Como consecuencia de la sentencia anterior, la lista queda reducidaen un elemento. Para comprobarlo contamos con la función len(), quenos devuelve el número de elementos de la lista: >>> len(li)2 Obsérvese que la anterior función también puede recibir comoargumento una tupla o un string. En general, len() funciona sobre tiposde objetos iterables.También es posible borrar un elemento de una lista a través de suvalor. Para ello contamos con el método remove(): >>> li.remove('d') Si un elemento aparece repetido en la lista, el método remove() soloborrará la primera ocurrencia que encuentre en la misma.Otro método para eliminar elementos es pop(). A diferencia de remove(), pop() devuelve el elemento borrado y recibe comoargumento el índice del elemento que será eliminado. Si no se pasaningún valor como índice, será el último elemento de la lista eleliminado. Este método puede ser útil cuando necesitamos ambasoperaciones (borrar y obtener el valor) en una única sentencia.
    • 76. Ordenación Los elementos de una lista pueden ser ordenados a través delmétodo sort() o utilizando la función sorted(). Como argumento sepuede utilizar reverse con el valor True o False. Por defecto, se utiliza elsegundo valor, el cual indica que la lista será ordenada de mayor amenor. Si por el contrario el valor es True, la lista será ordenadainversamente. Veamos un ejemplo para ordenar una lista de enteros: >>>>>>[1,>>>[9,>>>[3, lista = [3, 1, 9, 8, 7]sorted(lista)3, 7, 8, 9]sorted(lista, reverse=True)8, 7, 3, 1]lista1, 9, 8, 7] Como el lector habrá podido observar, la lista original ha quedadoinalterada. Sin embargo, si en lugar de utilizar la función sorted(), empleamos el método sort(), la lista quedará automáticamentemodificada. Ejecutemos las siguientes setencias para comprobarlo: >>> lista.sort()>>> lista[1, 3, 7, 8, 9] Tanto para aplicar sort() como sorted() debemos tener en cuentaque la lista que va a ser ordenada contiene elementos que son delmismo tipo. En caso contrario, el intérprete de Python lanzará un error.No obstante, es posible realizar ordenaciones de listas con elementosde distinto tipo si es el programador el encargado de establecer elcriterio de ordenación. Para ello, contamos con el parámetro key quepuede ser pasado como argumento. El valor del mismo puede ser unafunción que fijará cómo ordenar los elementos. Además, elmencionado parámetro también puede ser utilizado para cambiar laforma de ordenar que emplee el intérprete por defecto, aunque loselementos sean del mismo tipo. Supongamos que definimos lasiguiente lista: >>> lis = ['aA', 'Ab', 'Cc', ’ca'] Ahora ordenaremos con la función sorted() sin ningún parámetro
    • 77. adicional y observaremos que el criterio de ordenación que utiliza elintérprete, por defecto, es ordenar primero las letras mayúsculas: >>> sorted(lis)['Ab', 'Cc' , 'aA', 'ca'] Sin embargo, al pasar como argumento un determinado criterio deordenación, el resultado varía: >>> sorted(lis, key=str.lower)['aA', 'Ab', 'ca', 'Cc'] Otro método que contienen las listas relacionado con la ordenaciónde valores es reverse(), que automáticamente ordena una lista en ordeninverso al que se encuentran sus elementos originales. Tomando elvalor de la última lista de nuestro ejemplo, llamaremos al método paraver qué ocurre: >>> lista.reverse() >>> lista [9, 8, 7, 3, 1] Los métodos y funciones de ordenación no solo funcionan connúmeros, sino también con caracteres y con cadenas de texto: >>> lis = ['be', 'ab', 'cc', 'aa', 'cb']>>> lis.sort()>>> lis ['aa', 'ab','be', 'cb', 'cc'] Comprensión La comprensión de listas es una construcción sintáctica de Pythonque nos permite declarar una lista a través de la creación de otra. Estaconstrucción está basada en el principio matemático de la teoría decomprensión de conjuntos. Básicamente, esta teoría afirma que unconjunto se define por comprensión cuando sus elementos sonnombrados a través de sus características. Por ejemplo, definimos elconjunto S como aquel que está formado por todos los meses del año: S = {meses del año}
    • 78. Veamos un ejemplo prácticoconstrucción sintáctica en Python: para utilizar la mencionada >>> lista = [ele for ele in (1, 2, 3)] Como resultado de la anterior sentencia, obtendremos una lista contres elementos diferentes: >>> print(lista)[1, 2, 3] Gracias a la comprensión de listas podemos definir y crear listasahorrando líneas de código y escribiendo el mismo de forma máselegante. Sin la comprensión de listas, deberíamos ejecutar lassiguientes sentencias para lograr el mismo resultado: >>> lista = []>>> for ele in (1, 2, 3):... lista.append(ele)... Matrices Anidando listas podemos construir matrices de elementos. Estasestructuras de datos son muy útiles para operaciones matemáticas.Debemos tener en cuenta que complejos problemas matemáticos sonresueltos empleando matrices. Además, también son prácticas paraalmacenar ciertos datos, aunque no se traten estrictamente derepresentar matrices en el sentido matemático.Por ejemplo, una matriz matemática de dos dimensiones puededefinirse de la siguiente forma: >>> matriz = [[1, 2, 3],[4, 5, 6]] Para acceder al segundo elemento de la primera matriz, bastaríacon ejecutar la siguiente sentencia:
    • 79. >>> matriz = [0][1] Asimismo, podemos cambiar un elemento directamente: >>> matriz[0][1] = 33>>> m
    • 80. [[1, 33, 3],[4, 5, 6]]
    • 81. DICCIONARIOS Un diccionario es una estructura de datos que almacena una seriede valores utilizando otros como referencia para su acceso yalmacenamiento. Cada elemento de un diccionario es un par clave-valor donde el primero debe ser único y será usado para acceder alvalor que contiene. A diferencia de las tuplas y las listas, losdiccionarios no cuentan con un orden específico, siendo el intérpretede Python el encargado de decidir el orden de almacenamiento. Sinembargo, un diccionario es iterable, mutable y representa unacolección de objetos que pueden ser de diferentes tipos.Gracias a su flexibilidad y rapidez de acceso, los diccionarios sonuna de las estructuras de datos más utilizadas en Python. Internamenteson representadas como una tabla hash, lo que garantiza la rapidez deacceso a cada elemento, además de permitir aumentar dinámicamenteel número de ellos. Otros muchos lenguajes de programación hacenuso de esta estructura de datos, con la diferencia de que es necesarioimplementar la misma, así como las operaciones de acceso,modificación, borrado y manejo de memoria. Python ofrece la granventaja de incluir los diccionarios como estructuras de datosintegradas, lo que facilita en gran medida su utilización.Para declarar un diccionario en Python se utilizan las llaves ({}) entrelas que se encuentran los pares clave-valor separados por comas. Laclave de cada elemento aparece separada del correspondiente valorpor el carácter : . El siguiente ejemplo muestra la declaración de undiccionario con tres valores: >>> diccionario = {'a': 1, 'b': 2, 'c': 3} Alternativamente, podemos hacer uso de la función dict() quetambién nos permite crear un diccionario. De esta forma, la siguientesentencia es equivalente a la anterior: >>> diccionario = dict(a=1, b=2, c=3) Acceso, inserciones y borrados
    • 82. Como hemos visto previamente, para acceder a los elementos delas listas y las tuplas, hemos utilizado el índice en función de laposición que ocupa cada elemento. Sin embargo, en los diccionariosnecesitamos utilizar la clave para acceder al valor de cada elemento.Volviendo a nuestro ejemplo, para obtener el valor indexado por laclave 'c' bastará con ejecutar la siguiente sentencia: >>> diccionario 'c']3 Para modificar el valor de un diccionario, basta con acceder a travésde su clave: >>> diccionario['b'] = 28 Añadir un nuevo elemento es tan sencillo como modificar uno yaexistente, ya que si la clave no existe, automáticamente Python laañadirá con su correspondiente valor. Así pues, la siguiente sentenciainsertará un nuevo valor en nuestro diccionario ejemplo: >>> diccionario['d'] = 4 Tres son los métodos principales que nos permiten iterar sobre undiccionario: items(), values() y keys(). El primero nos da acceso tanto aclaves como a valores, el segundo se encarga de devolvernos losvalores, y el tercero y último es el que nos devuelve las claves deldiccionario. Veamos estos métodos en acción sobre el diccionariooriginal que declaramos previamente: >>> for k, v in diccionario.items():... print("clave={0}, valor= {1}".format(k, v))...clave=a, valor=1clave=b, valor=2clave=c, valor=3 >>> for k in diccionario.keys():... print("clave= {0}".format(k))...clave=aclave=bclave=c >>> for v in diccionario.values():... print("valor={0}".format(v))
    • 83. ...valor=1valor=2valor=3 Por defecto, si iteramos sobre un diccionario con un bucle for, obtendremos las claves del mismo sin necesidad de llamarexplícitamente al método keys() : >>> for k in diccionario:... print(k)abc A través del método keys() y de la función integrada list() podemosobtener una lista con todas las claves de un diccionario: >>> list(diccionario.keys()) Análogamente es posible usar values() junto con la función list() para obtener un lista con los valores del diccionario. Por otro lado, lasiguiente sentencia nos devolverá una lista de tuplas, donde cada unade ellas contiene dos elementos, la clave y el valor de cada elementodel diccionario: >>> list(diccionario.items())[ ('a', 1), (’b', 2), ('c', 3)] La función integrada del() es la que nos ayudará a eliminar un valorde un diccionario. Para ello, necesitaremos pasar la clave que contieneel valor que deseamos eliminar. Por ejemplo, para eliminar el valor quecontiene la clave 'c' de nuestro diccionario, basta con ejecutar: >>> del(diccionario['b']) El método pop() también puede ser utilizado para borrar eliminarelementos de un diccionario. Su funcionamiento es análogo alexplicado en el caso de las listas.Otra función integrada, en este caso len(), también funciona sobrelos diccionarios, devolviéndonos el número total de elementoscontenidos.El operador in en un diccionario sirve para comprobar si una claveexiste. En caso afirmativo devolverá el valor True y False en otro caso:
    • 84. >>> 'x' in diccionarioFalse Comprensión De forma similar a las listas, los diccionarios pueden también sercreados por comprensión. El siguiente ejemplo muestra cómo crear undiccionario utilizando la iteración sobre una lista: >>> {k: k+1 for k in (1, 2, 3)}{1: 2, 3: 4, 4: 5} La comprensión de diccionarios puede ser muy útil para inicializarun diccionario a un determinado valor, tomando como claves losdiferentes elementos de una lista. Veamos cómo hacerlo a través delsiguiente ejemplo que crea un diccionario inicializándolo con el valor 1 para cada clave: >>> {clave: 1 for clave in ['x', 'y', 'z']}{'x': 1, ’y': 1, 'z': 1} Ordenación A diferencia de las listas, los diccionarios no tienen el método sort(), pero sí que es posible utilizar la función integrada sorted() para obteneruna lista ordenada de las claves contenidas. Volviendo a nuestrodiccionario ejemplo inicial, ejecutaremos la siguiente sentencia: >>> sorted(diccionario)['a', 'b', 'c'] También podemos utilizar el parámetro reverse con el mismoresultado que en las listas: >>> sorted(diccionario, reverse=True)['c', ' b' , 'a']
    • 85. SENTENCIAS DE CONTROL,MÓDULOS Y FUNCIONES INTRODUCCIÓN Las sentencias de control es uno de los primeros aspectos quedeben ser abordados durante el aprendizaje de un lenguaje deprogramación. Entre las sentencias de las que dispone Python, lasbásicas son las que nos permiten crear condiciones y realizariteraciones. Es por ello que dedicaremos el primer apartado de estecapítulo a las mismas.Continuaremos entrando de lleno en uno de los principalesconceptos de la programación procedural: las funciones.Aprenderemos cómo se definen, cómo son tratadas por el intérprete ycómo pasar parámetros. Además, presentaremos a un tipo especialllamado lambda, muy utilizado en programación funcional.Python permite agrupar nuestro código en módulos y paquetes, gracias a los cuales podemos organizar adecuadamente nuestrosprogramas. De ellos hablaremos a continuación de las funciones.Por último, veremos qué son las excepciones y cómo podemostrabajar con ellas en Python. El mecanismo de tratamiento deexcepciones nos ahorra muchos problemas en tiempos de ejecución yse ha convertido en una de las más importantes funcionalidades queincorporan los modernos lenguajes de programación.
    • 86. PRINCIPALES SENTENCIAS DE CONTROL Al igual que otros lenguajes de programación, Python incorporauna serie de sentencias de control. Entre ellas, encontramos algunastan básicas y comunes a otros lenguajes como if/else, while y for, yotras específicas como pass y with. A continuación, echaremos unvistazo a cada una de estas sentencias. if, else y elif La sentencia if/else funciona evaluando la condición indicada, si elresultado es True se ejecutará la siguiente sentencia o sentencias, encaso negativo se ejecutarán las sentencias que aparecen acontinuación del else. Recordemos que Python utiliza la indentaciónpara establecer sentencias que pertenecen al mismo bloque. Además,en el carácter dos puntos ( : ) indica el comienzo de bloque. Acontinuación, vemos un ejemplo: x = 4y = 0if x == 4:y = 5else:y = 2 Obviamente, también es posible utilizar solo la sentencia if paracomprobar si se cumple una determinada condición y actuar enconsecuencia. Además, podemos anidar diferentes niveles decomprobación a través de elif : if X = = 4 :y = 1elif x = = 5y = 2elif x = = 6y = 3else:y = 5
    • 87. Como el lector habrá podido observar y a diferencia de otroslenguajes de programación, los paréntesis para indicar las condicioneshan sido omitidos. Para Python son opcionales y habitualmente nosuelen ser utilizados. Por otro lado, a pesar de que Python emplea laindentación, también es posible escribir una única sentencia acontinuación del final de la condición. Así pues, la siguiente línea decódigo es válida: if a > b: print("a es mayor que b") for y while Para iterar contamos con dos sentencias que nos ayudarán a crearbucles, nos referimos a for y a while. La primera de ellas aplica unaserle de sentencias sobre cada uno de los elementos que contiene elobjeto sobre el que aplicamos la sentencia for. Python incorpora unafunción llamada range() que podemos utilizar para iterar sobre unaserie de valores. Por ejemplo, echemos un vistazo al siguiente ejemplo: >>> for x in range(1, 3):... print(x)...123 Asimismo, tal y como hemos visto en el capítulo anterior, es muycomún iterar a través de for sobre los elementos de una tupla o de unalista: >>> lista = ["uno", "dos", "tres"]>>> cad = "">>> for ele in lista:... cad += ele...>>> cad"unodostres" Opcionalmente, for admite la sentencia else. Si esta aparece, todaslas sentencias posteriores serán ejecutadas si no se encuentra otrasentencia que provoque la salida del bucle. Por ejemplo, en laejecución de un bucle for que no contiene ningún break, siempre serán
    • 88. ejecutadas las sentencias que pertenecen al else al finalizar el bucle. Acontinuación, veamos un ejemplo para ilustrar este caso: >>> for item in (1, 2, 3):... print(item)... else:... print ("fin")...123fin Otra sentencia utilizada para iterar es while, la cual ejecuta una seriede sentencias siempre y cuando se cumpla una determinada condicióno condiciones.Para salir del bucle podemos utilizar diferentes técnicas. La mássencilla es cambiar la condición o condiciones iniciales para así dejarque se cumplan y detener la iteración. Otra técnica es llamardirectamente a break que provocará la salida inmediata del bucle. Estaúltima sentencia también funciona con for. A continuación, veamos unejemplo de cómo utilizar while: >>> x = 0>>> y = 3>>> while x < y:... print(x)... x += 1012 Al igual que for, while también admite opcionalmente else. Observemos el siguiente código y el resultado de su ejecución: >>> x = 0>>> y = 3>>> while x < y:... print(x)... x+ = 1... if x == 2:... break... else:... print("x es igual a 2")01 Si en el ejemplo anterior eliminamos la sentencia break,
    • 89. comprobaremos cómo la última sentencia print es ejecutada.Además de break, otra sentencia asociada a for y while es continue, la cual se emplea para provocar un salto inmediato a la siguienteiteración del bucle. Esto puede ser útil, por ejemplo, cuando nodeseamos ejecutar una determinada sentencia para una iteraciónconcreta. Supongamos que estamos iterando sobre una secuencia ysolo queremos imprimir los números pares: >>> for i in range(l, 10):... if i % 2 != 0:... continue... print(i)2468 pass y with Python incorpora una sentencia especial para indicar que no sedebe realizar ninguna acción. Se trata de pass y especialmente útilcuando deseamos indicar que no se haga nada en una sentencia querequiere otra. Por ejemplo, en un sencillo while: >>> while True:... pass Muchos desarrolladores emplean pass cuando escriben esqueletos de código que posteriormente rellenarán. Dado que inicialmente nosabemos qué código contendrá una determinada sentencia, es útilemplear pass para mantener el resto del programa funcional.Posteriormente, el esqueleto de código será rellenado con códigofuncional y la sentencia pass será reemplazada por otras que realicenuna función específica.La sentencia with se utiliza con objetos que soportan el protocolode manejador de contexto y garantiza que una o varias sentencias seránejecutadas automáticamente. Esto nos ahorra varias líneas de código, ala vez que nos garantiza que ciertas operaciones serán realizadas sinque lo indiquemos explícitamente. Uno de los ejemplos más claros esla lectura de las líneas de un fichero de texto. Al terminar esta
    • 90. operación siempre es recomendable cerrar el fichero. Gracias a with esto ocurrirá automáticamente, sin necesidad de llamar al método close(). Las siguientes líneas de código ilustran el proceso: >>> with open(r'info.txt') as myfile:... for line in myfile:... print(line)
    • 91. FUNCIONES En programación estructurada, las funciones son uno de loselementos básicos. Una función es un conjunto de sentencias quepueden ser invocadas varias veces durante la ejecución de unprograma. Las ventajas de su uso son claras, entre ellas, laminimización de código, el aumento de la legibilidad y la fomentaciónde la reutilización de código.Una de las principales diferencias de las funciones en Python conrespecto a lenguajes compilados, como C, es que estas no existenhasta que son invocadas y el intérprete pasa a su ejecución. Estoimplica que la palabra reservada def, empleada para definir unafunción, es una sentencia más. Como consecuencia de ello, otrassentencias pueden contener una función. Así pues, podemos utilizaruna sentencia if, definiendo una función cuando una determinadacondición se cumple.Internamente, al definir una función, Python crea un nuevo objeto yle asigna el nombre dado para la función. De hecho, una funciónpuede ser asignada a una variable o almacenada en una lista.Como hemos comentado previamente, la palabra reservada def nosservirá para definir una función. Seguidamente deberemos emplear unnombre y, opcionalmente, una serie de argumentos. Esta será nuestraprimera función: def test():print("test ejecutada") Para invocar a nuestra nueva función, basta con utilizar su nombreseguido de paréntesis. En lugar de utilizar el intérprete para comprobarsu funcionamiento, lo haremos a través de un fichero. Basta con abrirnuestro editor de textos favorito y crear un fichero llamado test.py conel siguiente código: def test () :print("test ejecutada")test()nueva = testnueva()
    • 92. Una vez que salvemos el fichero con el código, ejecutaremos elprograma desde la línea de comandos a través del siguiente comando: python test.py Como resultado veremos cómo aparece dos veces la cadena detexto test ejecutada. Paso de parámetros En los ejemplos previos hemos utilizado una función sinargumentos y, por lo tanto, para invocar a la misma no hemos utilizadoningún parámetro. Sin embargo, las funciones pueden trabajar conparámetros y devolver resultados. En Python, el paso de parámetrosimplica la asignación de un nombre a un objeto. Esto significa que, enla práctica, el paso de parámetros ocurre por valor o por referencia enfunción de si los tipos de los argumentos son mutables o inmutables.En otros lenguajes de programación, como por ejemplo C, es elprogramador el responsable de elegir cómo serán pasados losparámetros. Para ilustrar este funcionamiento, observemos el siguienteejemplo: >>> def test2 (a, b)... : a = 2... b = 3...>>> C = 5>>> d = 6>>> test2 (c, d)>>> print("c={0}, d={l}". format(x, y))c=5, d=6 Como podemos observar, el valor de las variables c y d, que soninmutables, no ha sido alterado por la función.Sin embargo, ¿qué ocurre si utilizamos un argumento inmutablecomo parámetro? Veámoslo a través del siguiente código de ejemplo: >>>...>>>>>>[3, def variable(lista):lista [0] = 3lista = [1, 2, 3]print(variable(lista))2, 3]
    • 93. Efectivamente, al pasar como argumento una lista, que es demutable, y modificar uno de sus valores, se modificará la variableoriginal pasada como argumento.Internamente, Python siempre realiza el paso de parámetros através de referencias, es decir, se puede considerar que el paso serealiza siempre por variable en el sentido de que no se hace una copiade las variables. Sin embargo, tal y como hemos mencionado, elcomportamiento de esta asignación de referencias depende de si elparámetro en cuestión es mutable o inmutable. Este comportamientoen funciones es similar al que ocurre cuando hacemos asignaciones devariables. Si trabajamos con tipos inmutables y ejecutamos lassiguientes sentencias, observaremos cómo el valor de la variable a semantiene: >>> a = 3>>> b = a>>> b = 2>>> print("a={0}, b={1}".format(a, b))a=3, b=2 Por otro lado, si aplicamos el mismo comportamiento a un tipomutable, veremos cómo varía el resultado: >>> a = [0, 1]>>> b = a>>> b[0] = 1>>> print("a={0}; b={1}".format(a, b))a= [1, 1] ; b= [1, 1] Si necesitamos modificar una o varias variables inmutables a travésde una función, podemos hacerlo utilizando una técnica, consistenteen devolver una tupla y asignar el resultado de la función a lasvariables. Partiendo del ejemplo anterior, reescribiremos la función dela siguiente forma: def test(a, b):a = 2b = 3return(a, b) Posteriormente, realizaremos la llamada a la función y la asignacióndirectamente con una sola sentencia: >>> c, d = test (c, d)
    • 94. A pesar de ser el comportamiento por defecto, es posible nomodificar el parámetro mutable pasado como argumento. Para ello,basta con realizar, cuando se llama a la función, una copia explícita dela variable: >>> variable(lista[:]) Dado el manejo que realiza Python sobre el paso de parámetros afunciones, en lugar de utilizar los términos por valor o por referencia, sería más exacto decir que Python realiza el paso de parámetros porasignación. Valores por defecto y nombres de parámetros En Python es posible asignar un valor a un parámetro de unafunción. Esto significa que, si en la correspondiente llamada a lafunción no pasamos ningún parámetro, se utilizará el valor indicadopor defecto. El siguiente código nos muestra un ejemplo: >>> def fun(a, b=1):... print(b)>>> fun(4)1 Hasta ahora hemos visto cómo pasar diferentes argumentos a unafunción según la posición que ocupan. Es decir, la correspondenciaentre parámetros se realiza según el orden. Sin embargo, Pythontambién nos permite pasar argumentos a funciones utilizandonombres y obviando la posición que ocupan. Comprobémoslo a travésdel siguiente código: >>> def fun(a, b, c):... print("a={0}, b={1}, c={2}".format(a, b, c))>>> fun(c=5, b=3, a=1)a=1, b=3, c=5 Obviamente, podemos combinar valores por defecto y nombres deargumentos para invocar a una función. Supongamos que definimos lasiguiente función: >>> def fun(a, b, c=4):... print("a={0}, b={1}, c={2}".format(a, b, c))
    • 95. Dada la anterior función, las siguientes sentencias son válidas: >>> fun(1, 2, 4)>>> fun(a=1, b=2, c=4)>>> fun(a=1, b=2) >>> fun(1, 2) Número indefinido de argumentos Python nos permite crear funciones que acepten un númeroindefinido de parámetros sin necesidad de que todos ellos aparezcanen la cabecera de la función. Los operadores * y ** son los que seutilizan para esta funcionalidad. En el primer caso, empleando eloperador *, Python recoge los parámetros pasados a la función y losconvierte en una tupla. De esta forma, con independencia del númerode parámetros que pasamos a la función, esta solo necesita elmencionado operador y un nombre. A continuación, un ejemplo quenos muestra esta funcionalidad: >>> def fun(*items):... for ítem in ítems:... print(ítem)...>> fun(1, 2, 3)123>>> fun(5, 6)56>>> t = ('a', 'b', 'c')>>> fun(t)'a''b''c' Como podemos comprobar, en el código anterior, elcomportamiento de la función siempre es el mismo, conindependencia del número de argumentos que pasamos.Por otro lado, gracias al operador ** podemos pasar argumentosindicando un nombre para cada uno de ellos. Internamente, Pythonconstruye un diccionario y los parámetros pasados a la función son
    • 96. tratados como tal. El siguiente ejemplo nos muestra cómo empleareste operador en la cabecera de una función: >>> def fun(**params):... print(params)...>>> fun(x=5, y=8){'x': 5, 'y': 8}>>> fun(x=5, y=8, z=4){'x': 5, 'y': 8, 'z': 4} También es posible que la cabecera de una función utilice uno ovarios argumentos posicionales, seguidos del operador * o **. Esto nosproporciona bastante flexibilidad a la hora de invocar a una función.Observemos la siguiente función: def print_record(nombre, apellido, **rec):print("Nombre: ", nombre)print("Apellidos:", apellidos)for k in rec:print("{0}: {1}".format(k, rec[k])) Las siguientes invocaciones a la función recién definida seríanválidas: >>> print_record("Juan", "Coll", edad=43, localidad="Madrid")>>> print_record("Manuel", "Tip", edad=34) Desempaquetado de argumentos En el apartado anterior hemos aprendido a utilizar los operadores * y ** en la cabecera de una función. Sin embargo, dichos operadorestambién pueden ser empleados en la llamada a la función. Elcomportamiento es similar y la técnica empleada se conoce como desempaquetado de argumentos. Por ejemplo, supongamos que unafunción tiene en su cabecera tres parámetros diferentes. En la llamadaa la misma, en lugar de utilizar tres valores, podemos emplear eloperador * , tal y como muestra el siguiente código: >>> def fun(x, y, z):... print(x, y, z)...>>> t = (1, 2, 3)>>> fun(*t)
    • 97. 1 2 3 En lugar de una tupla, el operador ** se basa en el uso de undiccionario. Tomando como ejemplo la función definida previamente,veamos el comportamiento de este operador: >>> d = {'y': 1, 'z': 2, ’x': 0}>>> fun(**d)0 1 2 También es posible combinar el paso de parámetros con eloperador ** utilizando valores por defecto en la cabecera de la función: >>>......>>>>>>3 4 def fun(a=1, b=2, c=3):print(a, b, c) d = {'a': 3, ’b’: 4}fun(**d)1 Funciones con el mismo nombre Python permite definir diferentes funciones con el mismo nombre ydiferente número de argumentos. Sin embargo, su comportamiento esdistinto al que hacen otros lenguajes de programación, como es elcaso de Java. Si definimos más de una función como el mismo nombrey con el mismo o diferente número de argumentos, Python emplearásiempre la última que ha sido definida. Este comportamiento se debe aque Python trata las funciones como un tipo determinado de objeto.De esta forma, al volver a definir una función, estamos creando unnueva variable que será asignada a un nuevo valor. Ilustremos estehecho con un ejemplo, donde vamos a definir dos funciones con elmismo nombre y diferente número de argumentos: >>> def fun(x, y):... print(x, y)...>>> def fun(x):... print(x)... Seguidamente invocaremos a la función pasando dos parámetros:
    • 98. >>> fun(1, 3)Traceback (most recent cali last):File "<stdin>", line 1, in <module>TypeError: fun() takes exactly 1 positional argument (2 given) Como el lector habrá podido observar, el intérprete de Pythonmuestra un error indicándonos que debemos pasar exactamente unúnico argumento. Sin embargo, la siguiente llamada a la función esválida: >>> fun(4) Para comprobar que Python trata a las funciones como un tipo dedato específico, basta con ejecutar la siguiente sentencia y echar unvistazo a su resultado: >>> type(fun)<class 'function'> Funciones lambda Al igual que otros lenguajes de programación, Python permite eluso de funciones lambda. Este tipo especial de función se caracterizapor devolver una función anónima cuando es asignada a una variable.Aquellos lectores que hayan trabajado con Lisp, u otros lenguajesfuncionales, seguro que están familiarizados con su uso. En definitiva,las funciones lambda ejecutan una determinada expresión, aceptandoo no parámetros y devuelven un resultado. A su vez, la llamada a estetipo de funciones puede ser utilizada como parámetros para otras.En Python, las funciones lambda no pueden contener bucles y nopueden utilizar la palabra clave return para devolver un valor. Lasintaxis para este tipo de funciones es del siguiente tipo: lambda <parámetros>:<expresión> Técnicamente, las funciones lambda no son una sentencia, sino unaexpresión. Esto las hace diferentes de las funciones definidas con def, ya que estas siempre hacen que el intérprete las asocie a un nombredeterminado, en lugar de simplemente devolver un resultado, tal ycomo ocurre con las lambda.
    • 99. En la práctica, la utilidad de las funciones lambda es que nospermite definir una función directamente en el código que va a haceruso de ella. Es decir, nos permite definir funciones inline. Esto puedeser útil, por ejemplo, para definir una lista con diferentes acciones queserán ejecutadas bajo demanda. Supongamos que necesitamosejecutar dos funciones diferentes pasando el mismo parámetro,estando ambas funciones definidas en una determinada lista. En lugarde definir tres funciones diferentes utilizando def, vamos a emplearfunciones lambda: >>> li = [lambda x: x + 2, lambda x: x + 3]>>> param = 4>>> for accion in li:... print(accion(param))...45 A continuación, veremos un ejemplo de asignación de funciónlambda a una variable y su posterior invocación: >>> lam = lambda x: x*5>>> print(lam(3))15 Equivalente en funcionalidad al anterior ejemplo, sería el siguientecódigo: >>> def lam(x):... return x*5...>>> print(lam(3))15 Para ilustrar el paso como parámetro de una función lambda a otrafunción convencional presentaremos primero a la función integrada dePython llamada map(). Esta función recibe dos parámetros, el primeroes una función que debe ser ejecutada para cada uno de los elementosque contiene el segundo parámetro. Como ejemplo tomaremos unalista con una serie de valores y aplicaremos sobre cada uno de ellosuna simple función: sumar el número dos. Después imprimiremos porla salida estándar el resultado. El código en cuestión sería el queaparece a continuación: >>> li = [1, 2, 3]
    • 100. >>> new_li = map(lambda x: x+2, li)>>> for item in new_li: print(item)...34 5 Tipos mutables como argumentos por defecto Cuando fijamos el valor de un argumento en la cabecera de unafunción es conveniente tener en cuenta que este debería ser de tipoinmutable. En caso contrario podríamos obtener resultadosinesperados. Es decir, no se debe emplear objetos como listas,diccionarios o instancias de clase como argumentos por defecto deuna función. Observemos el siguiente ejemplo, donde fijamos una listavacía como argumento por defecto: >>> fun(x=1, 1i = []) :li.append(x)...>>> fun()[1]>>> fun()[1, 1]>>> fun(2) [1, 1, 2] ¿Inesperado resultado? En lugar de devolver una lista con un únicovalor, dado que la lista es inicializada en el argumento por defecto,Python devuelve una lista con un nuevo valor cada vez que la funciónes invocada. Esto se debe a que el intérprete crea los valores pordefecto cuando la sentencia que define la función es ejecutada y nocuando la función es invocada. Sin embargo, ¿por qué elcomportamiento de nuestro parámetro x es diferente? Simplementeporque x es inmutable, mientras que li es mutable. Así pues, la listapuede cambiar su valor, que es precisamente lo que hace nuestrafunción de ejemplo, añadiendo un nuevo elemento cada vez que lamisma es invocada.¿Y si deseamos precisamente inicializar una lista cada vez quenuestra función ejemplo es invocada? Bastaría con una sencilla técnica
    • 101. consistente en utilizar el valor None por defecto para nuestroparámetro y en crear una lista vacía cuando esto ocurre. El siguientecódigo muestra cómo hacerlo: >>>...............>>>[1]>>>[1]>>>[2]>>>>>>[3, def fun(x=1, li=None):if lis is None:lis = []lis.append(x)print(lis) fun() fun() fun(2) li = [3, 4]fun(6, li)4, 6]
    • 102. MÓDULOS Y PAQUETES La organización del código es imprescindible para su eficientemantenimiento y posible reutilización. Cuanto mayor es el número deficheros que forman parte de un programa, más importante esmantener su organización. Para ello, Python nos ofrece dos unidadesbásicas: los módulos y los paquetes. Comenzaremos aprendiendosobre los primeros. Módulos Básicamente, un módulo de Python es un fichero codificado en estelenguaje y cumple dos roles principales: permitir la reusabilidad decódigo y mantener un espacio de nombres de variables único. Paraexplicar estos roles, debemos pensar en cómo Python estructura elcódigo. Un script de Python tiene un punto de entrada para suejecución, consta de varias sentencias y puede tener definidasfunciones que serán invocadas durante su ejecución. Sin embargo, sitenemos definidos dos scripts diferentes, en uno de ellos podemosinvocar a una o varias funciones que existan en el otro. Esto sería unejemplo básico de reutilización de código. ¿Qué ocurre si tenemosdefinidas funciones con el mismo nombre en ambos scripts? Aquí esdonde entra en juego el espacio de nombres único, ya que cadafichero es por sí mismo un módulo y como tal mantiene la unicidad desu espacio. Pongamos esta teoría en práctica a través del siguienteejemplo. En primer lugar crearemos un fichero llamado first.py con elsiguiente código: def say_hello():print("Hola Mundo!")print("Soy el primero") A continuación, crearemos nuestro segundo fichero, al quellamaremos second.py y añadiremos este código: import first
    • 103. first.say_hello() Ahora utilizaremos el intérprete para ejecutar este último scriptcreado. Desde la línea de comandos ejecutamos la siguiente sentencia: $ python second.py Siendo el resultado mostrado el siguiente: Soy el primeroHola Mundo! Como habrá observado el lector, hemos introducido una nuevasentencia llamada import. Esta nos permite indicar qué módulo va a serutilizado. A partir de esta sentencia, como hemos invocado a unmódulo con su propio espacio de nombres, podremos invocar acualquier objeto que esté definido en el primero. Para ello, hacemosuso del carácter punto que actúa como separador entre módulo yobjeto. Dado que una función es un objeto, esta puede ser utilizada ennuestro segundo script, igual como ocurriría, por ejemplo, con unavariable.Además de la sentencia import, también podemos utilizar from conun comportamiento similar. Ambas difieren en que la segunda nospermitirá utilizar objetos sin necesidad de indicar el módulo al quepertenecen. De esta forma, el script second.py puede reescribirse de lasiguiente forma: from first import say_hellosay_hello() Cuando utilicemos from hemos de tener cuidado de que no existaun objeto definido con el mismo nombre, en cuyo caso se utilizaría elúltimo en ser definido. Básicamente, en la práctica, el uso de import yfrom dependerán de este hecho.El operador * puede ser utilizado para importar todos y cada unode los objetos declarados en un módulo. Así pues, con una únicasentencia tendríamos acceso a todos ellos. Por otro lado, si solonecesitamos importar unos cuantos, podemos separarlos utilizando lacoma. Supongamos que nuestro primer script de ejemplo tuvieradefinidas cinco funciones, pero nosotros solo necesitamos utilizar dosde ellas. Bastaría con utilizar la siguiente sentencia:
    • 104. from first import say_hello, say_bye Adicionalmente, un módulo puede ser importado para serreferenciado con un nombre diferente. Para ello, se utiliza la palabraclave as seguida del nombre que deseamos emplear. Por ejemplo,supongamos que deseamos importar nuestro módulo first con elnombre de primero, bastaría con la siguiente sentencia: import first as primero A partir de la redefinición anterior, todas las referencias al módulo first se realizarán a través de primero, tal y como muestra el siguienteejemplo: primero.say_hello() Volviendo a nuestro ejemplo inicial, tal y como habremosobservado, la sentencia print("Soy el primero") ha sidoejecutada a pesar de no formar parte de la función say_hello(). Paraexplicar este comportamiento, debemos entender cómo funciona la importación de módulos en Python. FUNCIONAMIENTO DE LA IMPORTACIÓN Algunos lenguajes permiten trabajar de forma estructurada deforma análoga a como lo hace Python. Se pueden crear componentesy desde unos ficheros invocar a funciones y/o variables declaradas enotros. Sin embargo, a diferencia, por ejemplo del lenguaje C, en Pythonla importación de un módulo no es simplemente la inserción decódigo de un fichero en otro, ya que esta es una operación que ocurreen tiempo de ejecución. En concreto, se llevan a cabo tres pasosdurante la importación. El primero de ellos localiza el fichero físico decódigo que corresponde al módulo en cuestión. Para llevar a cabo estepaso, se utiliza un path de búsqueda determinado del que nosocuparemos en el apartado siguiente de este capítulo. Una vezlocalizado el fichero se procede a la generación del bytecode asociadoal mismo. Este proceso ocurre en función de las fechas del fichero decódigo (.py) y del bytecode asociado (.pyc) . Si la del primero esposterior a la del segundo, entonces se vuelve a generar todoautomáticamente. El último paso durante la importación es la
    • 105. ejecución del código del módulo importado. Esta es la razón por lacual se ejecuta la sentencia print de nuestro ejemplo.Dado que los pasos llevados a cabo durante la importación puedenser costosos en tiempo, el intérprete de Python solo importa cadamódulo una vez por proceso. Esto implica que sucesivas importacionesse saltarán los tres pasos y el Intérprete pasará directamente a utilizarel módulo contenido en memoria. No obstante, siendo este elcomportamiento por defecto, Python nos permite emplear la función reload del módulo imp, que se encuentra en la librería estándar.Gracias a la cual, es posible indicar que se vuelva a realizar laimportación de un determinado módulo. PATH DE BÚSQUEDA Anteriormente hemos mencionado que el primer paso del procesode importación utiliza un path para la búsqueda. Es decir, el intérpretenecesita saber desde qué localización o path del sistema de ficherosdebe comenzar a buscar. Por defecto, el primer lugar donde busca esel directorio donde reside el fichero que está siendo ejecutado. Asípues, dado que nuestros scripts de ejemplo han sido creados en elmismo directorio, la importación se realiza correctamente. El segundolugar que comprueba el intérprete es el directorio referenciado por elvalor de la variable de entorno PYTHONPATH. Esta variable puedeestar creada o no, dependiendo ello del sistema operativo y de siestamos utilizando la línea de comandos. Finalmente, se examinan losdirectorios de la librería estándar. A la misma, dedicaremos el siguienteapartado.Gracias al path de búsqueda que emplea el intérprete, es posibleimportar cualquier módulo con independencia de su localización en elsistema de ficheros. Obviamente, para ello es necesaria la definición dela mencionada variable PYTHONPATH. Además, esta variable nosaporta bastante flexibilidad, ya que la misma puede ser modificada entiempo de ejecución.Comprobar cuáles son los directorios actuales en el path debúsqueda es fácil gracias a la lista path que pertenece al módulo sys dela librería estándar de Python. Por ejemplo, ejecutemos las siguientessentencias en Windows y observemos el resultado:
    • 106. >>> import sys>>> sys.path['','C:\\Windows\\system32\\python32.zip','C:\\Python32\\DLLs', 'C:\\Python32\\lib','C:\\Python32', 'C:\\Python32\\lib\\site-packages'] Dado que sys.path nos devuelve una lista, esta puede sermodificada en tiempo de ejecución, logrando así cambiar losdirectorios por defecto asociados al path de búsqueda. LIBRERÍA ESTÁNDAR Python incorpora una extensa colección de módulos puestaautomáticamente a disposición del programador. A esta colección se leconoce con el nombre de librería estándar y, entre los módulos queincorpora, encontramos utilidades para interactuar con el sistemaoperativo, trabajar con expresiones regulares, manejar protocolos dered como HTTP, FTP y SSL, leer y escribir ficheros, comprimir ydescomprimir datos, manejar funciones criptográficas y procesarficheros XML.Toda la información referente a la librería estándar de Pythonpuede encontrarse en la página web oficial de la misma (verreferencias). Paquetes Hasta ahora hemos visto la relación directa entre ficheros ymódulos. Sin embargo, los directorios del sistema de ficheros tambiénpueden utilizarse en la importación. Esto nos lleva al concepto de paquete, que no es sino un directorio que contiene varios ficheros decódigo Python que guardan entre sí una relación conceptual basada ensu funcionalidad. La peculiaridad del intérprete es que,automáticamente, genera un espacio de nombres de variables a partirde un directorio. Ello también afecta directamente a cada subdirectorioque exista a partir del directorio en cuestión. De esta forma, podemosorganizar más cómodamente nuestro código.Para crear nuestro primer paquete, comenzaremos creando unnuevo directorio al que llamaremos mypackage. Copiaremos dentro
    • 107. del mismo nuestros scripts first.py y second.py. Por último, crearemosun fichero vacío llamado __init__.py . Ya tenemos listo nuestro paquete,ahora probaremos su funcionamiento a través de un nuevo fichero (main.py), que debe estar fuera del directorio creado, con el siguientecontenido: from mymodule import firstfirst.say_hello() La ejecución del último script creado nos mostrará cómo esrealizado el correspondiente import a través de nuestro paquete: $ python main.pySoy el primeroHola Mundo! Seguro que al lector no se le ha pasado por alto el nuevo fichero __init.py__ .Todos los directorios que vayan a ser empleados como paquetesdeben contenerlo. Se trata de un fichero en el que podemos añadircódigo y que se puede utilizar sin ninguno. En la práctica, este ficherose utiliza para incluir aquellas sentencias que son necesarias pararealizar acciones de inicialización. Por otro lado, el fichero __init__.py también se utiliza para que Python considere los directorios delsistema de ficheros como contenedores de módulos. De esta forma,cualquier directorio del sistema de ficheros puede ser un paquete dePython, permitiéndose utilizar diferentes niveles de subdirectorios. Lassentencias import y from son las que son empleadas para importar losdiferentes módulos que forman parte de un paquete. COMENTARIOS Al igual que en otros lenguajes de programación, en Pythonpodemos añadir comentarios a nuestro código. En realidad, estoscomentarios son un tipo de sentencias especiales que el intérpreteentiende que es código que no debe ejecutar. Los comentarios sonmuy útiles para describir qué acción lleva a cabo determinado código.En general, es una buena práctica de programación documentarnuestro código añadiendo comentarios. Estos son muy prácticos para
    • 108. entender el funcionamiento del código, tanto para otrosprogramadores, como para nosotros mismos. Es habitual, cuandotranscurre cierto tiempo, olvidar qué hace un determinado trozo decódigo, aunque lo haya escrito el mismo programador. De forma quelos comentarios nos serán muy útiles en casos como este.Python utiliza dos tipos de comentarios, los que se utilizan paracomentar una única línea y los que se emplean para más de una línea.El siguiente ejemplo muestra cómo utilizar los del primer tipo: # Esto es un comentarioprint("Hola Mundo!") Por otro lado, para los comentarios multilínea, emplearemos trescomillas dobles (") seguidas, tal y como muestra el siguiente ejemplo: """Comienzo de primera línea de comentarioSegunda línea de comentarioTercera línea de comentario""" EXCEPCIONES El control de excepciones es un mecanismo que nos ofrece ellenguaje para detectar ciertos eventos y modificar el flujo original delprograma en ejecución. Habitualmente, estos eventos son errores queocurren en tipo de ejecución. Este control de errores nos permitedetectarlos, realizar una serie de acciones en consecuencia y modificarel flujo de nuestro programa.Cuando un error ocurre en tiempo de ejecución, el intérprete dePython aborta la ejecución del programa. Python disparaautomáticamente un evento cuando uno de estos errores ocurre. Elcontrol de excepciones del lenguaje nos permitirá comprobar si uno deestos eventos ha sido lanzado. Además de controlar los errores, lasexcepciones nos permiten notificar el evento que se genera comoconsecuencia del error producido. Capturando excepciones Para explicar cómo funciona la captura de excepciones, partiremos
    • 109. de una lista que contiene dos elementos. Si intentamos acceder a lalista utilizando el valor de índice 2, ocurrirá un error: >>> li = [0, 1]>>> li [2]Traceback (most recent call last):File "<stdin>", line 1, in <module>IndexError: list index out of range Suponiendo que las líneas anteriores de código forman parte de unprograma, lo que ocurriría al ejecutar la última de ellas, es que elintérprete detendría automáticamente la ejecución del programa. Paraevitar esto, podemos capturar la excepción IndexError, la cual ha sidolanzada al detectar el error: >>> try:... print(li[2])... except IndexError:... print("Error: índice no válido")...Error índice no válido Empleando el código anterior la ejecución no será detenida y elintérprete continuará ejecutando el programa. Efectivamente, elbloque try/except es el utilizado para capturar excepciones. Justodespués de la palabra clave except debemos indicar el tipo deexcepción que deseamos detectar. Por defecto, si no indicamosninguna, cualquier excepción será capturada. A partir de la sentencia try, se tendrá en cuenta cualquier línea de código y si, comoconsecuencia de la ejecución de una de ellas, se produce unaexcepción serán ejecutadas las sentencias de código que aparecendentro de la sentencia except. En concreto, la sintaxis de la cláusula try/except es como sigue: try: <sentencias_susceptibles_de_lanzar_error>except [<Nombre_excepcion>]: <sentencias_ejecutadas_cuando_error>finally: <sentencias_ejecutadas_siempre>else:<sentencias_ejecutadas_si_no_error> Como ejemplo para ilustrar la sintaxis de try/except basta conmodificar ligeramente el último ejemplo de código:
    • 110. try: <li [2]except:<print("Error: índice no válido")else:<print("Sin error")finally:<print("Bloque ejecutado") Si ejecutamos el código anterior, comprobaremos cómo elintérprete lanzará la primera y tercera sentencia print. Sin embargo, sisustituimos la sentencia li[2] por li[0] observaremos cómo son las dosúltimas sentencias print las ejecutadas. Lanzando excepciones En determinadas ocasiones puede ser interesante que lancemosuna excepción concreta desde nuestro código. Podemos lanzarcualquiera definida en el lenguaje o una propia que nosotros creemos.De este último tipo nos ocuparemos en el siguiente apartado.La sentencia raise es la propuesta por Python para lanzarexcepciones. El ejemplo más sencillo posible, sería invocarladirectamente seguida del nombre de la excepción que deseamoslanzar. A continuación, he aquí el código en cuestión para nuestroejemplo: >>> try:... raise TypeError... except:... print("Error de tipo")...Error de tipo Es importante tener en cuenta que si lanzamos una excepción y estano es capturada, será propagada hacia el nivel superior de códigohasta encontrar un bloque que la maneje. Si ningún bloque la captura,el intérprete mostrará el error y detendrá la ejecución del código. Excepciones definidas por el usuario Antes de continuar, el lector deberá entender los fundamentos dela programación orientada a objetos. En concreto, es interesante saber
    • 111. cómo implementar una clase y cómo funciona la herencia. El siguientecapítulo está dedicado a cómo emplear este paradigma deprogramación en Python.El programador puede crear sus propios tipos de excepciones ylanzarlas cuando sea necesario. El nombre de excepción definido por elusuario debe corresponder a una clase que herede de la clase Exception, la cual está definida en la librería estándar de Python. Laclase en cuestión contendrá las acciones que deben ser ejecutadascuando la excepción es lanzada.Para utilizar las excepciones que definamos necesitaremos emplear raise para que puedan ser lanzadas en un momento determinado.Tengamos en cuenta que el intérprete no podrá lanzarlasautomáticamente, es por ello que raise es necesario.Trabajemos con un sencillo ejemplo donde crearemos y lanzaremosuna excepción a la que llamaremos ValorIncorrecto. Se trata decomprobar si un número está en un rango determinado de valores. Encaso afirmativo lanzaremos nuestra excepción. El primer paso es definirnuestra clase: class ValorIncorrecto(Exception):def __init__(self, val):print(("{0} no permitido").format(val)) En este caso concreto, la excepción definida simplemente imprimiráun mensaje informando de que el valor no está permitido.Obviamente, podemos utilizar diferentes sentencias para llevar a cabodistintas acciones. Por otro lado, el constructor de la clase utiliza unparámetro, que deberá ser el valor que estamos comprobando.Observemos cómo lanzar la excepción definida y lo que nos devuelveel intérprete: >>> val = 8>>> if val > 5 and val < 9:... raise ValorIncorrecto(val)...8 no permitidoTraceback (most recent call last):File "<stdin>", line 1, in <module>__main__.ValorIncorrecto Información sobre la excepción
    • 112. Python nos permite trabajar con la información generada cuando seproduce una excepción. Esto puede ser útil para indicarle al usuariodetalles sobre lo ocurrido. Si además utilizamos un fichero de log paravolcar esta información, tendremos una útil herramienta para detectarlo sucedido en tiempo de ejecución.El módulo sys de la librería estándar dispone de una funciónllamada exc_info() que nos devuelve una tupla con tres valoresprincipales: el tipo de la excepción, la instancia de clasecorrespondiente a la excepción y un objeto traceback que contienetoda la información que existe en el stack en el punto en el que seprodujo la excepción. Si volvemos al primer ejemplo con el quecomenzamos nuestro apartado sobre excepciones y justo después dela sentencia except añadimos el siguiente código, al ejecutarlo,veremos cómo el intérprete muestra la información requerida: >>> try:... print(li[2])... except IndexError:... import sys... sys.exc.info()... (<class 'IndexError'>, IndexError('list assignmentindex out of range',), <traceback object at 0x00000000022AB848>) Si quisiéramos solo mostrar el error ocurrido o bien volcarlo a unfichero de log, podríamos obtenerlo utilizando la siguiente sentencia: sys.exc.info()[2] El resultado de sustituir la anterior línea de código por la de sys.exc.info() nos lanzaría el siguiente resultado: IndexError('list assignment index out of range') Aquí finaliza este capítulo que sienta las bases para comenzar aprogramar con Python. Los siguientes capítulos del libro nos mostraráncómo profundizar en el lenguaje, comenzando por el paradigma de laorientación a objetos.
    • 113. ORIENTACION A OBJETOS INTRODUCCIÓN Entre los paradigmas de programación soportados por Pythondestacan el procedural, el funcional y la orientación a objetos. De esteúltimo y de su aplicación en el lenguaje nos ocuparemos en estecapítulo.En líneas generales, la orientación a objetos se basa en la definicióne interacción de unas unidades mínimas de código, llamadas objetos,para el diseño y desarrollo de aplicaciones. Este paradigma comenzó apopularizarse a mediados de los años 90 y, en la actualidad, es uno delos más utilizados. Algunos lenguajes, como Java, están basadoscompletamente en el uso del mismo. Otros como Ruby, C++ y PHP5también ofrecen un amplio y completo soporte de la orientación aobjetos. A este paradigma se le conoce también por sus siglas eninglés: OOP (Object Oriented Programming).La orientación a objetos es ampliamente utilizada por losprogramadores de Python, especialmente en aquellos proyectos que,por su tamaño, requieren de una buena organización que ayude a sumantenimiento. Además, modernos componentes, como son losframeworks web, hacen un uso intensivo de este paradigma. Entre lasclaras ventajas de la orientación a objetos podemos destacar lareusabilidad de código, la modularidad y la facilidad para modelarcomponentes del mundo real.A los programadores de C++ y Java les resultará familiar laterminología y conceptos explicados en este capítulo. Sin embargo,deberán prestar atención a las diferencias que presenta Python conrespecto a estos lenguajes.Para aquellos lectores que nunca han trabajado con orientación aobjetos, recomendamos la lectura de la página de Wikipediacorrespondiente (ver referencias).En este capítulo veremos cómo definir y utilizar clases y objetos.Explicaremos qué tipos de variables y métodos pueden ser declaradosen la definición de una clase. Descubriremos cuáles son las diferencias
    • 114. que presenta Python con respecto a otros lenguajes a la hora detrabajar con el paradigma de la OOP. Los dos últimos apartados estándedicados a dos conceptos fundamentales en la orientación a objetos:la herencia y el polimorfismo. Comencemos aprendiendo cómo definirclases y cómo instanciar objetos de las mismas.
    • 115. CLASES Y OBJETOS Hasta ahora hemos utilizado indistintamente el concepto de objetocomo tipo o estructura de datos. Sin embargo, dentro del contexto dela OOP, un objeto es un componente que tiene un rol específico y quepuede interactuar con otros. En realidad, se trata de establecer unaequivalencia entre un objeto del mundo real con un componentesoftware. De esta forma, el modelado para la representación yresolución de problemas del mundo real a través de la programación,es más sencillo e intuitivo. Si echamos un vistazo a nuestro alrededor,todos los objetos presentan dos componentes principales: un conjuntode características y propiedades y un comportamiento determinado.Por ejemplo, pensemos en una moto. Esta tiene características comocolor, marca y modelo. Asimismo, su comportamiento puede serdescrito en función de las operaciones que puede realizar, porejemplo, frenar, acelerar o girar. De la misma forma, en programación,un objeto puede tener atributos y se pueden definir operaciones quepuede realizar. Los atributos equivaldrían a las características de losobjetos del mundo real y las operaciones se relacionan con sucomportamiento.Es importante distinguir entre clase y objeto. Para ello,introduciremos un nuevo concepto: la instancia. La definición de losatributos y operaciones de un objeto se lleva a cabo a través de unaclase. La instanciación es un mecanismo que nos permite crear unobjeto que pertenece a una determinada clase. De esta forma,podemos tener diferentes objetos que pertenezcan a la misma clase.Crear una clase en Python es tan sencillo como emplear la sentencia class seguida de un nombre. Por convención, el nombre de la claseempieza en mayúscula. También suele estar contenida en un ficherodel mismo nombre de la clase, pero todo en minúscula. La clase mássencilla que podemos crear en Python es la siguiente: class First:pass Una vez que tenemos la clase definida, crearemos un objeto
    • 116. utilizando la siguiente línea de código: a = First () Al igual que otros lenguajes de programación, al declarar una clasepodemos hacer uso de un método constructor que se encargue derealizar tareas de inicialización. Este método siempre es ejecutadocuando se crea un objeto. Python difiere con otros lenguajes aldisponer de dos métodos involucrados en la creación de un objeto. Elprimero de ellos se llama __init__ y el segundo __new__ . Este último esel primero en ser invocado al crear el objeto. Después, se invoca a __init__ que es el que habitualmente se emplea para ejecutar todas lasoperaciones iniciales que necesitemos. En la práctica utilizaremos __init__ como nuestro método constructor. Sobre el método __new__ nos ocuparemos en el apartado "Métodos especiales" del presentecapítulo. Así pues, nuestra clase con su constructor quedaría de lasiguiente forma: class First:def __init__ (self):print("Constructor ejecutado") Al crear una nueva instancia de la clase, es decir, un nuevo objeto,veríamos cómo se imprime el mensaje: >>> f = First()Constructor ejecutado Seguro que el lector se ha percatado del parámetro formal quecontiene nuestro método constructor, nos referimos a self. Esteparámetro contiene una referencia al objeto que ejecuta el método y,por lo tanto, puede ser utilizada para acceder al espacio de nombresdel objeto. Debemos tener en cuenta que cada objeto contiene supropio espacio de nombres. Los métodos de instancia deben contenereste parámetro y debe ser el primero en el orden. Otros lenguajesutilizan un mecanismo similar a través de la palabra clave this, porejemplo PHP; sin embargo, en Python self no es una palabra clave ypodemos emplear cualquier nombre para esta referencia. Sin embargo,esto no es aconsejable, ya que, por convención, está ampliamenteaceptado y arraigado el nombre de self para este propósito. Por otrolado, cuando se invoca a un método, no es necesario incluir la
    • 117. mencionada referencia. Como ejemplo, modifiquemos nuestra claseañadiendo el siguiente método: def nuevo(self):print("Soy nuevo") Posteriormente, creemos un nuevo objeto de nuestra clase einvoquemos al método recién creado: >>> f = First()Constructor ejecutado>>> f.nuevo()Soy nuevo Variables de instancia Anteriormente hemos mencionado a los atributos y los hemosseñalado como la correspondencia a las características de los objetosdel mundo real. En la terminología de Python, a estos atributos se lesdenomina variables de instancia y siempre deben ir precedidos de lareferencia self. Volviendo a nuestro ejemplo de la moto, modelemosuna clase que contenga unas cuantas variables de instancia: class Moto:def __init__ (self, marca, modelo, color):self.marca = marcaself.modelo = modeloself.color = color Los atributos de nuestra clase son creados al inicializar el objeto,utilizando tres variables diferentes que son pasadas como parámetrosdel constructor. De esta forma, la siguiente creación e inicialización deobjetos sería válida: >>> bmw_r1000 = Moto("BMW", "r1OO", "blanca")>>> suzuki_gsx = Moto("Suzuki", "GSX", "negra") Dado que self es empleado en todos los métodos de instancia,podemos acceder a los atributos en cualquiera de estos métodos. Dehecho esta es una de las ventajas de usar self, ya que, cuando lohacemos seguido de un atributo, tenemos la seguridad de que nosreferimos al mismo y no a una variable definida en el espacio de
    • 118. nombres del método en cuestión. Para ilustrar este comportamiento,añadamos el siguiente nuevo método: def get_marca(self):marca = "Nueva marca"print(self.marca) Ahora creemos volvamos a crear un objeto e invoquemos a estemétodo: >>> honda_cbr = Moto("Honda", "CBR", "roja")>>> honda_cbr.get_marca()Honda Métodos de instancia Este tipo de métodos son aquellos que, en la práctica, definen lasoperaciones que pueden realizar los objetos. De hecho, nuestroejemplo anterior (get_marca) es un ejemplo de ello. Habitualmente aeste tipo de métodos se les llama simplemente métodos, aunque, tal ycomo veremos en sucesivos apartados, existen otros tipos de métodosque pueden definirse en una clase.Además de self, un método de instancia puede recibir un número n de parámetros. Una vez más, volvamos a nuestro ejemplo de la claseMoto y añadamos un nuevo método que nos permita acelerar: def acelerar(self, km):print("acelerando {0} km".format(km)) La llamada al nuevo método quedaría de la siguiente forma: >>> honda_cbr.acelerar(20)acelarando 20 km Formalmente, un método de instancia es una función que se definedentro de una clase y cuyo primer argumento es siempre unareferencia a la instancia que lo invoca. Variables de clase
    • 119. Relacionados con los atributos, también existen en Python lasvariables de clase. Estas se caracterizan porque no forman parte de unainstancia concreta, sino de la clase en general. Esto implica que puedenser invocados sin necesidad de hacerlo a través de una instancia. Paradeclararlas bastan con crear una variable justo después de la definiciónde la clase y fuera de cualquier método. A continuación, veamos unejemplo que declara la variable n_ruedas: class Moto:n_ruedas = 2def __init__(self, marca, modelo, color):self.marca = marcaself.modelo = modeloself.color = color Así pues, podemos invocar a la variable de clase directamente: >>> Moto.n_ruedas2 Además, una instancia también puede hacer uso de la variable declase: >>> m = Moto()>>> m.n_ruedas2 En realidad, estas variables de clase de Python son parecidas a lasdeclaradas en Java o C++ a través de la palabra clave static. Sinembargo, y a diferencia de estos lenguajes, las variables de clasepueden ser modificadas. Esto se debe a cómo Python trata lavisibilidad de componentes, aspecto del que nos ocuparemospróximamente. Propiedades Las variables de instancia, también llamados atributos, definen unaserie de características que poseen los objetos. Como hemos vistoanteriormente, se declaran haciendo uso de la referencia a la instanciaa través de self. Sin embargo, Python nos ofrece la posibilidad deutilizar un método alternativo que resulta especialmente útil cuando
    • 120. estos atributos requieren de un procesamiento inicial en el momentode ser accedidos. Para implementar este mecanismo, Python empleaun decorador llamado property. Este decorador o decorator es,básicamente, un modificador que permite ejecutar una versiónmodificada o decorada de una función o método concreto. En elsiguiente capítulo entraremos en detalle en la explicación de sufuncionamiento. De momento y para entender cómo funciona eldecorador property, nos bastará con la descripción recién realizada.Supongamos que tenemos una clase que representa un círculo ydeseamos tener un atributo que se refiera al área del mismo. Dado queesta área puede ser calculada en función del radio del círculo, parecelógico utilizar property para llevar a cabo el procesamiento requerido.Teniendo esto en cuenta, nuestra clase quedaría de la siguiente forma: from math import piclass Circle:def __init__(self, radio):self.radio = radio@propertydef area(self):return pi * (self.radio ** 2) Dada la anterior definición, podemos acceder directamente a área, tal y como lo haríamos con cualquier variable de instancia. El siguienteejemplo muestra cómo hacerlo: >>> c = Circle(25)>>> print(c.area)1661.9025137490005 Alternativamente, en lugar de emplear el decorator, también esposible definir un método en la clase y luego emplear la función property() para convertirlo en un atributo. Si optamos por estemecanismo, el código equivalente al método decorado sería elsiguiente: def area (self) :return pi * (self.radio ** 2)area = property(area) Ya que la declaración y uso del decorator es más sencilla y fácil deleer, recomendamos su utilización en detrimento de la mencionadafunción property().
    • 121. Seguro que los programadores de Java están habituados a utilizarmétodos setters y getters para acceder a atributos de clase. En Java yotros lenguajes similares, se emplea una técnica diferente a la que seusa en Python. Normalmente, sobre todo en Java, los atributos deinstancia se declaran con el modificador de visibilidad private y seemplea un método para acceder al atributo y otro para modificarlo. Alos del primer tipo se les llama getter y a los del segundo setter. Habitualmente, se emplea la nomenclatura get<Nombre_atributo> y set<Nombre_atributo>. Por ejemplo, tendríamos un método getRadio() y otro llamado setRadio(). Nuestro decorador en Python, de momento, solo nos sirve paraacceder al atributo en cuestión, pero no para modificarlo. De hecho, alejecutar la siguiente sentencia, obtendremos un error: >>> c.area = 23Traceback (most recent cali last) :File "Dropbox\libro_python\code\circle.py", line 17, in<module>print(c.area)File "Dropbox\libro_python\code\circle.py", line 10, in areareturn self.__areaAttributeError: 'Circle' object has no attribute '_Circle area' Para poder llevar a cabo esta acción, Python permite utilizar otrosmétodos disponibles a través del decorado property. En nuestroejemplo estamos calculando el área y devolviendo su valordirectamente a través del decorador, pero, para ilustrar el uso de setters y getters en Python, nos centraremos en el atributo radio denuestra clase Circulo. Haremos esto, simplemente porque tiene mássentido hacerlo con el radio que no es un campo calculable. Volviendoa nuestro objetivo, para modificar el atributo radio haremos una seriede cambios en nuestra clase original. En primer lugar eliminaremos elconstructor de la misma. Después, añadiremos el siguiente método,donde, hemos creado un atributo privado empleando el doble guiónbajo (__: @propertydef radius(self) :return self.__radio Seguidamente, escribiremos el método que se encargará de lamodificación de nuestro nuevo atributo de clase. El código es este:
    • 122. @radio.setterdef radio(self, radio):self. radio = radio Ahora veremos cómo emplear nuestra clase con estos cambios,para ello simplemente instanciaremos un nuevo objeto ymodificaremos su valor: >>> circulo = Circulo()>>> circulo.radio = 23>>> print(circulo.radio)23 El lector se habrá dado cuenta de que hemos utilizado el conceptode atributo privado, lo que implica que estamos trabajando con unmodificador de visibilidad. Para entender cómo realmente funciona lamisma en Python, pasaremos al siguiente apartado. Visibilidad Uno de los aspectos principales en los que difiere la orientación aobjetos de Python con respecto a otros lenguajes, como Java y PHP5,es en el concepto de visibilidad de componentes de una clase. EnPython no contamos con modificadores como public o private ysimplemente, todos los métodos y atributos pueden considerarsecomo public. Es decir, realmente no se puede impedir el acceso a unobjeto o atributo desde la instancia de una clase concreta. Pero ¿esposible de alguna forma indicar que un atributo o método solo debeser invocado desde la misma clase? Esto correspondería a utilizar elmodificador private en lenguajes como PHP5 y Java. La respuesta a lapregunta es sí, pero debemos tener en cuenta que hablamos de debe yno de puede. De esta forma, los programadores de Python utilizan unasencilla convención: Si un atributo debe ser privado, basta conanteponer un único guión bajo (underscore). Sin embargo, paraPython, esto no significa nada, simplemente es una convención entreprogramadores.No perdamos de vista que lo comentado sobre la visibilidad esaplicable, tanto a variables como a métodos. Así pues, si utilizamos elunderscore para declarar un método como privado, veremos cómo eso
    • 123. no impide su acceso. Trabajemos con un sencillo ejemplo, declarandouna clase y su correspondiente método: class Test:def _privado(self) :print("Método privado") Ahora pasemos a crear una instancia y observaremos cómo lallamada al método en cuestión es válida: >>> t = Test()>>> t._privadoMétodo privado Por otro lado, en el apartado anterior hemos visto cómo el dobleguión bajo (__) ha sido empleado para declarar un atributo privado deinstancia. Realmente, si el atributo ha sido declarado de esta forma,Python previene el acceso desde la instancia. Observemos la siguienteclase que declara un atributo de esta forma y comprobemos cómoPython no nos deja acceder al mismo: class Privado:def __ini__(self):self.__atributo = 1>>> p = Privado>>> p.__atributoTraceback (most recent call last):File "<stdin>", line 1, in <module>AttributeError: 'Privado' object has no attribute '__atributo' Sin embargo, para acceder a este atributo y dado que Python enrealidad no entiende el concepto de visibilidad como tal, el lenguajeemplea un mecanismo técnicamente denominado name mangling. Este consiste en convertir cualquier atributo declarado con doble guiónen simple guión seguido del nombre de la clase y luego doble guiónbajo para acceso directo al atributo. Así pues y continuando connuestra clase Privado, la forma de acceder a nuestro atributo sería lasiguiente: >>> p_Privado__atributo12 Algunos prefieren no emplear la denominación privado parareferirse a este tipo de atributos. En su lugar, prefieren llamarlos pseudoprivados.
    • 124. La técnica del name mangling se diseñó en Python para asegurarque una clase hija no sobrescribe accidentalmente los métodos y/oatributos de su clase padre. Esta es la verdadera razón de su existencia,no la de poder utilizar un modificador de visibilidad. Acabamos deintroducir el concepto de hija y padre para referirnos a dos tipos declases diferentes. Estos conceptos se engloban dentro del ámbito de la herencia, de la que nos ocuparemos detenidamente en apartadosposteriores. El código que viene a continuación ilustra cómo evitar lamencionada sobrescritura del atributo test: class Padre:def __init__(self):self.__test = "Padre"class Hijo:def __init__(self):self.__test = "Hijo" Sin aplicar la técnica del name mangling, en el ejemplo anterior, elatributo test de la clase padre sería sobrescrito por el de la clase hija yotra clase que heredara de la padre se vería afectada. Métodos de clase Ya hemos aprendido sobre atributos y métodos de instancia. PeroPython también permite crear métodos de clase. Estos se caracterizanporque pueden ser invocados directamente sobre la clase, sinnecesidad de crear ninguna instancia.Dado que no vamos a utilizar un objeto, no es necesario emplear elargumento self como referencia en los métodos de clase. En su lugar, síque debemos contar con otro parámetro similar que referenciará a laclase en cuestión. Por convención, al igual que se emplea self para losmétodos de instancia, cls es el nombre que suele ser empleado comoreferencia en los métodos de clase. Además, esto implica que elprogramador no tiene que pasar como parámetro la mencionadareferencia, ya que Python lo hace automáticamente. Eso sí, esresponsabilidad del programador utilizar el parámetro formal, paradicha referencia, en la definición del método. Esto es válido tanto para self como para cls.Por otro lado y al igual que en el caso de los métodos de instancia,
    • 125. se pueden utilizar parámetros adicionales para el método de clase. Loque sí que es importante que nunca olvidemos la referencia cls en sudefinición. El resto de parámetros son opcionales.Los métodos de clase requieren el uso de un decorador definidopor Python, su nombre es classmethod y funciona de forma similar acomo lo hace el comentado property. Gracias al decorator, solodebemos definir el método con el argumento referencia cls y escribirsu funcionalidad. Sirva como ejemplo el siguiente código: class Test:def __init__(self):self.x = 8@classmethoddef metodo_clase(cls, param1):print("Parámetro: {0}".format(param1)) Para poner en práctica el código anterior, primero invocaremosdirectamente al método de clase: >>> Test.metodo_clase(6)Parámetro: 6 También es fácil comprobar que el método de instancia puede serinvocado, creando previamente un objeto de la clase en cuestión: >>> t = Test ()>>> print(t.x)8 Es interesante saber que los métodos de clase también pueden serinvocados a través de una instancia de la misma. Es tan sencillo comoinvocarlo como si de un método de instancia se tratase. Siguiendo conel ejemplo anterior, la siguiente sentencia es válida y produce elresultado que podemos apreciar: >>> t.metodo_clase(5)Párametro: 5 Lógicamente, si un método de clase puede ser invocado desde unainstancia, también podemos crear un objeto e invocar directamente almétodo en cuestión en la misma línea de código: >>> Test().metodo_clase(3)Párametro: 3
    • 126. El comportamiento anteriormente explicado tiene sentido, ya que,en realidad, Python no tiene modificadores de visibilidad como tales.Es por ello, que un método de clase es también un método deinstancia. La diferencia es que un método de instancia no puede sercreado si esta no existe.Lenguajes como Java y C++ emplean la palabra clave static paradeclarar métodos de clase. Además, en estos lenguajes, no es posibleutilizar la referencia a la instancia, llamada en ambos casos this. Sinembargo, estos no pueden ser invocados desde una instancia, adiferencia de Python.Otros lenguajes como Ruby y Smalltalk no tienen métodos estáticos, pero sí de clase. Esto implica que estos métodos sí que tienenacceso a los datos de la instancia. Python cuenta tanto con métodosde clase como con métodos estáticos. De estos últimos nosocuparemos a continuación. Métodos estáticos Otro de los tipos de métodos que puede contener una clase enPython son los llamados estáticos. La principal diferencia con respectoa los métodos de clase es que los estáticos no necesitan ningúnargumento como referencia, ni a la instancia, ni a la clase.Lógicamente, al no tener esta referencia, un método estático no puedeacceder a ningún atributo de la clase.De la misma forma que los métodos de clase en Python usan eldecorador classmethod, para los estáticos contamos con otrodecorador llamado staticmethod. La definición de un método estáticorequiere del uso de este decorador, que debe aparecer antes de ladefinición del mismo.Para comprobar cómo funcionan los métodos estáticos, añadamosel siguiente método a nuestra clase Test: @staticmethoddef metodo_estatico(valor):print("Valor: {0}".format(valor)) Seguidamente, invoquémoslo directamente desde una instanciadeterminada de la clase:
    • 127. >>> t = Test()>>> t.metodo_ estático (33)Valor: 33 Echando un vistazo al código anterior, podemos apreciar que ladiferencia de un método estático y otro de instancia es, además deluso del decorado en cuestión, que como primer parámetro no estamosusando self para referenciar a la instancia. Pero este hecho tiene otraimplicación, tal y como hemos comentado previamente. Se trata deque no es posible acceder desde el mismo a un atributo de clase. Asípues si cambiamos el código del método anterior por el siguiente, seproduciría una excepción en tiempo de ejecución: @staticmethoddef metodo_estatico(valor):print(self.x) El error en cuestión, producido por la ejecución del método estáticodesde una instancia, sería el siguiente: NameError: global name self is not defined Es lógico que se produzca el error, no olvidemos que self es solouna convención para referenciar a la instancia de clase. Dado que elmétodo es estático y no existe tal referencia en el mismo, no es posibleutilizar ningún atributo de instancia en su interior. NameError es unaexcepción predefinida por Python y que es lanzada como consecuenciade intentar acceder al atributo.Algunos programadores prefieren crear una función en lugar de unmétodo estático. Se puede llamar a la función y utilizar su resultadointeractuando posteriormente con la instancia de la clase. Sinembargo, otros prefieren emplear los métodos estáticos,argumentando que, si la funcionalidad está estrechamente relacionadacon la clase, se mantiene y cumple el principio de abstracción (verreferencias), comúnmente utilizado en la programación orientada aobjetos. Métodos especiales Python pone a nuestra disposición una serie de métodos especiales
    • 128. que, por defecto, contendrán todas las clases que creemos. Enrealidad, cualquier clase que creemos será hija de una clase especialintegrada llamada object. De esta forma, dado que esta clase cuentapor defecto con estos métodos especiales, ambos estarán disponiblesen nuestros nuevos objetos. Es más, podemos sobrescribir estosmétodos reemplazando su funcionalidad.Cuando creamos una nueva clase esta hereda directamente de object, por lo que no es necesario indicar esto explícitamente alinstanciar un objeto de una clase concreta.Los métodos especiales se caracterizan principalmente porqueutilizan dos caracteres underscore al principio y al final del nombre delmétodo. CREACIÓN E INICIALIZACIÓN El principal de estos métodos especiales lo hemos presentado enapartados anteriores, se trata de __init__(). Tal y como hemoscomentado previamente, este todo será el encargado de realizar lastareas de inicialización cuando la instancia de una clase determinada escreada. Necesita utilizar como parámetro formal una referencia (self) ala instancia y, adicionalmente, pueden contener otros parámetros.Por otro lado, y relacionado con __init__() , encontramos a __new__() .En muchos lenguajes la operación de creación e inicialización deinstancia es atómica; sin embargo, en Python esto es diferente. Primerose invoca a __new__() para crear la instancia propiamente dicha yposteriormente se llama a __init__() para llevar a cabo las operacionesque sean requeridas para inicializar la misma. En la práctica, sueleutilizarse __init__() como constructor de instancia, de la misma formaque en Java, por ejemplo, se emplea un método que tiene el mismonombre que la clase que lo contiene. Sin embargo, gracias a estemecanismo de Python, podemos tener más control sobre lasoperaciones de creación e inicialización de objetos. Dado quecualquier clase los contendrá al heredar de object, estos métodospodrán ser sobrescritos con la funcionalidad que deseemos.A diferencia de __init__() , __new__() no requiere de self, en su lugarrecibe una referencia a la clase que lo está invocando. Realmente, eseste un método estático que siempre devuelve un objeto.
    • 129. El método __new__() fue diseñado para permitir la personalizaciónen la creación de instancias de clases que heredan de aquellas que soninmutables. Recordemos que para Python algunos tipos integradosson inmutables, como, por ejemplo, los strings y las tuplas. Es importante tener en cuenta que el método __new__() solo llamaráautomáticamente a __init__() si el primero devuelve una instancia. Espráctica habitual realizar ciertas funciones de personalización dentrode __new__ y luego llamar al método del mismo nombre de la clasepadre. De esta forma, nos aseguraremos que nuestro __init__() seráejecutado.Con el objetivo de comprender cómo realmente funcionan losmétodos de creación de instancias e inicialización de las mismas,vamos a crear una clase que contenga ambos métodos: class Ini :def __new__(cls):print("new")return super(Test, cls).__new__(cls)def __init__(self):print("init") Ahora crearemos una instancia y observaremos el resultadoproducido que nos indicará el orden en el que ambos métodos hansido ejecutados: >>> obj = Ini()newinit ¿Qué pasaría si el método __new__() de la clase anterior nodevolviera una instancia? La respuesta es sencilla: nuestro método __init__() nunca sería ejecutado. Realizar esta prueba es bastantesencillo, basta con borrar la última línea, la que contiene la sentencia return del método __new__ . Al volver a crear la instancia,comprobaremos cómo solo obtenemos como salida la cadena de texto"init".No olvidemos que cuando trabajamos con __new__() y este recibeparámetros adicionales, estos mismos deben constar como parámetrosformales en el método __init__() . Sirva como ejemplo el siguientecódigo: def __new__(cls, x):return super(Test, cls).__new__(cls)
    • 130. def __init__(self, x):self.x = x La invocación a la creación de un objeto de la clase que contendrálos módulos anteriores podría ser de la siguiente forma: >>> t = Test("param") DESTRUCTOR Al igual que otros lenguajes de programación, Python cuenta conun método especial que puede ejecutar acciones cuando el objeto va aser destruido. A diferencia de lenguajes como C++, Python incorporaun recolector de basura (garbage collector) que se encarga de llamar aeste destructor y liberar memoria cuando el objeto no es necesario.Este proceso es transparente y no es necesario invocar al destructorpara liberar memoria. Sin embargo, este método destructor puederealizar acciones adicionales si lo sobrescribimos en nuestras clases. Sunombre es __del__() y, habitualmente, nunca se le llama explícitamente.Debemos tener en cuenta que la función integrada del() no llamadirectamente al destructor de un objeto, sino que esta sirve paradecrementar el contador de referencias al objeto. Por otro lado, elmétodo especial __del__() es invocado cuando el contador dereferencias llega a cero.Volviendo a nuestra clase ejemplo anterior (Ini) podemos añadirle elsiguiente método, para posteriormente crear una nueva instancia yobservar cómo el destructor es llamado automáticamente por elintérprete. A continuación, el código del método en cuestión: def __del__(self):print("del") En lugar de utilizar directamente el intérprete a través de la consolade comandos, vamos a crear un fichero que contenga nuestra clasecompleta, incluyendo el destructor. Seguidamente invocaremos alintérprete de Python para que ejecute nuestro script y comprobaremoscómo la salida nos muestra la cadena del como consecuencia de laejecución del destructor por parte del recolector de basura. Al terminarla ejecución del script y no ser necesario el objeto, el intérprete invocaal recolector de basura, que, a su vez, llama directamente al
    • 131. constructor. Sin embargo, esta situación varía si empleamos la consolade comandos y creamos la instancia desde la misma, ya que eldestructor no será ejecutado inmediatamente. La explicación es trivial:el intérprete no invocará al recolector hasta que no sea necesario, si lohiciera antes de tiempo, perderíamos la referencia a nuestro objeto.Cuando ejecutamos el script, el intérprete detectada que se haproducido la finalización del mismo, que ya no es necesaria lamemoria, puesto que el objeto no va a ser utilizado y que puedeproceder a la llamada al recolector de basura. REPRESENTACIÓN Y FORMATOS En algunas ocasiones puede ser útil obtener una representación deun objeto, que nos sirva, por ejemplo, para volver a crear un objetocon los mismos valores que el original. La representación de un objetopuede obtenerse a través de la función integrada repr(). Por ejemplo, sideclaramos un string con un valor determinado e invocamos a lamencionada función, conseguiremos el valor de la cadena. De hecho,en el caso de un string este es el único valor que necesitamos pararecrear el objeto. Sin embargo, la situación cambia cuando creamosnuestras propias clases. En este caso, deberemos emplear el métodoespecial __repr__() , el cual será invocado directamente cuando se llamaa la función repr(). Pensemos en una clase que representa a un coche,donde sus atributos principales son la marca y el modelo. Dado queestos son los únicos valores que necesitamos para recrear el objeto,serán los que utilizaremos en nuestro método __repr__() , tal y comomuestra el siguiente ejemplo: class Coche:def __init__(marca="Porsche", modelo="911")self.marca = marcaself.modelo = modelodef __repr__(self):return("{0}- {1}".format(self.marca, self.modelo)) Al crear un objeto e invocar a la mencionada función derepresentación, obtendremos el siguiente resultado: >>> c = Coche()>>> print(repr(c))
    • 132. Con la información devuelta por la función repr podríamos crear unnuevo objeto con los mismos valores que el original: >>> arr = repr(c).split("-")>>> d = Coche(arr[0], arr[1]) El método especial __repr__() siempre debe devolver un string, encaso contrario se lanzará una excepción de tipo TypeError. Habitualmente, esta representación de un objeto se emplea parapoder depurar código y encontrar posibles errores.Relacionado con __repr__() encontramos otro método especialdenominado __str__() , el cual es invocado cuando se llama a lasfunciones integradas str() y print(), pasando como argumento unobjeto. El método __str__() puede ser considerado también unarepresentación del objeto en cuestión, la diferencia con __repr__() radica en que el primero debe devolver una cadena de texto que nossirva para identificar y representar al objeto de una forma sencilla yconcisa. De esta forma, la información que devuelve suele ser una líneade texto descriptiva o una cadena similar. Al igual que __repr__() , elmétodo __str__ debe devolver siempre un string. Para nuestra clase deejemplo Coche, bastará con añadir el siguiente código: def __str__(self):return("{0} -> {1}".format(self.marca, self.modelo) Si llamamos a la función print(), apreciaremos el resultado: >>> print(d)"Porsche->911" En nuestros ejemplos para la clase Coche no existe muchadiferencia entre las cadenas que devuelven ambos métodos derepresentación; sin embargo, si la clase es muy compleja, sí que es másfácil establecer diferencias entre los resultados devueltos. No obstante,esto siempre queda a criterio del programador.Similar a __str__ también existe el método especial __bytes()__ quedevuelve también una representación del objeto utilizando el tipopredefinido bytes. Este método será ejecutado cuando llamamos a lafunción integrada bytes(), pasando como argumento un objeto.Por último, __format__() es otro método especial utilizado paraobtener una representación en un formato determinado de un objeto.
    • 133. Es decir, podemos indicar, valores como, por ejemplo, la alineación dela cadena de texto producida. Esto nos ayudará a crear una cadena detexto convenientemente formateada. La función integrada format() esla responsable de llamar a este método especial, y al igual que losotros métodos de representación, requiere pasar la instancia de laclase en cuestión. COMPARACIONES Comparar tipos sencillos es fácil. Por ejemplo, pensemos en dosnúmeros. Establecer si uno es mayor que otro es trivial. Igual ocurrepara otras operaciones similares como son la igualdad, lacomprobación de si es igual o mayor que y la desigualdad. Sinembargo, este hecho cambia cuando debemos aplicar estasoperaciones a objetos de nuestras propias clases. Ello se debe a que elintérprete, a priori, no sabe cómo realizar estas operaciones. Parasolventar este problema, Python cuenta con una serie de métodosespeciales que nos ayudarán a escribir nuestra propia lógica aplicable acada operación de comparación concreta. Bastará con implementar elmétodo que necesitemos sobrescribiendo el original ofrecido por ellenguaje para esta situación. Concretamente, contamos con lossiguientes métodos especiales de comparación: __It__() : Menor que. __le__() : Menor o igual que. __gt__() : Mayor que. __ge__() : Mayor o igual que. __eq__() : Igual a. __ne__(): Distinto de. Cada uno de estos métodos será invocado en función del operadorequivalente que empleemos en la comparación. Supongamos quedeseamos saber qué modelo de coche es más moderno. Porsimplicidad, nos basaremos en el atributo que representa la marca ytendremos en cuenta que este solo puede ser un número. Cuantomayor es el número, más moderno será el coche en cuestión. En base a
    • 134. esta simple afirmación, escribiremos el código del método que seencargará de realizar la comprobación: def __gt__(self, objeto):if int(self.modelo) > int(objeto.modelo):return Truereturn False Si lo añadimos a nuestra clase Coche y creamos dos instancias,podremos emplear el operador > para llevar a cabo nuestra prueba: >>> modelo_911 = Coche("Porsche", 911)>>> modelo_924 = Coche("Porsche", 924)>>> if modelo_924 > modelo_911:... print("El {0} es un modelosuperior".format(modelo_924.modelo))...El modelo 924 es un modelo superior De forma análoga podemos emplear el resto de métodosespeciales de comparación. Es importante tener en mente que estosmétodos deben devolver True o False. Sin embargo, esto es solo unaconvención, ya que, en realidad, pueden devolver cualquier valor. HASH Y BOOL Los dos últimos métodos especiales que nos quedan por describirson __hash__() y_ __bool__() . El primero de ellos se ejecuta al invocar a lafunción integrada hash() y debe devolver un número entero que sirvepara identificar de forma unívoca a cada instancia de la misma clase.De esta forma, es fácil comparar si dos objetos son el mismo, ya quedeben tener el mismo valor devuelto por hash(). A través del métodoespecial __hash__() , podemos realizar operaciones que nos seannecesarias y devolver después el correspondiente valor. Por defectotodos los objetos de cualquier clase cuentan con los métodos __eq__() y __hash__() y siempre dos instancias serán diferentes a no ser que secomparen consigo mismas. A continuación, veamos un ejemplo deinvocación al método __hash__() desde dos instancias diferentes: >>> class TestHash:... pass...>>> t = TestHash()
    • 135. >>> t.__hash__()39175112>>> hash(t)39175112>>> x = TestHash()>>> x__hash__()2448448>>> hash(x)2448448>>> x == xTrue>>> t == xFalse Por otro lado, la función bool() será la encargada de llamar almétodo especial __bool__ cuando esta recibe como argumento lainstancia de una clase determinada. Por defecto, si no implementamosel mencionado método en nuestra clase, la llamada a bool() siempredevolverá True. Si implementamos __bool__() en nuestra clase, estedebe devolver un valor de tipo booleano, es decir, en la práctica, solopodremos devolver True o False. En el siguiente ejemplo,sobrescribiremos el método especial para que siempre devuelva False, cambiando así el resultado que se obtiene por defecto cuando estemétodo no está implementado: >>> class TestBool:... def __bool__():... return False...>>> t = TestBool()>>> t.__bool__()False>>> bool(t)False>>> if t: print("Es falso")...Es falso
    • 136. HERENCIA El concepto de herencia es uno de los más importantes en laprogramación orientada a objetos. En apartados anteriores de estecapítulo hemos adelantado la base en la que se fundamente esteconcepto. En términos generales, se trata de establecer una relaciónentre dos tipos de clases donde las instancias de una de ellas tengandirectamente acceso a los atributos y métodos declarados en la otra.Para ello, debemos contar con una principal que contendrá lasdeclaraciones e implementaciones. A esta la llamaremos padre,superclase o principal. La otra, será la clase hija o secundaria. La herencia presenta la clara ventaja de la reutilización de código,además nos permite establecer relaciones y escribir menos líneas decódigo, ya que no es necesario, por ejemplo, volver a declarar eimplementar métodos.Python implementa la herencia basándose en los espacios denombres, de tal forma que, cuando una instancia de una clase hijahace uso de un método o atributo, el intérprete busca primero en ladefinición de la misma y si no encuentra correspondencias accede alespacio de nombres de la clase padre. Técnicamente, Python construyeun árbol en memoria que le permite localizar correspondencias entrelos diferentes espacios de nombres de clases que hacen uso de laherencia.Habitualmente, los modificadores de visibilidad como public, prívateo protected, empleados por lenguajes como Java y C++, estándirectamente relacionados con la herencia. Por ejemplo, un métodoprivado es heredable, pero solo invocable a través de una instancia dela clase que lo implementa. En Python, tal y como hemos comentadoanteriormente, los modificadores de visibilidad, como tal, no existen,ya que cualquier atributo o método es accesible.Lenguajes como C++, Java y PHP5 soportan la herencia yestablecen diferentes sintaxis para declararla. Python no es unaexcepción y también permite el uso de esta técnica. Además, adiferencia, por ejemplo de Java, Python soporta dos tipos de herencia:la simple y la múltiple. De ambos nos ocuparemos en los siguientes
    • 137. apartados. Simple La herencia simple consiste en que una clase hereda únicamente deotra. Como hemos comentado previamente, la relación de herenciahace posible utilizar, desde la instancia, los atributos de la clase padre.En Python, al definir una clase, indicaremos entre paréntesis de la claseque hereda. Comenzaremos definiendo nuestra clase padre: class Padre:def __init__(self):self.x = 8print("Constructor clase padre")def metodo(self):print("Ejecutando método de clase padre") Crear una clase que herede de la que acabamos de definir es biensencillo: class Hija:def met_hija(self):print("Método clase hija") Ahora procederemos a crear una instancia de la clase hija y acomprobar cómo es posible invocar al método definido en su padre: >>> h = Hija()Constructor clase padre>>> h.método()Ejecutando método clase padre Seguro que el lector se ha dado cuenta de que el método __init__() de clase padre ha sido invocado directamente al invocar la instancia dela clase hija. Esto se debe a que el método constructor es el primero enser invocado al crear la instancia y cómo este existe en la clase padre,entonces se ejecuta directamente. Pero ¿qué ocurre si creamos unmétodo constructor en la clase hija? Sencillamente, este será invocadoen lugar de llamar al de padre. Es decir, habremos sobrescrito elconstructor original. De hecho, esto puede ser muy útil cuandonecesitamos nuestro propio constructor en lugar de llamar al de padre.Si modificamos nuestra clase Hija, añadimos el siguiente método yvolvemos a crear una instancia, podremos apreciar el resultado:
    • 138. def __init__(self):print("Constructor hija")>> z = Hija()Constructor hija Pero no solo los métodos son heredables, también lo son losatributos. Así pues, la siguiente sentencia es válida: >>> h.x7 Obviamente, varias clases pueden heredar de otra en común, esdecir, una clase padre puede tener varias hijas. En la práctica,podríamos definir un clase Vehículo, que será la padre y otras dos hijas,llamadas Coche y Moto. De hecho, la relación que vamos a establecerentre ellas será de especialización, ya que un coche y una moto son untipo de vehículo determinado. A continuación, mostramos el códigonecesario para ello: class Vehiculo:n_ruedas = 2def __init__(self, marca, modelo):self.marca = marcaself.modelo = modelodef acelerar(self):passdef frenar(self):passclass Moto(Vehiculo):passclass Coche(Vehiculo):pass En este punto podemos crear dos instancias de las dos clases hija ymodificar el atributo de clase: >>>>>>>>>>>>2 c = Coche("Porsche", "944")c.n_ruedas = 4m = Moto("Honda", "Goldwin")m.n_ruedas Como las motos y los coches tienen en común las operaciones deaceleración y frenado, parece lógico que definamos métodos, en laclase padre, para representar dichas operaciones. Por otro lado,cualquier clase que herede de Vehiculo, también contendrá estasoperaciones, con independencia del número de ruedas que tenga.
    • 139. Múltiple Como hemos comentado previamente, Python soporta la herenciamúltiple, del mismo modo que C++. Otros lenguajes como Java y Rubyno la soportan, pero sí que implementan técnicas para conseguir lamisma funcionalidad. En el caso de Java, contamos con las clasesabstractas y las interfaces, y en Ruby tenemos los mixins. La herencia múltiple es similar en comportamiento a la sencilla, conla diferencia que una clase hija tiene uno o más clases padre. EnPython, basta con separar con comas los nombres de las clases en ladefinición de la misma. Pensemos en un ejemplo de la vida real paraimplementar la herencia múltiple. Por ejemplo, una clase genérica sería Persona, otra Personal y la hija sería Mantemiento. De esta forma, unapersona que trabajara en una empresa determinada como personal demantenimiento, podría representarse a través de una clase de lasiguiente forma: class Mantenimiento(Persona, Personal):pass De esta forma, desde la clase Mantenimiento, tendríamos acceso atodos los atributos y métodos declarados, tanto en Persona, como en Personal. La herencia múltiple presenta el conocido como problema deldiamante. Este problema surge cuando dos clases heredan de otratercera y, además una cuarta clase tiene como padre a las dos últimas.La primera clase padre es llamada A y las clases B y C heredan de ella,a su vez la clase D tiene como padres a B y C . En esta situación, si unainstancia de la clase D llama a un método definido en A, ¿lo heredarádesde la clase B o desde la clase C ?. Cada lenguaje de programaciónutiliza un algoritmo para tomar esta decisión. En el caso de Python, setoma como referencia que todas las clases descienden de la clasepadre object. Además, se crea una lista de clases que se buscan dederecha a Izquierda y de abajo arriba, posteriormente se eliminantodas las apariciones de una clase repetida menos la última. De estaforma queda establecido un orden.La figura geométrica que representa la relación entre clasesdefinida anteriormente tiene forma de diamante, de ahí su nombre.Podemos apreciar esta relación en la figura.
    • 140. Teniendo en cuenta esta figura, Python crearía la lista de clases D, B,A, C, A. Después eliminaría la primera A, quedan el orden establecidoen D, B, C, A.
    • 141. Fig. 4-1 El problema del diamante
    • 142. Dadas las ambigüedades que pueden surgir de la utilización de laherencia múltiple, son muchos los programadores que decidenemplearla lo mínimo posible, ya que, dependiendo de la complejidaddel diagrama de herencia, puede ser muy complicado establecer suorden y se pueden producir errores no deseados en tiempo deejecución. Por otro lado, si mantenemos una relación sencilla, laherencia múltiple es un útil aliado a la hora de representar objetos ysituaciones de la vida real.
    • 143. POLIMORFISMO En el ámbito de la orientación a objetos el polimorfismo hacereferencia a la habilidad que tienen los objetos de diferentes clases aresponder a métodos con el mismo nombre, pero conimplementaciones diferentes. Si nos fijamos en el mundo real, estasituación suele darse con frecuencia. Por ejemplo, pensemos en unaserpiente y en un pájaro. Obviamente, ambos pueden desplazarse,pero la manera en la que lo hacen es distinta. Al modelar estasituación, definiremos dos clases que contienen el método desplazar, siendo su implementación diferente en ambos casos: class Pajaro:def desplazar(self):print("Volar") class Serpiente:def desplazar(self):print("Reptar") A continuación, definiremos una función adicional que se encarguede recibir como argumento la instancia de una de las dos clases y deinvocar al método del mismo nombre. Dado que ambas clasescontienen el mismo método, la llamada funcionará sin problema, peroel resultado será diferente: >>> def mover(animal):... animal.desplazar()>>> p = Pajaro()>>> s = Serpiente()>>> p.desplazar()Volar>>> s.desplazar()Reptar>>> mover(p)Volar>>> mover(s)Reptar Como hemos podido comprobar, en Python la utilización delpolimorfismo es bien sencilla. Ello se debe a que no es un lenguajefuertemente tipado. Otros lenguajes que sí lo son utilizan técnicas para
    • 144. permitir y facilitar el uso del polimorfismo. Por ejemplo, Java cuentacon las clases abstractas, que permiten definir un método sinimplementarlo. Otras clases pueden heredar de una clase abstracta eimplementar el método en cuestión de forma diferente.
    • 145. INTROSPECCIÓN La instrospección o inspección interna es la habilidad que tiene uncomponente, como una instancia o un método, para examinar laspropiedades de un objeto externo y tomar decisiones sobre lasacciones que él mismo puede llevar a cabo, basándose en lainformación conseguida a través de la examinación.Python posee diferentes herramientas para facilitar la introspección,lo que puede resultar muy útil para conocer qué acciones es capaz derealizar un objeto determinado. Una de las más populares de estasherramientas es la función integrada dir() que nos devuelve todos losmétodos que pueden ser invocados para una instancia concreta. Deesta forma, sin necesidad de invocar ningún método, tenemosinformación sobre un objeto. Por ejemplo, definamos una clase con unpar de métodos y lancemos la función dir() sobre una instanciaconcreta: >>> class Intro:def __init__(self, val):self.x = valdef primero(self):print("Primero")def segundo(self):print("Segundo")...>>> i = Intro("Valor")>>> dir(i)['__class__', '__delattr__', __dict__', '__doc__', __eq__','__format__', '__ge__', '__getattribute__', '__gt__', '__hash__','__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__','__reduce__', '__reduce_ex__', '__repr__', '__setattr__','__sizeof__', '__str__', '__subclasshook__', '__weakref__','primero', 'segundo', 'x'] Como el lector habrá descubierto, la salida de la sentencia dir(i) devuelve una lista de strings con el nombre de cada uno de losmétodos que pueden ser invocados. Los tres últimos son los métodosde instancia y el atributo que hemos creado, mientras que el resto sonmétodos heredados de la clase predefinida object. Además de dir(), otras dos prácticas funciones de introspección son
    • 146. isinstance() y hasattr(). La primera de ellas nos sirve para comprobar siuna variable es instancia de una clase. En caso afirmativo devolverá True y en otro caso False. Continuemos con el ejemplo de la claseanteriormente definida y probemos este método: >>> isinstance(i, Intro)True Por otro lado, hashattr() devuelve True si una instancia contiene unatributo. Esta función recibe como primer parámetro la variable querepresenta la instancia y como segundo el atributo por el quedeseamos preguntar. Dada nuestra clase anterior, escribiremos elsiguiente código para comprobar su funcionamiento: >>> if (i,Instancia:>>> if (i,Atributo z 'x'): print("Instancia: i. Clase: Intro. Atributo: x")i. Clase: Intro. Atributo: x'z'): print("Atributo z no existe")no existe Si utilizamos la herencia, las funciones de introspección puedensernos muy útiles, además nos permiten descubrir cómo los métodos yatributos son heredados. Si creamos una nueva clase que herede de laanterior, pero con un nuevo método, al llamar a dir() veremos cómo losatributos y métodos están disponibles directamente: >>> class Hijo(Intro):def tercero(self):print("Tercero")...>>> h = Hijo()>>> dir(h)['__class__', '__delattr__', '__dict__', '__doc__', '__eq__',__format__', '__ge__', '__getattribute__', '__gt__', '__hash__','__init__', '__le__', '__lt__', '__module__', '__ne__', '__new__','__reduce__', '__reduce_ex__', '__repr__', '__setattr__','__sizeof__', '__str__', '__subclasshook__', '__weakref__,'primero', 'segundo', 'tercero', 'x'] De igual forma, podemos emplear la función hasattr() paracomprobar si el atributo x existe también en la clase h: >>> if hasattr(h, 'x'): print("h puede acceder a x")h puede acceder a x
    • 147. Obviamente, aunque las clases Intro e Hijo estén relacionadas sondiferentes. Sin embargo, la función isinstance() nos devolverá True si
    • 148. pasamos como primer argumento una instancia de cualquier de ellas ycomo segundo el nombre de una de las dos clases: >>> if isinstance(h, Intro): print("h es instancia de Intro")h es instancia de Intro Entonces, ¿cómo podemos averiguar si una Instancia pertenece a laclase padre o hija? Fácil, basta con emplear la función Integrada type(), tal y como muestra el siguiente ejemplo: >>> type(i)<class '__main__.Intro'>>>> type(h)<class '__main__.Hija'> Otro método relacionado con la introspección es callable(), el cualnos devuelve True si un objeto puede ser llamado y False en casocontrario. Por defecto, todas las funciones y métodos de objetospueden ser llamados, pero no las variables. Gracias a este métodopodemos averiguar, por ejemplo, si una determinada variable es unafunción o no. Como ejemplo, echemos un vistazo al siguiente código: >>> cad = "Cadena">>> callable(cad)False>>> def fun():... return("fun")>>> callabe(fun)True Siguiendo la misma explicación, una instancia de objeto devolverá False cuando invocamos a callable(): >>> callable(i)False Echando un vistazo al resultado de la ejecución de la sentenciaprevia dir(h), descubriremos métodos que pueden ser llamados através de una instancia y que también sirven para la instrospecclón.Nos referimos a __getattribute__() y __setattr__() . El primero nos sirvepara obtener el valor de un atributo a través de su nombre, el segundorealiza la operación inversa, es decir, recibe como parámetro el nombredel atributo y el valor que va a serle fijado. Habitualmente, estosmétodos no son necesarios, ya que podemos acceder directamente aun atributo de instancia directamente usando el nombre de la variable
    • 149. de instancia, seguido de un punto y del nombre del atributo encuestión. Sin embargo, los métodos __getattribute__() y __setattr__() pueden ser muy útiles, cuando, por ejemplo, necesitamos leer losatributos de una lista o diccionario. Supongamos que tenemos unaclase con tres atributos de instancia definidos y deseamos fijar su valorempleando para ello un diccionario. Comenzaremos definiendo unanueva clase: class Test:def__init__(self):self.x = 3self.y = 4self.z = 5 Ahora crearemos un diccionario y una instancia de clase: >>> attrs = {"x": 1, "y": 2, "z": 3}>>> t = Test() Seguidamente, pasaremos a asignar los valores del diccionario anuestros atributos de clase: >>> for k, v in attrs.items():... t.__setattr__(k, v) Para comprobar que los atributos han sido fijados correctamente,emplearemos la función __getattribute__() , tal y como muestra elsiguiente código: >>>......y =x =z = for k in attrs.keys():print("{0}={1}".format(k, t.__getattribute__(k)) 213 Debemos tener en cuenta que attrs es un diccionario y como tal esuna estructura de datos no ordenada, por lo cual la salida del orden delos atributos puede diferir durante sucesivas ejecuciones de laiteración.
    • 150. PROGRAMACIÓN AVANZADA INTRODUCCIÓN En este capítulo nos adentraremos en una serie de aspectosavanzados que Python pone a nuestra disposición. Los conceptosexplicados en este capítulo, aun no siendo imprescindibles para elaprendizaje del lenguaje, sí que son muy interesantes de cara a tenerun amplio conocimiento del mismo y a poder escribir código de formaelegante. Asimismo, nos ayudarán a sacar mayor provecho dellenguaje.En concreto, en este capítulo, comenzaremos descubriendo qué sony cómo utilizar los iterators y generators. Después pasaremos a explicarla técnica del empleo de closures y continuaremos centrándonos en losútiles decorators. Dado que la programación funcional va ganandoadeptos día a día, también hemos creído conveniente dedicar unapartado a la misma. Finalmente, serán las expresiones regulares y laordenación eficiente de elementos los apartados que cerrarán elpresente capítulo.
    • 151. ITERATORS Y GENERATORS Python cuenta con dos mecanismos específicos que nos permiteniterar sobre un conjunto de datos: los iterators y los generators. Iterators Hasta el momento hemos visto una serie de ejemplos que nos hanmostrado cómo iterar sobre una serie de objetos, habitualmenteempleando un bucle for y a través del operador in. Para refrescar cómopuede llevarse a cabo una iteración, basta con echar un vistazo alsiguiente código: >>> lista = [1, 2, 3]>>> for ele in lista:... print(ele)...123 En realidad, Python nos permite iterar directamente sobre ciertosobjetos, como son las listas y los diccionarios, incluso es posible iterarsobre un string, tal y como podemos ver en el siguiente código: >>> for letra in "hola":... print(letra)...hola Este mecanismo de iteración sobre objetos es bastante potente enPython y puede sernos muy útil para realizar tareas que son máscomplejas en otros lenguajes, como, por ejemplo, la lectura de laslíneas que forman un fichero de texto. Esta operación en Python esmuy sencilla gracias a que un fichero es un objeto iterable, al igual quelo son las listas, los diccionarios y los strings, tal y como hemos
    • 152. comentado previamente. Aunque contamos con un capítulo dedicadoa cómo trabajar con distintos tipos de ficheros, adelantaremos unsencillo ejemplo de código que nos enseña cómo leer todas las líneasde un fichero: >>> for linea in open("fich.txt"):... print(linea) En realidad, la iteración es posible en Python gracias a que elintérprete y el lenguaje disponen de una técnica que hace posible suimplementación y ejecución a través de un determinado protocolo. Asípues, a bajo nivel, cuando empleamos la sentencia for, lo querealmente ocurre es que se realiza una llamada a un iterator determinado que se encarga de realizar la función de iteración sobre elobjeto concreto.En general, cualquier tipo de objeto en Python que implementeunos métodos especiales, que forman parte del mencionadoprotocolo, es un iterator. En concreto, para construir un objeto de estetipo, deberemos implementar al menos dos métodos: __iter__() y __next__() . El primero de ellos es empleado para devolver unareferenciaa la Instancia de la clase que ¡mplementa el iterator, mientras que elsegundo debe implementar la funcionalidad que será ejecutadacuando se lleva a cabo la iteración sobre cada elemento. Supongamosque necesitamos Iterar sobre un valor que será pasado comoparámetro, para Imprimir todos los valores desde ese mismo hastallegar a cero, es decir, se trata de implementar una sencilla cuentaatrás. Nuestra clase iterator quedaría de la siguiente forma: class CuentaAtras(object):def __init__(self, start):elf.count = startdef __iter__(self):return selfdef __next__(self):if self.count <= 0:raise StopIterationr = self.countself.count -= 1return r El constructor de nuestra clase recibirá el valor inicial y lo asignará auna variable de instancia llamada count. Al ejecutar el método __next__() comprobaremos el valor de dicha variable y si es igual o
    • 153. inferior a cero deberemos detener la iteración. Para ello, contamos conla excepción StopIteration, que será lanzada justo en ese momento. Siesta condición no se cumple, disminuiremos el valor de la variable deInstancia y devolveremos el valor de la misma antes de que se lleve acabo el decremento. Una vez que tenemos nuestro objeto podemosemplearlo junto a un bucle for y al operador in, tal y como muestra elsiguiente ejemplo: >>> for ele in CuentaAtras(5):... print(ele)...54321 De igual forma, podríamos trabajar con más de un argumento. Estesería el caso, por ejemplo, de necesitar iterar sobre un rango denúmeros. Veamos cómo hacerlo construyendo un nuevo iterator: class Rango:def __init__(self, low, high):self.current = lowself.high = highdef __iter__(self):return selfdef __next__(self):if self.current > self.high:raise StopIterationself.current += 1return self.current - 1 La invocación a nuestro iterator sería como sigue: >>> for ele in Rango(5,9)... print(ele)...56789 FUNCIONES INTEGRADAS
    • 154. A diferencia de las versiones 2.x de Python, la serie 3 de estelenguaje cuenta con una serie de funciones integradas que devuelvenobjetos iterators en lugar de listas. Entre estas funciones encontramosa range(), map(), zip() y filter(). Por ejemplo, map() aplica una funcióndeterminada a cada uno de los valores que se le indiquen comoparámetro. En Python 3, la llamada a esta función nos devolverá unobjeto de tipo map que es un iterator. A continuación, el código deejemplo que ilustra este hecho: >>> map(abs, (-1, 2, -3)<map object at 0x00000000026484E0> Si deseamos obtener una lista a aplicar la función map(), tal y comoocurre en Python 2.x, deberemos invocar explícitamente a list(), comomuestra el siguiente ejemplo: >>> list(map(abs, (-1, 2, -3))[1, 2, 3] Nótese que la función abs() calcula el valor absoluto de un valordado que recibe como parámetro.Similar comportamiento encontramos en la función zip() quecombina valores de los argumentos que recibe. Echemos un vistazo alsiguiente código para comprobar su funcionalidad: >>> list(zip (("123", "abc")))[("1", "a"), ("2", "b"), ("3", "c")] Relacionados con los iterators encontramos a los generators, de losque nos ocuparemos en el siguiente apartado. Generators Si tuviéramos que definir qué es un generator podríamos decir quees una función que devuelve una secuencia de resultados en lugar deun único valor. La relación entre los iterators y los generators es muyestrecha, ya que en la mayoría de las ocasiones, en lugar de crear unobjeto iterator, podemos crear una simple función que lleve a cabo lamisma funcionalidad. De esta forma conseguimos minimizar las líneasde código y hacemos que el código sea más legible. Para comprobar
    • 155. este hecho, vamos a reescribir nuestro primer ejemplo de iterator empleando una simple función que hará de generator: def cuenta_atras(ele):while ele > 0:yield nn -= 1 Si ahora realizamos la siguiente llamada, observaremos cómo elresultado es idéntico a emplear el iterator al que hemos llamado CuentaAtras : >>> for ele in cuenta_atras(5):... print(ele) Como el lector habrá podido observar, en nuestro nuevo ejemplohemos introducido el uso una nueva función llamada yield(). Esta es laencargada de detener la ejecución de nuestra función, producir unnuevo resultado y reanudar la ejecución de la función. Así pues, cadavez que se alcanza yield se procede a ejecutar el código que aparecedentro de nuestro bucle for, cuando esta acción termina, continúa laejecución de cuenta_atras(). Internamente, el intérprete de Python creaun objeto iterator cada vez que se llama a la función generator. Un generator no tiene por qué ser forzosamente una función,también puede ser una expresión. De hecho, cuando explicamos lacomprensión de listas, en el capítulo 2, vimos un ejemplo de unaexpresión que es un generator. Un ejemplo similar es el siguientecódigo donde, a través de una expresión, construimos un objeto generator : >>> lista = [1, 2, 3]>>> (7*ele for ele in lista)<generator object <genexpr> at 0x00000000022AD750> Si asignamos nuestro nuevo objeto a una variable, podremosemplear la misma para realizar iteraciones, tal y como demuestran lassiguientes sentencias: >>> gen = (7*ele for ele in lista)>>> for ele in gen:... print(ele)...71421
    • 156. A diferencia de otros objetos, la iteración sobre un generator solo sepuede hacer una única vez. Si deseamos hacerla más veces, deberemosvolver a crear el generator primero. Esto no ocurre, por ejemplo, conlas listas. Una vez definida es posible iterar n veces sobre la misma sinnecesidad de volver a declarar. Teniendo en cuenta este hecho ytomando como base nuestro último ejemplo de código, si volvemos aejecutar el bucle for, comprobaremos cómo no se imprime ningúnnúmero por la salida estándar.
    • 157. CLOSURES En Python es posible anidar una función dentro de otra. Unafunción no es más que un objeto del lenguaje, que, en realidad, es unasentencia ejecutable y como tal, puede aparecer en cualquier lugar delscript. Cuando se anidan funciones debe ser tenido en cuenta elespacio de nombres de las mismas, ya que cualquier variable definidaen la función que contiene a otra puede ser accedida desde la funciónanidada. El siguiente ejemplo muestra este hecho: >>> def principal():... x = 8... def contenida():... print(x)... contenida()...>>> principal()8 El ejemplo de código anterior muestra cómo la variable x esaccesible a través de la función contenida, que a su vez se encuentraanidada dentro de principal. Estas reglas de acceso del espacio denombres se mantienen incluso si la función principal devuelve lafunción contenida como resultado de su ejecución. Si modificamos elcódigo anterior y lo sustituimos por el siguiente, comprobaremoscómo x es accesible: def principal():x = 7def anidada():print(x)return anidada Ahora es la función principal la que va a devolvernos comoresultado otra función, que, en este caso, es la función anidada quecontiene. Dado que una función queda definida por su nombre y elintérprete pasa a referenciar el espacio de memoria que necesita através del nombre, es posible devolver una función como si de unavariable cualquiera se tratase. De hecho, es simplemente eso, un objeto que Python referencia a través de un identificador. Teniendo esto en
    • 158. mente, podemos invocar a la función principal() y asignar su resultadoa una variable: >>> res = principal() Si ahora invocamos a la función a través de la nueva variable,observaremos cómo la variable x es accesible desde la función anidada: >>> res ()7 Debe ser tenido en cuenta que ya no podemos invocardirectamente a principal(), sino que debemos hacerlo a través de lanueva variable res. En realidad, al devolver una función, aunque elámbito de principal() ya no se encuentra en memoria sí que es posibleacceder a la funcionalidad que la misma contiene, dado que su interiorha sido creada una función y devuelta como resultado de la ejecuciónde principal(). Es decir, esto es lo que ocurre si invocamos ahora a principal(): >>> principal()<function anidada at 0x0000000002577748> A esta técnica se la conoce como closure o factory function. Estaúltima nomenclatura hace referencia a que una clase es capaz de crearotra diferente, es decir, funciona como una factoría de funciones.En términos generales, un closure es una técnica de programaciónque permite que una función pueda acceder a variables que, a priori,están fuera de su ámbito de acceso. Son muchos los lenguajes deprogramación que soportan esta técnica, como por ejemplo Ruby, queutiliza el concepto de blocks para aplicarla.Los closures suelen ser empleados cuando es necesario disponer deuna serie de acciones que debe llevarse a cabo como respuesta a unevento que maneja otra función y que ocurre en tiempo de ejecución.Por ejemplo, pensemos en una aplicación que cuenta con una interfazde usuario. Habitualmente, estas funciones disponen de manejadoresde eventos, es decir, de funciones o métodos que responden a ciertotipo de eventos, como puede ser el pulsar un botón o hacer clic sobreuna caja de texto. Supongamos que tenemos un evento que seencarga de controlar y responder cuando una caja de texto pierde el
    • 159. enfoque. Además, necesitamos realizar una serie de acciones enfunción del texto que la caja contenga. Dado que no sabemos a prioriel texto que contendrá, ya que este cambiará en tiempo de ejecución,necesitamos otra función que, dentro del manejador del evento, seacapaz de realizar ciertas acciones en función del contenido del texto dela caja.Aunque hemos descrito un sencillo ejemplo de closure, obviamente,las funciones que forman parte del mismo pueden recibir y trabajarcon parámetros. Veamos un sencillo ejemplo donde sumaremos elvalor de los argumentos que reciben tanto la función contenedoracomo la anidada: >>>............>>>>>>21>>>23 def contenedora(y):def anidada(z):return y + zreturn anidada fun = contenedora (9)fun(12) fun(14) En este punto, si creamos una nueva variable pasando un valordiferente a contenedora(), apreciaremos cómo el resultado varía: >>> otr = contenedora(1)>>> otr(2)3 Además, podemos seguir invocando, tanto a fun(), como a otr(), lasveces que deseemos. Si comparamos estos ejemplos de código con elcaso de la interfaz de usuario, anteriormente comentado,comprobaremos que existe una analogía entre ellos. En concreto, lafunción contenedora() sería la responsable de responder al evento,mientras que anidada() se encargaría de procesar el valor del campode texto y devolver un resultado diferente en función del mencionadovalor.
    • 160. DECORATORS En el capítulo anterior, hemos descubierto cómo utilizar un decorator, para por ejemplo, indicar que un método es de clase. Eneste apartado describiremos qué es un decorator, cómo se utilizan ycómo implementar los nuestros propios. Patrón decorator, macros y Python decorators Antes de describir qué es un decorator en Python, convieneentender las diferencias y similitudes entre tres conceptos diferentes: elpatrón decorator, las macros y los decorators de Python.Aquellos desarrolladores acostumbrados a implementar patrones dediseño, seguro que conocen y utilizan el patrón conocido como decorator. Básicamente, este patrón nos permite modificardinámicamente el comportamiento de un objeto. En la práctica, el decorator pattern puede ser considerado como una alternativa adeclarar una clase que hereda de otra, es decir, a la herencia simple. Enlos lenguajes compilados, como C++, el cambio de comportamiento sehace en tiempo de ejecución, no en tiempo de compilación.Por otro lado, las macros son utilizadas en programación paramodificar una serle de elementos del lenguaje. Los programadores deC estarán acostumbrados a trabajar con macros de preprocesador, Javay C# emplean las annotations, que es una técnica muy similar a lasmencionadas macros.Los decorators de Python no deben ser confundidos con el patrónde diseño decorator, ya que su funcionalidad está más cerca de lasmacros. De esta forma, un decorator o decorador de Python nospermite inyectar código para modificar el comportamiento de la¡mplementaclón original de un método, función o clase. Por ejemplo,supongamos que deseamos realizar una serle de operaciones antes ydespués de ejecutar una determinada función. Para este caso podemosescribir funciones implementadas por decoradores y aplicar estosdirectamente a nuestra función. Además, esto presenta la ventaja de
    • 161. que las funciones implementadas pueden ser utilizadas comodecoradores en otras funciones de nuestro programa, consiguiendo asíreutilizar nuestro código eficientemente. Declaración y funcionamiento Cuando deseamos aplicar un decorador, por ejemplo, a unafunción, basta con escribir el nombre del mismo, precedido de laarroba (@), en la línea justamente anterior a la que declarara lafunción. Un sencillo ejemplo que nos permite aplicar el decorador firstDecorator a una función llamada función, sería el siguiente: @firstDecoratordef funcion():print("Ejecutando la función") Básicamente, la arroba es un operador que indica que un objetofunción debe ser pasado a través de otra función, asignando elresultado a la función original. Esta sintaxis es más elegante que utilizarla misma funcionalidad realizando diferentes llamadas a las funciones.Así pues, el ejemplo anterior es similar a declarar una nueva función yrealizar una llamada empleando el resultado obtenido previamente: def firstDecorator(obj):passres = firstDecorator(funcion) En realidad, los decoradores de Python siguen la idea de aplicarcódigo a otro código, como hacen las macros, pero a través de unaconstrucción y sintaxis dada directamente por el lenguaje.Los decorators ofrecen bastante flexibilidad, ya que cualquierobjeto, como una clase o función de Python puede ser utilizada comodecorador sobre otro objeto, siendo habitualmente este último unafunción.Para entender en la práctica cómo funcionan los decorados, noscentraremos en aplicar los mismos empleando clases y funciones. En elsiguiente apartado, comenzaremos por las clases.
    • 162. Decorators en clases Para ilustrar el funcionamiento de la aplicación de decoradoresutilizando clases, vamos a partir de un ejemplo, donde declararemosuna clase que posteriormente será utilizada como decorador en unafunción.En primer lugar, vamos a declarar una clase en la queimplementaremos y sobrescribiremos los métodos __init__() y __new__() : class Decorador(object):def __init__(self, f):self.f = fdef __call__(self):print("inicio", self.f.__name__)self.f()print("fin", self.f.__name__) El siguiente paso será definir una función a la que aplicaremosnuestra clase como decorador: @Decoradordef funcion():print("soy función") Si ahora procedemos a llamar a nuestra función, podremosobservar el siguiente resultado que nos lanza el intérprete: >>> funcion()inicio funcionsoy funciónfin funcion La explicación al resultado obtenido puede explicarse fácilmente.Cuando llamamos a la función, se procede a modificar sucomportamiento original aplicando el decorador. Así pues, dado queeste es una clase, pasa a ejecutarse el constructor de la misma, querecibe como parámetro la función invocada. El constructorsimplemente asigna la misma a un atributo de clase llamado f .Posteriormente, se llama automáticamente al método __call__() queimprime el primer mensaje en la salida estándar y después invoca a lafunción original a través del atributo de clase definido previamente.Dado que esta función originalmente imprime el mensaje soy función,
    • 163. esto es simplemente lo que ocurre. Continuando con la ejecución de __call__() , se imprime el último mensaje. Tal y como habremosobservado, __name__ es un atributo que nos permite acceder alnombre de la función, en este caso. De esta forma, estamosaprovechando las funcionalidades de introspección que Python nosofrece. Queda demostrado que el comportamiento original de lafunción llamada funcion ha sido modificado gracias al decorador quehemos creado previamente y aplicado automática y posteriormente.Por convención, el nombre del decorador suele comenzar porminúscula. Sin embargo, en nuestro ejemplo hemos utilizado lamayúscula. Esto se debe a que los nombres de las clases, también porconvención, comienzan por mayúscula. Si se desea puede optarse porcambiar la nomenclatura de nuestro ejemplo, renombrando el nombrede la clase para que la primera letra comience con minúscula. De estaforma, el nombre del decorador también deberá empezar porminúscula. Lo importante es que mantengamos la correspondenciaentre nombres, en cualquier otro caso el intérprete lanzará un errorsintáctico. Funciones como decorators Si en el apartado anterior hemos descubierto cómo aplicar clasescomo decoradores a una función, en éste nos ocuparemos de crearuna función como decorador que será aplicado a otra.Siguiendo con el ejemplo anterior, implementaremos unafuncionalidad similar pero con funciones en lugar de con instancias declases.Lo primero que deberemos tener en cuenta es que vamos a trabajarcon un closure. Este concepto hace referencia a que una función puedeser evaluada en un contexto que puede, a su vez, uno o máscomponente dependiendo de otro entorno diferente. En nuestro caso,tendremos una función declarada dentro de otra, devolviendo laprincipal el resultado de la ejecución de la función contenida en lamisma. Aunque parece un poco confuso, es mejor entenderlo echandoun vistazo al siguiente código: def principal(f):
    • 164. def nueva():print("ini", f.__name__)f()print("fin", f.__name__)return nueva El siguiente paso es definir una nueva función a la que llamaremos para_decorar. Será esta nueva función a la que aplicaremos comodecorador la función principal(): ©principaldef decorada() :print("decorada") Ahora procederemos a invocar a la función decorada() yobservaremos la salida dada por el intérprete del lenguaje: >>> decorada()ini decoradadecoradafin decorada Cuando invocamos a decorada(), se evalúa el decorador y se invocaa la función principal(). Esta simplemente define otra función cuyoresultado es devuelto al finalizar la ejecución de principal(). Dado quedentro de nueva() se invoca a la función decorada(), el mensaje"decorada" es impreso entre "ini decorada" y "fin decorada".Debemos tener en cuenta que el objeto que hace de decoradordebe recibir como parámetro del objeto que lo invoca. En este caso,cuando decimos objeto nos referimos a uno interno de Python, comopuede ser una clase o una función. No se trata de un objeto en elsentido de instancia de una clase. Así pues, en el ejemplo del apartadoanterior, era el constructor ( __init__() ) de la clase el encargado derecibir la función como parámetro. En el ejemplo del presenteapartado, es la misma función ( principal() ) la que recibe comoparámetro la otra función ( (decorada() ) pasada como tal. Utilizando parámetros Hasta el momento hemos utilizado los decoradores de Python sintener en cuenta el paso de parámetros. Sin embargo, es posible
    • 165. invocar a una función decorada pasando a su vez un númerodeterminado de parámetros. Es más, se pueden dar dos casosdiferentes, uno donde solo la función decorada recibe parámetros yotro donde es el decorador el que admite ciertos parámetros. En esteapartado veremos ambos casos, comencemos pues, por el primero deellos. DECORADOR SIN PARÁMETROS Volviendo a nuestro ejemplo inicial en el que el objeto decoradorera una clase, podemos modificarlo para añadirle parámetros en lafunción decorada. Para ello, bastará con cambiar el método __call__() ,añadiendo un nuevo parámetro, que en este caso será *argumentos. Tal y como vimos en el capítulo 3, vamos a emplear la técnica paradesempaquetado de argumentos. Así pues, podremos recibir n parámetros. Pasando directamente al código, nuestra clase quedaríareescrita de la siguiente forma: class Decorador(object):def init (self, f):self.f = fdef call (self, *argumentos):print("inicio", self.f.__name__)self.f(*argumentos)print("fin", self.f.__name__) Por otro lado, la función decorada va a recibir, por ejemplo, tresparámetros diferentes. De esta forma, el nuevo código para la funcióndecorada sería el siguiente: @Decoradordef funcion(p1, p2, p3):print("soy función con argumentos", p1, p2, p3) La invocación a la función decorada con tres parámetros diferentesy su correspondiente resultado sería como sigue: >>> funcion("uno", "dos", "tres")inicio funcionsoy función con argumentos uno dos tresfin funcion Como habremos podido comprobar, este paso de parámetros es
    • 166. sencillo. Simplemente hemos tenido en cuenta los mismos al igual queen una función convencional. DECORADOR CON PARÁMETROS Otro caso diferente al anteriormente tratado es cuando eldecorador contiene diferentes parámetros durante su invocación. Esdecir, al utilizar el decorador es posible también que este puedatrabajar con diferentes argumentos.Para ilustrar el paso de parámetros en el decorator vamos a trabajarcon funciones. Tendremos dos principales: el decorador y la decorada.Esta última contendrá a su vez otras dos anidadas que nos ayudarán atratar con los argumentos del decorador y de la función decorada.Comencemos por la función decorada, que contendrá el siguientecódigo: @decorator_args(1, 2, 3)def fun(a, b):print ("fun args:", a, b) En nuestro ejemplo, la función decorada recibirá dos parámetros,siendo su funcionamiento original la impresión de los mismos por lasalida estándar.Por otro lado, declararemos la función decorador empleando elsiguiente código: def decorator_args(arg1, arg2, arg3):def wrap(f):print( "ini wrap()")def wrapped_f(*args):print( "ini wrapped_f")print( "decorator args:", arg1, arg2, arg3)f(*args)print( "fin wrapped_f")return wrapped_freturn wrap En este punto, estamos en condiciones de proceder a la invocaciónde la función decorada con diferentes valores pasados comoparámetros, siendo la salida la que aparece a continuación: >>> fun('p1", "p2")ini wrap()
    • 167. ini wrapped_fdecorator args: 1 2 3fun args: p1 p2fin wrapped_f En la salida anterior comprobaremos cómo los parámetros queutiliza el decorador y la función decorada son diferentes.Si estamos trabajando con el intérprete, al terminar de definir lafunción decorada, veremos cómo, automáticamente, el intérpretellama al decorador. Posteriormente, podemos invocar a la funcióndecorada con sus parámetros. Este sería el resultado utilizandodirectamente el intérprete de Python: >>> @decorator_args(1, 2, 3)... def fun(a, b) :... print ("fun args:", a, b)...ini wrap() A pesar de haber definido y aplicado un único decorador porfunción, también es posible aplicar n decoradores sobre la misma. Deesta forma podemos ganar en reutilización de código y aumentar elnúmero de operaciones realizadas que sustituyen a la funcionalidadimplementada en la función original.Hasta aquí nuestro recorrido por los decorators. A pesar de habermostrado sencillos ejemplos para ilustrar el funcionamiento básico delos mismos, la flexibilidad que nos ofrecen es significativa a la vez quepotente. Sin duda, los decorators son uno de los aspectos másinteresantes que nos ofrece Python.
    • 168. PROGRAMACIÓN FUNCIONAL Tanto la orientación a objetos como la programación procedural,son dos de los paradigmas más utilizados por los programadores dePython. Sin embargo, también es posible emplear la programación funcional con Python. Básicamente, este paradigma se fundamenta enel empleo de funciones aritméticas sin tener en cuenta cambios deestado. En este tipo de programación el valor generado por unafunción depende exclusivamente de los argumentos que recibe y nodel estado del resto del programa cuando la misma es invocada. Unprograma escrito utilizando este paradigma únicamente contienefunciones, pero no en el sentido de las funciones de la programaciónprocedural, sino en el sentido matemático de expresión. Otracaracterística de la programación funcional es que no utiliza laasignación de variables. Tampoco se emplean construccionessecuenciales como la iteración, por el contrario se hace un usointensivo de la recursión.Para aplicar la programación funcional con Python contamos convarias técnicas y recursos como son: las funciones lambda, los iterators y generators, la comprehensión de listas y una serie de funcionesintegradas. Dado que solo nos queda ocuparnos de estas últimas,dedicaremos las siguientes líneas a este propósito.En concreto, entre las funciones integradas que Python pone anuestra disposición para escribir código utilizando programaciónfuncional, destacan map(), filter() y reduce(). La primera de ellas permiteaplicar una función sobre un conjunto de elementos. Por ejemplo,supongamos que tenemos una lista con varias cadenas de texto ynecesitamos generar una nueva lista con las mismas cadenas pero enminúsculas. Haremos uso del método lower(), junto con map() y lafunción list() para obtener una lista como resultado. Para ilustrarnuestro ejemplo, construiremos una pequeña función que hará de wrapper sobre el mencionado método lower(): >>> def lower(t) : return t.lower()...>>> texto = ["HOLA", "mUNdO", "AdiOS"]>>> list(map(lower, texto))
    • 169. ["hola", "mundo", "adios"] El mismo ejemplo podría haber sido realizado utilizando lacomprehensión de listas: >>> [cad.lower() for cad in texto] Efectivamente, la función map() implementa la misma funcionalidadque las expresiones que producen generators, que es, en realidad, loque hemos hecho con la sentencia anterior de código.Por otro lado, la función filter() devuelve un iterator sobre todos loselementos de una secuencia que cumplen una determinada condición.Para comprobar esta condición se suele emplear un predicado que es,básicamente, una función que solo devuelve el valor True cuando secumple una condición. Por ejemplo, supongamos que necesitamossolo los valores pares de una lista determinada. En primer lugarconstruiremos la función con nuestro predicado : def par(val):return (val%2) == 0 Seguidamente pasamos a utilizar directamente la función filter(), junto con list(), sobre una determinada lista: >>> lista = [0, 3, 5, 6, 8, 9]>>> list(filter(par, lista))[0, 6, 8] Por último, reduce() aplica una función tomando como argumentoslos valores correlativos de una secuencia, devolviendo un únicoresultado. Un ejemplo sencillo sería definir una función que suma losvalores pasados como parámetros. A través de reduce() podemos irsumando los valores de una lista de forma secuencial. Nuestra funciónquedaría como sigue: def sumar(x, y):return x+y Después podríamos invocar a reduce() sobre una lista con contienevalores del 1 al 4, donde sumar() se encargaría de realizar las siguientesoperaciones: 1+2=3+3=6+4=10
    • 170. Los números en negrita representan los elementos de la lista y elcódigo completo para realizar la operación sería el siguiente: >>> from functools import reduce>>> lista = [1, 2, 3, 4]>>> reduce(sumar, lista)10 Seguro que el lector ha observado cómo hemos importado lafunción reduce() de un módulo llamado functools. Este es un móduloIntegrado en Python3 y que ofrece varios métodos para generar iterators y que, por lo tanto, pueden utilizarse para la programaciónfuncional. En la versiones 2.x de Python la función reduce() existía comotal en la librería estándar, no como parte del mencionado módulo.
    • 171. EXPRESIONES REGULARES Las expresiones regulares definen una serie de patrones que seutilizan para trabajar con cadenas de texto. Podemos considerar a lasmismas como un lenguaje específico diseñado para localizar textobasándonos en un determinado patrón previamente definido.Básicamente, las expresiones regulares son utilizadas con dospropósitos a menudo relacionados: la búsqueda y reemplazo de texto.Habitualmente, a las expresiones regulares simplemente se lasllama regex y son muchos los programadores que prefieren utilizar estetérmino. Además, como nomenclatura, algunos desarrolladoresemplean el prefijo regex para designar variables que representan a unaexpresión regular.Al igual que la mayoría de los lenguajes de programación, Pythonincluye un completo soporte para las expresiones regulares. Estelenguaje dispone de un módulo específico, llamado re, que permiterealizar búsquedas, sustituciones y obtener parte de una cadena detexto empleando expresiones regulares como patrones.El módulo re de Python utiliza las reglas definidas en el modelo NFA tradicional (autómata finito no determinista), el cual se basa en lacomparación de cada elemento de la expresión regular con la cadenade texto de entrada. Este modelo se encarga de mantener unseguimiento de las posiciones que referencian a la selección entre dosopciones dentro de la expresión regular. Si una de estas opciones falla,se busca la última entre las más recientes cuya posición ha sidoguardada. Para más detalles sobre este modelo podemos consultar lapágina (ver referencias) de Wikipedia al respecto. Patrones y metacaracteres Para componer una expresión regular, debemos emplear una seriede patrones que son considerados como básicos. La tabla 5-1 muestralos más utilizados dentro de esta categoría y el significado asociados aellos.
    • 172. Patrón Significado Cualquier carácter excepto el que representa una nueva línea \n Nueva línea \r Retorno de carro \t Tabulador horizontal \w Cualquier carácter que represente un número o letra en minúscula \W Cualquier carácter que represente un número o letra en mayúscula \s Espacio en blanco (nueva línea, retorno de carro, espacio y cualquier tipo de tabulador) \S Cualquier carácter que no es un espacio en blanco \d Número entre 0 y 9 Inclusive \D Cualquier carácter que no es un número
    • 173. ^ Inicio de cadena $ Fin de cadena \ Escape para caracteres especiales [] Rango. Cualquier carácter que se encuentre entre los corchetes ^[] Cualquier carácter que no se encuentre entre los corchetes \b Separación entre un número y/o letra Tabla 5-1. Patrones básicos para definir expresiones regulares en Python Por otro lado, cualquier carácter se representa a sí mismo, teniendoen cuenta que algunos constituyen por sí mismo un patrón básico. Porejemplo, el carácter $ indica el final de una cadena; por lo tanto, nopodemos emplearlo, cuando, por ejemplo, buscamos este carácter talcual en una cadena de texto. Si necesitamos buscar el carácter comotal, podemos emplear el patrón básico de escape ( \ ). De esta forma,para busca el carácter $ deberemos construir una expresión regularque utilice la secuencia \$ .Aparte de los patrones básicos, también debemos conocer cuálesson los caracteres que representan las repeticiones que pueden darse.
    • 174. Supongamos que estamos buscando en una cadena de texto unpatrón que corresponde a un determinado número de veces queaparece en la misma un carácter. Para ello, recurriremos a losmetacaracteres de repetición, los cuales aparecen explicados en latabla 5-2. Metacarácter Significado + Una o más veces * Cero o más veces ? Cero o una vez {n} El carácter se repite n veces Tabla 5-2. Metacarácteres de repetición en Python Una vez que conocemos los patrones básicos y los metacaracteresde repetición, podemos comenzar a definir sencillas expresionesregulares. Por ejemplo, la siguiente expresión regular representa a queun número puede aparecer una o más veces: [\d]+ En los siguientes apartados nos centraremos en la funcionalidadque Python ponemos a nuestra disposición para realizar búsquedas ysustituciones de texto basándonos en expresiones regulares. Búsquedas
    • 175. La función básica de la que dispone el módulo re de Python es search(). Esta función recibe como argumento una expresión regular,representada por un patrón, y una cadena donde realizar la búsqueda.El objetivo es buscar una cadena dentro de otra, quedando la cadenaque deseamos buscar definida por una expresión regular. Nuestroprimer ejemplo consistirá en buscar la letra o en la cadena de texto hola. El siguiente código muestra cómo hacerlo: >>> import re>>> re.search(r"o", "hola")
    • 176. <_sre.SRE_Match object at 0x00000000021A15E0> Efectivamente, la simple cadena de texto "o" es por sí misma unaexpresión regular. Nótese que nuestra cadena va precedida por la letra"r", esto le indica al intérprete que se trata de un expresión regular. Porotro lado, el segundo argumento de search() es la cadena dondevamos a buscar.Si reescribimos la última línea de código asignado el resultado auna variable, podremos acceder a los diferentes métodos del objetoque representa los resultados obtenidos al realizar la búsqueda. Porejemplo, el método group() nos devolverá la subcadena que coincidecon nuestro patrón de búsqueda. Veamos un sencillo ejemplo: >>> m = re.search(r"o", "hola")>>> m.group()"o" Como habremos podido comprobar, la cadena devuelta es elpropio carácter que buscamos. Pasemos a un ejemplo un poco máscomplicado, supongamos que necesitamos encontrar cualquiersubcadena que contenga justamente tres números seguidos. En estecaso buscaremos nuestro patrón en tres cadenas de texto diferentes:>>> m = re.search(r"\d\d\d", "hola402adios").group() 402>>> m = re.search(r"\d\d\d", "986hola").group()986>>> m = re.search(r"\d\d\d", "adios123").group()123 Si pensamos utilizar la misma expresión regular para búsquedas endiferentes cadenas de texto, es aconsejable compilar la expresiónregular. Esto se puede hacer directamente a través de la función compile. Gracias a ello ganamos en eficiencia y simplificamos nuestrocódigo. A continuación, aplicaremos esta función tomando como baselos ejemplos anteriores: >>>>>>402>>>986>>>123 patron = re.compile("\d\d\d")patron. search ("hola402adios").group() patron.search("986hola") patron.search("adios123")
    • 177. ¿Qué ocurre si el patrón no coincide con ninguna parte de lacadena donde se realiza la búsqueda? Sencillo, el método nosdevolverá None: >>> m = re.search(r"\d\d\d", "98x65adios")>>> if m is None: print("No existen coincidencias") Obsérvese, que en este último caso, no hemos utilizadodirectamente el método group(), dado que el resultado es None, siaplicáramos directamente la llamada, obtendríamos un error en tiempode ejecución. Es decir, se produciría una excepción.En realidad, group() es capaz de agrupar resultados. De esta forma,podemos utilizar paréntesis en nuestra expresión regular con elobjetivo de agrupar la subcadenas encontradas. Gracias a ello, esposible tener más control sobre el resultado. Supongamos quenecesitamos buscar un patrón que representa una serie de númerosseguidos de un guión y de otro conjunto de letras. Además, queremosagruparlo en dos grupos, uno para los números y otro para las letras.Nuestra expresión regular quedaría de la siguiente forma: >>> regex = re.compile(r"(\d+)-([A-Za-z]+") Ahora pasaremos a buscar en una cadena concreta: >>> m = regex.search("23-cDb") La variable m representa el resultado de nuestra búsqueda. Dadoque la cadena cumple el patrón, podremos llamar directamente almétodo group(): >>> m.group(1)23>>> m.group(2) cDb Por otro lado, si llamamos a group() utilizando el índice 0,obtendremos la cadena encontrada completa: >>> m.group(O)23-cDb Hasta ahora no hemos tenido en cuenta el principio y fin decadena, simplemente estamos buscando un patrón en toda la cadena.Observemos las siguientes sentencias y su resultado:
    • 178. >>> re.search(r" ^hola$", "adiosholaadios")>>> re.search(r"hola", "adiosholaadios")<_sre.SRE_Match object at 0x00000000021A15E0> En el primer caso, no encontramos coincidencias, ya que, estamosindicando que la cadena debe comenzar y terminar con los caracteresque se encuentra entre el ¡nielo ( ^ ) y el fin ( $ ).Otra de las funciones de búsqueda con las que cuenta el módulo re es findall(), que puede ser utilizada junto con los grupos para obteneruna lista de tuplas que representa todas las coincidencias encontradas.El caso más sencillo sería utilizar findall() sin emplear grupos. Porejemplo, supongamos que necesitamos una lista con todos los valoresde una cadena de texto que cumplen el patrón, que nuestro caso serácuando aparezca exactamente tres números seguidos. Para ello,podemos emplear el código que viene a continuación: >>> re.findall(r"\d{3}", "345abcd78ghx678")[345, 678] Si combinamos la función anterior con grupos, el resultado será unalista de tuplas, tal y como hemos avanzado previamente: >> re.findall(r"([a-z]+)-(\d+)", "345abcd-78ghx-678")[(abcd", "78"), ("ghx", "678"] En el ejemplo anterior hemos podido observar cómo los valores dela lista contienen una tupla con cada uno de los valores de la cadenaque coinciden con los grupos del patrón. Sustituciones La principal función que nos ofrece el módulo re para realizarreemplazos de texto en una determinada cadena, empleando para elloexpresiones regulares, es sub. Como argumentos, esta función recibecuatro argumentos diferentes. El primero de ellos es una expresiónregular que indica el patrón que será buscado. El segundo argumentoes la cadena de texto que se utilizará como reemplazo de lacoincidencia indicada por el primer argumento. El tercero de losargumentos es la cadena de texto donde se llevará a cabo elreemplazo. El cuarto y último argumento es opcional e indica el
    • 179. número de ocurrencias que deben ser reemplazadas, por defecto sereemplazarán todas las que se encuentren. La función sub() devuelve elresultado de aplicar el reemplazado, quedan sin modificar losargumentos recibidos por la misma.Supongamos que contamos con una cadena de texto que contieneletras y números y deseamos reemplazar todos los números quecontenga por guiones. El siguiente código nos muestra cómo hacerlo: >>> re.sub(r"\d", "-", "345abcdef987")---abcdef--- Si en el código anterior utilizáramos el cuarto parámetro que aceptala función sub(), por ejemplo usando el valor 2, solo se reemplazaríanlos tres primeros números, tal y como podemos apreciar en elsiguiente ejemplo: >>> re.sub(r"\d", "-", "345abcdef987", 3)---abcdef987 Por otro lado, sub() también no permite utilizar grupos y sustituircada uno de ellos por un valor diferente. Un ejemplo de ello podría serel que ilustra el siguiente código: >>> re.sub(r"(\w+)@(\w+)", r"\1@123", "abc@efg")abc@123 En el ejemplo anterior los caracteres \1 del segundo parámetroindican que el primer grupo del patrón debe conservarse, siendosustituido el segundo grupo por el valor indicado después de \1. Sisustituimos el segundo parámetro indicando un 2 en lugar de un 1, elresultado sería diferente: >>> re.sub(r"(\w+)@(\w+)", r"\2@123", "abc@efg")efg@123 Tanto para las búsquedas como para las sustituciones, Python 3puede trabajar directamente con caracteres Unicode. No olvidemos,que por defecto, todos los strings son de este tipo en esta versión delintérprete del lenguaje. De esta forma, la siguiente sentencia escompletamente válida y funcional: >>> re.sub(r"ñ", "n", "niño")niño
    • 180. Separaciones Interesante es la también función split() ofrecida por el módulo paraexpresiones regulares de Python. Básicamente esta función sirve paraobtener una lista donde cada elemento es el resultado de utilizar comocriterio de separación el patrón dado por la expresión regular. Estafuncionalidad es más fácil de entender utilizando un ejemplo: >>> cad = "Uno<a>Dos<b>Tres<c>Cuatro"["Uno", "Dos", "Tres", "Cuatro"] Tal y como el lector habrá podido deducir, esta funcionalidad essimilar al método del mismo nombre de los strings, con la diferenciaque el incluido en el módulo re admite expresiones regulares. Modificadores Cuando compilamos una expresión regular utilizando la función compile() podemos emplear una serie de modificadores que alteran elcomportamiento del patrón. Estos modificadores son muy útiles, porejemplo, para cuando deseamos ignorar mayúsculas y minúsculas(case insensitive). En concreto, los modificadores más comunes son lossiguientes: IGNORECASE: No tiene en cuenta mayúsculas ni minúsculas.DOTALL: Permite que el metacarácter punto (.) tenga encuenta las líneas en blanco.MULTILINE: Sirve para que se puedan utilizar los caracteres deinicio ( ^ ) y fin ( $ ) de línea en una cadena que contiene más deuna línea. A continuación, veamos un ejemplo de sustitución de cadena conindependencia de si son mayúsculas o minúsculas: >>> regex = re.compile(r"abc", re.IGNORECASE)>>> regex.search("124AbC")AbC
    • 181. Para ver cómo funcionan las búsquedas utilizando el modificador MULTILINE, buscaremos un determinado patrón entre una cadena detexto que incluye el carácter salto de carro (\n). En realidad, estacadena estará compuesta por varias líneas, tal y como ocurre, porejemplo, cuando leemos de un fichero, donde cada línea es unacadena en sí misma. El código mostrado a continuación nos enseñacómo usar el mencionado modificador en estos casos: >>> regex = re.compile(r"^Text: (\d+)$", re. MULTILINE)>>> cad = "Text: 34\nText: 35\nText: aa\nText: 24\n">>> regex.search(cad).group(1)Text: 34 Patrones para comprobaciones cotidianas En este apartado vamos a mostrar una serie de expresionesregulares que suelen ser empleadas asiduamente para realizarcomprobaciones y validaciones. Por ejemplo, es común en muchasaplicaciones web comprobar si una determinada cadena de textointroducida por el usuario es, sintácticamente, una cuenta de correoelectrónico. La tabla 5-3 muestra un conjunto de este tipo deexpresiones regulares. Expresión regular Funcionalidad ^d{1,6}$ Números desde el 0 hasta el 999999 ^#([a-fA-FO-9]){3}(([a-fA-FO-9]) {3})?$ Código hexadecimal para HTML ^\d\d/\d\d/\d\d\d\d$ Fecha en formato dd/mm/yyyy
    • 182. ^.*\// UNIX path ^([0-9afA-F]{2}:){5}[0-9afA-F]{2}$ Dirección MAC Cuenta de
    • 183. ^[0-9a-zA-Z]([-.\w] * [0-9a-zA-Z_+]) * @([0-9a- zA-Z][-\w] * [0-9a-zAZ]\.)+[a-zA-Z{2,9}$ correoelectrónico Tabla 5-3. Expresiones regulares útiles para validaciones * (https?):\/\/([0-9a-zA-Z][-\w+] [0-9a-zA- Z]\.)+[a-zA-Z] * {2,9})(:\d{1,4})?([-\w/\#~:?+=&%@~] ) HTTP URL ^(\d|[01]?\d\d|2[0-4]\d|25[0-5])\.(\d|[01]?\d\d|2[0- 4]\d|25[0- 5])\.(\d|[0-1]?\d\d|2[0-4]\d|25[0-5])\.(\d[01]? \d\d|2[0-4\d|25[0- 5])$ Dirección IPv4
    • 184. ORDENACIÓN DE DATOS En el presente apartado nos centraremos en aprender cómoordenar datos. La función más sencilla que Python nos ofrece es lallamada sorted(), que básicamente, recibe como argumento una lista ydevuelve otra con los elementos de la original ya ordenados. Enprincipio, es el intérprete quien decide los criterios de ordenación,siendo esto trivial cuando los elementos de la lista son números ocaracteres. Observemos el siguiente ejemplo, donde ordenaremos unalista de números: >>> lista = [5, 1, 9, 8, 3]>>> sorted(lista)[1, 3, 5, 8, 9] De forma análoga, podemos ordenar una serie de caracteres ocadenas de texto: >>> cads = ["ca", "dc", "cb", "ab", "db"]>>> sorted(cads)["ab", "ca", "cb", "db", "dc"] Python también tiene en cuenta el orden si las cadenas de textoutilizan mayúsculas y minúsculas, teniendo preferencia las primerassobre las segundas, tal y como podemos comprobar en el siguientecódigo: >>> cads = ["ca", "Cc", "cb", "aB", "db"]>>> sorted(cads)["Cc", "aB", "ca", "cb", "db"] Adicional y optativamente, la función sorted() puede utilizar unsegundo parámetro para indicar si el orden debe hacerse ascedente odescendentemente. Este parámetro se llama reverse y por defecto suvalor es False. Así pues, para ordenar nuestra primera lista en ordendescendente, basta con ejecutar la siguiente sentencia: >>> sorted(lista, reverse=True)[9, 8, 5, 3, 1] En caso de que deseemos indicar cuál será el criterio de ordenación
    • 185. que debe emplear la función sort(), podremos hacerlo gracias alparámetro key que acepta esta función. El valor de este parámetrodeberá ser una función, que bien, podemos implementar nosotrosmismos o una ya integrada en el intérprete. Por ejemplo, supongamosque deseamos ordenar una lista en función del número de elementosque contienen. En este caso, basta con utilizar la función integrada len(), tal y como muestra el siguiente ejemplo: >>> lista = ["abc", "de", "fghij"]>>> sorted(lista)["abc", "de", "fghij"]>>> sorted(lista, key=len)["de", "abc", "fghij"] Método itemgetter() Para ordenaciones más complejas puede resultar muy útil emplearel método itemgetter() del módulo integrado operator. Este métodopermite generar un objeto que utilizará el valor pasado comoparámetro para devolver el elemento que ocupa la posición que indicael mencionado valor cuando el objeto es invocado pasando comoparámetro una serie de elementos. En realidad, es más fácil de ver conun sencillo ejemplo de código que entender la explicación queacabamos de ofrecer: >>> from operator import itemgetter>>> fun = itemgetter(2)>>> fun( [1, 2, 3, 4])3 El valor devuelto es 3 porque este es el valor que ocupa la posicióndos en la lista pasado como parámetro, recordemos que el índice delos elementos de una lista comienza por cero y no por uno.Si combinamos la función sorted() junto con el método itemgetter(), podemos realizar complejas ordenaciones. Supongamos que contamoscon una lista de tuplas, donde cada uno de los elementos de cadatupla es un número y una cadena de texto. Dada esta estructura dedatos, necesitamos ordenar teniendo en cuenta el segundo valor de latupla, que en nuestro caso será un número. Veamos cómo hacerlo através del ejemplo que viene a continuación:
    • 186. >>> lista = [("Madrid", 4), ("Barcelona", 1), ("Sevilla", 5),("Valencia", 3)] [("Madrid", 4), ("Barcelona", 1), ("Sevilla", 5), ("Valencia", 3)]>>> sorted(lista, key=itemgetter(1))[("Barcelona", 1), ("Valencia", 3), ("Madrid", 4), ("Sevilla", 5)] Tal y como podemos apreciar, al ordenar se ha tenido en cuenta elsegundo valor de cada tupla, es por ello que hemos utilizado el valor 1 como parámetro para el método itemgetter(). No olvidemos que en lastuplas los índices también comienzan por cero, al igual que en laslistas.El método itemgetter() puede recibir más de un argumento.imaginemos que tenemos una lista similar a la anterior que queremosordenar, primero teniendo en cuenta el segundo valor y luego elprimero de cada una de las tuplas que forma la lista. Bastaría con pasarun argumento adicional al mencionado método, tal y como muestra elsiguiente ejemplo: >>> lista = [("a", 2), ("c", 2), ("b", 3), ("d", 3), ("z", 1),("a", 1), ("d", 1)]>>> sorted(lista, key=itemgetter(0,1))[("a", 1), ("a", 2), ("b", 3), ("c", 2), ("d", 1), ("d", 3), ("z", 1) ] De forma análoga, podemos invertir el orden de los parámetrospara conseguir una ordenación diferente: >>> sorted(lista, key=itemgetter(1,0))[("a", 1), ("d", 1), ("z", 1), ("a", 2), ("c", 2), ("b", 3), ("d",3)] Funciones lambda Una técnica habitual para ordenar a través de la función sorted() esemplear funciones lambda() como valor para el parámetro key. Graciasa esta técnica es fácil ordenar, por ejemplo, una lista que contiene unaserie de objetos con unos valores determinados. Vamos a trabajar conuna clase que representará al empleado de una empresa,posteriormente construiremos una lista de objetos Empleado yfinalmente la ordenaremos en función del atributo que corresponde asus apellidos. Comenzamos declarando nuestra clase:
    • 187. class Empleado:def __init__(self, apellidos, puesto, edad):self.apellidos = apellidosself.puesto = puestoself.edad = edaddef __repr__(self):return repr((self.apellidos, self.puesto, self.edad)) Llegamos al momento de crear nuestra lista con diferentes objetosde la clase Empleado : >>> empleados = [Empleado("Fernández", "Administración", 25),... Empleado("Dominguez", "Finanzas", 38),... Empleado("Amaro", "Contabilidad", 21)]...>>> print(sorted(empleados, key=lambda empleado:empleado.apellidos))[("Amaro", "Contabilidad", 21), ("Dominguez", "Finanzas", 38),("Fernández", "Administración", 25)] De forma análoga podemos emplear otro atributo como criterio deordenación, por ejemplo, la edad: >>> print(sorted(empleados, key=lambda empleado:empleado.apellidos) ) [("Amaro", "Contabilidad", 21), ("Fernández", "Administración",25)m ("Dominguez", "Finanzas", 38)]
    • 188. FICHEROS INTRODUCCIÓN Los sistemas operativos almacenan los datos de forma estructuradaen unas unidades básicas de almacenamiento a las que llamamos ficheros o archivos. Tarde o temprano, los programadores necesitantrabajar con ficheros, ya sea para leer datos de los mismos o paracrearlos. Es por ello que los lenguajes de programación suelen incluirlibrerías que contienen funciones para el tratamiento de ficheros. EnPython contamos con una serie de funciones básicas, incluidas en sulibrería estándar, para leer y escribir datos en ficheros. A estasfunciones dedicaremos el primer apartado del presente capítulo.Una vez que aprendamos todo sobre lo básico sobre el manejo deficheros en Python, pasaremos a adentrarnos en el concepto de señalización, el cual está directamente relacionado con los ficheros.Descubriremos qué herramientas nos ofrece el lenguaje y qué módulosde la librería estándar pueden ser empleados para serializar datos enficheros. Además, continuaremos haciendo un repaso a los tresprincipales formatos utilizados para serializar: XML, JSON y YAML.Estos formatos no solo se usan para serializar, sino que también suelenser empleados para guardar información estructurada y para elintercambio de la misma. Por ejemplo, son muchas las aplicacionesque utilizan el formato YAML para guardar información sobre unadeterminada configuración.Dado que el formato CSV es uno de los más sencillos y popularespara guardar información estructurada en ficheros, dedicaremos unapartado específico a ver cómo puede Python manejar este tipo deficheros. Aplicaciones como MS Office y LibreOffice pueden exportar eimportar datos en formato CSV, de forma que necesitamos desarrollaruna aplicación que trabaje con este formato y que pueda intercambiardatos con este tipo de programas, podremos emplear Python comolenguaje de programación.Python cuenta con un módulo específico que permite leer ficherosen formato INI. Este es muy popular en los sistemas Windows y suelen
    • 189. ser empleado para guardar información relativa a una determinadaconfiguración. También en este capítulo aprenderemos lo básico parapoder trabajar con ficheros del mencionado tipo.Hoy en día es muy común trabajar con ficheros comprimidos quenos permiten almacenar más información en menos espacio. Formatoscomo ZIP, RAR o gzip se han convertido en formatos de facto y lamayoría de los sistemas operativos permiten utilizar herramientas paracomprimir o descomprimir ficheros. Teniendo en cuenta estos factores,parece útil disponer de funcionalidades que permitan trabajar con estetipo de ficheros desde un lenguaje de programación. En Pythoncontamos con varios módulos de su librería estándar que nos facilitanel trabajo, de ellas nos ocuparemos en el último apartado de estecapítulo.
    • 190. OPERACIONES BÁSICAS Python incorpora en su librería estándar una estructura de datosespecífica para trabajar con ficheros. Básicamente, esta estructura esun stream que referencia al fichero físico del sistema operativo. Dadoque el intérprete de Python es multiplataforma, el manejo interno y abajo nivel que se realiza del fichero es transparente para elprogramador. Entre las operaciones que Python permite realizar conficheros encontramos las más básicas que son: apertura, creación,lectura y escritura de datos. Apertura y creación La principal función que debemos conocer cuando trabajamos conficheros se llama open() y se encuentra integrada en la librería estándardel lenguaje. Esta función devuelve un stream que nos permitirá operardirectamente sobre el fichero en cuestión. Entre los argumentos queutiliza la mencionada función, dos son los más importantes. El primerode ellos es una cadena de texto que referencia la ruta (path) delsistema de ficheros. El otro parámetro referencia el modo en el que vaa ser abierto el fichero. Es importante tener en cuenta que Pythondiferencia entre dos tipos de ficheros, los de texto y los binarios. Estadiferenciación no existe como tal en los sistemas operativos, ya queson tratados de la misma forma a bajo nivel. Sin embargo, a través del modo podemos indicarle a Python que un fichero es un tipo u otro. Deesta forma, si el modo del fichero es texto, los datos leídos seránconsiderados como un string, mientras que si el modo es binario, losdatos serán tratados como bytes. Antes de comenzar a ver un ejemplo de código, abriremos nuestroeditor de textos favorito, añadiremos una serie de líneas de texto y losalvaremos, por ejemplo, con el nombre fichero.txt. Seguidamente,ejecutaremos el intérprete de Python desde la interfaz de comandos yescribiremos la siguiente línea:
    • 191. >>> fich = open("fichero.txt") En nuestro ejemplo, hemos supuesto que el fichero creado seencuentra en la misma ruta desde la que hemos lanzado el intérprete,de no ser así es necesario indicar el path absoluto o relativo al fichero.Por otro lado, no hemos indicado ningún modo, ya que, por defecto,Python emplea el valor r para ficheros de texto. Antes de continuar,debemos tener en cuenta que los sistemas operativos suelen empleardiferentes caracteres como separadores entre los directorios queforman parte de una ruta del sistema de ficheros. Por ejemplo,supongamos que nuestro fichero se encuentra en un directoriollamado pruebas. Si estamos trabajando en Windows, nuestra línea decódigo para abrir el fichero sería la siguiente: >>> fich = open("pruebas\fichero.txt") Sin embargo, para realizar la misma operación en Linuxnecesitaríamos esta otra línea de código: >>> fich = open("pruebas/fichero.txt") Dado que Python es multiplataforma, es deseable que el mismocódigo funcione con independencia del sistema operativo que loejecuta, pero, en nuestro ejemplo, este código es distinto. ¿Cómoresolver este problema? Es sencillo, para ello Python nos facilita unafunción que se encuentra integrada en el módulo os de su libreríaestándar. Se trata de join() y se encarga de unir varias cadenas de textoempleando el separador adecuado para cada sistema operativo. Deesta forma, sería más práctico escribir el código anterior para laapertura del fichero de la siguiente forma: >>> from os import path>>> ruta_fich = path.join("pruebas", "fichero.txt")>>> fich = open(ruta_fich) Por otro lado y como complemento a la función join(), tambiénexiste la variable sep, que también pertenece al módulo os. Estarepresenta el carácter propiamente dicho que cada sistema operativoemplea para la separación entre directorios y ficheros.Respecto al valor que se puede indicar para el modo de aperturadel fichero, Python nos permite utilizar un valor
    • 192. determinado enfunción de la operación (lectura, escritura, añadir y lectura/escritura) y
    • 193. otro para señalar si el fichero es de texto o binario. Obviamente,ambos tipos de valores se pueden combinar, para, por ejemplo, indicarque deseamos abrir un fichero binario para solo lectura. En concreto, elvalor "r" significa "solo lectura"; con "w" abriremos el fichero parapoder escribir en él; para añadir datos al final de un fichero yaexistente emplearemos "a" y, por último, si vamos a leer y escribir en elfichero, basta con utilizar "+". Por otro lado, con "b" señalaremos queel fichero es binario y con "t" que es de texto. Como hemoscomentado previamente, por defecto, la función open() interpreta quevamos a abrir un fichero de texto como solo lectura. Tanto el valorpara realizar una operación como para indicar el tipo de fichero debeindicarse como argumentos del parámetro mode. Así pues, para abrirun fichero binario para lectura y escritura basta con ejecutar elsiguiente comando: >>> open("data.bin", "b+") Además de los mencionados parámetros de la función open(), existen otros que podemos utilizar. En concreto, contamos con cincomás. El primero de ellos es buffering y permite controlar el tamaño del buffer que el intérprete utiliza para leer o escribir en el fichero. Elsegundo parámetro es encoding y permite indicar el tipo decodificación de caracteres que deseamos emplear para nuestro fichero.Otro de los parámetros es errors que indica cómo manejar erroresdebidos a problemas derivados de la utilización de la codificación decaracteres. Para controlar cómo tratar los saltos de línea contamos con newline. Por último, closefd es True y si cambiamos su valor a False eldescriptor de fichero permanecerá abierto aunque explícitamenteinvoquemos al método close() para cerrar el fichero.Hasta el momento hemos hablado de abrir ficheros, utilizando paraello diferentes parámetros. Pero ¿cómo podemos crear un fichero ennuestro sistema de archivos desde Python? Basta con aplicar el modode escritura y pasar el nombre del nuevo fichero, tal y como muestra elsiguiente ejemplo: >>> f_nuevo = open(nuevo.txt", "w") Debemos tener en cuenta que, pasando el valor w sobre un ficheroque ya existe, este será sobrescrito, borrando su contenido.Ahora que ya sabemos cómo abrir y crear un fichero, es hora de
    • 194. aprender cómo leer y escribir datos. Lectura y escritura La operación básica de escritura en un fichero se hace en Python através del método write(). Si estamos trabajando con un fichero detexto, pasaremos una cadena de texto a este método como argumentoprincipal. Supongamos que vamos a crear un nuevo fichero de textoañadiendo una serie de líneas, bastaría con ejecutar las siguienteslíneas de código: >>>>>>14>>>14>>> fich = open(texto.txt", "w")fich.write("Primera línea\n") fich.write("Segunda línea\n") fich.close() El carácter "\n" sirve para indicar que deseamos añadir un retornode carro, es decir, crear una nueva línea. Después de realizar lasoperaciones de escritura, debemos cerrar el fichero para que elintérprete vuelque el contenido al fichero físico y se pueda tambiéncerrar su descriptor. Si necesitamos volcar texto antes de cerrar elfichero, podemos hacer uso del método flush() y, posteriormente,podemos seguir empleando write(), sin olvidar finalmente invocar a close(). En nuestro ejemplo, observaremos cómo, después de cadaoperación de escritura, aparece un número. Este indica el número debytes que han sido escritos en el fichero.Adicionalmente, el método writelines() escribe una serie de líneasleyéndolas desde una lista. Por ejemplo, si deseamos emplear estemétodo en lugar de varias llamadas a write(), podemos sustituir elcódigo anteriormente mostrado por este otro: >>> lineas = ["Primera línea\n", "Segunda línea\n"]>>> fich.writelines(lineas) Ahora que ya tenemos texto en nuestro fichero, es hora de pasar ala operación inversa a la escritura, hablamos de la lectura desde elfichero. Para
    • 195. ello, Python nos ofrece tres métodos diferentes. Elprimero de ellos es read(), el cual lee el contenido de todo el fichero y
    • 196. devuelve un única cadena de texto. El segundo en cuestión es readline() que se ocupa de leer línea a línea del fichero. Por último,contamos con readlines() que devuelve una lista donde cada elementocorresponde a cada línea que contiene el fichero. Los siguientesejemplos muestran cómo utilizar estos métodos y su resultado sobre elfichero que hemos creado previamente: >>> fich = open(texto.txt", "w")>>> fich.read()'Primera línea\nSegunda línea\n'>>> fich.seek(O)0>>> fich.readlines(fich)['Primera línea\n', 'Segunda línea\n']>>> fich.seek(O)0>>> fich.readline()'Primera línea\n' Seguro que al lector no le ha pasado desapercibido el uso de unnuevo método. Efectivamente, seek() es otro de los métodos quepodemos usar sobre el objeto de Python que maneja ficheros y quesirve para posicionar el puntero de avance sobre un puntodeterminado. En nuestro ejemplo, y dado que deseamos volver alprincipio del fichero, hemos empleado el valor 0. Debemos tener encuenta que cuando se hace una operación de escritura, Python manejaun puntero para saber en qué posición deberá ser escrita la siguientelínea con el objetivo de no sobrescribir nada. Sin embargo, estepuntero avanza de forma automática y el método seek() sirve parasituar el mencionado puntero en una determinada posición del fichero.Para leer todas las líneas de un fichero no es necesario emplear seek() tal y como muestra el ejemplo anterior de código. Existe unmétodo más sencillo basado en emplear un iterator para ello: >>> for line in open "texto.txt"):print(line)...Primera líneaSegunda línea Además, también es práctica habitual emplear with para leer todaslas líneas de un fichero: >>> with open(texto.txt") as fich:
    • 197. ... print (fich.read())...Primera líneaSegunda línea Hasta el momento, nuestros ejemplos se han referido en exclusiva aficheros de texto, pero, obviamente, es posible crear, abrir, leer yescribir en ficheros binarios. La forma de llevar a cabo estasoperaciones es similar a como hemos visto previamente para losficheros de texto. Por ejemplo, podemos crear un fichero binario quesolo contiene una secuencia de bytes, en concreto, vamos a escribir enel fichero tres bytes representados por tres números en hexadecimal. Elcódigo necesario para ello sería el siguiente: >>> fich = open("test.bin", "bw")<_io.BufferedWriter name='test.bin'>>>> fich.write(b"\x33\xFA\x1E")3>>> fich.close() El número 3 que el intérprete devuelve al escribir nuestra secuenciade bytes corresponde, efectivamente, a los tres bytes que hemosescrito en el fichero. Recordemos, que cada número en formatohexadecimal ocupa justamente un byte. Para leer el contenido queacabamos de escribir, volveremos a abrir el fichero, esta vez en modode solo lectura: >>> fich = open("test.bin", "br")>>> fich.read(3)b'\x33\xFA\x1E' En este caso, el parámetro pasado al método read() indica elnúmero de bytes que deseamos leer. Si hubiéramos utilizado 1 enlugar de 3 , entonces, el resultado hubiera sido b'\x33'. El fichero quehemos creado puede ser leído por un editor que soporte la lectura yedición de este tipo de ficheros.El método seek() suele ser utilizado con frecuencia para desplazarsepor un fichero binario, a diferencia de los de texto, que habitualmentesuelen ser leídos línea a línea. El mencionado método soporta dostipos de parámetros diferentes, el primero de ellos indica el número debytes que debe moverse el puntero interno de señalización del ficheroy, el segundo, marca el punto de referencia desde el que debe sermovido dicho puntero. Este segundo parámetro puede tomar tres
    • 198. valores: 0 (comienzo del fichero), 1 (posición actual) y 2 (fin de fichero).Por defecto, si no se indica este parámetro, el valor es 0. De esta forma,para leer el último byte de nuestro fichero, emplearíamos las siguientessentencias: >>> fich.seek(-1, 2)2>>> fich.read(1)b'\xle' Cuando se emplea como punto de referencia el final del fichero, sedeben indicar valores negativos para el desplazamiento, tal y comohabremos podido comprobar en el ejemplo anterior de código.Relacionado con la creación, lectura y escritura de ficheros binariosse encuentra el concepto de serialización de objetos del que nosocuparemos en el siguiente apartado.
    • 199. SERIALIZACIÓN La serialización es un proceso mediante el cual una estructura dedatos es codificada de un modo específico para su almacenamiento,que puede ser físicamente en un fichero, una base de datos o un buffer de memoria. Habitualmente, la serialización se lleva a cabo utilizandoinstancias de objetos, aunque son muchos los lenguajes deprogramación que permiten aplicar este proceso a cualquier estructurade datos que cumpla ciertas condiciones. El propósito de este proceso,además del almacenamiento propiamente dicho, suele ser eltransporte de datos a través de una red o, simplemente para crear unacopia exacta de un objeto determinado. En computación distribuida esmuy común señalizar objetos para su transporte entre diferentesmáquinas, también es habitual emplear la serialización en protocoloscomo CORBA (Common Object Request Broker Architecture), quepermite invocar a métodos y funciones escritos en un lenguajedeterminado desde otro diferente. En general, el término de serialización es conocido como marshalling en el ámbito de lasciencias de la computación.Diferentes lenguajes de programación emplean diferentesalgoritmos para señalizar. Python soporta la serialización de objetos, através de dos módulos diferentes de su librería estándar: pickle y marshal. Dado que los algoritmos empleados por estos módulos sondiferentes, es necesario escoger uno de ellos a la hora de señalizardatos en Python. Al proceso de convertir un objeto cualquiera en unconjunto de bytes es llamado en Python pickling, siendo el procesoinverso llamado unpickling. Es por ello, que habitualmente, el módulo pickle es el más utilizado para la serialización de objetos en Python.Debemos tener en cuenta que el módulo marshal señaliza de unaforma que no es compatible entre Python 2.x y Python 3; sin embargo, pickle nos garantiza la portabilidad del código entre versiones delintérprete. Por otro lado, marshal no puede ser utilizado para señalizarlas clases propias definidas por el programador, mientras que esto síque es posible con pickle. Dado que el formato en el que sonseñalizados los datos, a través de pickle, es específico de Python, no es
    • 200. posible deserializar aquellos objetos señalizados con este móduloempleando otros lenguajes de programación.La versión 3 de Python incluye cuatro protocolos diferentes con losque puede trabajar pickle. Cada uno de ellos es identificado por unnúmero entre 0 y 3. El primero de ellos es compatible con versionesanteriores a Python 3 y serializa utilizando un formato humanredeable. El segundo de los protocolos emplea un formato binario y escompatible con las primeras versiones de Python. El tercero,identificado por el número 2, fue incluido por primera vez en Python2.3 y es mucho más eficiente que sus antecesores. Por último,contamos con uno nuevo incluido en Python 3.0, que no puede serutilizado por la serie 2.x de Python para la deserialización y que esactualmente el recomendado para la serie 3.x del lenguaje. Además, elmencionado módulo incluye dos constantes diferentes: HIGHEST_PROTOCOL, que se identifica a la versión más alta disponibley DEFAULT_PROTOCOL, que actualmente está asociada al protocolo 3.Respecto a los tipos de datos que pueden ser señalizados enPython con pickle, debemos tener en cuenta que son los siguientes: Aquellos que toman los valores True, False y None. Números enteros, complejos y reales.Cadenas de texto, bytes y listas de bytes.Funciones definidas en el nivel superior de un módulo.Funciones integradas en el intérprete que residan en el nivelsuperior de un módulo.Tuplas, listas, conjuntos y diccionarios que contenganelementos que pertenezcan a los tipos anteriores.Clases definidas por el programador que contenganelementos que pertenezcan a los tipos anteriores. Como complemento a pickle, Python pone a nuestra disposiciónotro módulo llamado pickletools, que contiene una serie deherramientas para analizar los datos en el formato generado alserializar con pickle.
    • 201. Ejemplo práctico Básicamente, para serializar objetos en Python, a través del módulo pickle, emplearemos dos funciones diferentes: dump() para serializar y load() para la operación inversa. Para nuestro ejemplo práctico vamos acrear una clase, similar a la del capítulo anterior que representaba a unempleado. En este caso vamos a definir una clase que representa a unalumno, tal y como muestra el siguiente código: >>> class Alumno:... def __init__(self, nombre_completo, titulación, edad):... self.apellidos = nombre_completo['apellidos']... self.nombre = nombre_completo['nombre']... self . titulación = titulación... self.edad = edad... def __repr__(self):... return repr(self.nombre, self.apellidos, self.titulación) Como el lector habrá podido adivinar, vamos a emplear cadenas detexto, enteros y un diccionario como tipos de datos que contendrá laInstancia de nuestra clase. Dado que todos cumplen con lascondiciones para señalizar, no tendremos problema para llevar a caboeste proceso. Antes de ello, crearemos una Instancia que será laseñalizada y, posteriormente, deserializada: >>> nombre_completo = {"apellidos": "Rodríguez", "nombre":"Lucas"}>>> alumno = Alumno(nombre_completo, "Grado en Derecho", 21) El siguiente paso será llevar a cabo la serialización, previamenteimportaremos el módulo pickle y posteriormente Invocaremos a lafunción dump(). Los datos de nuestra Instancia serán señalizados en unfichero binario llamado alumnos.bin. Efectivamente, los ficherosbinarlos suelen ser los empleados para llevar a cabo este proceso, deahí la relación entre este tipo de ficheros y la serialización, tal y comohabíamos avanzado en el apartado anterior del presente capítulo. Encuanto al código, el siguiente nos muestra cómo crear el fichero yaplicar dump(): >>> import pickle>>> with open(alumnos.bin", "wb") as fich:pickle.dump(alumno, fich)
    • 202. Para llevar a cabo el proceso inverso, es decir, la deseríalízacíón, esnecesario invocar al método load(). Así pues, y siguiendo con nuestroejemplo, para leer los datos que acabamos de señalizar, basta conejecutar las siguientes líneas de código: >>> with open(alumnos.bin", "rb") as fich:... pickle.load(fich)Lucas Rodríguez Grado en Derecho 21 Es importante tener en cuenta que cuando se emplea la función load() para deseríalízar los datos de una clase, esta debe ser accesibleen el mismo ámbito. En nuestro ejemplo y dado que hemos utilizado laconsola de comandos del intérprete, cumplimos con esta condición.
    • 203. FICHEROS XML, JSON Y YAML Con la serialización se encuentran relacionados una serie deformatos de ficheros que suelen ser utilizados para guardar datos enun determinado formato y, que pueden compartirse entre diferentesmáquinas y tratarse en distintos lenguajes de programación. Entreestos formatos, destacan tres: XML, JSON y YAML. En este apartadodescubriremos cómo podemos trabajar con estos formatos desdePython. Comenzamos por uno de los más populares, el XML. XML Estas tres siglas vienen de eXtensible Markup Language yreferencian a un lenguaje de etiquetas desarrollado por el WC3 (WorldWide Web Consortium). En realidad, este lenguaje no es más que unaadaptación del original SGML (Standard Generalized Markup Language) y fue diseñado con el objetivo de tener una herramienta que fueracapaz de ayudar a definir lenguajes de marcas y etiquetas, adaptados adiferentes necesidades. Además, también puede ser utilizado como unestándar para el intercambio de datos estructurados. De hecho, es estauna de las aplicaciones más usuales del formato XML. Dado que esposible definir de forma personalizada una estructura concreta dedatos, estos pueden ser almacenados en un simple fichero de texto,haciendo el mismo totalmente portable entre máquinas y lenguajes deprogramación. En la práctica, podemos almacenar en un fichero XMLdesde información relativa a una configuración específica, hasta datosque habitualmente podrían almacenarse en una base de datos. Es más,comúnmente el formato XML es empleado para serializar datos yexisten multitud de librerías que permiten, dada una definición de unobjeto, volcarlo directamente a un fichero en este formato. En laactualidad, XML es un formato estándar de facto, empleado pormultitud de aplicaciones web y de escritorio.Los datos en formato XML pueden ser accedidos a través de dosinterfaces diferentes: DOM (Document Object Model) y SAX (Simple API
    • 204. for XML). La primera de ellas se basa en utilizar y tratar los datosbasándose en una estructura de árbol, donde se definen nodos y unajerarquía que permite acceder a los diferentes elementos que formanparte de los datos contenidos en XML. Por otro lado, SAX estáorientado a eventos y permite trabajar en un sección del documentoXML en lugar de hacerlo en todo el conjunto, tal y como necesitaDOM.Son muchos los lenguajes de programación que ofrecenherramientas para trabajar con XML y Python se encuentra entre ellos.En concreto, el módulo xml de la librería estándar pone a disposiciónde los programadores diferentes analizadores sintácticos para leer yescribir información en formato XML Tres son los submódulos quepermiten interactuar con XML de formas diferentes. El primero de elloses xml.sax.handler que utiliza el módulo SAX para analizarsintácticamente ( parsear ). El segundo se denomina xml.dom.minidom que ofrece una implementación ligera para el modelo DOM. El últimode los tres es xml.etree.ElementTree y básicamente ofrece la mismafuncionalidad que xml.dom, solo que empleando una forma específicade Python para parsear el formato.La estructura de un documento XML puede ser muy compleja; sinembargo, para nuestros ejemplos prácticos definiremos un sencillodocumento y veremos cómo puede ser accedido empleando los tresmódulos diferentes que pone Python a nuestra disposición. El lectorinteresado en profundizar en todas y cada una de las opcionesexistentes de estos módulos, puede echar un vistazo a ladocumentación oficial de Python al respecto (ver referencias).Comenzamos definiendo un documento XML con el contenido querepresenta a la información de un varios álbumes musicales de unartista determinado: <albums><interprete>U2</interprete><titulo>Achtung Baby</titulo> <titulo>Zooropa</titulo><titulo>The Joshua Tree</titulo> <genero>rock</genero></albums> Una vez definida la estructura, podemos guardar el contenido en unnuevo fichero llamado albums.xml. Seguidamente, veremos cómo
    • 205. mostrar la información relativa a los diferentes títulos que contiene,empleando para ello los diferentes módulos de Python para análisis deXML. Comenzamos por ElementTree : >>> from xml.etree.ElementTree import parse>>> xml_doc = parse('albums.xml')>>> for ele in xml_doc.findall('titulo'):... print(ele.text)...Achtung BabyZooropaThe Joshua Tree La función parse() se encarga de cargar y la estructura del fichero enmemoria y prepararla para poder recorrerla y acceder a su información.El método findall() localiza todas las etiquetas que contiene el nombreindicado, y, finalmente, para acceder a la información de los títulos encuestión, utilizamos text que es un atributo que contiene el valor encuestión.De funcionalidad análoga al ejemplo anterior tenemos el siguientecódigo que emplea las herramientas de minidom para acceder eimprimir los títulos de nuestro fichero XML: >>> from xml.dom.minidom import parse, Node>>> xmltree = parse('albums.xml')>>> for nodo in xmltree.getElementsByTagName('titulo'):... for nodo_hijo in nodo.childNodes:... if nodo_hijo.nodeType == Node.TEXT_NODE:... print(nodo_hijo.data)...Achtung BabyZooropaThe Joshua Tree Observando el código anterior, descubriremos cómo pasando elparámetro titulo al al método getElementsByTagName() podemosdeclarar un bucle para recorrer uno a uno todos los nodos quecontiene que cumplen con la condición especificada. Además,anidamos otro bucle para recorrer todos los hijos de estos nodos.Dentro de este último bucle for debemos comprobar si el nodo es detipo texto, en cuyo caso accederemos al atributo data que contiene elvalor que buscamos. La constante TEXT_NODE representa el tipo textoque contiene una etiqueta determinada. En nuestro caso, correspondeal título en cuestión de cada disco.
    • 206. El último de nuestros ejemplos muestra cómo emplear el métodoSAX para recorrer nuestro fichero e imprimir el título de cada álbum.Para procesar el fichero vamos a crear una clase que se encargará deresponder a los diferentes eventos que se producen cuando se utilizael parser de SAX. Así pues, nuestra clase estará definida por el siguientecódigo: import xml.sax.handlerclass AlbumSaxHandler(xml.sax.handler.ContentHandler):def __init__(self):self.in_title = False def startElement(self, name, attributes):if name == 'titulo':self.in title = Truedef characters(self, data):if self.in_title:print(data) def endElement(self, name):if name == 'titulo':self.in_title = False La clase AlbumSaxHandler hereda de una clase incluida en elmódulo que Python incluye para trabajar con SAX. Los métodos startElement() y endElement() son los encargados de llevar a cabo unaserie de acciones cuando se detecta el principio y el fin,respectivamente, de cada etiqueta del fichero XML. Por otro lado, characters() comprobará si el atributo de clase in title es True, en cuyocaso imprimirá el contenido del elemento referenciado por <titulo>. Una vez que tenemos nuestra clase, solo nos queda invocar al parser, para ello necesitaremos el siguiente código: >>> import xml.sax>>> parser = xml.sax.make_parser()>>> sax_handler = AlbumSaxHandler()>>> parser.setContentHandler(sax_handler)>>> parser.parse('albums.xml')Achtung BabyZooropaThe Joshua Tree La elección de la técnica y módulo de Python para utilizar a la horade parsear y trabajar con XML's depende de varios factores. Dos de losmás importantes son el tamaño del fichero y el tipo de estructura quepresenta. Estos tienen gran impacto sobre la capacidad de
    • 207. procesamiento y memoria requerida para su análisis. JSON El formato JSON se ha convertido en uno de los más populares parala serialización e intercambio de datos, sobre todo en aplicaciones webque utilizan AJAX. Recordemos que esta técnica permite intercambiardatos entre el navegador cliente y el servidor sin necesidad de recargarla página. JSON son las siglas en inglés de JavaScript Object Notation yfue definido dentro de uno de los estándares (ECMA- 262) en los queestá basado el lenguaje JavaScript. Es en este lenguaje donde, a travésde una simple función ( eval()), podemos crear un objeto directamentea través de una cadena de texto en formato JSON. Este factor hacontribuido significativamente a que sean muchos los servicios webque utilizan JSON para intercambiar datos a través de AJAX, ya que elanálisis y procesamiento de datos es muy rápido. Incluso, son muchaslas que han sustituido el XML por el JSON a la hora de intercambiardatos entre cliente y servidor.Para estructurar la información que contiene el formato, se utilizanlas llaves ({}) y se definen pares de nombres y valor separados entreellos por dos puntos. De esta forma, un sencillo ejemplo en formatoJSON, para almacenar la información de un empleado, sería elsiguiente: {"apellidos": "Fernández Rojas", "nombre": "José Luis","departamento": "Finanzas", "ciudad": "Madrid"} JSON puede trabajar con varios tipos de datos como valores; enconcreto, admite cadenas de caracteres, números, booleanos, arrays y null. La mayoría de los modernos lenguajes de programación incluyenAPI y/o librerías que permiten trabajar con datos en formato JSON. EnPython disponemos de un módulo llamado json que forma parte de lalibrería estándar del lenguaje. Básicamente, este método cuenta condos funciones principales, una para codificar y otra para descodificar. Elobjetivo de ambas funciones es traducir entre una cadena de texto conformato JSON y objetos de Python. Obviamente, utilizando las
    • 208. funciones de ficheros de Python podemos leer y escribir ficheros quecontengan datos en este formato. No obstante, debemos tener encuenta que las funciones contenidas en json no permiten trabajardirectamente con ficheros, sino con cadenas de texto.Volviendo a nuestro ejemplo de fichero XML, los mismos datospueden ser codificados en formato JSON de la siguiente forma: {"albums": {"titulos": ["Achtung Baby", "Zooropa", "The JoshuaTree"], "genero": "Rock"}} La anterior cadena puede ser salvada en un fichero llamado albums.json, siendo este el que utilizaremos para nuestros posterioresejemplos.Para leer los datos de nuestro nuevo fichero, basta con ejecutar lassiguientes líneas de código: >>>>>>>>>>>>>>> import jsonfich = open('albums.json')line = fich.readline()fich.close()data = json.loads(line) La variable data contendrá un diccionario de Python que secorresponde con la estructura de nuestro fichero JSON. Así pues, paraobtener los títulos de nuestros álbumes basta con ejecutar: >>> data['albums'] ['titulos']['Achtung Baby', 'Zooropa', 'The Joshua Tree'] Realizar el paso inverso es sencillo, partiendo del nuevo diccionario data, podemos directamente invocar a dumps(), pasando comoargumento nuestro diccionario, de esta forma obtendremos unacadena de texto JSON, que posteriormente, puede ser guardada en unfichero. El siguiente código realizaría todas estas operaciones,incluyendo la modificación de la cadena origina, añadiendo un nuevotítulo: >>>>>>>>>>>>>>> data['albums'] ['titulos'].append("Rattle and Hum")cad = json.dumps(data)new_fich = open('albums_new.json',
    • 209. 'w')new_fich.writeline(cad)new_fich.close() Si abrimos el nuevo fichero albums_new.json y echamos un vistazo a
    • 210. su contenido, encontraremos el siguiente texto en formato JSON: {"albums": {"títulos": ["Achtung Baby", "Zooropa", "The JoshuaTree", "Rattle and Hum"], "genero": "Rock"}} Además del módulo json, también existen otros módulos paratrabajar con JSON. Uno de ellos es simplejson (ver referencias), que noforma parte de la librería estándar pero que puede ser instaladofácilmente. En el capítulo instalación y distribución de software nosocuparemos de cómo instalar módulos adicionales que pueden serutilizados por nuestros propios programas. YAML El último de los formatos de ficheros relacionados con laserialización de datos es YAML, acrónimo de YAML Ain't MarkupLanguage. Este formato fue diseñado con el objetivo de disponer de unformato de texto para serializar datos que fuese sencillo y fácil de leerpara las personas. A pesar de no ser tan popular como JSON y XML, esinteresante conocer este formato, ya que puede ser procesadorápidamente y resulta muy fácil de leer. El conocido framework web Ruby on Rails emplea el formato YAML para definir determinadosficheros de configuración, como es, por ejemplo, el que declara losdatos para la conexión a las diferentes bases de datos de cadaentorno.Básicamente, YAML utiliza pares clave: valor para almacenar yestructurar los datos. Además, la indentaclón es empleada para separardatos que pertenecen a otros de una jerarquía superior. Un ejemplo deuna cadena YAML podría ser la siguiente, donde se definen tresentornos diferentes y cada uno de ellos cuenta con una dirección IP ypuerto diferentes: desarrollo:IP: 127.0.0.1puerto: 8000staging:IP: 192.168.1.2puerto: 8002producción:IP: 192.168.1.2
    • 211. puerto: 8003 Python no dispone de ningún módulo en su librería estándar paratrabajar con ficheros YAML; sin embargo, existen varias librerías thirdparty para ello. Una de las más populares es PyYAML (ver referencias).Su instalación puede ser llevada a cabo directamente a través de sucódigo fuente, el cual puede ser descargado desde la correspondientepágina web (ver referencias). En cuanto descarguemos el fichero zip con el código fuente, pasaremos a descomprimirlo. Seguidamente,desde la línea de comandos y desde el directorio creado aldescomprimir, ejecutaremos el siguiente comando: python setup.py install En cuanto termine la ejecución del comando anterior, tendremosdisponible un nuevo módulo para Python llamado yaml. Esto implicaque, desde el intérprete de Python, podemos invocar al nuevo módulocon la siguiente sentencia: >>> import yaml Para la parte práctica partiremos de la cadena YAML que hemosdefinido previamente, creando un nuevo fichero denominado servidores.yml. De forma similar al módulo json, yaml cuenta con dosfunciones principales, una para leer una cadena de texto a través de unfichero y transformarla en un diccionario de Python, y otra que realizala operación Inversa, es decir, obtiene una cadena de texto a partir deun diccionario de Python. La primera de ellas se denomina load() y lasegunda dump(). Así pues, para leer nuestro fichero YAML, basta conejecutar la siguiente línea de código: >>> data = yaml.load(open('servidores.yml')) La variable data contiene ahora un diccionario donde las claves son desarrollo, staging y producción. Además, cada una de estas claves daacceso a otro diccionario cuyas claves son IP y puerto que da a su vezacceso a los valores de nuestro fichero. Dada esta estructura, para, porejemplo, acceder al puerto del entorno de producción, basta conejecutar la siguiente línea:
    • 212. >>> data['producción'] ['puerto']8003
    • 213. Por otro lado, la estructura puede ser modificada y crear con ella unnuevo fichero YAML. Supongamos que necesitamos añadir un nuevoentorno llamado pruebas, junto con los datos de un nuevo servidor. Enprimer lugar, crearíamos un nuevo diccionario y lo asignaríamos a data, tal y como muestra el código a continuación: >>> data['pruebas'] = {'IP': '192.168.2.8', 'puerto': 8004} Seguidamente, invocamos a la función dump pasando comoargumento nuestro diccionario original: >>> yaml.dump(data)'desarrollo: {IP: 192.168.2.8, puerto: 8004}\nproduccion: {IP:192.168.1.2, puerto: 8003}\npruebas: {IP: 192.168.1.8, puerto:8004}\n'\nstaging: {iP: 192.168.1.2, puerto: 8002}\n' Ahora podemos guardar nuestra cadena YAML en un nuevo fichero: >>> fich = open('new_servidores', 'w')>>> fich.write(yaml.dump(data))>>> fich.close()
    • 214. FICHEROS CSV El formato CSV (Comma Separated Values) es uno de los máscomunes y sencillos para almacenar una serle de valores como si deuna tabla se tratara. Cada fila se representa por una línea diferente,mientras que los valores que forman una columna aparecen separadospor un carácter concreto. El más común de los caracteres empleadospara esta separación es la coma, de ahí el nombre del formato. Sinembargo, es habitual encontrar otros caracteres como el signo deldólar ( $ ) o el punto y coma ( ; ). Gracias al formato CSV es posibleguardar una serle de datos, representados por una tabla, en un simplefichero de texto. Además, software para trabajar con hojas de cálculo,como, por ejemplo, Microsoft Excel, nos permiten importar y exportardatos en este formato.Dentro de su librería estándar, Python incorpora un móduloespecífico para trabajar con ficheros CSV, que nos permite, tanto leerdatos, como escribirlos. Son dos los métodos básicos que nosposibilitan realizar estar operaciones: reader() y writer(). El primero deellos sirve para leer los datos contenidos en un fichero CSV, mientrasque el segundo nos ayudará a la escritura.Supongamos que tenemos un sencillo fichero CSV, llamado empleados.csv, que contiene las siguientes líneas: Martínez,Juan,Administración,BarcelonaLópez,María,Finanzas.ValenciaRo dríguez,Manuel.Ventas,GranadaRojas,Ana,Dirección,Madrid Cada línea de nuestro fichero identifica a un empleado, donde, elprimer valor es el primer apellido, el segundo es el departamentodonde trabaja y por último, el tercer valor es la ciudad en la que seencuentra. Para leer este fichero y mostrar la información indicando elnombre de cada campo y su valor asociado, bastaría con ejecutar elsiguiente código: >>> import csv>>> with open('empleados.csv') as f:... reader = csv.reader(f)... for row in reader:
    • 215. ... print("Apellido: {O}; Nombre: {1}; Departamento: {2};Ciudad: {3}".format(row[0], row[1], row[2], row[3]))...Apellido: Martínez; Nombre: Juan; Departamento: Administración;Ciudad: BarcelonaApellido: López; Nombre: María; Departamento: Finanzas; Ciudad:ValenciaApellido: Rodríguez; Nombre: Manuel; Departamento: Ventas; Ciudad:GranadaApellido: Rojas; Nombre: Ana; Departamento: Dirección; Ciudad:Madrid Efectivamente, el método reader() es de tipo iterable y nos daacceso a todas las filas del fichero, es por ello, que en nuestro ejemplo,utilizamos un bucle for para iterar sobre el objeto devuelto. Por otrolado, cada elemento iterable es una lista que contiene tantoselementos como valores separados por el carácter de separación tengacada línea de nuestro fichero. En caso de que empleemos un carácterde separación diferente de la coma, deberemos indicarlo a través delparámetro delimiter. Si cambiamos nuestro fichero empleados.csv yreemplazamos la coma por, por ejemplo, el símbolo del dólar,tendríamos que emplear delimiter de la siguiente forma: >>> reader = csv.reader(f, delimiter='$') La operación de escritura en ficheros de tipo CSV se lleva a cabo através de los métodos writer() y writerow(). Este último escribedirectamente una fila en el fichero y como argumento debemos pasaruna lista donde cada elemento representa cada valor de la columnacorrespondiente a cada fila. Volvamos a tomar un par de líneas denuestro ejemplo anterior y creemos un nuevo fichero a partir de ellas: >>> fich = open('nuevo.csv', 'w')>>> fich_w = csv.writer(fich, delimiter='$')>>> empleados = [['Martínez', 'Juan', 'Administración','Barcelona'], ['López', 'María', 'Finanzas', 'Valencia']]>>> for empleado in empleados:... fich_w.writerow(empleado)...>>> fich.close() En esta ocasión hemos creado un nuevo fichero (nuevo.csv) quecontiene solo los datos de un par de empleados; además, el carácterde separación es el símbolo del dólar. Si abrimos el fichero reciéncreado por código, veremos que tiene el siguiente contenido:
    • 216. Martinez$Juan$Administracion$BarcelonaLopez$Maria$Finanzas$Valenci a Finalizamos en este punto nuestro recorrido por el tratamiento deficheros CSV con Python. El lector interesado en descubrir más sobre elmódulo csv puede echar un vistazo a la página web oficial del mismo(ver referencias).
    • 217. ANALIZADOR DE FICHEROS DE CONFIGURACIÓN En los sistemas Windows es muy popular el formato INI, quepermite almacenar una serie de datos en formato propiedad = valor. Además, se pueden agrupar los datos por secciones, siendorepresentada cada una de ellas por un nombre entre corchetes ( [] ). Elregistro de Windows emplea un formato ligeramente diferente al INI,pero inspirado claramente en el mismo.Estos tipos de ficheros no son exclusivos de Windows, ya que sonmuchas las aplicaciones que las utilizan, sobre todo, para guardardatos relativos a cierta configuración. Por ejemplo, supongamos quetenemos que guardar la información de una serie de servidores,incluyendo el usuario y puerto por defecto empleados para laconexión. Podríamos utilizar una sección por servidor y dospropiedades diferentes, una para el usuario y otra para el puerto. Elfichero INI, por ejemplo, conf.ini, sería como sigue: [server1.dominio1]user = firstport = 22 [server2,dominio2]user = secondport = 88 En lugar de guardar datos de configuración en una base de datos ode tenerlos directamente en el código (hardcoded), los ficheros INI sonmuy prácticos para almacenar y actualizar valores sin necesidad decambiar el código fuente o de acceder a una base de datos.Para el manejo de estos ficheros INI, Python pone a nuestradisposición un módulo llamado configparser, que forma parte de sulibrería estándar. Este módulo incorpora una clase base denominada ConfigParser que nos permite tanto leer como crear ficheros delmencionado tipo.Nuestra práctica va a comenzar importando el módulo y cargandoel fichero que hemos creado previamente: >>> from configparser import ConfigParser>>> config = ConfigParser()
    • 218. >>> config.read("config.ini") Ahora podemos, por ejemplo, obtener una lista con todas lassecciones de nuestro fichero: >>> config.sections()['server1.dominio1', 'server2.dominio2'] Una vez que tenemos nuestra instancia config de la clase ConfigParser, podemos acceder a los valores de cada sección. Para ello,bastará con tener en cuenta que cada nombre de sección es la clave deun diccionario que, a su vez, nos proporciona acceso a otro diccionariodonde cada clave es el valor de la propiedad. Al acceder a estasegunda clave dispondremos del valor correspondiente de lacorrespondiente propiedad de la sección. De esta forma, para accederal valor port de la segunda sección de nuestro fichero de ejemplo,bastará con ejecutar la siguiente sentencia: >>> config = ['server2.dominio2']['port']88 Análogamente, si deseamos obtener el valor de la propiedad user de la primera sección del fichero, emplearíamos el siguiente código: >>> config['server1.dominio1'] ['user']first Además de leer las propiedades, obviamente, también podemosescribir otras nuevas. Supongamos que tenemos que añadir una nuevasección con una serie de propiedades y valores diferentes. El primerpaso sería crear un nuevo diccionario vacío y posteriormente añadirlelas nuevas propiedades y valores, tal y como muestran las siguienteslíneas: >>> config['server3.dominio3'] = {}>>> config['server3.dominio3'] ['protocol'] = ’ssh'>>> config['server3.dominio3'] ['timeout'] = '30' Seguidamente, deberemos abrir el fichero original y escribir en él através del método write() que pertenece a la clase ConfigParser. >>> with open ('config.ini', 'w') as fich:... config.write(fich)...
    • 219. Si ahora abrimos nuestro fichero INI con cualquier editor de textos,comprobaremos cómo contiene las siguientes líneas: [server1.dominio1]ser = firstport = 22 [server2.dominio2]user = secondport = 88 [server3.dominio3]protocol = sshtimeout = 30 Por otro lado, y dado que cada sección está representada por undiccionario, también es posible obtener un listado con las propiedadesque tiene una determinada sección. El siguiente código nos muestralas propiedades para la sección que hemos creado anteriormente: >>> for propiedad in config['server3.dominio3']:... print(propiedad)...protocoltimeout Aunque hemos trabajado con una estructura básica de ficheros INI,estos pueden contener otra ligeramente diferente. Para averiguarcómo está definida esta estructura, aconsejamos visitar la página oficialde la documentación de Python al respecto (ver referencias). COMPRESIÓN Y DESCOMPRESIÓN DE FICHEROS Python nos ofrece la opción de trabajar con distintos formatos decompresión para archivos. Es decir, es posible crear ficheroscomprimidos que contengan uno o varios ficheros a su vez, ydescomprimir un archivo para extraer su contenido. En concreto, lalibrería estándar de Python cuenta con cuatro módulos diferentes quepermiten trabajar con los formatos estándar de compresión ZIP (.zip), tarball (.tar), gzip (.gz) y bunzip (.bz2). A continuación, veremos unaserie de ejemplos para comprimir y descomprimir ficheros utilizandocada uno de estos formatos.
    • 220. Formato ZIP El nombre del módulo de la librería estándar que nos permitetrabajar con este formato se llama zipfile. En concreto, podemos crear,leer, escribir, añadir y listar el contenido de un fichero con esteformato. Respecto a la encriptación, aún no es posible crear un ficheroencriptado y, aunque la desencriptación es posible, no esrecomendable, dado que el proceso requiere de mucho tiempo deprocesamiento. El módulo zipfile también soporta la creación deficheros ZIP de tamaño superior a 4 GB.Dentro del mencionado módulo, la principal clase es Zipfile yencapsula la mayoría de operaciones que pueden ser llevadas a cabosobre los ficheros ZIP. Adicionalmente, existe otra clase llamada PyZipFile, muy similar a la anteriormente mencionada, pero con unaserie de mejoras sobre ella. Por otro lado, cuando invocamos a losmétodos de Zipfile, que nos dan información sobre el contenido de unfichero comprimido, estos nos devuelven como resultado un objeto detipo ZipInfo, el cual encapsula la respuesta ofrecida por los métodos encuestión.Comenzaremos nuestra práctica creando un fichero ZIP quecontendrá una serie de ficheros. Podemos elegir varios ficheros que yatengamos en nuestro disco, tanto binarios como de texto. El siguientecódigo muestra cómo crear el fichero first.zip con tres ficherosdiferentes: >>> import zipfile>>> with zipfile.ZipFile('first.zip', 'w') as fzip:... fzip.write('empleados.csv')... fzip.write('fich.txt')fzip.write('test.dat') Si ahora abrimos el recién creado fichero first.zip con unaherramienta como WinZip, Winrar o similar, comprobaremos cómo,efectivamente el fichero comprimido contiene los tres ficheros quehemos elegido y comprimido desde nuestro código Python. Además,dado que es posible, listar el contenido desde Python, será estenuestro siguiente paso, para el que emplearemos el siguiente código: >>> fzip = zipfile.ZipFile('first.zip')>>> fzip.printdir()
    • 221. Filenameempleados.csvfich.txttest.dat Modified2012-01-07 21:47:142012-02-07 22:32:022012-03-07 20:24:54 Size13391 En este último ejemplo de código, el método Zipfile solo recibe unúnico argumento, el nombre del fichero, ya que, por defecto, el ficherose abre en modo de solo lectura. Sin embargo, cuando hemosinvocado al mismo método para crear el fichero, el valor w ha sidopasado como parámetro para indicar que el fichero debe ser creado.Este modo es similar al que utiliza la función open(), tal y como hemosaprendido previamente.Relacionado con el método printdir(), encontramos a namelist(), elcual nos devuelve una lista que contiene un elemento por ficherocontenido en el ZIP original. Podemos invocarlo directamente: >>> fzip.namelist()['empleados.csv', 'fich.txt', 'test.dat'] La operación inversa a la compresión es la descompresión y, paraello, la clase Zipfile cuenta con los métodos extract() y extractall(). Elprimero de ellos extrae solo uno de los ficheros que se encuentrancomprimidos, mientras que el segundo se encarga de extraer todo elcontenido del fichero ZIP. Por ejemplo, si ejecutamos el siguientecódigo, comprobaremos cómo se crean los tres ficheros de los queconsta el fichero ZIP que hemos creado previamente: >>> fzip.extractall()(path="..") El paramétro path sirve para indicar en qué directorio del sistemade ficheros queremos que sean descomprimidos los ficheros. Ennuestro ejemplo, hemos elegido el directorio padre desde el que seestá ejecutando la consola del intérprete de Python. Adicionalmente, siel fichero hubiera sido creado con una password, podemos indicar lamisma a través del parámetro pwd. Como hemos mencionado previamente, una instancia de la clase ZipInfo es devuelta por los métodos que nos ayudan a obtenerinformación del fichero comprimido; en concreto, dichos métodos son infolist() y getinfo(). Ambos devuelven el mismo tipo de información, ladiferencia
    • 222. reside en que el primer método lo hace sobre todos y cadauno de los ficheros que forman parte del ZIP y el segundo necesita
    • 223. recibir como parámetro uno de los ficheros para devolver lainformación relativa al mismo. Entre la información que puede serobtenida desde la clase ZipInfo, destacamos el nombre de cada fichero,la fecha y hora de la última modificación, el tipo de compresión y eltamaño que ocupa cada fichero antes y después de la compresión.Sirvan como ejemplo las siguientes líneas de código que nos ofreceninformación relativa al nombre, fecha de última modificación y tamañode cada fichero comprimido: >>> info = fzip.infolist()>>> for arch in info:... print(arch.filename, arch.date_time,arch.compress_size)...empleados.csv (2012, 2, 7, 21, 47, 14) 133fich.txt (2012, 2, 8, 18, 48, 26) 9test.dat (2012, 2, 8, 18, 51, 6) 1 Respecto a la fecha y hora, en realidad, el valor devuelto es unatupla de seis elementos que indican año, mes, día del mes, hora,minutos y segundos.Para más información sobre otros métodos ofrecidos por el módulo zipfile, aconsejamos visitar la documentación oficial del mismo (verreferencias). Formato gzip El módulo gzip de Python permite comprimir y descomprimirficheros tal y como lo hacen las herramientas gzip y gunzip. Ambosprogramas pertenecen a GNU y son muy populares en el mundoUNIX/Linux. De hecho, la gran mayoría de los sistemas operativosbasados en uno u otro incluyen ambas herramientas.Técnicamente, el módulo gzip es una simple interfaz sobre lasmencionadas herramientas de GNU, ya que, en realidad, el módulo zlib es el que proporciona la compresión real de datos.La principal clase de gzip es GzipFile y simula la mayoría de losmétodos disponibles para el tipo de dato fichero de Python. De estaforma, los mismos valores para abrir y crear un fichero que emplea lafunción open(), son aplicables al constructor de la mencionada clase.
    • 224. Para crear un fichero comprimido a partir de un fichero original,basta con abrir el fichero original, invocar al método open() del módulo gzip, y posteriormente invocar al método writelines(), tal y comomuestra el siguiente código: >>> import gzip>>> with open('fich.dat', ' rb') as f_original:with gzip.open('fich.txt.gz', ’wb') as fich:... fich.writelines(f_original)... Es importante tener en cuenta que el módulo gzip solo trabaja condatos en binario, es decir, que las cadenas de texto no estánsoportadas a través de los métodos de lectura y escritura. Esta es larazón por la cual nuestro ejemplo ha utilizado el valor b para marcar eltipo de modo.Si necesitamos crear un fichero gz a partir de una serie de datosbinarios, también podemos hacerlo, en este caso, a través del método write(): >>> datos_binarios = b"Este string es binario">>> with gzip.open('nuevo.gz', 'wb') as fich:... fich.write(datos binarios)... Por otro lado, el módulo gzip de Python también nos ofrece laopción de comprimir cadenas de texto, sin necesidad de trabajar conficheros. De este modo, el siguiente código nos permitiría disponer deuna cadena comprimida: >>> datos_binarios = b"Comprimiendo cadena">>> comprimidos = gzip.compress(datos_binarios) El método que realiza la operación contraria a la compresión sedenomina decompress() y funciona de forma inversa a como lo hace compress(). Formato bz2 Para trabajar con ficheros en formato bunzip, Python nos ofrece elmódulo llamado bz¡p2, que, básicamente, cuenta con la clase BZ2File.
    • 225. Esta representa a un fichero con este tipo de compresión y dispone demétodos para crear, leer, comprimir y descomprimir datos. Desde elpunto de vista técnico, este módulo es una interfaz a la librería decompresión bz2. A pesar de que este formato no es tan popular comoel tarball y el ZIP, cada vez son más los que lo emplean, gracias a quees capaz de ajustar bastante el tamaño final de los ficheroscomprimidos.La compresión y descompresión de datos se puede realizarfácilmente a través de los métodos compress() y decompress(), respectivamente. Por ejemplo, para comprimir una cadena binaria: >>> import bz2>>> cad = b"Cadena binaria">>> cad_comprimida = bz2.compress(cad) La operación inversa, es decir, la descompresión, se realizaría deforma similar: >>> cad_descomprimida = bz2.decompress(cad_comprimida) De forma similar a la que hemos visto previamente en los ejemplosde gzip, podemos crear un fichero comprimido en formato bz2, a partirde uno original sin compresión. Para ello, observemos el siguientecódigo: >>> with open('fich.dat', 'rb') as f_original:... with bz2.BZ2File('fich.dat.bz2', 'wb') as fich:... fich.writelines(f_original) Nótese que, a diferencia de gzip, el módulo bz2 dispone delmétodo open() a través de la clase BZ2File. Formato tarball El formato tar es uno de los más populares para compresión dearchivos en sistemas UNIX. En la actualidad, la gran mayoría de lasdistribuciones de Linux Incorporan por defecto utilidades para trabajarcon este formato. El mismo hecho puede ser aplicado para Mac OS X,mientras que en Windows, el formato más popular sigue siendo el ZIP.Es común encontrar ficheros tar a los que, además, se les ha
    • 226. añadido compresión empleando gzip o bz2. En realidad, tar nocomprime por sí mismo, simplemente agrupa ficheros. Los ficheros queutilizan tar junto a gzip o bz2 son llamados tarball y pueden tener laextensión tar.gz o tar.bz2. El módulo de Python que permite trabajar con tarballs se llama tarfile y permite comprimir, descomprimir y listar el contenido de estetipo de ficheros. La clase base del módulo se denomina Tarfile yfunciona deforma similar a Zipfile. Para crear un fichero tarball a partir de una serie de ficheros,podemos emplear el siguiente conjunto de sentencias: >>>>>>>>>>>>>>>>>> import tarfileftar = tarfile.open('first.tar.gz', 'w:gz')ftar.add('empleados.csv')ftar.add('fich.txt')ftar.add('test.dat')ftar.close( ) Si abrimos el nuevo fichero first.tar.gz, veremos que efectivamentecontiene los ficheros que hemos añadido. Por ejemplo, paracomprobar que la operación se ha realizado correctamente, en lainterfaz de comandos de Linux podemos ejecutar: $ tar -zvtf first.tar.gz Como el lector habrá podido observar, el método open() ha pasadocomo parámetro, además del nombre del nuevo fichero, el valor w:gz, el cual indica que el fichero debe crearse utilizando gzip paracomprimir. Si solo indicáramos el valor w, simplemente se crearía unfichero tar, pero sin compresión. Para evitar errores, es convenienteque los ficheros que no utilicen compresión lleven la extensión tar enlugar de tar.gz. Al igual que ZipFile, la clase TarFile cuenta con los métodos extract() y extractall() que nos permite extraer todo el contenido del tarball o deun fichero concreto que forma parte del mismo. Si deseamos extraertodos los ficheros basta con ejecutar: >>> ftar = tarfile.open('first.tar.gz', 'r:gz')>>> ftar.extractall() En caso de necesitar listar el contenido del tarball, basta con invocaral método list():
    • 227. >>> ftar.list()rw-rw-rw- 0/0 133 2012-01-07 21:47:14rw-rw-rw- 0/0 9 2012-02-07 22:32:0rw-rw-rw- 0/0 1 2012-03-07 20:24:54 empleados.csvfich.txttest.dat Efectivamente, la primera columna de la salida de list() muestrainformación relativa a los permisos que existen sobre el fichero. Esta esuna de las habilidades de los tarballs, ya que permiten agrupar ycomprimir una serie de ficheros manteniendo la jerarquía de permisosque existe sobre ellos.Relacionado con el método anteriormente comentado, tambiéndisponemos de otro denominado getnames(), que nos devuelve unalista con el nombre de los ficheros que pertenecen al tarball. Siguiendocon nuestro ejemplo, podemos invocarlo tal y como muestra lasiguiente línea de código: >>> ftar.getnames()['empleados.csv', 'fich.txt', 'test.dat'] Hasta aquí todo lo relativo a los ficheros y a su tratamiento. Elsiguiente capítulo lo dedicaremos a otro aspecto relacionado con elalmacenamiento de datos: las bases de datos.
    • 228. BASES DE DATOS INTRODUCCIÓN No cabe duda de que las bases de datos juegan un papel muyimportante en el mundo del software. Muchas de las aplicacionescorporativas que se ejecutan diariamente en todo el mundo seríaninconcebibles sin el uso de base de datos. Incluso los servicios web queusamos a diario cuentan con una base de datos para almacenar lainformación que utilizan.Tradicionalmente, las bases de datos han sido identificadasdirectamente con un tipo específico, las llamadas bases de datosrelacionales o RDBMS ( Relational Database Management System). Enrealidad, estas denominaciones se refieren al motor empleado paragestionar la obtención y almacenamiento de información en ficherosfísicos en disco duro. Sin embargo, existen varios tipos de bases dedatos, además de las relacionales, como son, por ejemplo, lasorientadas a objetos y las NoSQL. Estas últimas han ganado granpopularidad en los últimos años y motores como Cassandra y MongoDB son utilizados por grandes empresas para manejar grancantidad de información.En este capítulo nos centraremos en los dos tipos de bases dedatos que más se utilizan en la actualidad: las relacionales y las NoSQL.Descubriremos cómo interactuar, desde Python, con varios de losmotores más populares de cada uno de estos tipos. En concreto,trabajaremos con MySQL, PostgreSQL, Oracle y SQLite3, en lo que a losrelacionales se refiere, y con Cassandra, Redls y MongoDB en el casode los NoSQL.Entre las principales operaciones, aprenderemos a conectarnos ydesconectarnos de una base de datos, realizar operaciones deconsultar e inserción y a mostrar resultados obtenidos comoconsecuencia de una operación de consulta.Por otro lado, dentro de la categoría de las bases de datosrelacionales, existen una serie de componentes a los que se les conocecomo ORM (Object-Relational Mapping), que, básicamente, permiten
    • 229. interactuar con una base de datos con independencia de cuál sea sumotor. Esto permite desarrollar código portable, ya que, en lugar deutilizar el propio lenguaje SQL, se utilizan una serie de objetos ymétodos que actúan como wrappers. Además, los ORM permitenestablecer una relación directa entre objetos de nuestro programa ytablas de una base de datos. Ello se logra a través de la técnicaconocida como mapping. Incluso, las relaciones establecidas entrediferentes clases se corresponden a relaciones entre tablas a través de,por ejemplo, claves foráneas. En Python, dos son los módulos máspopulares que se emplean como ORM: SQLObject y SQLAchemy. Decómo trabajar con ambos nos ocuparemos también en este capítulo.Suponemos que el lector está familiarizado con el lenguaje SQL y, almenos, tiene la experiencia básica de trabajar con bases de datosrelacionales. Igual suposición haremos para las bases de datos NoSQL.Ello se debe a que no explicaremos fundamentos de ninguno deambos tipos de bases de datos, sino que nos centraremos en cómointeractuar con ellas desde Python. Además, para ejecutar y comprobarlos ejemplos de código de este capítulo, recomendamos quepreviamente se lleve a cabo la instalación, en la máquina local, de cadamotor de base de datos con los que vamos a trabajar. Si esto no esposible, otra opción podría ser la de acceder a una máquina quecuenta previamente con cada motor de base de datos previamenteinstalado y configurado. Para consultar información al respecto,aconsejamos echar un vistazo a los enlaces referenciados en lasreferencias para el presente capítulo.
    • 230. RELACIONALES Previamente a descubrir cómo interactuar con los gestores de basesde datos relacionales anteriormente mencionados, describiremos unaserie de datos que pueden ser almacenados en una tabla y en unabase de datos manejada por cualquiera de estos gestores.Posteriormente, utilizaremos el ejemplo de tabla descrito en esteapartado como base para trabajar e interactuar con Python.Para nuestro ejemplo utilizaremos una entidad que nos permitarepresentar una serie de países que almacenen información sobre supoblación, en millones de habitantes, el continente al que pertenece yla moneda que se utiliza en el mismo. Así pues, necesitaremos crearuna tabla llamada países que cuenta con cuatro atributos principales (nombre, habitantes, moneda y continente ), más uno adicional (id) quees un número y que nos servirá para identificar de forma unívoca acada país. De esta forma, nuestra tabla contendrá varios países, tal ycomo representamos a continuación: ID País Continente Habitantes Moneda 1 España Europa 47 Euro 2 Alemania Europa 82 Euro 3 Canadá América 34 Dólar canadiense 4 China Asia 1340 Yuan 5 Brasil América 204 Real
    • 231. Tabla 7-1. Tabla con información sobre países Antes de continuar, es conveniente crear una nueva base de datos ala que llamaremos prueba. Seguidamente, crearemos una tabla, en lanueva base de datos, utilizando para ello la siguiente sentencia SQL: CREATE TABLE países('id' INT(11) NOT NULL AUTOINCREMENT,'nombre' VARCHAR(1OO) NOT NULL,'continente' VARCHAR(20) NOT NULL,'habitantes' INT(11) NOT NULL,
    • 232. 'moneda' VARCHAR(30) NOT NULL,PRIMARY KEY ('id’)) Debemos tener en cuenta que la anterior sentencia SQL puedevariar en función del gestor de base de datos. Basta con comprobar lasintaxis para asegurarnos de que el campo 'id' será la clave de nuestratabla y de que los tipos de cada uno de los otros campos coincidencon los indicados por la nuestra sentencia SQL.Ahora que ya tenemos tanto la nueva base de datos, como la tablade ejemplo de países, podemos pasar a los siguientes apartadosdonde aprenderemos a conectarnos a la base de datos, insertarregistros en la nueva tabla y a extraer datos de la misma. MySQL La interacción de Python con MySQL puede ser llevada a cabo através de un módulo llamado MySQLdb. Dado que este no forma partede la librería estándar, necesitaremos instalarlo en nuestro sistema.Esta instalación puede ser llevada a cabo a través del gestor pip , delque nos ocuparemos en el capítulo 9 Instalación y distribución depaquetes. De momento, daremos por supuesto que tenemos instaladoeste gestor de paquetes para Python. Así pues, para la instalación de MySQLdb, bastará con ejecutar el siguiente comando desde unaterminal: pip install MySQLdb Una vez que tengamos nuestro módulo instalado, podemosinvocarlo como si de un módulo de la librería estándar se tratara. Deesta forma, dentro del intérprete de Python ejecutaremos: >>> import MySQLdb Ya estamos listos para ¡nteractuar con el gestor de base de datos.Nuestra primera operación será obtener una conexión, para ellolanzaremos la siguiente sentencia: >>> con = MySQLdb.connect('localhost', 'usuario', 'password','prueba')
    • 233. La función connect() recibe como parámetros el nombre de lamáquina que está ejecutando el gestor de base de datos, el usuariopara conectarnos a nuestra base de datos, su contraseña y el nombrede la base de datos en cuestión. Si el servidor de base de datos correen nuestra máquina local, el valor localhost debe ser el indicado. Porotro lado, usuario es el valor de ejemplo que hemos elegido para elnombre del usuario en cuestión y password el valor para su contraseña.Obviamente, debemos sustituir estos valores por los reales de nuestrabase de datos. Como respuesta a la invocación de connect() obtendremos un objeto que representa una conexión a la base dedatos, a partir de la cual podremos realizar diversas operaciones.Tanto para ejecutar una sentencia SQL para actualizar, Insertar oborrar datos, como para consultar datos, necesitaremos un objetoespecífico llamado cursor. Así pues, antes de realizar cualquiera deestas operaciones, procedemos a la obtención del mismo: >>> cursor = con.cursor() Ahora que ya disponemos del objeto cursor, podemos, por ejemplo,insertar un registro: >>> sql = 'INSERT INTO(nombre, continente, habitantes, moneda)VALUES ('España', 'Europa', 47, 'Euro')'>>> cursor.execute(sql) Los demás países de nuestro ejemplo pueden ser insertados de lamisma forma, cambiando, lógicamente, la sentencia SQL. Elprocedimiento para ejecutar sentencias SQL de tipo UPDATE o DELETE es similar, basta con escribir la sentencia en cuestión e invocar a execute(). Sin embargo, si empleamos una sentencia de tipo SELECT deberemos manejar el resultado devuelto a través de la llamada amétodos adicionales. Supongamos, por ejemplo, que necesitamosobtener todos los países de Europa y mostrar toda la informaciónrelativa a los mismos. El código necesario para ello sería el siguiente: >>>>>>>>>>>>......(1,(2, sql = 'SELECT * FROM paises WHERE continente='Europa''cursor.execute(sql)rows = cursor.fetchall()for row in
    • 234. rows:print(row) 'España', 'Europa', 47, 'Euro')'Alemania', 'Europa, 82, 'Euro')
    • 235. El método fetchall() extrae todos los registros que cumplen con lacondición especificada, si asignamos su resultado a la variable rows, podemos iterar y obtener la información de todos los registros. Enrealidad, obtendremos una tupla donde cada uno de los valores secorresponden con el de los campos de la tabla. Así pues, siquisiéramos obtener solamente el valor correspondiente a los millonesde habitantes de cada país, bastaría sustituir la sentencia print(row) denuestro bucle for, por la siguiente: print(row[3]) Dado que el número índice para acceder a cada campo no es muyintuitivo, es posible emplear un tipo especial de cursor que nospermitirá utilizar como índice el nombre de cada campo de losregistros de la tabla. Para ello, tendremos que emplear el método cursor pasando un parámetro específico, tal y como muestra lasiguiente sentencia: >>> cursor = con.cursor(MySQLdb.cursors.DictCursor) Seguidamente, invocaremos al método fetchall(), tal y como hemoshecho previamente y cambiaremos nuestro bucle for por el siguiente,donde solo vamos a imprimir el nombre de país y su moneda: >>> for row in rows:... print('País: {o}. Moneda: {1}'.format(row['nombre'],row['moneda']))...País: España. Moneda: EuroPaís: Alemania. Moneda: Euro Otro método interesante que podemos invocar desde el objeto cursor es fetchone(), el cual solo obtiene un registro. Este método esespecialmente útil cuando necesitamos obtener la información relativaa un único registro. Por ejemplo, supongamos que debemos leer lamoneda de China. Dado que solo puede haber un país con esenombre, ejecutaríamos: >>>>>>>>>>>>(4, sql = 'SELECT * FROM países WHERE nombre='China''cursor = con.cursor()cursor.execute(sql)cursor.fetchone()'China', 'Asia', 1340, 'Yuan')
    • 236. Si el método fetchone() es lanzado sobre un cursor que devuelvemás de un resultado, solo el primero será obtenido por este método,quedando el resto de registros inaccesibles a través del cursor encuestión.Las transacciones también pueden ser manejadas por el módulo MySQLdb, siempre y cuando nuestro gestor de MySQL estéconfigurado para este propósito. Las operaciones básicas sobretransacciones que puede lanzar el módulo en cuestión, son commit y rollback. La primera de ellas realizaría las operaciones que forman latransacción, mientras que la segunda desharía todas si se producealgún error. Habitualmente, se suele utilizar un bloque try/except paraejecutar transacciones, tal y como muestra el siguiente ejemplo: >> try:... cursor.execute(sql)... cursor.commit()... except MySQLdb.Error:... con.rollback()... Cuando una sentencia SQL en la que solo cambian los valores seejecuta repetidamente, por cuestiones de eficiencia, es convenienteutilizar los llamados prepared statements. Un típico ejemplo es cuandodebemos lanzar varias sentencias SELECT donde solo cambia el valorde un cierto campo del WHERE. Eso es bastante habitual enaplicaciones web, donde diferentes usuarios simultáneamente accedena una determinada página que lanza una consulta. Además, en estetipo de aplicaciones los prepared statement evitan un posible ataquepor inyección SQL. En la práctica, basta con parametrizar los valoresque cambian en la sentencia SQL y pasarlos como un argumentoadicional. En el caso de MySQLdb podemos hacerlo utilizando unatupla con los valores en cuestión, pasado como segundo parámetrodel método execute(). El siguiente ejemplo de código muestra cómoemplear un prepared statement para realizar una consulta donde solocambia el continente: >>> sql = 'SELECT moneda, nombre FROM países WHERE continente=%s'>>> cursor.execute(sql, ('Europa'))>>> cursor.execute(sql, ('Asia')) No olvidemos que es importante cerrar la conexión a la base dedatos en cuanto finalicemos nuestro trabajo con ella. En caso contrario,
    • 237. estaremos consumiendo recursos innecesariamente y el rendimientode la aplicación bajará considerablemente. Para cerrar una conexiónque previamente tenemos abierta, basta con invocar al método close(), en nuestro ejemplo ejecutaríamos la siguiente sentencia: >>> con.close() El módulo MySQLdb incluye otras clases y métodos que tambiénpueden ser utilizadas para interactuar con MySQL. Una vez que hemosaprendido los fundamentos de utilización de ese módulo, dejamos allector como ejercicio que eche un vistazo a la documentación de estemódulo para complementar lo expuesto en este apartado. PostgreSQL El módulo más popular para trabajar con bases de datosPostgreSQL es psycopg2 y, al igual que en el caso de MySQLdb, este noforma parte de la librería estándar de Python, por lo que hay queinstalarlo para poder ser utilizado. Contando con que tenemos pip instalado, bastaría con ejecutar desde la línea de comandos: pip install psycopg2 En cuanto lo tengamos instalado, podremos cargarlo desde elintérprete de Python: >>> import psycopg2 La conexión a una base de datos es sencilla y similar a como hemosvisto previamente para MySQLdb, basta con invocar al método connect() pasando como parámetros de conexión el nombre delservidor donde se está ejecutando la base de datos, el nombre de labase de datos en cuestión, el usuario y su contraseña. Como ejemplo,echemos un vistazo a la siguiente sentencia: >>> con = psycopg2.connect('dbname=paises user=usuariopassword=password host=localhost')
    • 238. Para lanzar una sentencia SQL, también necesitamos contar con elobjeto cursor, así pues, obtendremos una instancia ejecutando:
    • 239. >>> cursor = con.cursor() El método execute() es el encargado de lanzar las sentencias SQL,sirva como ejemplo una actualización del número de habitantes deBrasil: >>> sql = 'UPDATE países SET habitantes=205 WHERE nombre='Brasil''>>> cursor.execute(sql) Para leer la información obtenida a través de sentencias SELECT, contamos con dos métodos principales homónimos a los de MySQLdb:fetchone() y fetchall(). Su funcionamiento es indéntico al mencionadomódulo de MySQL. Adicionalmente, el método fetchmany() puede serutilizado para obtener un determinado número de filas, basta conpasar como argumento el número de las que necesitamos, tal y comomuestra el siguiente ejemplo: >>> sql = 'SELECT nombre FROM países WHERE habitantes > 80'>>> cursor.execute(sql)>>> cursor.fetchmany(2)('Alemania, 'Europa', 87, 'Euro')'('China', 'Asia', 1340, ’Yuan)' Otros métodos comunes a MySQLdb que también existen en psycopg2 son close(), commit() y rollback(), teniendo estos análogafuncionalidad. Además, este módulo para interactuar con PostgreSQLofrece la opción de trabajar con prepared statement de la misma formaque MySQLdb, es decir, parametrlzando con una tupla los valores de lasentencia SQLEl lector interesado puede ampliar su conocimiento sobre psycopg2 consultando la documentación oficial (ver referencias) que puede serencontrada en el sitio web del módulo. Oracle En el mundo empresarial Oracle es uno de los gestores de bases dedatos relacionales más utilizados. Este gestor es conocido por sucapacidad de procesamiento, en especial cuando trata con elevadosconjuntos de datos. A diferencia de MySQL y PostgreSQL, Oracle no es open source y debe ser utilizado bajo previo pago de la
    • 240. correspondiente licencia de software.Desde Python también podemos trabajar con bases de datosgestionadas por Oracle, para ello contamos con el módulo llamado cxOracle. Dado que este no existe en la librería estándar, también debeser Instalado manualmente. Esta operación puede ser realizada, comohemos visto en casos anteriores, a través del gestor de paquetes pip: pip install cx_Oracle Para importar el módulo desde el intérprete o desde un script,escribiremos: import cx_Oracle La conexión a la base de datos se realiza a través de una cadena detexto con los datos necesarios para la conexión. Esta cadena debe serpasada a la función connect(), tal y como muestra el siguiente ejemplo: >>> cad_con = 'usuario/password@localhost/paises'>>> con = cx_Oracle.connect(cad_con) A partir de que obtengamos nuestro objeto de conexión y, deforma similar a como hemos aprendido en MySQLdb y psycopg2, necesitamos un cursor sobre el que ejecutar nuestras sentencias SQL. Elmétodo para obtener el cursor es similar al de los módulosanteriormente mencionados y el código para ello sería el siguiente: >>> cursor = con.cursor() Para obtener una lista de tuplas donde cada elemento representaun registro de la tabla, contamos con el método fetchAII(), el cualpuede ser invocado directamente sobre el cursor. Por otro lado, si solonecesitamos un número determinado de registros, el método fetchmany() podrá ayudarnos. Este método debe pasar comoparámetro el número de filas que deseamos obtener; para ello,fijaremos el valor de numRows, tal y como muestra el siguienteejemplo: >>> rows = cursor.fetchmany(numRows=3)
    • 241. A la hora de utilizar prepared statements, el módulo cxOracle lohace de forma diferente a MySQLdb y psycopg2, ya que emplea unmétodo llamado prepare() que recibe la consulta y se vale de execute()
    • 242. para pasar los parámetros necesarios. De esta forma, el siguienteejemplo muestra cómo parametrizar una sentencia SELECT: >>>>>>>>>>>> cursor = con.cursor ()sql = 'SELECT * FROM países WHERE moneda= :moneda')cursor.prepare(sql)cursor.execute(None, {'moneda': 'Euro'}) En el caso de cx_0rade se emplean diccionarios para las consultasparametrizadas, donde cada clave corresponde a la variable que va aser parametrizada y su valor se corresponde con el que va a ser pasadopara ejecutar la sentencia SQL.Oracle permite trabajar con procedimientos almacenados que suelenestar escritos en un lenguaje propio llamado PI/SQL. Este tipo decomponentes permiten ganar en eficiencia y es habitual que las basesde datos Oracle hagan uso de ellos, sobre todo para complicadasconsultas y operaciones. Desde Python podemos invocar a estosprocedimientos almacenados gracias al método callfunc() del objeto cursor. Supongamos que nuestra base de datos cuenta con uno deestos procedimientos al que hemos llamado miproc. Este debe recibircomo argumento el valor para una variable, llamada x, que es de tipoentero. La invocación a través de cx_Oracle sería como sigue: >>> cursor = con.cursor()>>> res = cursor.callfunc('miproc', cx_Oracle.NUMBER, ('x', 33) ) A continuación, nos ocuparemos de los ORM más popularescompatibles con Python 3. SQLite3 SQLite3 es un gestor de bases de datos relacionales caracterizadopor su motor, el cual se encuentra contenido en una librería de C.Gracias a este hecho, no es necesaria la instalación y configuración delgestor, basta con instalar la correspondiente librería en el sistema. Unade sus principales ventajas es que ocupa menos de 300 KB, lo que lahace muy portable, especialmente para dispositivos que cuentan conrecursos hardware
    • 243. limitados. Por otro lado, una base de datos SQLite3está compuesta por un único fichero binario.
    • 244. A diferencia de los casos anteriormente explicados para Oracle,MysQL y PostgreSQL, para conectarnos a SQLite3 no necesitaremosningún módulo adicional, debido a que Python incluye uno en sulibrería estándar. Así pues, para su utilización, bastará con importarlodirectamente, tal y como muestra la siguiente sentencia: >>> import sqlite3 Para establecer la conexión a nuestra base de datos, lanzaremos elsiguiente comando: >>> con = sqlite3.connect('países.db') De forma análoga a los ejemplos anteriores, para la ejecución desentencias SQL necesitaremos valernos de un cursor, la obtención delmismo se realiza así: >>> cursor = con.cursor() En cuanto tengamos el cursor, podremos, por ejemplo, consultarnuestra tabla países, empleando para ello el método execute(): >>> cursor.execute('select * from paises')>>> for pais in cursor:... print(pais) Si lanzamos una sentencia que provoca un cambio en la base dedatos, por ejemplo, una sentencia de tipo insert o delete, deberemosinvocar al método commit() para que sea efectiva. El siguientefragmento de código muestra cómo hacerlo: >>> query = 'INSERT INTO pais(nombre, continente, habitantes,moneda) VALUES ('Japón, 'Asia', 127, 'Yen)'>>> cursor.execute(query)>>> cursor.commit() Una vez finalizado el recorrido por los gestores de bases de datos,llega el turno de los ORM, de los que nos ocuparemos en el siguienteapartado. ORM
    • 245. Dos son los ORM más utilizados en Python: SQLAlchemy y SQLObject. Ambos son similares en funcionalidad y, básicamente,ofrecen una serie de técnicas para interacturar con una base de datoscon independencia del gestor. Es decir, el código escrito que utiliza unORM para trabajar con una base de datos puede ejecutarse en MySQL,PostgreSQL, Oracle y cualquier otro motor soportado por el ORM encuestión. Además, los ORM proporcionan una interfaz basada enobjetos, donde sus tablas son representadas por clases, los registrospor instancias de estas clases y los campos de las tablas por losatributos de instancia. Así pues, es muy sencillo establecer unacorrespondencia directa en los componentes de una base de datos yuna serie de objetos de Python.Otra de las ventajas de emplear un ORM en las aplicaciones Pythonque necesitan interactuar con una base de datos, es que no esnecesario escribir código SQL, ya que los ORM proporcionan una seriede métodos que hacen de interfaz a diferentes sentencias SQL. Sinembargo, son muchos los ORM que también permiten escribirdirectamente código SQL para trabajar con la base de datos. Algunosprogramadores prefieren emplear esta técnica para tener un mayor decontrol sobre el SQL que es ejecutado.En la actualidad, los ORM son empleados por diferentes lenguajes ytecnologías y su uso se ha popularizado y, podríamos decir estandarizado, gracias a que son muchos los modernos frameworks web, que, o bien integran uno propio, o bien ofrecen facilidades paraintegrar el que el programador desee.Comenzaremos centrándonos en SQLAlchemy, uno de los primerosy más populares ORM para Python. SQLAlchemy La primera versión de este ORM fue liberada en 2006 yrápidamente se convirtió en uno de los más aceptados por lacomunidad de desarrolladores de Python. Es open source y sedistribuye bajo la MIT license. Entre las funcionales de SQLAlchemy encontramos las que nospermiten conectarnos a una base de datos para insertar, leer, modificary borrar datos. También es posible crear diferentes clases
    • 246. estableciendo relaciones entre ellas que luego serán traducidas en labase de datos empleando claves foráneas. Además, las operaciones join se realizan automáticamente por el ORM cuando es necesarioextraer información de diversas tablas que están relacionadas entre sí. SQLAlchemy soporta diferentes gestores de bases de datosrelacionales, como son, por ejemplo, MySQL, PostgreSQL, SQLite, Oracle y MS SQL Server. Para los ejemplos que veremos en este apartado noscentraremos en MySQL, aunque, lógicamente, son totalmenteextrapolares a otro gestor. Básicamente, lo único dependiente delgestor en sí, será la cadena de conexión que empleemos en la función create_engine(), tal y como veremos más adelante.A pesar de que SQLAlchemy incluye una amplia funcionalidad, eneste apartado nos centraremos en descubrir aquellas funcionalidadesque son consideradas como básicas. Si el lector está interesado enexplotar todo lo que este módulo tiene que ofrecernos,recomendamos consultar la documentación oficial del mismo (verreferencias).Es necesario instalar SQLAlchemy en nuestro equipo, ya que estemódulo no forma parte de la librería estándar de Python. Tal y comohemos aprendido previamente, este módulo puede ser instaladodirectamente a través de pip ; para ello, basta con ejecutar el siguientecomando desde un terminal: pip install sqlalchemy El primer paso para poder comenzar a utilizar nuestro nuevomódulo será importarlo, de lo que se encargará la siguiente sentencia: >>> import sqlalchemy Por simplicidad y, dado que vamos a emplear varias clases de SQLAlchemy, en nuestro ejemplo comenzaremos cargando aquellasque necesitamos. Para ello, bastará con ejecutar las siguientessentencias: >>> from sqlalchemy.ext.declarative import declarative_base>>> from sqlalchemy import Column, Integer, String, create_engine Seguidamente, estableceremos una conexión con nuestra base dedatos. Como hemos comentado previamente, utilizaremos MySQL como ejemplo:
    • 247. >>> cad_con = 'mysql://usuario:password@localhost/prueba'>>> engine = create_engine(cad_con) Las clases que vayan a ser creadas para representar a las tablas dela base de datos deben heredar de una clase propia de SQLAlchemy, llamada Base. Esta debe ser obtenida a través de la llamada a unafunción concreta: >>> Base = declarative_base() SQLAlchemy requiere que cada clase, que vaya a declararse paraque se corresponda con una determinada tabla, tenga al menos dosmétodos principales. El primero de ellos será un constructor querecibirá una serie de parámetros que deben corresponder con losvalores de un registro determinado. Este constructor se debe encargarde asignar estos valores a cada uno de los atributos de clase. Por otrolado, el segundo método en cuestión será utilizado para identificar acada instancia de clase. Con sobrescribir el método especial __repr__() ,será suficiente. Teniendo en cuenta estos requisitos, nuestra clasequedaría de la siguiente forma: class Pais(Base):__tablename__ = 'país'id = Column(Integer, primary_key=True)nombre = Column(String(100))continente = Column(String(20))habitantes = Column(Integer)moneda = Column(String(30)) def __init__(self, nombre, continente, habitantes, moneda):self.nombre = nombreself.continente = continenteself.habitantes = habitantesself.moneda = moneda def __repr__(self):return '<Pais('%s')>' % (self.nombre) Ahora es el momento de crear la tabla físicamente en la base dedatos, a partir de la definición de nuestra clase Pais. Esta acción essencilla y puede ser llevada a cabo a través de la siguiente sentencia: >>> Base.metadata.create_all(engine)
    • 248. Conectándonos a la base de datos podremos comprobar que,efectivamente, la nueva tabla ha sido creada. Sin embargo, las
    • 249. operaciones para interactuar con ella deben ser ejecutadas a través deuna instancia específica de la clase Session. Esta debe ser creadaempleando el siguiente código: >>>>>>>>>>>> Session = sessionmaker(bind=engine)Session = sessionmaker()Session.configure(bind=engine)session = Session() Dado que nuestra nueva tabla no contiene ningún registro, elsiguiente paso será añadir uno nuevo: >>> p = Pais('Tailandia', 'Asia', 65, 'Baht')>>> session.add(p)>>> session.commit() Justo después de lanzar la última sentencia, se ejecutará elcomando INSERT que insertará nuestro nuevo registro. Podemoscomprobar que, efectivamente, ha sido así, lanzando una consulta querecorra toda la tabla y muestre el nombre del país, tal y como muestrael siguiente código: >>> rows = session.query(Pais, Pais.nombre) .all ()>>> for row in rows:... print(row.nombre)...Tailandia SQLAlchemy incluye varios métodos, llamados filters, para realizarconsultas que contengan una cláusula WHERE. Por ejemplo, sideseamos obtener todos los países cuya moneda es el euro, podemoslanzar la siguiente sentencia: >>> res = session.query(Pais).filter(Pais.moneda == 'Euro') Para la ordenación podemos emplear el método order_by, porejemplo, para obtener todos los países ordenados por su id: >>> session.query(Pais).order_by(Pais.id) La actualización de un valor de un determinado campo de una tablaes muy sencilla, basta con asignar un nuevo valor al correspondienteatributo de
    • 250. instancia e invocar al método commit(), tal y como muestrael siguiente código: >>> p.habitantes = 67
    • 251. >>> session.commit() Una vez aprendido lo básico sobre la utilización de SQLAlchemy, eshora de comenzar a descubrir SQLObject. SQLOBJECT Al igual que SQLAlchemy, SQLObject es otro de los más popularesORM disponibles para Python. Se distribuye bajo la licencia libre LGPLy también puede ser instalado a través del gestor de módulos pip . Apesar de que incluye las mismas funcionalidades básicas que SQLAlchemy, este último cuenta con un mayor número de clases,métodos y funciones que aportan mayor funcionalidad. Sin embargo, SQLObject es perfectamente utilizable para la gran mayoría deaplicaciones que requieren de un ORM rápido y sencillo de utilizar.La primera versión liberada de SQLObject fue en mayo 2002 y, en laactualidad, la sintaxis y la forma de realizar la correspondencia entretablas y objetos son muy similares a la de Active Record, el popularORM empleado por el framework web Ruby on Rails. Si el lector tieneexperiencia previa con este ORM, seguro que no tiene ningunadificultad en familiarizarse rápidamente con SQLObject. Por otro lado,es este el módulo elegido por Turbogears, conocido Framework webpara Python, como ORM por defecto. SQLObject puede trabajar con diferentes gestores de bases dedatos relacionales, como MySQL, PostgreSQL, MS SQL Server, SQLite y Sybase. Al igual que en el caso de SQLAlchemy vamos a trabajar conuna base de datos MySQL como ejemplo.Nuestra práctica comienza instalando el correspondiente móduloen nuestro sistema: pip install sqlobject A partir de la correcta instalación del módulo podemos importarlocomo tal, como muestra la siguiente sentencia: >>> import sqlobject Al igual que hemos hecho con SQLAlchemy, comenzaremoscreando nuestra clase que será la que represente a, la ya popular, tabla paises. La definición
    • 252. de la clase en cuestión es como sigue:
    • 253. >>> class Pais(sqlobject.SQLObject):... nombre = sqlobject.StringCol(length=100)... continente = sqlobject.StringCol(length=20)... habitantes = sqlobject.IntCol()... moneda = sqlobject.StringCol(length=30) El siguiente paso será establecer una conexión con nuestra base dedatos pruebas ; para ello, nos apoyaremos en el método connectionForURI() y asignaremos el valor que nos devuelva el mismo auna variable llamada processConnection. Con esta asignación nosaseguramos de que todas las clases que definamos interactuarán conla conexión previamente establecida. El código para realizar estasoperaciones es el siguiente: >>> db_cad = 'mysql://cruceros:cruceros@localhost/cruceros'>>> con = sqlobject.connectionForURI(db_cad)>>> sqlobject.sqlhub.processConnection = con Ahora estamos ya listos para crear la tabla pais partiendodirectamente de la clase anteriormente definida: >>> Pais.createTable() Si consultamos la base de datos pruebas, veremos cómo tenemos lanueva tabla pais, que, por el momento, está vacía. Así pues, pasaremosa crear un nuevo país en la misma a través de una nueva instancia denuestra clase: >>> Pais(nombre='Francia',continente='Europa',moneda='Euro',habitantes=66) Simplemente, ejecutando la sentencia anterior, ya tendremos unnuevo registro en nuestra tabla. Para comprobar este hecho,utilizaremos el método get() que recibirá como argumento el valor 1, indicando que deseamos obtener aquel cuyo id coincide con dichovalor: >>> p = Pais.get(1)>>> p.nombreFrancia Si ahora deseamos modificar uno de los campos del registro reciéncreado, bastará con invocar al método set(), tal y como muestran lassiguientes
    • 254. líneas:
    • 255. >>> p.set(habitantes=67)>>> p = Pais.get(1)>>> p.habitantes67 La consulta de registros puede ser llevada fácilmente a cabo graciasal método selectBy() que puede recibir como argumento el nombre delatributo y su valor que correspondería con la cláusula WHERE de SQL.Por ejemplo, para obtener todos los países de Europa, ejecutaríamos: >>> paises = Pais.selectBy(continente='Europa')>>> from pais in paises:... print(pais.nombre)...Francia El módulo SQLObject nos permite representar relaciones 1-N y N-M de tablas. Además, ofrece una serie de sencillos métodos para obtenervalores de las tablas relacionadas, haciendo automáticamente cuántasoperaciones JOIN sean necesarias.Comparado con SQLAlchemy, SQLObject es más sencillo e intuitivo,aunque sí es cierto que el primero ofrece más funcionalidades. Laelección de uno u otro dependerá de varios factores, aunque sibuscamos un ORM que consuma pocos recursos y sea rápido, SQLObject puede ser una buena elección. NOSQL Las bases de datos NoSQL (Not Only SQL) son diferentes a las basesde datos relacionales tanto en estructura como en el tipo de relacionesque se establecen y en la forma de interactuar con los datos. Si en lasrelacionales el lenguaje SQL es el encargado de trabajar directamentecon los datos, en las NoSQL no se utiliza este tipo de lenguaje.Además, la mayoría de bases de datos NoSQL no soportan JOINS, yaque no se establecen las típicas relaciones 1-N y N-M. Frente a las bases de datos relacionales, las de tipo NoSQL sonfácilmente escalables, ofrecen mínimos tiempos de consulta y puedentrabajar con grandes volúmenes de datos. Gracias a estascaracterísticas se han vuelto muy populares para aplicaciones web dealto tráfico, como son las ofrecidas por empresas como Google,
    • 256. Facebook o Twitter.Básicamente, una base de datos NoSQL almacena una serie depares claves:valor y, en vez de hablar de registros , se habla de documentos. En la actualidad, son tres los gestores de bases de datosNoSQL más populares: Redis, Cassandra y Redis. De cómo interactuarcon ellos desde Python nos ocuparemos en esta parte final delpresente capítulo. Debemos tener en cuenta que el lector estáfamiliarizado con el funcionamiento de estos gestores y conoce losfundamentos sobre ellos. Asimismo, vamos a aprender lo básico sobrela conexión y manejo de datos desde Python contra estos gestores. Siel lector está interesado en profundizar en el tema, es aconsejableechar un vistazo a la documentación de los módulos de Python con losque vamos a trabajar para interactuar con cada uno de losmencionados gestores NoSQL. Redis Para trabajar con Redis contamos con varios módulos de Python.Uno de los más sencillos y fáciles de usar se llama redis y ofrece lasfuncionalidades básicas para fijar un par clave:valor, para leer registrosy para borrarlos.Dado que el mencionado módulo para trabajar con Redis seencuentra disponible a través de pip, su instalación es bastante sencilla,simplemente deberemos ejecutar el siguiente comando desde unaterminal: pip install redis Si la instalación ha finalizado correctamente, el módulo podrá serimportado en cualquiera de nuestros scripts. Es más, podemos probardirectamente en la interfaz de comandos del intérprete de Python: >>> import redis Fijar el valor de una clave es tan sencillo como conectarnos a unade las bases de datos de nuestro servidor Redis, e invocar al método set(). El primer paso será establecer la conexión, tal y como muestra lasiguiente sentencia:
    • 257. >>> con = redis.StrictRedis(host='localhost', port=6379, db=0) Los tres parámetros pasados al constructor de la clase StrictRedis indican: el servidor al que vamos a conectarnos, el puerto en el queestá corriendo y el número que identifica a la base de datos. Si nopasamos ninguno de estos valores, se utilizarán los que hemosempleado en nuestra línea ejemplo. Redis permite simplemente fijar pares clave:valor, a diferencia de MongoDB y Cassandra que soportan el almacenamiento más complejode valores básandose en la misma estructura. De esta forma, lainserción de un valor en nuestra base de datos Redis, sería como sigue: >>> con.set('clave', 'valor') La recuperación de un valor asociado a una clave, es decir, la lecturade un registro almacenado en la base de datos, se realiza invocando almétodo get(). El valor que hemos insertado previamente puede serrecuperado ejecutando la siguiente sentencia: >>> con.get('clave')valor El registro insertado puede ser borrado sencillamente gracias almétodo delete(). Así pues, la siguiente línea de código eliminará elregistro que hemos insertado previamente: >>> con.delete('clave') El módulo redis soporta trabajar con pools de conexiones, con loque ganamos en eficiencia a la hora de establecer diferentesconexiones con la base de datos. La clase ConnectionPool() se encargade realizar la conexión empleando el pool de conexiones yseleccionando aquella que se encuentre libre. Las siguientes dos líneasde código son necesarias para utilizar el pool y obtener una conexiónlibre que podamos emplear para nuestro trabajo: >>> pool = redis.ConnectionPool()>>> con = redis.Redis(connection_pool=pool)
    • 258. Aprendido lo básico para interactuar con una base de datos Redis desde Python, continuaremos descubriendo cómo realizar operacionessimilares con MongoDB.
    • 259. MongoDB El módulo más popular para trabajar con MongoDB desde Pythones el llamado PyMongo. Este puede ser instalado fácilmente a través de pip, así pues basta con invocar a este gestor directamente desde lalínea de comandos: pip install pymongo En cuanto finalice la instalación, podrá ser importado a través de lasiguiente sentencia: >>> import pymongo Para nuestros ejemplos vamos a partir de que tenemos previamentecreada una base de datos llamada pruebas, conectarnos a la misma esbastante sencillo. Primero debemos obtener una conexión y despuéspodremos conectarnos a la base de datos. El código necesario paraello sería el siguiente: >>> con = pymongo.Connection()>>> db = con.pruebas Hemos de tener en cuenta que si no pasamos ningún parámetro ala hora de realizar la conexión, por defecto se entenderá que deseamosconectarnos a localhost y al puerto estándar (27017) que emplea MongoDB. En caso contrario, podemos pasar dos parámetrosdiferentes, uno para el nombre o IP del servidor y otro para el puertoen cuestión.Los datos son almacenados en MongoDB empleando el formatoJSON; así pues, podemos crear un simple documento para representarun país en cuestión: >>> pais = {'nombre': 'Alemania', 'habitantes': 82, 'continente':'Europa', 'moneda': 'euro'}>>> paises = db.paises>>> paises.insert(pais) En Python utilizaremos un diccionario para emular el formato JSON,tal y como nos muestra la primera línea del ejemplo anterior. Una vezque tenemos nuestro documento, creamos una colección y añadimosdicho documento a la misma.
    • 260. Para comprobar que el documento ha sido insertadocorrectamente, basta con invocar al método find_one() que seencargará de consultar nuestra colección: >>> paises.find_one({'nombre': 'Alemania}){'nombre': 'Alemania', 'habitantes': 82, 'continente': 'Europa','moneda': 'euro'} Si necesitamos hacer una consulta que devuelva más de undocumento, podemos emplear el método find() e iterar sobre elresultado, ya que en este caso obtendremos una lista de diccionarios.Por ejemplo, supongamos que tenemos varios países que tienen eleuro como moneda y queremos extraer los mismos de la base dedatos. Para ello, bastaría con ejecutar las siguientes líneas: >>> p_euro = paises.find({'moneda': 'Euro'})>>> for pin p_euro:... print(p) {'nombre': 'Alemania', 'habitantes': 82, 'continente': 'Europa','moneda': 'euro'}{'nombre': 'España, 'habitantes': 47, 'continente': 'Europa','moneda': 'euro'}{'nombre': 'Francia', 'habitantes': 67, 'continente': 'Europa','moneda': 'euro'} Otro de los métodos útiles que nos ofrece PyMongo es count(), elcual nos devuelve el número de documentos que cumplen ciertacondición. Así pues, si ejecutamos la siguiente sentencia, obtendremoslos tres países que utilizan el euro y que se encuentran en nuestra basede datos: >>> paises.find({'moneda': 'Euro'}).count()3 Ahora que ya hemos aprendido lo básico sobre PyMongo, pasaremos a ocuparnos de cómo trabajar desde Python con Cassandra. Cassandra El gestor de bases de datos Cassandra almacena la información deforma diferente a como lo hace MongoDB. Cassandra se basa en los
    • 261. conceptos de keyspace y column familiy para almacenar la informaciónde forma estructurada. Al fin y al cabo, también se emplea el conceptode clave y valor, solo que también se permite estructurar lainformación en columnas y en supercolumnas. A pesar de que existen varios módulos para trabajar con Cassandra desde Python, uno de los más populares y fáciles de usar es pycassa. De forma análoga a como hemos instalado otros módulos que noforman parte de la librería estándar de Python, ejecutaremos desde lalínea de comandos, la siguiente orden: pip install pycassa Para trabajar con el módulo que acabamos de instalar, basta con importarlo : >>> import pycassa La conexión a la base de datos se hará a través de un objetoespecífico que representa a un pool de conexiones, al que indicaremosel nombre del keyspace que deseamos utilizar, más los datos deconexión del servidor: >>> from pycassa.pool import ConnectionPool>>> pool = ConnectionPool('mainspace') En caso de que tengamos Cassandra corriendo en un puertodiferente al estándar o en un servidor que no sea la máquina local( localhost ), podemos indicar estos valores a través de una lista, tal ycomo muestra la siguiente línea de código: >>> pool = ConnectionPool('mainspace', ['192.168.0.3', '8080']) Una vez que tengamos acceso a nuestro keyspace, es necesarioindicar la column family a la que deseamos conectarnos. Estaoperación puede ser llevada a cabo a través de una clase llamada ColumnFamily. Las siguientes líneas muestran cómo realizar estaoperación: >>> from pycassa.columnfamily import ColumnFamily>>> col = ColumnFamily(pool, 'maincolumn')
    • 262. Ahora ya estamos listos para insertar nuestro primer registro; paraello, emplearemos el método insert(), cuyo primer argumentoreferencia a la clave y el segundo es un diccionario con los pares
    • 263. clave:valor que deseamos insertar. Veamos cómo el siguiente códigonos servirá para insertar un nuevo país: >>> col.insert('Alemania', {'habitantes': 82, 'continente':'Europa', 'moneda': 'euro'}) El recién insertado registro puede obtenerse fácilmente gracias almétodo get(). Las siguientes líneas muestran cómo utilizarlo: >>> col.get('Alemania'){'habitantes': 82, 'continente': 'Europa', 'moneda': 'euro'} Como resultado de pasar la clave Alemania, obtenemos undiccionario de Python con los valores requeridos. Si necesitáramos dosregistros diferentes en una única sentencia, podríamos hacerlo a travésdel método multiget(), tal y como muestra el siguiente ejemplo: >>> col.multiget(['Alemania', 'Francia']){'Alemania': {'habitantes': 82, 'continente': 'Europa', 'moneda':'euro'},'Francia': {'habitantes': 67, 'continente': 'Europa', 'moneda' :'euro'}} Finalizamos así el capítulo dedicado a las bases de datos.Esperamos que el lector haya logrado una buena visión de conjuntoque le permita trabajar, tanto con bases de datos relacionales, comocon bases de datos NoSQL, desde Python.
    • 264. INTERNET INTRODUCCIÓN En este capítulo nos ocuparemos de aquellos aspectos másrelevantes relacionados con Python y la programación relativa aservicios de Internet. Ante el innegable auge de la red de redes, es muyinteresante descubrir cómo interactuar, a través de la misma,empleando Python como lenguaje de programación para escribiraplicaciones que puedan hacer uso de servicios como el correoelectrónico, la web, los web Services o la conexión directa a través deprotocolos específicos como FTP.Comenzaremos nuestro recorrido aprendiendo cómo realizarconexiones con servidores accesibles a través de Internet, empleandodos protocolos ampliamente utilizados, como son TELNET y FTP.Gracias a utilizar un lenguaje como Python, descubriremos cómo esposible automatizar muchas tareas que se realizan de forma manualtrabajando con estos protocolos.Seguiremos el camino ocupándonos de los servicios web queemplean el protocolo XML-RPC como estándar para la comunicación eintercambio de información entre el cliente y el servidor.Al finalizar con los protocolos anteriormente mencionados, llegarála hora del correo electrónico. Aprenderemos cómo conectarnos einteractuar con servidores que empleen diferentes protocolos estándarcomo son IMAP4, POP3 y SMTP.El último apartado del presente capítulo se ocupará de la web,donde nos centraremos en aprender lo básico sobre el manejo deprotocolos como CGI y WSGI. Además, descubriremos quéherramientas podemos emplear para realizar web scraping, una de lastécnicas más utilizadas en la actualidad para la obtención deinformación desde sitios web. Por último, llegará el turno de los frameworks web, también de rabiosa actualidad, gracias al conjunto decomponentes y herramientas que ofrecen para desarrollar y mantenerfácilmente complejas aplicaciones web.Es conveniente tener en cuenta que este capítulo no pretende
    • 265. realizar un exhaustivo análisis de todos los módulos, herramientas ycomponentes existentes y relacionados con Internet y disponibles paraPython. El objetivo es otro, tratándose el mismo de repasar aquellosaspectos, técnicas y módulos que consideramos más interesantes parapoder comenzar a desarrollar rápidamente aplicaciones en Python quepuedan sacar provecho del potencial que nos ofrece Internet.En la actualidad existe un mayor conjunto de módulos ycomponentes para interactuar con Internet en la versión 2 de Pythonque en la 3. No obstante, esta situación está cambiando y son muchosdesarrolladores que se encuentran trabajando en la migración de suscomponentes. A pesar de ese hecho, es fácil encontrar módulos quefuncionan correctamente en la última versión del intérprete de Python.Una muestra de ellos son los que descubriremos y utilizaremos en elpresente capítulo.A continuación, comenzamos aprendiendo a trabajar con losprotocolos FTP y TELNET desde Python.
    • 266. TELNET Y FTP Dos de los más utilizados protocolos en Internet para interactuarcon servidores son FTP y TELNET. Ambos permiten comunicarnos conuna máquina desde otra a través de una conexión a Internet. Elprotocolo FTP es ampliamente utilizado tanto para subir como para bajar ficheros. Si alguna vez hemos usado en servicio de hosting, seguro que hemos utilizado este protocolo a través de algún clienteque lo soportara. Por otro lado, gracias a TELNET podemos abrir unterminal y ejecutar comandos como si físicamente estuviéramossentados enfrente de la máquina remota. Este protocolo se utilizatípicamente en servidores UNIX, Linux y Mac OS X Server. Aunque esmenos frecuente, también algunas versiones de Windows lo soportan.En este apartado descubriremos qué módulos tenemos disponiblesen Python para poder trabajar con los mencionados protocolos paracomunicarnos remotamente con otras máquinas configuradas comoservidores. Comenzamos ocupándonos del protocolo TELNET. telnetlib Python cuenta en su librería estándar con un módulo llamado telnetlib, el cual encapsula la funcionalidad básica para conectarnos ydesconectarnos a un servidor, enviar comandos y obtener respuestas,todo a través del protocolo TELNET.El mencionado módulo pone a nuestra disposición una claseprincipal denominada Telnet. Al crear una instancia de la mismaobtendremos un objeto que nos permitirá interactuar con el servidor.El método open() será el encargado de abrir una conexión a unservidor que acepte el protocolo TELNET. Como parámetros delconstructor de la mencionada clase podemos pasar tres diferentes, unoque indique nombre o IP del servidor, otro para especificar el puertode conexión y un tercero que servirá para fijar un número de segundoscomo máximo para esperar respuesta. El único de estos parámetrosrequeridos es el primero, ya que, por defecto, la conexión se realizará
    • 267. al puerto estándar (23) para TELNET.Antes de comenzar a ver los ejemplos de código de utilización de telnetlib, si no disponemos de conexión a ningún servidor medianteTELNET, es fácil instalar un servidor en nuestra máquina local. Siutilizamos Mac OS X bastará con ejecutar el servicio que corre unservidor de TELNET en el puerto 21. Para ello, abriremos un terminal ylanzaremos el siguiente comando: $ sudo launchctl load -w/System/Library/LaunchDaemons/telnet.plist La mayoría de las distribuciones de GNU/Linux cuentan conservidores de TELNET entre sus paquetes binarios. Así pues, porejemplo, en Ubuntu podemos abrir una consola de comandos y lanzarel siguiente comando: $ sudo apt-get install telnetd Para instalar un servidor de TELNET en Fedora lanzaremos elsiguiente comando: $ sudo yum install telnet-server En cuanto tengamos listo nuestro servidor local o los datos deacceso a uno remoto, podemos comenzar a practicar con el códigoejemplo que veremos a continuación.La primera operación será conectarnos a nuestro servidor, para ello,ejecutemos las siguientes sentencias: >>> from telnetlib import Telnet>>> tel = Telnet('localhost') En lugar de invocar al método open() para abrir una conexión,vamos directamente a llamar al método read_until(), el cual seencargará de abrir la conexión por nosotros y comenzar a leer desde elservidor hasta que este ofrezca la respuesta que coincida con elargumento pasado al mencionado método. En nuestro ejemplo y dadoque vamos entrar en la máquina empleando un login y password correspondientes a un usuario concreto, vamos pasar como parámetrola cadena binaria "login: " , tal y como muestra el siguiente código:
    • 268. >>> tel.read_until(b'login: ')'\r\r\nDarwin/BSD (yoda.local) (ttysOOO)\r\r\n\r\r\nlogin:'
    • 269. En cuanto el servidor responda, deberemos mandar el nombre deusuario para hacer login, para ello contamos con el método write(), elcual se encarga de mandar una serie de bytes al servidor. Esta será lasentencia que necesitaremos, donde usuario debe ser reemplazado porel nombre en cuestión del usuario que va a realizar login en el servidor: >>> tel.write(b'usuario\n') A través de la cadena \n indicamos que un retorno de carro debeser lanzado justo después del nombre de usuario. Ello simularía lapulsación de la tecla enter si estuviéramos utilizando el comando telnet directamente desde una terminal. El siguiente paso será esperar a queconteste el servidor pidiéndonos una contraseña: >>> tel.read_until(b'Password: ')' usuario\r\nPassword:' De la misma forma que el nombre de usuario, ahora mandaremosnuestra contraseña, a través del método write(): >>> tel .write (b'password\n' ) Si todo va bien, el login se realizará correctamente y podremosenviar otros comandos. Probemos con un sencillo listado delcontenido del directorio del usuario que se ha conectado al servidor: >>> tel.write(b'ls\n') Seguidamente y, previo paso a leer los datos de respuesta enviadospor el servidor, procederemos realizar el logout del servidor, para ellobastará con mandar el comando exit, soportado por el protocoloTELNET: >>> tel.write(b'exit\n') Para leer la salida mandada desde el servidor contamos con elmétodo read_all() que nos devolverá todos los datos enviados comorespuesta desde el servidor, incluyendo, tanto la salida del comando ls, como los que se obtienen al ejecutar el comando exit:
    • 270. >>> tel. read_all ()'\r\nLast login: Mon Feb 20 17:10:37 on ttys003\r\n[arturo@yoda ~\xlb[0;32m]\x1b[0;37m$ls\r\nApplications\t\t\tfile.txt\r\nDesk
    • 271. top\t\t\t\tftdetect\r\nDocuments\t\t\tftplugin[arturo@yoda~\x1b[0;32m]\x1b[ 0;37m $\r [arturo@yoda ~\x1b[0;32m]\x1b[0;37m$ exit\r\nlogout\r\n' Por último, solo nos queda cerrar la conexión al servidor. Acciónque puede realizarse gracias al método close(), tal y como muestra lasiguiente línea: >>> tel.close() El módulo telnetlib también puede ser utilizado para conectarnos aotros servicios que corren en máquinas remotas y que aceptan elprotocolo TELNET. Como ejemplo de estos servicios están losservidores de correo electrónico que soportan SMTP y POP3, losservidores de caché como memcached y redis, el servidor de basedatos NoSQL. ftplib Sin necesidad de recurrir a ningún módulo fuera de la libreríaestándar de Python, para el manejo de conexiones a través delprotocolo FTP (File Transfer Protocol) contamos con el módulo ftplib. Este incluye una clase principal llamada FTP que se encarga degestionar la conexión con otros servidores. Entre las acciones que lamisma permite llevar a cabo, destacan las que nos permiten abrir ycerrar una conexión, autenticarnos, mandar cualquier comandoaceptado por el protocolo y leer los resultados enviados comorespuesta por el servidor. Utilizar esta clase desde Python nos permitiráautomatizar sencillas tareas como la descarga o subida de ficheros eincluso aquellas más complejas, como pueden ser, por ejemplo, lacreación y mantenimiento de un servidor mirror. La obtención de una instancia de la clase principal FTP se realiza através de su constructor, el cual recibe como parámetros la IP onombre del servidor y el puerto donde realizar la conexión. Si no sepasa ningún entero para el puerto, se usará el empleado por defecto yconvención en los servidores. De esta forma, para conectarnos a unservidor, crearemos previamente una instancia de la mencionada clase:
    • 272. >>> from ftplib import FTP>>> con = FTP('ftp.miservidor.com') En cuanto tengamos creada la instancia podremos comenzar ainteractuar con el servidor. Si necesitamos autenticarnos, invocaremosal método login(), tal y como muestra la siguiente sentencia: >>> con.login('usuario', 'password')'230 User usuario logged in.' Son muchos los servidores FTP que aceptan un usuario especialllamado anonymous, cuya password coincide con el nombre de usuario.Es práctica habitual colocar la información pública que ofrecen losservidores FTP bajo el acceso de este usuario especial. Así pues, paraconectarnos empleando el mismo bastará con invocar al método login() sin pasar ningún parámetro.Una vez conectados al servidor mediante FTP, podemos, porejemplo, echar un vistazo al contenido del directorio al que hemosaccedido por defecto. Para ello invocaremos al método retrlines(), elcual obtiene el contenido de un fichero o lista los que forman parte deun determinado directorio. En nuestro ejemplo, vamos a mandar elcomando LIST para obtener una cadena de texto con el contenido deldirectorio al que hemos accedido por defecto al realizar la conexión alservidor: >>> con.retrlines('LIST')total 234drwxrwsr-x 5 usuario usuario 1520 Feb 16 09:22 .dr-xr-srwt 15 usuario usuario 1520 Feb 16 09:25 ..-rw-rw-rw- 20 usuario usuario 230 Feb 18 10:12 myfile.txt Subir un fichero a un servidor es sencillo gracias al método storbinary(). Simplemente deberemos abrir el fichero en cuestión einvocar al mencionado método pasando dos parámetros. El primero deellos será una cadena de texto que contiene el valor 'STOR', seguidodel nombre que deseemos darle al fichero remoto. El segundo encuestión será un objeto de tipo fichero. El siguiente código nosmuestra cómo acceder a un fichero local llamado prueba.json y subirlocon el nombre test.json: fich = open ('prueba.json', 'rb')>>> con.storbinary('STOR test.json', fich)'226 Transfer complete. '
    • 273. >>> fich.close() Efectivamente, para abrir nuestro fichero hemos empleado b como flag para indicar que el fichero, aunque sea de texto, debe ser tratadocomo binario. De esta forma podremos utilizar su contenido sinproblema a través del método storbinary(). Análogamente, también es posible descargarnos un fichero desdeel servidor FTP. Para esta acción contamos con el método retrbinary(), el cual también recibe dos parámetros: uno para indicar que se va allevar a cabo la operación de descarga y otro para pasar el objetofichero donde se almacenará el contenido del fichero descargado.Como ejemplo podemos descargarnos el fichero que hemos subidopreviamente. En esta ocasión nos bajaremos el fichero test.json y losalvaremos con el nombre miprueba.json. El siguiente código nosmuestra cómo hacerlo: >>> fich = open('miprueba.json', 'wb')>>> con.retrbinary('RETR test.json', fich.write)'226 Transfer complete.'>>> fich.close() Es importante cerrar al final el fichero, en caso contrario el ficherono será creado. Igualmente ocurre en el caso de la subida, hasta queno cerremos el fichero, no será volcado su contenido al servidorremoto.Para enviar un comando FTP válido existe el método sendcmd(), querecibe como cadena de texto el comando en cuestión que seráejecutado. Como resultado obtendremos la salida de la ejecución delmismo. Por ejemplo, para indicar que vamos a cambiar a modo binario,podríamos lanzar la siguiente sentencia: >>> con.sendcmd('TYPE i') Otro método interesante con los que cuenta la clase FTP es size() que sirve para averiguar cuánto ocupa un determinado fichero endisco. La invocación puede realizarse directamente, pasando comoparámetro el nombre del fichero en cuestión, tal y como muestra elsiguiente ejemplo: >>> con.size('myfile.txt')230
    • 274. Para trabajar con directorios dentro del servidor existen diferentesmétodos, como son cwd(), pwd(), mkd() y rmd(). El primero de ellossería para cambiar a un directorio determinado, el cual será fijadocomo actual, para ello deberemos especificar como parámetro elnombre del directorio en cuestión. El segundo simplemente nosdevolverá el path del directorio actual. Por otro lado, mkd() creará unnuevo directorio, mientras que rmd() servirá para borrar aqueldirectorio que ya no necesitemos.No olvidemos cerrar la conexión al servidor en cuanto finalicemosnuestro trabajo: >>> con.quit() Como habremos podido comprobar la interacción con servidoresFTP es bastante sencilla desde Python, lo cual nos abre un interesantecampo para crear scripts o aplicaciones que requieran de dichainteracción.
    • 275. XML-RPC El protocolo XML-RPC se basa en la utilización de la invocaciónremota a funciones o métodos a través de la codificación de los datosnecesarios para la llamada en el formato XML. Para el transporte dedatos se emplea el protocolo HTTP, es por ello que XML-RPC es unode los protocolos más utilizados para la implementación de webServices. En Python 3 contamos con dos módulos principales para trabajarcon XML-RPC. El primero de ellos es xmlrpc.server y nos ofrece unaserie de funcionalidades para desarrollar un servidor que puedainteractuar con un cliente a través del mencionado protocolo. Por otrolado, el segundo módulo en cuestión es xmlrpc.client, diseñado paraimplementar clientes que puedan interactuar con cualquier servidorque sea capaz de entender y trabajar con este protocolo. Ambosmódulos forman parte de la librería estándar de Python, lo quesignifica que podemos utilizarlos directamente, sin necesidad derealizar ninguna instalación adicional.A continuación, veremos una serie de ejemplos prácticos, tanto decliente, como de servidor, para aprender lo básico para trabajar conXML-RPC en Python. Comenzamos por el módulo que se ocupa delservidor. xmlrpc.server Para ilustrar el funcionamiento de este módulo, vamos aimplementar un sencillo servidor que contendrá dos funcionesdiferentes que podrán ser llamadas desde un cliente a través de,lógicamente, XML-RPC. En realidad, vamos a desarrollar un sencillo web Service que correrá en nuestra máquina local. El mismo códigopuede ejecutarse en un servidor accesible a través de Internet. Es decir,con xmlrpc.server podemos desarrollar cualquier web Service en Pythoncapaz de interactuar con clientes, escritos o no en el mismo lenguaje.Nuestro web Service contendrá una función llamada say_bye(), la
    • 276. cual recibirá como parámetro una cadena de texto y devolverá unsencillo mensaje, concatenando la cadena pasada como parámetro.Además, el mencionado web service también contará con una clase conun método, al que llamaremos say_hello(), que devolverá el clásico Hola Mundo!. Tanto la función como el método en cuestión seránaccesibles directamente como funciones desde un cliente XML-RPC.Dos clases del módulo xmlrpc.server son fundamentales para laimplementación de un servidor: SimpleXMLRPCServer y SimpleXMLRPCRequestHandler. Ambas clases contienen lasfuncionalidades necesarias para crear un servidor que pueda entenderlas peticiones que se realizan desde un cliente y responder a lasmismas a través de las funciones previamente definidas.El primer paso en la implementación de nuestro servidor será crearuna clase que herede de SimpleXMLRPCRequestHandler y quecontenga una variable indicando la ruta ( path ) a través del cual seráaccesible nuestro web service. El código Python para ello sería elsiguiente: >>> from xmlrpc.server import SimpleXMLRPCServer>>> from xmlrpc.server import SimpleXMLRPCRequestHandler>>> class RequestHandler(SimpleXMLRPCRequestHandler):... rpc_paths = ('/mywebservice',) Seguidamente, crearemos una instancia de la clase SimpleXMLRPCServer a la que pasaremos dos parámetros diferentes.Uno de ellos será un tupla que contendrá el nombre o IP del servidormás el puerto donde correrá el web service. El segundo parámetroindicará cuál será la clase que manejará las peticiones recibidas. Dadoque estamos trabajando con nuestra máquina, en local, indicaremoscomo servidor localhost. Como puerto vamos a elegir, por ejemplo, el8080. La mencionada clase que gestionará las peticiones de los clientesserá la que hemos definido previamente. Así pues, el código encuestión para crear la instancia de nuestro web service, es el siguiente: >>> servidor = SimpleXMLRPCServer(("localhost", 8080),requestHandler=RequestHandler) Ahora llega el momento de crear nuestra primera función invocabledesde el cliente. Simplemente definiremos la función y la registraremos empleando
    • 277. el método register_function(). Si no registramos la función,no será posible su invocación. No olvidemos este paso, pues es
    • 278. importante. A continuación, el código necesario que define nuestrafunción y que la registra en el servidor: >>> def say_bye(name):... return("Bye, bye {0}".format(name))...>>> servidor.register_function(say_bye) El módulo xmrpc.server no solo nos permite definir funciones,también es posible crear clases con sus correspondientes métodos yhacer estos accesibles a los clientes que interactúen con el web service. Para ello, deberemos seguir los mismos pasos que hemos vistopreviamente para las funciones, es decir, bastará con definir nuestraclase y registrarla. La principal diferencia es que emplearemos elmétodo register_instance(), en lugar de register_function(). El código encuestión para ambas operaciones es el que viene a continuación: >>> class MySer:... def say_hello(self):... return 'Hola Mundo!'...>>> servidor.register_instance(MySer()) Con el objetivo de que los clientes puedan tener acceso a lasfunciones que expone nuestro web service, vamos a invocar a unmétodo determinado, tal y como muestra la siguiente línea de código: >>> servidor.register_introspection_functions() Una vez definidas y registradas la clase y la función de nuestroservidor, solo nos quedará lanzarlo para que los clientes puedantrabajar con él. Este paso se lleva a cabo a través de un métodoespecífico llamado server_forever(): >>> servidor. serve forever() Ya tenemos nuestro servidor web desarrollado y ejecutándose,desde este momento, los clientes, escritos en cualquier lenguaje quepermita escribir clientes que se ajusten a las especificaciones delprotocolo XML-RPC, podrán conectarse a nuestro servidor y realizar lasllamadas a las funciones expuestas por el mismo. En el siguienteapartado, vamos a escribir uno de
    • 279. estos clientes en Python de estaforma, descubriremos cómo el módulo xmlrpc.client puede ayudarnosa ello.
    • 280. xmlrpc.client En Python el módulo xmlrpc.client de su librería estándar nos ofreceuna serie de funcionalidades para implementar sencillamente clientesque interactúen con servidores a través de XML-RPC. Como ejemplo,vamos a desarrollar un cliente que se pueda conectar al servidor quehemos creado en el apartado anterior y que invoque a sus funcionesdefinidas.La clase principal que permite establecer una conexión con elservidor se llama ServerProxy. A través de una instancia de esta clasepodremos, directamente, invocar a los métodos que nos ofrezca elservidor. Así pues, el primer paso será realizar la mencionada conexión;sirva el siguiente código como ejemplo: >>> from xmlrpc.client import ServerProxy>>> url = 'http://localhost:8080/mywebservice'>>> cli = ServerProxy(url) Efectivamente, la variable url contiene tanto el nombre y puerto delservidor, como la ruta que han sido indicados previamente en nuestroservidor web. Por otro lado, la variable cli será la instancia queemplearemos para llamar a las funciones del servidor. Antes decontinuar, invocaremos a un método, accesible a través de nuestrainstancia de cliente, que nos mostrará las funciones expuestas en elservidor web: >>> cli.system.listMethods()['say_bye', 'say_hello', 'system.listMethods','system.methodHelp', 'system.methodSignature'] Como el lector habrá podido comprobar, los dos primeros valoresde la lista devuelta por el método listMethods() corresponden a lasfunciones que pueden ser invocadas desde el cliente. El resto devalores hacen referencia a otras funciones accesibles, por defecto, através de system, que a su vez tiene acceso desde la instancia denuestro cliente. De esta forma, ya estamos listos para invocar a lasfunciones de nuestro web service, tal y como muestra el siguientecódigo: >>> cli.say_hello()Hola Mundo!
    • 281. >>> cli.say_bye('Lucas')Bye, bye Lucas Si en lugar de emplear el intérprete de Python desde la línea decomandos, creamos un fichero, copiamos el código y lo lanzamosdesde un terminal, comprobaremos cómo cada petición desde elcliente origina un mensaje de log en la terminal donde se estáejecutando nuestro servidor. Por ejemplo, justo después de invocar alservidor desde el cliente, obtendremos una línea en la terminal, dondeha sido lanzado el servidor, como la siguiente: localhost - - [20/Feb/2012 11:13:53] "POST /mywebserviceHTTP/1.1"200 - Tal y como hemos podido comprobar, a través de un sencilloejemplo, Python pone a nuestra disposición todo lo básico paratrabajar con servidores y clientes XML-RPC en dos sencillos módulosque, además, forman parte de su librería estándar. De esta forma, esmuy fácil escribir tanto clientes, como servidores, ya que toda lafuncionalidad y gestión a bajo nivel del protocolo, está encapsulada enlas diferentes clases, métodos y funciones que nos ofrecen losmencionados módulos.
    • 282. CORREO ELECTRÓNICO De los servicios utilizados a través de Internet, sin duda el correoelectrónico es uno de lo más utilizados diariamente por millones depersonas. Para el envío y recepción de correo electrónico son varios losprotocolos de red empleados. Entre los más populares se encuentranIMAP4, POP3 y SMTP. Este último se utiliza para el envío, mientras queIMAP4 y POP3 se encargan de la recepción.Gracias a una serie de módulos contenidos en la librería estándarde Python, podemos escribir programas que se encarguen de trabajarcon los protocolos anteriormente mencionados para enviar y recibircorreo electrónico. Por ejemplo, podemos desarrollar un sencillo scriptque se conecte a un servidor SMTP y realice el envío de uno o varioscorreos. Incluso podríamos escribir nuestra propia aplicación cliente decorreo, con funcionamiento similar a los populares Thunderbird y Outlook, para enviar y recibir correo electrónico.En este apartado nos centraremos en tres módulos específicos quepermiten interactuar con servidores de IMAP4, SMTP y POP3 desdePython. En concreto nos ocuparemos de poplib, imaplib y smtplib. Comenzaremos por el primero de ellos. pop3 El módulo poplib es el encargado de manejar las operacionesnecesarias, utilizando el protocolo POP3, para interactuar conservidores de correo electrónico. Básicamente, cuenta con dos clasesprincipales, POP3 y POP3_SSL. Ambas implementan la mismafuncionalidad, con la diferencia de que la segunda permite manejarconexiones al servidor a través del protocolo seguro SSL. De hecho, POP3_SSL está implementada como una subclase de POP3. Entre los métodos disponibles en las mencionadas clasesencontramos los que nos permiten realizar una conexión ydesconexión al servidor, pasar un usuario y contraseña, listar losmensajes disponibles para dicho usuario, obtener Información sobre
    • 283. un buzón y, cómo no, obtener los correos recibidos.Para comenzar a trabajar con poplib, lo primero que deberemoshacer es establecer una conexión con el servidor. Ello se lleva a cabofácilmente a través de una instancia de la clase POP3, que recibirá dosparámetros principales: el nombre o IP del servidor más el puertodonde se está ejecutando. Por defecto, se utilizará el puerto 110, quees el empleado por convención por los servidores POP3.Adicionalmente, se puede emplear un tercer parámetro ( timeout ) paraindicar el número de segundos que se deben esperar para obtener unarespuesta del servidor, si en esos segundos no obtenemos respuesta,la conexión será fallida y no será establecida. Las siguientes líneas decódigo muestran cómo conectarnos a un servidor POP3: >>> from poplib import P0P3>>> servidor = POP3('miservidor.com') Si la conexión se ha realizado satisfactoriamente, estaremos encondiciones de conectarnos al buzón de un determinado usuario. Paraello, emplearemos dos métodos diferentes, uno para pasar el nombrede usuario y otro para indicar su contraseña: >>> servidor.user('nombre_de_usuario')>>> servidor,pass_('password_de_usuario') Ahora que ya tenemos acceso al buzón del usuario, obtengamos,por ejemplo, el número de correos que existen en su buzón. Estaacción es tan sencilla como invocar al método list(), que nos devolveráuna lista con todos los correos recibidos, además la longitud delsegundo elemento hará referencia al número de ellos. El siguientecódigo nos muestra un ejemplo de utilización del mencionadométodo: >>> num = len(servidor.list()[1])>>> "El usuario tiene {0} mensajes".format(num)El usuario tiene 5 mensajes El método retr() es el encargado de obtener el contenido de losmensajes que se encuentran en un buzón. Por ejemplo, para imprimirpor pantalla todos los correos de nuestro usuario ejemplo, podríamosemplear el siguiente código:
    • 284. >>> i = 0>>> for i in range(num):
    • 285. ......... for mensaje in servidor.retr(i+1)[1]:print(mensaje) En cuanto terminemos de realizar las operaciones que necesitemoscon el servidor P0P3, deberemos cerrar la conexión al servidor. Estaacción se lleva a cabo a través del método quit(), tal y como muestra lasiguiente sentencia: >>> servidor.quit() En ocasiones es interesante obtener una traza de lo que estáocurriendo al interactuar con el servidor. Es por ello, que existe elmétodo set_debuglevel() que imprime por pantalla información sobre laacción que produce en el servidor cualquier operación que ejecutemosa través de la clase POP3. El mencionado método para depuraciónadmite como parámetro un entero que indica el nivel de log quedeseamos sea mostrado. Cuanto mayor sea este número, mayor será lainformación que obtenemos.Previamente hemos mencionado que la clase POP3_SSL cuenta conla misma funcionalidad que POP3. Como ejemplo de utilización de POP3_SSL vamos a conectarnos al servidor de GMail, que requiere estetipo de autenticación y, además, fijaremos el nivel de depuración a 2,con el fin de obtener información ofrecida por el servidor sobre lo queestá ocurriendo cuando invocamos a los métodos de la clase empleadapara la conexión. El siguiente código muestra cómo realizar lamencionada conexión y la información ofrecida por el servidor al pasarnuestro usuario: >>> from poplib import P0P3_SSL>>> ser = P0P3_SSL('pop.gmail.com', 995)>>> ser.set_debuglevel(2)>>> ser.user('arturofernandezm@gmail.com')*cmd* 'USER arturofernandezm@gmail.com'*put* b'USER arturofernandezm@gmail.com'*get* b'+OK send PASS\r\n'*resp* b'+OK send PASS'b'+OK send PASS'>>> ser.quit()*cmd* 'QUIT'*put* b'QUIT'*get* b'+OK Farewell.\r\n'*resp* b'+OK Farewell.'b'+OK Farewell.'
    • 286. No olvidemos que GMail emplea como usuario de correo elnombre seguido de la arroba más el dominio. Sin embargo, otrosservidores de correo utilizan nombres de usuario que no contienen nila arroba ni el dominio en cuestión. Además, el puerto empleado porGMail no es el estándar, sino el 995, es por ello que hemos indicado elmismo al crear nuestra instancia de la clase POP3_SSL. Ahora que hemos aprendido lo básico para conectarnos y acceder ala información de un determinado usuario en un servidor de correoelectrónico compatible con POP3, es el momento de pasar a ver cómotrabajar con otro protocolo diferente que nos permita enviar correos,en lugar de tener acceso a los recibidos. smtp El protocolo de red para enviar correos más utilizado es SMTP( Simple Mail Transfer Protocol). Python cuenta en su librería estándarcon el módulo llamado smtplib, el cual permite conectarse a cualquierservidor que cuente con un servidor de correo, que emplee elmencionado protocolo para enviar correos electrónicos.De forma similar a poplib, el módulo smtplib cuenta con dos clasesprincipales, una para realizar la conexión no segura y otra para emplearel protocolo seguro SSL. Los nombres de ambas clases son,respectivamente, SMTP y SMTP_SSL. La utilización de una u otra clasedependerá del servidor al que vayamos a conectarnos. Entre losmétodos ofrecidos por ambas clases encontramos los que nospermiten conectarnos y desconectarnos de un servidor, pasar usuario ycontraseña, y por supuesto, realizar el envío de un correo electrónico.Para conectarnos a un servidor SMTP basta con crear una instanciade la mencionada clase SMTP o SMTP_SSL, en función de si el servidortrabaja con SSL o no. Como parámetros deberemos pasar el nombre oIP del servidor y el puerto de conexión, siendo obligatorio únicamenteel primer parámetro, ya que, por defecto, si no se indica ningún valor,se empleará el puerto estándar para SMTP, que es el 25. El siguientecódigo establece la conexión a un servidor determinado: >>> from smtplib import SMTP>>> servidor.SMTP('miservidor.com')
    • 287. Una vez que tenemos la conexión realizada, si el servidor así lorequiere, será necesario pasar el nombre de usuario y contraseña delusuario en cuestión que va a realizar el envío del correo electrónico.Para ello, basta con invocar al método login(), tal y como podemosobservar en la siguiente sentencia: >>> servidor.login('usuario', 'password') En este momento, estamos en condiciones de realizar el envío delcorreo electrónico en cuestión. El método que nos permitirá hacerlo sellama sendmail() y requiere de varios parámetros para su invocación. Elprimero de ellos hace referencia a la dirección desde la que se realizaráel envío. Como segundo parámetro debe indicarse la dirección deldestinatario. El tercer parámetro será el cuerpo del correo en cuestión.Opcionalmente se puede indicar también el asunto del mensaje comootro parámetro adicional. Así pues, para nuestro ejemplo vamos acrear tres variables diferentes antes de invocar al método que realiza elenvío del correo electrónico propiamente dicho: >>>>>>>>>>>> email_from = 'miusuario@miservidor.com'email_to = 'destinatario@miservidor.com'email_body = 'Mensaje de prueba'servidor.sendmail(email_from, email_to, email_body) En caso de que el método sendmail() falle, se lanzará una excepcióndiferente en función del error producido. Para estos casos, el módulo smtplib cuenta con cuatro excepciones diferentes que son: SMTPRecipientRefused, SMTPHeloError, SMTPSenderRefused y SMTPError. Capturando estas excepciones podremos indicarle alusuario qué tipo de error ha ocurrido y actuar en consecuencia.Al igual que hemos visto previamente en el caso de poplib, encuanto finalicemos la interacción con el servidor de SMTP deberemoscerrar la conexión que hemos creado al principio. Bastará con invocaral método quit(), tal y como muestra la siguiente sentencia: >>> servidor.quit()
    • 288. Otro método análogo al set debuglevel() de poplib es el homónimoque existe en smtplib. La invocación debe realizarse a través de lainstancia creada de la clase SMTP o SMTP SSL Por otro lado, el método starttls permite emplear TLS ( Transport
    • 289. Layer Security) para aquellos servidores que lo requieran.Adicionalmente, smtplib cuenta también con un método llamado helo() que permite ejecutar el comando del mismo nombre en el servidor. imap4 IMAP son las siglas de Internet Message Access Protocol, unprotocolo para el acceso a servidores de correo, consulta de buzones ydescarga de mensajes. IMAP4 hace referencia a la versión 4 delmencionado protocolo, siendo esta la más reciente y sustituyendo a laversión 3, ampliamente utilizada durante muchos años.Junto con POP3, IMAP4 es uno de los dos protocolos más utilizadosen la actualidad para obtener correo electrónico desde un servidor.Además, la mayoría de clientes de correo son compatibles con ambosprotocolos. Por otro lado, servidores que ofrecen correo electrónico deforma gratuita, como GMail, sorportan ambos protocolos además de,obviamente, la interfaz web.En Python el módulo que ofrece la interacción con servidoresIMAP4 se llama imaplib y cuenta con tres clases principales: IMAP4,IMAP4_SSL y IMAP4_stream. Las dos primeras son análogas, con laexcepción de que la segunda permite el acceso a través de SSL. Latercera en cuestión soporta la utilización de la entrada y salidaestándar como descriptores de ficheros, permitiendo la ejecución decomandos y salida de resultados empleando las mismas.De forma similar a como hemos aprendido previamente, en el casode POP3 y SMTP, imaplib ofrece métodos para conectarnos a unservidor, autenticarnos e interactuar con el mismo. La conexión alservidor es sencilla, basta con crear una instancia de la clase IMAP4, taly como podemos apreciar en el siguiente ejemplo: >>> from imaplib import IMAP4>>> con = IMAP4() A diferencia de las clases SMTP y POP3, al constructor de IMAP4 nole hemos pasado ningún parámetro. En este caso y por defecto, elservidor utilizado será localhost y el puerto en cuestión será el 143,siendo este el que usan por convención los servidores. Obviamente,
    • 290. podemos pasar al mencionado constructor diferentes valores paraestos parámetros, siendo el primero el servidor y el segundo el puertodel mismo.La autenticación se lleva a cabo a través del método login(), quenecesita el usuario y contraseña como parámetros: >>> con.login('usuario', 'password') Una vez establecida la conexión y realizada la autenticación, en casode que ello sea necesario, estaremos en disposición de interactuar conel servidor. Los mensajes de un determinado buzón pueden ser leídosa través del método search(), que se utiliza para buscar mensajes quecumplan un criterio dado. Antes de invocar a este método, debemosseleccionar el buzón que va a ser leído. Esta acción se ejecuta gracias almétodo select(), que recibe como parámetros el nombre del buzón yun flag para indicar si el acceso será de solo lectura o no. Si elmencionado método no recibe ningún parámetro, el buzón del usuariopreviamente autenticado será el utilizado. Así pues bastará con invocara este método para seguir con nuestro ejemplo: >>> con.select() Ahora que tenemos seleccionado nuestro buzón, podemos obtener,por ejemplo, todos los mensajes que existen en el mismo. Esto es tansencillo como ejecutar la siguiente sentencia: >>> tipo, datos = con.search(None, 'ALL') El anterior método devolverá una tupla donde el segundo elementode la misma es una lista que contendrá los datos referentes a losmensajes encontrados y que cumplen con el criterio de búsqueda.Como parámetros del método search(), hemos utilizado None paraindicar que vamos a emplear el conjunto de caracteres por defecto delservidor y la cadena de texto ALL para indicar que deseamos recuperartodos los mensajes del buzón. En caso, por ejemplo, de necesitar todoslos mensajes enviados desde una dirección concreta, bastaría conpasar un par de parámetros adicionales. Supongamos que deseamosobtener todos los mensajes recibidos y que han sido enviados por lacuenta prueba@miservidor.com . En este caso, realizaríamos la siguientellamada:
    • 291. >>> tip, data = con.search(None, ’FROM','"prueba@miservidor.com"') Volviendo a nuestra llamada anterior, cuando esta sea ejecutada ytengamos los valores tipo y datos, podremos escribir un bucle para, porejemplo, imprimir por pantalla todos los mensajes obtenidos delbuzón. Sirva para ello el siguiente código: >>> for mensaje in datos [0] .split():typ, data = con.fetch(mensaje, '(RFC822)')print('Mensaje {O}: {1}'.format(mensaje, data[0][1]))... Efectivamente, el método fetch() es el responsable de obtener lainformación de cada mensaje, ya que search() nos devuelve lainformación que debemos procesar. Como primer argumento de fetch() hemos pasado, precisamente, información recogida gracias a search(). Por otro lado, el segundo argumento hace referencia alestándar RFC822 que define el formato de los mensajes que debentener los correos electrónicos.No olvidemos que, al finalizar nuestra interacción con el servidorIMAP4, deberemos desconectarnos del mismo, siendo esto posiblegracias al método close(). Además, si nos hemos autenticadopreviamente, deberemos hacer logout. Dado que así lo hemos hechoen nuestro ejemplo, para finalizar nuestro trabajo con imaplib invocaremos a ambos métodos: >>> con.close()>>> con.logout() Aparte de las funcionalidades básicas que hemos comentado, imaplib pone a nuestra disposición otras más avanzadas, como, porejemplo, la copia de un mensaje a un determinado buzón, el borradocompleto de un buzón, el listado de los nombres de todos los buzonesque cumplen una determinada condición o la posibilidad de trabajarcon ACL (Access Control List). En contraste con POP3, la interacción con IMAP4 desde Python esmás compleja. Ello se debe a que, de por sí, el protocolo IMAP4 utilizaun acceso diferente a los buzones y que, además, ofrece la posibilidadde ejecutar distintas acciones.
    • 292. WEB La web es uno de los servicios que más peso tiene en Internet, juntocon el correo electrónico. Los sitios web ya se cuentan por millones ycada vez se encuentran más tipos de aplicaciones que ofrecen sufuncionalidad a través de la web. Tanto es así que el desarrollo deaplicaciones web es uno de los campos a los que más profesionales sededican dentro del ámbito de la ingeniería del software. Python es unabuena opción a la hora de elegir un lenguaje de programación paradesarrollar aplicaciones web, tal y como podremos comprobar acontinuación. Una de las principales razones para ello es la cantidad demódulos, tanto dentro como fuera de la librería estándar, que existenpara interactuar con la web, ya sea, a través de protocolos como CGI yWSGI, o empleando modernos frameworks web.En este apartado también aprenderemos a aplicar la técnica del webscraping para acceder, de forma automática, a la información queofrecen los sitios web. CGI Durante largos años, el estándar CGI (Common Gateway Interface) fue la técnica más utilizada para generar páginas web dinámicas. Estatécnica consiste en ejecutar un programa en el servidor que se encargade construir una página web como respuesta a una petición que serealiza desde un cliente web, siendo el más habitual de estos unnavegador. El lenguaje de programación Perl fue uno de los másutilizados para desarrollar estos programas de servidor a los que se lesllamaba Scripts CGI. La forma de procesar la petición y generar larespuesta se realiza basándose en una serie de reglas que define elestándar CGI (RFC 3875). Floy en día, aún se utiliza el CGI para construirsitios web dinámicos, aunque de forma minoritaria. Ello es debido aque existen otras opciones que ofrecen ventajas significativasrelacionadas con la seguridad y la eficiencia. Incluso se han llegado adesarrollar soluciones específicas para algunos lenguajes y tecnologías,
    • 293. como es el caso de los servlets de Java, Rack para Ruby, mod_php paraPHP y WSGI para Python.En ocasiones puede ser interesante escribir un script CGI para, porejemplo, dotar de una interfaz web a un programa que ya tenemosdesarrollado. Supongamos que tenemos un script escrito en Pythonque se encarga de analizar una serie de ficheros de log y mostrarinformación sobre los mismos. Adaptarlo para que pueda mostrar elresultado a través de una página web es fácil gracias a CGI. Además, nonecesitaremos montar ningún servidor de aplicaciones ni ningunacompleja arquitectura de servidores web. Bastará con que el script seejecute en cualquier servidor web que pueda ejecutar Scripts CGI. En lapráctica, la mayoría de los modernos servidores web tienen estacapacidad, entre ellos Apache, nginx y lighttpd. En la librería estándar de Python encontramos dos módulos quenos permitirán desarrollar scripts CGI, nos referimos a cgitb y a cgi. Ambos módulos nos ofrecen funcionalidades para analizar la peticiónenviada desde el cliente, generar contenido en formato HTML, leervalores de variables pasadas por el método GET y procesar formularios.Para realizar pruebas es conveniente contar con un servidor webcapaz de interpretar y gestionar CGI. No vamos a entrar en detallecómo configurar el servidor y daremos por hecho que el lector poseelos conocimientos básicos sobre el funcionamiento de CGI. Losusuarios de Mac OS X lo tienen sencillo, ya que, por defecto, Apacheestá instalado y configurado para ejecutar CGI. Los scripts CGI debenresidir en el directorio /Library/WebServer/CGI-Executables/. Como ejemplo, vamos a escribir un sencillo script que lanzará unmensaje de bienvenida, leyendo el nombre de una persona quepasaremos como parámetro, a través del método GET de HTTP. Lalectura de este tipo de variables y de aquellas enviadas por el métodoPOST, como ocurre habitualmente con los formularios, se lleva a caboa través de la clase FieldStorage(), la cual pertenece al módulo cgi. Acontinuación, reproducimos el código completo de nuestro primer CGIen Python: #!/usr/local/bin/python3import cgitbimport cgi cgitb.enable()
    • 294. vars = cgi.FieldStorage()nombre = vars.getvalue('nombre') print("Content-type: text/html")print()print("<html>")print(" <body>")print("<h2>Bienvenid@ {0}</h2>".format(nombre))print(" </body>")print("</html>") Para probar nuestro script, deberemos copiar el código en unnuevo fichero al que llamaremos, por ejemplo, hola.py. Luegopasaremos a alojarlo en el directorio determinado del servidor webdonde deben residir los CGI's según la configuración del mismo. Lospermisos de ejecución del fichero deben estar activos, en otro casoobtendremos un error al realizar la petición desde el navegador. Encuanto estemos listo para la prueba, abriremos un navegador y nosdirigiremos a la siguiente URL: http://localhost/cgi-bin/hola.py?nombre=Lucas Si el script se ejecuta correctamente, veremos un mensaje como elque muestra la figura: Figura. Página web generada por el script CGI En la URL hemos pasado una variable llamada nombre con el valor Lucas; para recoger su valor, hemos creado una instancia de la clase FieldStorage(). El método getvalue() de la mencionada clase nos daacceso a la variable pasada por GET.Por otro lado, la primera línea de nuestro script es necesaria paraindicar dónde se encuentra el intérprete de Python que debe utilizarsepara procesar el código del script en cuestión. El método enable() se
    • 295. ejecuta para llevar a cabo una serie de inicializaciones necesarias paratrabajar con CGI. Con la primera sentencia print comienza a generarsela salida HTML que será enviada al navegador. Es por ello, que loprimero que hacemos es fijar el tipo de contenido, en este caso será,obviamente, HTML. También es posible indicar, por ejemplo, que lasalida será texto o json, útil para responder a llamadas realizadas víaAJAX. Justamente después, necesitamos una llamada a print() sinparámetros para indicar que comienza a generarse lo que será elcódigo HTML propiamente dicho. A partir de este punto construimosla página HTML de salida, empleando para ello una serie de etiquetasHTML y el valor de la variable nombre que hemos recogido gracias a laclase FieldStorage(). WSGI Originalmente los frameworks para desarrollar aplicaciones web enPython presentaban la restricción de que cada uno de ellos necesitabaun determinado servidor web que implementara la interfaz necesariapara la comunicación entre ambos. De esta forma, por ejemplo, si unframework había sido desarrollado utilizando mod_python comointerfaz, el servidor web Apache era requerido para la ejecución deaplicaciones. Con objetivo de eliminar este requisito restrictivo sedesarrolló WSGI (Web Server Gateway Interface), una interfaz de bajonivel que permite la comunicación entre diferentes frameworks yservidores web, haciendo portables las aplicaciones entre amboscomponentes.La interfaz WSGI define dos componentes diferenciados, uno es el servidor o gateway y otro es la aplicación o framework que la sirve. El gateway se encarga de gestionar el intercambio de información entreel cliente y la aplicación propiamente dicha.Para trabajar con WSGI, Python pone a nuestra disposición elmódulo wsgiref de su librería estándar, el cual incluye funcionalidadespara procesar variables de entorno, procesar peticiones y generarrespuestas para un cliente web.Como ejemplo de utilización del módulo wsgiref, vamos a construirun sencillo servidor web capaz de manejar peticiones WSGI y
    • 296. responder a las mismas generando contenido que pueda ser leído porun navegador web. Nuestro sencillo servidor devolverá el famosomensaje Hola Mundo! cuando un navegador invoque a una URLdeterminada a la que responderá nuestro servidor. Antes de comenzary para probar nuestro servidor, todo el código que vamos a irmostrando y explicando debe salvarse en un fichero, el cuallanzaremos por la línea de comandos. La primera línea de código seencargará de importar la función necesaria para crear el mencionadoservidor web: from wsgiref.simple_server import make_server Justo después definiremos una función que responderá a la URLraíz de nuestro servidor, devolviendo el mencionado mensaje: def simple_app(environ, start_response):status = '200 OK'header = [('Content-type', 'text/html; charset=utf-8')]start_response(status, header)ret = b"<html><body><h2>Hola Mundo</h2></body></html>"return [(ret)] La variable status devolverá el valor 200, que es el código HTTPestándar para indicar que la respuesta se ha generado correctamente.Por otro lado, la variable header Indicará que vamos a generarcontenido HTML, siendo el conjunto de caracteres elegido el UTF-8.Por último, devolveremos el código HTML que generará en el cliente lapágina web de respuesta. Observemos cómo deberemos devolver unalista que contenga una tupla, en nuestro caso, con un único valor.Ya solo nos queda crear nuestro servidor web y hacer que seejecute. Estas acciones son llevadas a cabo por la función make_server() y por el método serve_forever(), respectivamente. Además, vamos alanzar un mensaje que indique que el servidor ha sido iniciado. Elcódigo necesario para todo ello sería el siguiente: if __name__ == '__main__':httpd = make_server('', 8080, simple_app)print("Lanzando servidor en puerto 8080...")httpd.serve_forever() Ahora solo nos queda invocar a nuestro script por línea decomandos, en cuanto sea lanzado apreciaremos que obtendremos unmensaje como el siguiente:
    • 297. $python servidor_wsgi.pyLanzando servidor en puerto 8080... Si abrimos nuestro navegador y nos conectamos a la URL http://localhost:8080 , obtendremos como respuesta una sencilla páginacon el mencionado Hola Mundo! Volviendo a la terminal donde hemoslanzado nuestro servidor, comprobaremos que aparece una nuevalínea: localhost - - [21/Feb/2012 12:59:21] "GET / HTTP/1.1" 200 45 La anterior línea es similar a la lanzada por el servidor web Apache,la cual habitualmente se refleja en el fichero access_log. Tal y como el lector habrá podido comprobar, con muy pocas líneasde código, Python nos ofrece todo lo necesario para construir unservidor WSGI capaz de responder a peticiones HTTP. Diferentesframeworks se basan en el módulo wsgiref para desarrollar sus propiosservidores, ajustando así como manejar peticiones y respuestas yofreciendo, por ejemplo, capas adicionales de middleware. Web scraping El web scraping es la técnica mediante la cual se extraen datos deuna determinada página web. Habitualmente se utiliza el web scraping para simular el comportamiento de una persona en la navegación deun sitio web, a través de un programa. De esta forma, se automatiza laconexión a un determinado sitio web, la navegación sobre el mismo yla final extracción de la información que contiene. Los robots que sededican al indexado de la web, conocidos como bots, hacen uso deesta técnica para poder leer el contenido de los sitios web. Otrosejemplos de aplicaciones que emplean el scraping son loscomparadores de precios y aquellas que utilizan datos ofrecidos porsitios de terceros para integrarlos con sus propios sitios web.Para realizar el web scraping necesitamos llevar a cabo varias tareascomo la conexión a un sitio web, la autenticación de diferentes tipos, lagestión de cookies, la navegación a través de distintos enlaces incluidosen el propio sitio web y el análisis de la estructura HTML para obtenerla información que contiene. Para esta última fase necesitaremos leer
    • 298. un documento HTML y ser capaces de extraer información analizandolas distintas etiquetas que contiene. Existe un módulo para Python quepodemos emplear para ello, su nombre es Ixml. Por otro lado, el restode acciones comentadas que forman parte del scraping pueden serllevadas a cabo por un módulo de la librería estándar de Pythondenominado urllib.request. De ambos módulos nos ocuparemos acontinuación. URLLIB.REQUEST El módulo urllib.request ofrece la funcionalidad necesaria para abrirconexiones HTTP y HTTPS, obteniendo el resultado ofrecido por unservidor web a través de estos protocolos. Dado que en este procesopuede ser necesario realizar diferentes tipos de autenticación, gestiónde cookies y redirecciones, urllib.request pone a nuestra disposición unconjunto de clases y funciones para realizar estas acciones de formafácil y eficiente.La principal función que nos servirá para conectarnos a una URLdeterminada es urlopen(), la cual recibirá la URL en cuestión comoparámetro. Supongamos que vamos a hacer web scraping para obtenerla previsión del tiempo para la ciudad de Madrid para los próximoscinco días. El servicio Weather de Yahoo! ofrece esta informacióndirectamente en una página web concreta, cuya URL es http://weather.yahoo.com/spain/madrid/madrid-766273/?unit=c . Asípues, para conseguir esta información de forma automática,deberemos conectarnos a la mencionada URL, leer su contenido yanalizarlo. El primer paso lo vamos a realizar empleando lamencionada función urlopen(), mientras que del segundo se encargaráel módulo Ixml, tal y como veremos más adelante. De esta forma,procederemos a importar el correspondiente módulo y a invocar a lafunción en cuestión: >>> import urllib.request>>> url = 'http://weather.yahoo.com/spain/madrid/madrid- 766273/?unit=c'>>> pagina = urllib.request.urlopen(url) Seguidamente, cargaremos el contenido leído en una cadena detexto para que su posterior análisis sea más sencillo. Para ello, basta
    • 299. con invocar al método open() del objeto HTTPResponse, que a su vezfue devuelto por la función urlopen(): >>> contenido = pagina.read() Si en este momento hacemos un print de la variable contenido, observaremos cómo tendremos todo el código HTML que forma lapágina web a la que hemos accedido por la URL en cuestión.En caso de que la URL a la que nos conectemos requiera deautenticación básica HTTP, podemos emplear la clase HTTPBasicAuthHandler(), la cual se encargará de gestionar laautenticación. La siguiente línea muestra cómo instanciar lamencionada clase: >>> auth = urllib.request.HTTPBasicAuthHandler() En cuanto tengamos la instancia, el siguiente paso será añadir lainformación relativa al usuario y contraseña. Ello puede realizarseempleando el método add_password(), tal y como muestran lassiguientes líneas: >>> auth.add_password(user='usuario', passwd= 'password') Después invocaremos a un par de métodos para gestionar laautenticación de forma transparente y llamar a la URL en cuestión de lamisma forma que si esta no necesitara autenticación: >>> opener = urllib.request.build_opener(auth)>>> urllib.request.install_opener(opener)>>> pagina = urllib.request.urlopen('http://localhost/login/') En ocasiones es útil pasar una cabecera (header) en la solicitud deuna página web. Por ejemplo, para indicar que deseamos utilizar undeterminado User-Agent. Esto es práctico cuando necesitamoscomunicarle al servidor que nuestra petición simula ser un navegadorconcreto. A través del método addheaders(), es posible enviar unacabecera HTTP determinada. Supongamos que vamos a pedir unapágina web pero queremos identificarnos como si fuéramos elnavegador web Safari, ejecutado desde la
    • 300. versión 10.6.8 de Mac OS X.El código Python necesario para ello sería el siguiente: >>> opener = urllib.request.build_opener()>>> opener.addheaders([('Useragent',
    • 301. Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_8) ')])>>> opener.open(url) Efectivamente, al método addheaders() debemos pasarle una listaque contenga tantas tuplas como valores diferentes admitidos por unacabecera HTTP necesitemos.En nuestro ejemplo, simplemente hemos indicado un único valor, eldel mencionado User-Agent. Cuando navegamos por un sitio web es común hacer uso de las cookies para guardar información única de cada sesión que semantiene abierta por el navegador web. Dado este hecho, al emplearel web scraping, es habitual tener que pasar la información de lascookies entre diferentes páginas web del mismo sitio web. Esto es fácilen Python gracias a la clase HTTPCookieProcessor, que realiza el trabajopor nosotros de forma transparente. Esta clase acepta en suconstructor, como parámetro, un objeto de tipo CookieJar, cuya clasese encuentra definida en el módulo http.cookiejar , también de lalibrería estándar de Python. Si la petición a una URL determinadarequiere del manejo de cookies, podemos utilizar el siguiente códigopara realizar la gestión: >>>>>>>>>>>>>>> import http.cookiejarcookie = http.cookiejar.CookieJar()handler = urllib.request.HTTPCookieProcessor(cookie)opener = urllib.request.build_opener(handler)opener.open(url) Tal y como habremos podido comprobar, el módulo urllib.request ofrece lo básico para realizar conexiones y gestiones de cookies,cabeceras HTTP y autenticación. Siendo todas estas operaciones laparte inicial del web scraping, nos queda averiguar cómo extraemos losdatos del código HTML. De ello nos ocuparemos en el siguienteapartado, que describe el funcionamiento básico del módulo Ixml. LXML A pesar de que su nombre puede confundir, el módulo Ixml permiteanalizar, tanto ficheros XML como HTML. Desde el punto de vistatécnico, este
    • 302. módulo es un binding para Python de las populareslibrerías libxml2 y libxslt, escritas ambas en el lenguaje de
    • 303. programación C. Haciendo uso de la estructura ElementTree y lascorrespondientes clases y funciones, es posible manejar con soltura laestructura de un documento XML y HTML, lo cual resultará muypráctico para el web scraping. Básicamente, la estructura ElementTree proporciona acceso a los elementos del documento como si lasetiquetas y nodos del mismo estuvieran estructurados en forma deárbol. En la actualidad son muchos los componentes software que sevalen de este tipo de estructura para el análisis de documentosestructurados SGML, como lo son el formato XML y el HTML.Dado que lxml no forma parte de la librería estándar de Python,deberemos acudir a un gestor de paquetes, por ejemplo pip, para suinstalación. Así pues nos bastará con ejecutar la siguiente orden desdela línea de comandos: pip install lxml Las funcionalidades ofrecidas por el módulo lxml son extensas yentre ellas se encuentran las que nos permiten utilizar XPath, XSLT ydiferentes tipos de parsers, como son html5lib y BeautifulSoup. Sinembargo, dado que estamos tratando el tema del web scraping, noscentraremos en la parte específica del análisis de HTML ofrecido por lxml. En concreto, las clases y métodos para este propósito seencuentran en el módulo lxml.html. Para ilustrar de forma sencilla elfuncionamiento del mismo, nos centraremos en el ejemplo queempezamos en el apartado anterior. De esta forma, partiremos de queya tenemos volcado el contenido de la página web del tiempo,ofrecida por Yahoo!, en una variable a la que hemos llamado pagina. Teniendo este hecho en cuenta, procederemos a buscar la informaciónque necesitamos. Esta se encuentra en el interior de un elemento <tr> cuyo atributo class tiene el valor fiveday-temps. Partiendo de este dato,bastará con invocar al método find_class(), el cual nos devolverá unobjeto que representa al elemento HTML en cuestión. Además,llamaremos a la función fromstring() que se encargará de leer unacadena de texto y formar un objeto que cumpla con la estructura ElementTree. Finalmente, solo nos quedará emplear el método text_content() para obtener el contenido del elemento HTML quebuscamos dentro de la página web en cuestión. El código requeridopara todas estas operaciones es el siguiente:
    • 304. >>> from lxml.html import fromstring>>> doc = fromstring(pagina)>>> ele = doc.find_class('fiveday-temps')>>> ele [0] .text_content()'High: 12\xb0 Low: -3\xbOHigh: 14\xb0 Low: -3\xbOHigh: 17\xb0 Low:-3\xbOHigh: 17\xb0 Low: -2\xbOHigh: 17\xb0 Low: 1\xb0' Otro práctico método para acceder al contenido de un determinadonodo de un documento HTML es get_element_id(), el cual se basa en labúsqueda del atributo id de los elementos HTML.Además de acceder a la información de un documento HTML,también podemos modificar su estructura. De ello se encargan dosmétodos diferentes, drop_tree() y drop_tag(), que permiten borrar unelemento y todos sus hijos y borrar un elemento manteniendo sushijos y el texto que el mismo contiene, respectivamente.Para el trabajo con enlaces y formularios presentes en undocumento HTML, el módulo lxml.html ofrece una serie de fundonesadicionales que facilitan en gran medida el trabajo. Ejemplos de ellosson aquellas funciones que nos permiten acceder a todos loselementos de un formulario, las que nos devuelven el tipo de elemento input o las que nos permiten iterar directamente por todos los enlacespresentes en el documento.El lector interesado en profundizar en todos los componentes de lxml.html puede consultar la documentación de referenciacorrespondiente (ver referencias). Frameworks Para el desarrollo de aplicaciones web complejas, los frameworks web ofrecen diversas herramientas y utilidades para agilizar eldesarrollo y facilitar el mantenimiento. En los últimos años su uso se hapopularizado enormemente y en la actualidad es fácil encontrarmultitud de ellos para lenguajes como Java, PHP, Ruby y, cómo no,Python. Uno de los más conocidos y utilizados para este últimolenguaje es Django, el cual, en el momento de escribir estas líneas, aúnno es compatible con Python 3. Sin embargo, contamos con otros quesí lo son, como es el caso de Pyramid y Pylatte, de los que nosocuparemos en este apartado. Más que hacer un análisis exhaustivo de
    • 305. ellos, nos dedicaremos a ver qué puede ofrecernos cada uno en líneasgenerales. PYRAMID El proyecto open source llamado Pylons Project es el responsable deldesarrollo y mantenimiento del framework web Pyramid. El desarrollode este componente software nace con la idea de disponer de un framework web que sea fácil de utilizar, minimalista en cuanto alconjunto de componentes de los que se compone y que sea rápidopara la ejecución de aplicaciones web.La primera versión de Pyramid fue liberada allá por diciembre de2010, siendo la última versión la 1.3 totalmente compatible con Python3. Como interfaz de comunicación emplea WSGI y se inspira en otros frameworks como Zope, Pylons y Django. Pyramid se basa en el patrón de diseño MVC ( Model ViewController ), aunque no lo implementa de la manera tradicional. Esdecir, no existen clases o componentes que actúen directamente conun controlador o un modelo. En cambio, una aplicación construida coneste framework cuenta con un árbol de recursos, que representa laestructura del sitio web. Adicionalmente existen una serie de vistas yun conjunto de clases para representar los modelos de la aplicación.Las vistas son las encargadas de conectar y procesar las peticionesgeneradas en un cliente para ofrecer una salida basada en lainformación de los datos representados por los modelos. Sin embargo,y a diferencia de otros frameworks, Pyramid no ofrece facilidades paraconectar los datos que existan en una base de datos con las clases quedefinen los modelos. No obstante, sí que ofrece facilidades, para, porejemplo, integrar un ORM que realice este trabajo de conexión o mapping. En comparación con otros frameworks web de tipo full stack, Pyramid es minimalista en el sentido de que ofrece lo básico parapoder desarrollar aplicaciones web, siendo el programador elresponsable de añadir módulos adicionales para implementarfuncionalidades avanzadas, como es el caso, por ejemplo, de un ORMpara interactuar con una base de datos.Una de las características principales de Pyramid es que permite,
    • 306. haciendo gala de su carácter minimalista, la creación de aplicacionesweb utilizando un único fichero. Lo cual resulta muy cómodo pararealizar prototipos o para desarrollar pequeñas aplicaciones.Pyramid incluye herramientas para que los programadores puedandepurar de forma fácil y rápida sus aplicaciones. Con este objetivo, labarra de herramientas de depuración (debug toolbar) ofreceinformación como, por ejemplo, sobre las variables enviadas en cadapetición, la configuración actual del servidor e información sobre elrendimiento de la aplicación.Gracias a una serie de componentes, la internacionalización ylocalización de aplicaciones es posible en Pyramid, utilizando comobase gettext para la generación de cadenas de texto en diferentesidiomas.Al igual que otros frameworks, Pyramid permite trabajar consesiones HTTP, definir fácilmente las URL de las rutas accesibles de laaplicación, servir ficheros estáticos y utilizar plantillas (templates) parala generación de páginas HTML.A diferencia de otros, Pyramid ofrece la opción de utilizar eventosdurante el ciclo de vida de las peticiones que recibe. De esta forma, esposible crear manejadores de eventos que respondan con unadeterminada acción cuando se produzca un evento determinado.Incluso es posible definir eventos propios creados por el programador.Ello nos da gran flexibilidad y control sobre la forma de responder a lasdiversas peticiones originadas desde un cliente web.La funcionalidad de Pyramid puede ser extendida gracias a losllamados addons, que son componentes software que permitenutilizar funcionalidades no definidas originalmente en el framework. Entre estos componentes, contamos con algunos que permitenintegrar diferentes librerías JavaScript, emplear JSON o utilizar undeterminado sistema de plantillas.La instalación de Pyramid puede ser llevada a cabo directamente através del gestor de paquetes de Python pip. Para ello, bastará conejecutar desde la línea de comandos la siguiente orden: pip install pyramid Durante el proceso de instalación, serán descargados y tambiéninstalados una serie de paquetes adicionales que Pyramid requierepara su funcionamiento. Dado que este proceso es transparente, no
    • 307. será necesario llevar a cabo ninguna acción adicional.En cuanto finalice la instalación podremos comenzar a escribirnuestra primera aplicación. Como sencillo ejemplo, vamos a crear unsimple script que responda a una determinada URL generando elmensaje Hola Mundo!. Para ello, comenzaremos creando un nuevofichero, al que llamaremos hola_mundo.py, que contendrá lassiguientes líneas iniciales de código: from wsgiref.simple_server import make_serverfrom pyramid.config import Configuratorfrom pyramid.response import Response Seguidamente, pasaremos a crear una función que será ejecutadacomo respuesta a una petición y que, simplemente, mostrará elmencionado mensaje inicial. El código para la función sería el quemostramos a continuación: def hola_mundo(request):return Response('Hola Mundo!' % request.matchdict) Ahora debemos escribir el código de entrada de nuestra aplicaciónPyramid. Inicialmente, instanciaremos la clase que lee la configuraciónpara la aplicación. Dado que estamos trabajando con un ejemplomínimo, no es necesario crear ninguna configuración adicional. La líneade código que se encarga de ello será la siguiente: config = Configurator() El siguiente paso será establecer la ruta de la URL e indicar quéfunción debe ejecutarse como resultado de su petición. En nuestrocaso, vamos a responder a la URL /hola, devolviendo un mensaje através de la función hola_mundo(), la cual ha sido previamentedefinida. El código en cuestión necesario para estas acciones es elsiguiente: config.add_route('hola', '/hola')config.add_view(hola_mundo, route_name='hola') Ya solo nos queda crear un servidor WSGI e indicar que nuestraaplicación debe ser servida por él mismo. Ello es bastante sencillo,siendo este el
    • 308. código que necesitamos: app = config.make_wsgi_app()server = make_server('0.0.0.0', 8081, app)
    • 309. server.serve_forever() A través del primer y segundo parámetro de la función make_server() hemos indicado que el servidor identificado por la IP0.0.0.0, es decir, localhost, debe servir nuestra aplicación Pyramid en elpuerto 8081. Al invocar a nuestra aplicación desde la interfaz decomandos, quedará automáticamente accesible a través del navegadorweb. Así pues, al pedir la URL http://localhost/hola , obtendremos elmensaje Hola Mundo! Una vez descubierto lo básico sobre Pyramid es hora de continuarcon Pylatte nuestro recorrido por los frameworks web para Python 3. PYLATTE Los frameworks web más populares y utilizados en la actualidadsolo son compatibles con Python 2.x. Este es el caso de Django, Flask,Turbogears y web2py. Es por ello que Pylatte nace con la idea dedesarrollar un framework web específicamente diseñado para serejecutado con Python 3.Pylatte es open source y la primera versión fue liberada en octubrede 2011, es por lo tanto un proyecto bastante joven. Sin embargo, suestado es estable, con lo que puede ser empleado sin ningúnproblema.Una de las características principales de Pylatte es que emplea unformato específico llamado pyl, que contiene tanto código HTML comocódigo Python. Internamente, un componente de Pylatte se encarga deanalizar y procesar este tipo de ficheros y producir la correspondientesalida en HTML, la cual será enviada al cliente web.Para la correspondencia entre URL y las acciones que deben serllevadas a cabo, Pylatte utiliza ficheros XML donde se realiza estaconfiguración. Pylatte permite aplicar una serie de filtros, es decir,acciones que se llevan a cabo previa o posteriormente alprocesamiento de una petición. Su configuración también se realiza através de ficheros XML.Dado que Pylatte no incluye un ORM como tal, sino una serie defacilidades para interactuar con la base de datos. De momento, soloMySQL está soportado, siendo necesaria la instalación del módulo MySQLdb para trabajar con el mencionado gestor de bases de datos
    • 310. relacionales.Componentes incluidos el framework facilitan la obtención deparámetros recibidos por GET y POST, así como el uso de sesionesHTTP. Pylatte también incluye un servidor web para poder servir lasaplicaciones que desarrollemos empleando este framework.La instalación de Pylatte se puede realizar a partir de su códigofuente, el cual se encuentra disponible en la página web de descargasdel proyecto (ver referencias).En comparación con otros frameworks web de Python, Pylatteofrece menos componentes para facilitar el desarrollo web, siendo losmismos demasiado básicos. Por otro lado, no implementa como tal elpatrón MVC y permite la mezcla de código Python y HTML en elmismo fichero, lo que, en términos generales, no es aconsejable. Porotro lado, es un framework totalmente pensado para trabajar conPython 3, lo que supone una ventaja significativa con respecto a suscompetidores.La elección de un determinado framework web no es una tareasencilla, influyendo en la decisión diversos factores técnicos yhumanos. Es conveniente tener en cuenta aspectos como quécomponentes necesitamos teniendo en cuenta la funcionalidad de laaplicación, el rendimiento en tipo de ejecución o cómo es la curva deaprendizaje. Animamos al lector a investigar sobre otros frameworksweb para Python con el objetivo de elegir aquel que más se adapte asus necesidades.
    • 311. INSTALACIÓN YDISTRIBUCIÓN DE PAQUETES INTRODUCCIÓN En capítulos anteriores nos hemos valido de ciertos módulos queno están presentes en la librería estándar de Python, para tener accesoa determinadas funcionalidades. Para emplear estos módulos,simplemente los hemos instalado a través de una herramienta llamadapip. Pero ¿qué es esta herramienta? ¿Cómo podemos instalarla yutilizarla? La primera parte de este capítulo se ocupará de los métodosde instalación de módulos, donde obtendremos respuestas a laspreguntas anteriores y aprenderemos lo básico para poder instalar ytrabajar con módulos desarrollados por otras personas y que noforman parte de la librería estándar de Python. En concreto,aprenderemos a utilizar el método estándar, proporcionado por elmódulo distutils y a trabajar con dos gestores de paquetes: pip y easy_install. Como desarrolladores, nosotros también podemos crear nuestrospropios paquetes y distribuirlos para que puedan ser empleados porotras personas. De hecho, este proceso suele formar parte deldesarrollo de software. Preparar nuestros programas para que puedanser distribuidos es una tarea importante que no debe ser pasada poralto. Más aún si vamos a obtener por ello un beneficio económico, yaque nuestros clientes necesitarán instalar el software que hemosdesarrollado para ellos. De la misma forma, en el ámbito del FOSS (Free and Open Source software), este proceso también es fundamentalsi queremos que otros puedan trabajar con nuestro software. En lasegunda parte del presente capítulo nos ocuparemos del proceso dedistribución de software, aprendido a empaquetar nuestros propiosmódulos.Cuando hablamos sobre la distribución e instalación de software deterceros en Python, empleamos tanto el término paquete como módulo. Recordemos que, por convención, un módulo de Python
    • 312. puede ser un simple script escrito en este lenguaje, mientras que un paquete es un conjunto de ficheros Python que guardan entre sí unarelación funcional. Sin embargo, para referirnos a un script de Pythonno solemos emplear la palabra módulo, sino programa o, simplemente script. Es por ello que suele ser habitual emplear módulo cuandotécnica y estrictamente deberíamos decir paquete. Este hecho nos llevaa emplear el término módulo y paquete indistintamente, tal y comohemos hecho a lo largo de los diferentes capítulos de este libro.La parte final del capítulo estará dedicada a los entornos virtuales, los cuales nos permiten tener un control total sobre los módulos queinstalamos en nuestro sistema. Explicaremos cómo crear diferentesentornos y veremos cómo ellos nos permiten tener diferentesversiones del mismo módulo instalados en la misma máquina.
    • 313. INSTALACIÓN DE PAQUETES La instalación en nuestro sistema de paquetes Python desarrolladospor terceros puede ser llevada a cabo, básicamente, de dos formasdiferentes. La primera de ellas es a través de un gestor, como pip, y laotra es directamente a partir del código fuente. La utilización de uno uotro método dependerá de qué método han decidido losdesarrolladores para distribuir su software. Supongamos quenecesitamos un determinado paquete, ofrecido como open source ycuyos desarrolladores han decidido distribuirlo exclusivamenteutilizando el código fuente. En este caso no podremos recurrir a ungestor de paquetes, ya que los desarrolladores no han ofrecido laforma de instalarlo a través del mismo. En este caso la única opción esrealizar la instalación desde el fuente. En otros casos, será posibleutilizar ambos métodos, quedando a nuestra elección el queprefiramos.A continuación, describiremos ambos métodos para la instalaciónde módulos en Python. Comenzamos aprendiendo cómo realizar lainstalación a partir del código fuente. Instalación desde el código fuente Python nos ofrece un módulo en su librería estándar que incluyeuna serie de herramientas para poder empaquetar y distribuir elsoftware que hemos desarrollado en este lenguaje. De esta forma, apartir del código fuente y utilizando estas herramientas es posiblecrear un fichero listo para su distribución e instalación. Este ficheroestará comprimido y el formato del mismo dependerá del sistemaoperativo. Por ejemplo, en Mac OS X y Linux se emplea un tarball, mientras que en Windows es un ZIP. De los detalles de este procesonos ocuparemos más adelante. El módulo en cuestión se llama distutils y la utilización del mismo está considerada como la forma estándar decrear y distribuir paquetes de Python.Si necesitamos instalar un paquete creado con distutils, bastará con
    • 314. obtener el fichero en el que está siendo distribuido, descomprimir elmismo y ejecuta un comando determinado. Por ejemplo, supongamosque nuestra máquina está corriendo Fedora Linux y que el paqueteque vamos a instalar viene en un fichero llamado hoia-1.O.tar.gz. Elprimer paso será descomprimirlo, acción que llevaremos a cabo através de un terminal: $ tar -xzvf hola-1.0.tar.gz Seguidamente, accederemos al directorio que ha sido creado comoconsecuencia de la descompresión del fichero y ejecutaremos un scriptllamado setup.py, que encontraremos en el nuevo directorio. A estescript le pasaremos un argumento: install. Ambas acciones comentadasserán ejecutadas con los siguientes comandos: $ cd hola-1.0$ python setup.py install En cuanto lancemos el último comando se procederá a lainstalación automática del nuevo paquete. Al finalizar el proceso, elmismo quedará directamente accesible y podremos importarlo tantoen nuestros programas, como desde la línea de comandos delintérprete de Python. Esto se debe a que, por defecto, el script setup.py se ha encargado de copiar los ficheros Python al directorio donde, porconvección, se almacenan los paquetes de terceros. Este directoriovariará en función del sistema operativo. Por ejemplo, en Windowsestará en C:\Python3.2/Lib/site- packages. En los sistemas Linux, elmenionado directorio suele estar en /usr/lib/python/lib/site-packages. Debemos tener en cuenta que esta ruta puede variar en función de laversión de Python que tengamos instalada o de si hemos escogidopara la instalación un directorio diferente al que se emplea pordefecto. En cualquier caso, el directorio site-packages suele encontrarsecomo subdirectorio de lib. El script setup.py no solo se encarga de copiar los ficheros aldirectorio comentado anteriormente, sino que también realiza otrasoperaciones, como, por ejemplo, la compilación de código. Pensemosen el caso de un paquete que incluye código C o C++ que es invocadodesde Python. Dado que estamos distribuyendo el código fuente yC/C++ necesita ser compilado, para la instalación del paquete seránecesario realizar este proceso. De ello se ocupará automática y
    • 315. transparentemente el script setup.py. Sin embargo, existe unargumento que puede ser pasado al mencionado script para solocompilar el código fuente, tal y como muestra el siguiente comando: $ python setup.py build No olvidemos que deberemos invocar al mismo script pasando elargumento install para realizar la instalación, una vez que hayaterminado la compilación. En caso contrario solo habremos compiladoy el módulo no será instalado.Si en lugar de realizar la instalación de nuevo módulo en el path pordefecto preferimos hacerla en otro diferente, podemos pasar un tercerargumento al script setup.py. En concreto podemos emplear --user, para realizar la instalación en el directorio home del usuario que estállevando a cabo la instalación; --home, para un directorio determinado,y --prefix, utilizado en el caso de Windows, ya que el concepto de home suele ser exclusivo de sistemas UNIX. Por ejemplo, para que unmódulo quede instalado en el directorio c:\Temp\Python\Lib\site-packages de una máquina Windows, ejecutaremos el siguientecomando: python setup.py install --prefix=\Temp\Python Puede que en un momento determinado nos interese tener uncontrol sobre qué tipo de ficheros se instalan en qué directorioscuando invocamos a la instalación de un método. Para este casocontamos con una serie de parámetros que también pueden serpasados a setup.py. De esta forma la personalización del esquema deinstalación es completa. En concreto, contamos con argumentos paraindicar dónde instalar los módulos puros escritos en Python; aquellosque son de extensión, escritos en C/C++; todos los módulos, sindiferencia de tipo; aquellos ficheros que son considerados scripts y quedeben ser alojados en un directorio accesible a través de la variable deentorno PATH; los datos puramente dichos y las headers de losficheros C. Por ejemplo, supongamos que vamos a instalar un móduloque solo contiene dos ficheros Python y que podrán ser invocadosdirectamente por línea de comando. Es decir, consideraremos que son scripts, similares a los de bash y que, por tanto, queremos que seencuentren, por ejemplo, en el directorio /usr/local/bin/. En este caso,bastará con ejecutar el siguiente comando:
    • 316. $ python setup.py --install-scripts=/usr/local/bin/ Otro parámetro interesante, que también acepta setup.py para lainstalación de módulos que contienen extensiones escritas en C o C++,es -compiler. Este nos sirve para indicar qué compilador de C/C++deseamos utilizar, siendo los valores diferentes en función del sistemaoperativo donde estamos realizando la instalación. Por ejemplo, enWindows podemos usar algunos diferentes como cygwin, mingw32 y Borland C++. El siguiente comando sería utilizado, por ejemplo, paraindicar que deseamos compilar solo con cygwin : python setup.py build --compiler=cygwin Obviamente, para utilizar cualquiera de los compiladoresanteriormente mencionados, deberemos tenerlos instalados en nuestramáquina.Ahora que hemos aprendido a instalar un paquete a partir de sucódigo fuente, estamos en condiciones de aprender el segundométodo, del que nos ocuparemos a continuación. Gestores de paquetes Un gestor de paquetes es un software que nos permite realizardiversas acciones como son, la consulta, la búsqueda y, por supuesto,la instalación de módulos de Python. Gracias a estos gestorespodemos realizar la instalación de paquetes con solo un comando.También son prácticos para descubrir si existen algunos paquetesrelacionados con un criterio específico. Los usuarios de Linux estánacostumbrados a sistemas similares como son, por ejemplo, apt-get y yum, los cuales permiten instalar, desinstalar y buscar, entre otrasoperaciones, software para nuestro sistema operativo.Tal y como hemos comentado previamente, para Python contamoscon easy install y pip. Aunque son diferentes, ambos implementan lamisma funcionalidad básica y, lo que es más importante, los dosutilizan la misma fuente para instalar los paquetes. ¿De dóndeobtienen estos gestores la información sobre los paquetes que puedenser instalados? La respuesta a esta pregunta es sencilla, ambos usancomo origen un servicio denominado Python Package Index (PyPi). Este
    • 317. servicio está accesible a través de Internet y es considerado elrepositorio oficial de paquetes para Python. En otros lenguajes existenservicios similares como CPAN para Perl y PEAR para PHP. A través delsitio web de PyPi (ver referencias) podemos acceder a informacióncomo el número total de paquetes que hay, el listado completo depaquetes o los últimos 40 que han sido añadidos. Además, en elmismo sitio web, se ofrece información para aquellos desarrolladoresinteresados en publicar sus propios módulos para que quedenaccesibles desde el repositorio. Actualmente, más de 19.000 paquetespueden ser instalados a través de PyPi.A continuación, descubriremos cómo emplear los gestores easy_install y pip para interactuar con PyPi. EASY_INSTALL Existe un módulo para Python llamado distribute que contiene,entre otros componentes, un script llamado easy_install. Este scriptserá el que utilicemos para invocar al gestor de paquetes del mismonombre. Dado que distribute no forma parte de la librería estándar dePython, recurriremos a su código fuente para realizar la instalación delmismo. Para ello, seguiremos una serie de sencillos pasos. El primeroserá descargarnos un script que se encargará de descargar el códigonecesario y de realizar automáticamente la instalación. Así pues,deberemos descargar el fichero distribute_setup.py que se encuentraaccesible a través de la página web (ver referencias) de descargas delmencionado módulo para Python. En sistemas Linux y Mac OS X eshabitual contar con herramientas como wget y curl, que permiten ladescarga de ficheros desde la línea de comandos. Si tenemos uno deestos dos programas instalados, podremos descargarnos elmencionado fichero empleando, por ejemplo, el siguiente comandoque hace uso de curl : $ curl -O http://python-distribute.org/distribute_setup.py En cuanto dispongamos del script de instalación en nuestramáquina, procederemos a la instalación propiamente dicha del módulo distribute. Para ello, solo necesitaremos ejecutar el siguiente comandodesde un terminal:
    • 318. python distribute_setup.py Dado que distribute es un módulo empaquetado siguiendo elestándar para ello propuesto oficialmente por Python, es posiblerealizar la instalación del mismo directamente a través del tarball ofrecido por los desarrolladores del módulo en cuestión. Es decir,opcionalmente, en lugar de realizar la instalación como hemos vistoanteriormente, podemos recurrir al método estándar. Para ello, enprimer lugar, nos descargaremos el mencionado tarball. En elmomento de escribir estas líneas, la última versión es la 0.6.24, asípues, será esta la que empleemos para mostrar cómo realizar lainstalación. Como ejemplo, partiremos de un sistema Linux, aunque elproceso es prácticamente igual para Mac OS X y Windows.Comenzamos con la descarga del fichero en cuestión: $ curl -Ohttp://pypi.python.org/packages/source/d/distribute/distribute0.6.24.tar.gz Después procederemos a la descompresión del fichero descargado: $ tar -zxvf distribute-0.6.24.tar.gz Ahora llega el momento de acceder al nuevo directorio creado einvocar a setup.py para que comience la instalación: $ cd distribute-0.6.24$ python setup.py install Al finalizar la instalación, con independencia del método empleado,ya tendremos acceso al mencionado módulo y al script easy_install , através del cual podremos instalar, buscar o consultar paquetes dePython que se encuentran registrados en PyPi. Para comprobar que lainstalación se ha realizado correctamente y que, efectivamentetenemos acceso al gestor de paquetes, comprobaremos la existenciade un fichero llamado easy_install.py, en el caso de sistemas UNIX o de easy_install.exe, en el caso de Windows. Si estamos trabajando en esteúltimo sistema operativo y el directorio raíz de la instalación de Pythones C:\Python3.2, podremos comprobar cómo existirá un subdirectoriollamado Scripts donde localizaremos el fichero easy_install.exe. Conindependencia del sistema
    • 319. operativo que estemos utilizando, esinteresante que el script easy_install sea accesible a través de la
    • 320. variable de entorno PATH. Si realizamos esta configuración, tanto enMac OS X, como en Linux y Windows, tendremos acceso directo a easy_install desde la línea de comandos. Así pues, nuestra primerainvocación al script será para echar un vistazo a las opciones que nosofrece. Para realizar esta acción, por ejemplo en Linux, bastará conlanzar el siguiente comando: $ easy_install --help Al ejecutar el comando anterior observaremos cómo obtenemosinformación sobre las opciones y los argumentos adicionales quepodemos pasar al script para realizar diferentes acciones.La principal acción que puede ser llevada a cabo con easy_install es,lógicamente, la instalación de paquetes para Python. Ello es tansencillo como indicar como argumento el nombre del paquete encuestión. Por ejemplo, supongamos que deseamos instalar el paquete lxml. El comando para ello será el siguiente: $ easy_install lxml Automáticamente, easy_install se encargará de acceder a PyPi,buscar el fichero de distribución dado por los desarrolladores, compilarel código C/C++ -en caso de que sea necesario- y proceder a la copiade los correspondientes ficheros al directorio site-packages de nuestrointérprete de Python. Debemos tener en cuenta que PyPi actúa comoregistro de los paquetes, pero en ocasiones no directamente comorepositorio. Esto significa que algunos desarrolladores alojan el ficherode distribución de un módulo en un servidor distinto. Este es el caso,por ejemplo, de lxml. Durante el proceso de instalación del mismocomprobaremos cómo se van lanzando una serie de líneas que nosvan informando de qué acción se está llevando a cabo en cadamomento. Observando esta información, comprobaremos cómo ladescarga del fichero de distribución de lxml se ha realizado desde laURL http://lxml.de/files/lxml-2.3.3.tgz. En lugar del nombre del paquete, easy_install también acepta elpaso como argumento de una URL que indique dónde se encuentra elfichero de distribución correspondiente al módulo en cuestión quedeseamos instalar. Esto también permite instalar paquetes que noestén referenciados por PyPi.Otra interesante funcionalidad referente a la instalación de
    • 321. paquetes es la opción de Indicar el número de versión que deseamosinstalar de un determinado paquete. En este caso, deberemos indicarel número de versión concreto, tal y como muestra el siguienteejemplo: $ easy_install lxml==2.3.3 Además de la instalación, también es posible actualizar a la versiónmás reciente que exista un paquete que tengamos previamenteinstalado. Esta acción puede ser llevada a cabo empleando la opción —upgrade, tal y como muestra el siguiente ejemplo: $ easy_install --upgrade lxml Cuando ya no necesitemos un paquete podremos borrarlodirectamente a través de easy_install. Para realizar esta acción, soloserá necesario indicar el parámetro -m precedido por el nombre delpaquete que deseemos desinstalar. Por ejemplo, el siguiente comandodesinstalaría el paquete lxml de nuestro sistema: $ easy_install -m lxml Si por defecto deseamos emplear una fuente adicional a PyPi parala instalación de paquetes, podremos hacerlo a través de un fichero deconfiguración llamado setup.cfg. Este fichero, por defecto, residirá en eldirectorio desde donde estamos realizando la instalación.Adicionalmente, es posible también crear un fichero en el directorio home del usuario, al que llamaremos pydistutils.cfg. De esta forma,conseguiremos que la configuración indicada en este último ficherosea utilizada por easy_install, con independencia del directorio dondesea invocado. Las siguientes líneas, correspondientes al mencionadofichero de configuración, muestran cómo indicar que vamos a emplearun servidor adicional para la instalación de paquetes: [easy_install]find_links = http://miservidor/python/paquetes/ Hasta aquí todo lo básico para trabajar con easy_install, seguimosnuestro recorrido por los gestores de paquetes con pip .
    • 322. PIP
    • 323. El gestor de paquetes pip nació con la idea de ofrecer unaalternativa a easy_install, que además, ofreciera funcionalidadesadicionales. Además de la funcionalidad básica de instalación depaquetes, pip permite actualizaciones, búsquedas, desinstalaciones eincluso es capaz de mostrar información sobre todas las versionesespecíficas de cada paquete instalado en un sistema.Para instalar pip deberemos previamente tener instalado distribute, si aún no lo hemos hecho, antes de continuar, deberemos realizar lainstalación de este módulo, tal y como hemos explicado en el apartadoanterior.La instalación de pip se puede realizar de dos formas diferentes.Una de ellas es a través del script de instalación ofrecido por susdesarrolladores. La otra forma es empleando el fichero de distribucióne invocando al script setup.py. Ambas técnicas son sencillas, tal y comopodremos comprobar. El primer método requiere de la descarga delinstalador en cuestión, acción que puede ser realizada directamente através de herramientas como curl o wget. En este ejemplo, utilizamos wget desde Fedora Linux: $ wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py En cuanto tengamos descargado el script, procederemos a suejecución: $ sudo python get-pip.py Para la instalación alternativa necesitaremos el tarball dedistribución, el cual también puede ser descargado a través de curl owget. En esta ocasión, vamos a invocar a curl, tal y como muestra elsiguiente comando: $ curl -O http://pypi.python.org/packages/source/p/pip/pip-1.0.tar.gz El siguiente paso será la descompresión del tarball que acabamosde descargar: $ tar -zxvf pip-1.0.tar.gz Ahora es el turno de acceder al nuevo directorio y proceder a lainvocación del script setup.py, tal y como muestran las siguientessentencias:
    • 324. $ cd pip-1.0$ python setup.py install La instalación de módulos en sistemas UNIX se realiza, por defecto,en un determinado directorio en el que solo el usuario root tienepermisos de escritura. Este hecho implica que para instalar paquetes,tanto con easy_install, como con pip, deberemos invocar a estosprogramas como el mencionado usuario root. En sistemas operativoscomo Ubuntu, esto es tan sencillo como ejecutar los scripts a través delcomando sudo. En otro caso, es necesario cambiar a root antes deinvocar a cualquiera de estos scripts. En Windows no tendremos esteproblema, ya que, por defecto, la instalación se realiza en un directoriodonde el usuario tendrá acceso de escritura.Una vez que la instalación de pip ha finalizado satisfactoriamente,podremos ejecutarlo, por ejemplo, para mostrar las opciones que nosofrece. El siguiente comando se encargará de esta acción: $ pip --help Tal y como hemos aprendido en capítulos anteriores, la instalaciónde un paquete a través de pip es tan sencilla como invocar al comando install, seguido del nombre del paquete que deseamos instalar. Comoejemplo sirva el siguiente comando: $ pip install lxml La actualización de paquetes a la versión más reciente liberadatambién se realiza empleando el comando install, pasándole comoparámetro -U, tal y como muestra el siguiente ejemplo: $ pip install -U lxml Al igual que easy_install, pip también admite la instalación desde undeterminado tarball accesible a través de una URL Para este tipo deinstalación bastará con pasar la URL en cuestión al comando install. Muy interesante resulta la opción de poder instalar un paquetedirectamente a través de un sistema de control de versiones. Enalgunos casos, esto puede sernos muy útil, para, por ejemplo, realizarinstalaciones desde nuestro propio sistema de control de versiones opara asegurarnos de obtener el
    • 325. último código que está siendodesarrollado. El parámetro -e , seguido de la URL en cuestión, será el
    • 326. que deberemos pasar al comando install para realizar este tipo deinstalación. Por ejemplo, para instalar el paquete prueba que seencuentra en un sistema de control de versiones git, ejecutaríamos elsiguiente comando: $ pip -e git://miservidor.com/prueba.git#egg=prueba A diferencia de easy_install, pip sí que nos permite realizarbúsquedas de paquetes. Gracias a esta funcionalidad, es posible pasarcomo argumento un nombre de paquete y pip se encargará deofrecernos una serie de resultados, consultando para ello PyPi ybuscando todos los paquetes que coinciden con el criterio debúsqueda. Por ejemplo, supongamos que deseamos buscar paquetesrelacionados con XML. El siguiente comando será el que emplearemospara realizar la búsqueda: $ pip search xml Por la salida estándar recibiremos un completo listado, indicando,tanto el nombre del paquete, como una pequeña descripción. Siademás uno de los paquetes listado coincide con que ya lo tengamosinstalado, se nos informará de ello, mostrándonos informaciónadicional sobre la versión más reciente que existe sobre el mismo.Como ejemplo, mostramos unas cuantas líneas obtenidas al invocar alcomando anterior de búsqueda: ll -xist - Extensible HTML/XML generator, cross-platformtemplating language, Oracle Utilities and various othertoolslxml - Powerful and Pythonic XML processing library combininglibxml2/libxslt with the ElementTree API.INSTALLED: 2.3.3LATEST: 2.3beta1Chameleon - Fast HTML/XML Template Compiler.INSTALLED: 2.7.3 (latest)generateDS - Generate Python data structures and XML parser fromXschemaarchgenxml - UML to code generator for Plonelibxml2dom - PyXML-style API for the libxml2 Python bindingsbridge - General purpose XML library forCPython and IronPythonAmara - Library for XML processing in Python
    • 327. La desinstalación de módulos puede ser ejecutada fácilmentegracias al comando uninstall, el cual se encargará de eliminar aquellosficheros instalados a través de un comando install. Consultar todos los paquetes instalados en nuestro sistema esposible gracias al comando freeze, el cual produce un listado con losnombres y versiones de todos ellos. Después del nombre de cadapaquete aparecerán dos símbolos de igual, seguidos de la versión encuestión. Por ejemplo, tras invocar al siguiente comando, obtendremosuna serie de líneas como las que se indican a continuación: $ pip freezewsgiref==0.1.2wxPython==2.8.8.1wxPythoncommon==2.8.8.1wxaddons==2.8.8.1xattr==0.5zope.deprecation==3.5.0zo pe.interface==3.8.0 En lugar de pasar diferentes opciones para cada comando, puedeser interesante emplear un fichero de configuración donde indiquemoslas opciones, con sus correspondientes valores que necesitemos. Elfichero en cuestión se llama pip.conf o pip.ini, en el caso de Windows, ysu formato se corresponde con un INI estándar, donde podemos tenerdiferentes secciones y donde cada línea indica propiedad igual a valor. El mencionado fichero puede ser encontrado en un subdirectorio,llamado pip o .pip, dentro del directorio home del usuario en cuestiónque invoca a pip. En un momento dado puede ser interesante obtener informaciónsobre la sintaxis de cada uno de los comandos de pip. Para realizar estaacción, bastará con emplear otro comando llamado help. Así pues, porejemplo, para consultar la información sobre el comando search, ejecutaremos la siguiente orden: $ pip help search Tanto easy_install como pip son capaces de manejarautomáticamente las dependencias entre paquetes. Esto implica que, siun determinado paquete requiere de otros para su funcionamiento,estos serán instalados automáticamente. Esta funcionalidad es unagran ventaja y nos asegura que cada paquete será instalado yfuncionará correctamente.
    • 328. En este punto termina nuestro recorrido por la instalación depaquetes, en el siguiente apartado trataremos sobre cómo prepararnuestro software para que pueda ser distribuido e instalado por otrosdesarrolladores o usuarios.
    • 329. DISTRIBUCIÓN Para que otros desarrolladores puedan instalar y utilizar nuestrosmódulos de Python, deberemos preparar estos de una formadeterminada. En la librería estándar de Python existen un módulo quenos ayudará a esta tarea, llamado distutils. Gracias al mismo podremos empaquetar nuestros módulos Python para que otros puedaninstalarlos, por ejemplo, a través de PyPi. El proceso general para crear un fichero de distribución y ponerlo adisposición de otras personas consta de los siguientes pasos: Creación del fichero setup.py con la información sobre elmódulo.Creación del fichero para distribución, puede ser un tarball o ZIP.Registro del paquete en PyPi, si deseamos que pueda serindexado.Subida del paquete al repositorio de PyPi. Este paso esopcional. A través de un ejemplo básico, vamos a descubrir cómo aplicar lospasos anteriormente mencionados. Partiremos de la idea de quenuestro paquete estará compuesto por un solo fichero Python, el cualcontendrá una clase. Nuestra clase contendrá un constructor y unmétodo para imprimir el popular Hola Mundo!. En concreto el códigode nuestro hola.py, será el siguiente: class Hola:def __init__(self):passdef say_hello(self):print('Hola Mundo!") Ahora necesitamos crear un directorio al que copiaremos nuestronuevo fichero. Podemos llamarlo hola y dentro del mismo vamos acrear el fichero setup.py. El contenido del mismo estará formado por
    • 330. las siguientes líneas: from distutils.core import setupsetup(name='hola', version='1.0', py_modules= [' hola']) A través de la función setup() vamos a indicar diferente tipo deinformación sobre el paquete. Hemos elegido la información mínimapara nuestro ejemplo, indicando, simplemente, el nombre del paquete,la versión y los módulos Python de los que consta. Esta función aceptaargumentos opcionales para indicar datos, como el tipo de licencia,una descripción sobre el paquete, el nombre y correo electrónico delautor y una lista de clasificadores. Esta lista sirve para indicar qué tipode paquete es, a qué audiencia va dirigido o para qué versión dePython ha sido desarrollado. En general esta información va dirigida apoder clasificar el paquete, de forma que pueda ser fácilmenteindexado por PyPi. Una lista completa de todos los clasificadoressoportados puede consultarse en la página web (ver referencias) dePyPi dedicada a ello.El siguiente paso será invocar a un comando que nos crearádirectamente el fichero de distribución. En Windows será un ZIP,mientras que en Mac OS X y Linux será un tarball. El comando encuestión es el siguiente: $ python setup.py sdist Si echamos un vistazo a nuestro directorio hola, comprobaremoscómo se han creado un nuevo directorio, llamado dist, y un fichero conel nombre MANIFEST. El contenido de este fichero está formado por lassiguientes líneas, que hacen referencia a una serie de informaciónsobre el paquete: Metadata-Version: 1.0Name: holaVersión: 1.0Summary: UNKNOWNHome-page: UNKNOWNAuthor: UNKNOWNAuthor-email: UNKNOWNLicense: UNKNOWNDescription: UNKNOWNPlatform: UNKNOWN En lo que al directorio dist respecta, comprobaremos cómo se ha
    • 331. creado el fichero de distribución, en nuestro caso, se llama hola-1.O.tar.gz. Ya estamos preparados para distribuir nuestro paquete hola. Podríamos hacer el fichero accesible desde un servidor web o FTP, paraque pudiera ser descargado. Para poder instalarlo, habría que seguir elprocedimiento estándar que hemos visto previamente, es decir, bajarseel fichero, descomprimirlo, entrar en el directorio y ejecutar pythonsetup.py install. Opcionalmente, podemos hacer que PyPi indexe nuestro paquetepara que pueda ser buscado e instalado a través de gestores como easy_install y pip. Antes de continuar y dado que PyPi requiere que elnombre y correo electrónico del autor de cada paquete sea indicado,deberemos modificar nuestro setup.py para añadir estos datos y volvera generar el tarball. Por otro lado, PyPi también necesita que el autor,que vaya a registrar y/o subir su paquete, esté registrado en susistema. Cualquiera puede registrarse a través de un formulario webdisponible en la correspondiente página web (ver referencias) de PyPi.En cuanto cumplamos con la formalidad del registro esteremoslistos para registrar nuestro nuevo paquete. Esto es tan sencillo conejecutar el siguiente comando y seguir las instrucciones que se nosirán dando: $ python setup.py register Justo después de ejecutar el comando anterior, veremos cómoaparece un prompt que nos preguntará si deseamos utilizar un usuariode PyPi existente, crear uno nuevo o abortar el proceso. Daremos porhecho que ya contamos con un usuario, así pues, elegiremos laprimera opción, introduciremos nuestro usuario y contraseña.Automáticamente se procederá al registro del paquete en cuestión.Una vez registrado el paquete, también podemos subirlo alrepositorio de PyPi, con el objetivo de que pueda ser descargadodesde el mismo. En este caso, simplemente bastará con ejecutar elsiguiente comando: $ python setup.py sdist upload Llegados a este punto, cualquiera podrá instalar nuestro paquetedesde, por ejemplo, pip. Si el paquete es instalado, podrá serimportado en cualquier entorno virtual o sistema donde haya sidoinstalado. De hecho, podemos comprobar que funciona correctamente
    • 332. ejecutando las siguientes líneas de código: >>> import hola>>> h = hola.Hola()>>> h.say_hello()Hola Mundo! Como el lector habrá podido comprobar, Python nos ofrece, através de su módulo distutils y del sistema PyPi, un completo sistemapara distribuir nuestros paquetes.
    • 333. ENTORNOS VIRTUALES Hasta el momento hemos aprendido a instalar paquetes en eldirectorio asignado por defecto para ello y que afectan a todo elsistema. Esto implica que si tenemos dos paquetes instalados que, a suvez, emplean un tercero, ambos tendrán que usar la versión instaladade este último. Pero ¿qué ocurre si actualizamos a una nueva versiónde este tercer paquete? En principio, uno de los dos que lo utilizanpodría no funcionar correctamente por los cambios aplicados en lanueva versión. Además, ¿qué pasaría si uno de estos paquetes sí querequiere de esta nueva versión? Por otro lado, ¿cómo podríamosutilizar exclusivamente una determinada versión de un módulo del quedepende otro? ¿Qué pasaría si deseamos instalar una serie de módulospero no tenemos acceso al directorio por defecto? Estas preguntassuelen hacérselas muchos administradores de sistemas cuando tienenque mantener un servidor y diferentes aplicaciones Python que correnen el mismo. También son muchos los desarrolladores que seenfrentan a estos escenarios, cuando por ejemplo, necesitan probar siuna nueva versión de un determinado módulo es compatible con laaplicación ya desarrollada.Dadas las preguntas anteriores y las situaciones que las originan,parece conveniente encontrar una solución que nos ayude a resolver elproblema. Es aquí donde podemos contar con los entornos virtuales. Existen para Python un conjunto de herramientas que permiten crearentornos aislados, donde cada uno de ellos es responsable de suspropios paquetes. Es decir, es posible crear una sandbox en la queinstalar diferentes módulos que serán independientes, tanto de los queexisten a nivel de sistema (directorio site-packages), como unos deotros. De esta forma, por ejemplo, para comprobar si un determinadomódulo es compatible con nuestra aplicación, bastará con crear unentorno virtual, instalar todos los paquetes, incluida la nueva versióndel paquete en cuestión. Si la aplicación funciona correctamente,entonces podremos instalar esta nueva versión en nuestro entorno deproducción.
    • 334. virtualenv Para la gestión de entornos virtuales en Python, necesitamosinstalar un módulo llamado virtualenv, el cual está accesible a través dePyPi. Ello implica que podremos instalarlo con un gestor de paquetescomo easy_install y pip. Por ejemplo, empleando este últimoejecutaríamos el siguiente comando: $ pip install virtualenv Una vez instalado virtualenv, tendremos acceso a un script Pythoncon el mismo nombre, el cual puede ser invocado directamente. Porejemplo, en Linux podremos lanzar el siguiente comando: $ virtualenv -help Para crear un nuevo entorno virtual, bastará con pasar la ruta deldirectorio donde queremos que sea creado. El siguiente ejemplo crearáun entorno virtual en el directorio /tmp/env de un sistema Linux: $ virtualenv /tmp/env --no-site-packages Observando la salida del comando anterior descubriremos cómo lasherramientas proporcionadas por distribute y el gestor de paquetes pip son instalados también. Si nos fijamos en el directorio que hemospasado como argumento al comando virtualenv, comprobaremoscómo se han creado tres subdirectorios diferentes. El primero de elloses bin y contiene una serie de scripts que podemos utilizar desdenuestro entorno. Entre ellos encontraremos al mencionado pip y otroscomo actívate, del cual nos ocuparemos más adelante. Otro de losdirectorios es include, que contendrá el intérprete de Python y unaserie de ficheros fuente que serán empleados, en caso de que algunosde los paquetes que instalemos requiera de ello, para compilar. Elúltimo directorio en cuestión es lib, que contendrá, entre otros ficherosy directorios, el subdirectorio site-packages. Serán en este directoriodonde se instalen los paquetes que pertenezcan a nuestro entornovirtual.Ya tenemos nuestro entorno creado, así que nuestro siguiente
    • 335. pasoserá activarlo para poder comenzar a trabajar con él. Esta acción selleva a cabo a través del script activate, que se encuentra en el
    • 336. directorio bin. En Linux, ejecutaríamos los siguientes comandos paraactivar nuestro entorno recién creado: $ cd /tmp/env$ source bin/activate(env)$ La última línea indica que el prompt de la línea de comandos habrácambiado para mostrar, entre comas, el nombre del entorno que estáactivado. A partir de este momento, si instalamos un módulo loharemos solo en el entorno virtual en cuestión, quedando el mismoaccesible exclusivamente en dicho entorno. Debemos tener en cuentaque esto ha sido posible gracias a que pasamos el argumento —no-site-packages al crear nuestro entorno. Si no lo hubiéramos hecho, elentorno virtual tendría acceso también a los paquetes del sistema. Estoúltimo podría ser interesante en algunos casos, pero generalmente, sesuele emplear el mencionado argumento para lograr entornos virtualescompletamente aislados.Al lanzar el siguiente comando, comprobaremos cómo los paqueteslistados son diferentes a los que obtendremos lanzando el mismocomando fuera de nuestro entorno virtual: (env)$ ./bin/pip freeze Esto nos demuestra que, efectivamente, los paquetes quedanaislados unos de otros, quedando nuestro entorno aislado del resto depaquetes Python instalados en el sistema. Cada vez que creemos unnuevo entorno virtual lo haremos en un directorio diferente delsistema de ficheros. virtualenvwrapper Con el objetivo de facilitar el manejo de virtualenv y los diferentesentornos virtuales de Pyton que pueden ser instalados en la mismamáquina, se desarrolló otro paquete conocido como virtualenvwrapper. Como su propio nombre indica, este paquetecontiene una serie de herramientas que actúan como wrapper sobre virtualenv, y facilitan su utilización. Entre las funcionalidades queincluye, encontramos la de crear diferentes entornos virtuales bajo el
    • 337. mismo directorio del sistema de ficheros, activar un entorno a travésde un nombre dado, permitir fácilmente desactivar un entorno paraactivar otro diferente, borrar un entorno y copiar completamente unentorno con un nombre distinto.La instalación de virtualenvwrapper es muy sencilla y puede serllevada a cabo a través de un gestor de paquetes como pip. De estaforma, bastaría con invocar al siguiente comando para realizar lainstalación: $ pip install virtualenvwrapper Antes de comenzar a trabajar con las herramientas que ofrece virtualenvwrapper es conveniente modificar el fichero de configuraciónde la terminal (.bashrc, .profile o similar) para añadir un par de líneasque nos faciliten la gestión de los entornos. La primera de ellasindicará cuál será el directorio a partir del que se crearán los diferentesentornos. La segunda línea se encargará de invocar automáticamenteal shell script virtualenvwrapper.sh , cada vez que abramos una terminal.Las líneas en cuestión son las siguientes: export WORKON_HOME=$HOME/.virtualenvssource /usr/bin/virtualenvwrapper.sh En nuestro ejemplo hemos decidido que el subdirectorio .virtualenvs, que pertenecerá al directorio home de nuestro usuario,será el que albergará los diferentes entornos que sean creados. Porotro lado, la segunda línea de configuración hace referencia a la rutadonde, por defecto, existirá el shell script instalado por virtualenvwrapper. Ahora nos encontramos en disposición de usar virtualenvwrapper, siendo nuestra primera acción la creación de un nuevo entornollamado prueba, tal y como muestra el siguiente comando: $ mkvirtualenv prueba --no-site-packages Por defecto, el comando mkvirtualenv se encargará tanto de crearel entorno prueba como de activarlo. De esta forma, justo después deejecutar el comando anterior, estaremos en condiciones de comenzar ainstalar paquetes en nuestro nuevo entorno virtual.Si tenemos varios entornos virtuales,
    • 338. podemos cambiar de uno aotro directamente a través del comando workon. Este comando se
    • 339. encargará tanto de desactivar el entorno actualmente activado, comode activar el nuevo. Si ningún entorno está activado, pasará a activar elindicado. Por ejemplo, supongamos que nos encontramos en elentorno prueba y deseamos pasar a otro denominado test, el comandopara ello sería el siguiente: (prueba)$ workon test Un listado completo de todos los entornos virtuales que tenemosinstalados en una máquina puede ser obtenido invocando al comando workon, sin pasar ningún argumento.La desactivación del entorno actualmente activado se realiza con elcomando deactivate, el cual puede ser utilizado directamente. pip y los entornos virtuales Anteriormente hemos aprendido a consultar los paquetes queexisten, bien en el sistema, bien en un determinado entorno virtual. Esla opción freeze del gestor pip la que nos permite realizar esta acción.Además de listar los paquetes, esta opción puede sernos útil para crearun fichero con todos los paquetes que tenemos instalados. Bastaríacon redireccionar la salida estándar a un fichero, tal y como muestra elsiguiente comando: $ pip freeze > paquetes.txt Si tenemos el mencionado fichero, será posible crear un nuevoentorno a partir del mismo. Esto es muy útil cuando, por ejemplo,tenemos diferentes máquinas de desarrollo y tenemos que mantenerlos mismos paquetes en ambas. Existe un parámetro adicional quepuede ser pasado al comando install de pip , que se encargará de leerun fichero de texto e Instalar los paquetes listados en el mismo.Gracias a este comando, es posible utilizar el fichero creado con freeze para realizar la instalación automática de los paquetes, bien en unanueva máquina, bien en un nuevo entorno. Continuando con nuestroejemplo, la Instalación se realizaría ejecutando el siguiente comando: $ pip install -r paquetes.txt
    • 340. Tanto los gestores de paquetes, como los entornos virtuales, sonherramientas muy útiles para el desarrollo, administración ymantenimiento de aplicaciones Python. En la actualidad son muchoslos desarrolladores y administradores de sistemas que emplean estasherramientas diariamente en su trabajo.
    • 341. 10
    • 342. PRUEBAS UNITARIAS INTRODUCCIÓN Una de las fases más importantes en el proceso del desarrollo desoftware es la de pruebas. Con ella pretendemos demostrar que elsoftware desarrollado cumple con la funcionalidad para la que ha sidodiseñado e implementado.Existen diferentes tipos de pruebas para el software, como son, porejemplo, las unitarias, las funcionales y las de integración. El primertipo se emplea para demostrar que una serie de componentescumplen su función correctamente. Por otro lado, las de integraciónnos aseguran que diversos componentes software que interactúanentre sí lo hacen de forma correcta. Las pruebas funcionales seencargan de comprobar que la funcionalidad general para la que hasido diseñado un software específico es correcta. Por ejemplo, unaprueba unitaria se encargaría de comprobar que una función suma dosvalores de forma correcta. Otra prueba garantizaría que otra funcióntambién lo hace al llamar a la función de suma. Por último, una pruebafuncional demostraría que, cuando un usuario introduce dos valoresempleando la interfaz gráfica, la suma devuelve el valor esperado.Las pruebas unitarias son muy importantes, ya que puedenconsiderarse como una unidad mínima y, además, son la base de lafase de pruebas en el proceso de desarrollo de software. Cada pruebaunitaria comprobará que cada parte de código (función, método osimilar) cumple su función por separado, es decir, de formaindependiente.Una de las principales características de las pruebas unitarias es queestas deben ser automáticas. Es decir, una vez diseñadas, podrán serejecutadas directamente a través de un comando o script. Esto,además, garantiza que las pruebas se pueden repetir sucesivas veces.Entre las ventajas de emplear pruebas unitarias, destacan el hechode simplificar la integración de componentes, el aislamiento yacotación de errores, la facilitación de la refactorización de código y laseparación entre la interfaz de usuario y los detalles de
    • 343. implementación.En los últimos años, la amplia aceptación de metodologías dedesarrollo como TDD ( Test-Driven Development) y BDD (BehaviorDriven Development), han puesto de manifiesto el interés por llevar acabo pruebas sobre el software y han demostrado la necesidad de lasmismas para desarrollar software de calidad.En este capítulo final nos ocuparemos de las herramientas quepueden ser utilizadas en Python para escribir y ejecutar pruebasunitarias. Comenzaremos explicando una serie de conceptos básicos ydespués pasaremos a centrarnos en los más utilizados frameworks dePython que existen para pruebas.
    • 344. CONCEPTOS BÁSICOS Para llevar a cabo las pruebas unitarias, es importante estarfamiliarizado con una serie de conceptos básicos, de los cuales nosvamos a ocupar a continuación. El primero de ellos es el llamado fixture, que referencia a aquellos datos iniciales que vamos a necesitarpara nuestras pruebas. Por ejemplo, supongamos que vamos a probaruna serie de acciones que tienen lugar entre un conjunto de usuarios.Para ello, previamente, necesitaremos unos cuantos usuarios, estosserían nuestros fixtures. Otro ejemplo, sería una serie de ficheros odirectorios, en caso de que las pruebas requieran de ellos.Probablemente, el concepto más importante en el ámbito de laspruebas unitarias sea el de caso de prueba, también conocido como test case. El caso de prueba representa aquella funcionalidad que va aser probada. Se trata de comprobar una serie acciones en base a unosdatos de entrada. Como resultado obtendremos éxito o fallo, lo quedemostrará si las acciones probadas funcionan correctamente, es decir,tomando unos valores de entrada conseguimos un determinadoresultado esperado.Dado que una prueba unitaria puede constar de varios casos deprueba, se define la suite de pruebas (test suite) como un conjunto decasos de prueba que deben ser ejecutados de forma agregada paradeterminar el resultado final de una prueba.Una vez que tenemos preparados nuestros casos de prueba,incluyendo o no suites de los mismos, y los fixtures, solo nos queda uncomponente que se encargue de lanzar las pruebas y obtener unresultado. A este será al que llamamos test runner y también seráresponsable de ofrecer el resultado de una forma fácil de interpretar.En ocasiones se utilizan gráficos y otras, simplemente texto; encualquier caso debemos obtener información sobre el número depruebas ejecutadas y si el resultado ha sido satisfactorio o no.Para probar una aplicación se suelen crear diferentes casos deprueba, incluyendo varias suites de prueba. Debe tenerse en cuentaque cada funcionalidad, considerada como tal, necesitará de su propiocaso de prueba. Por ejemplo, partamos de un sencillo ejemplo, un
    • 345. pequeño módulo Python que contiene tres funciones diferentes. Eneste caso se debería escribir un caso de prueba para cada una de lasfunciones. No existe un criterio general para establecer unacorrespondencia entre los casos de prueba y los componentes decódigo. En algunas ocasiones, la elección de caso de prueba es trivial,como en el ejemplo previo, pero en otras necesitaremos definir quéfunciones, clases u otros componentes intervienen en un único caso deprueba. Igual escenario ocurre con las suites de prueba, quedando acriterio de los programadores la decisión sobre la creación de lasmismas para probar determinado código.Las pruebas unitarias en Python pueden ser llevadas a cabo con laayuda de diferentes frameworks, los cuales ofrecen una serie deherramientas para facilitarnos el trabajo. A continuación, nosocuparemos de los frameworks más populares.
    • 346. UNITTEST Python incorpora un módulo en su librería estándar que permiteescribir y ejecutar pruebas unitarias, su nombre es unittest y puede serinvocado de la siguiente forma: >>> import unittest Para crear casos de prueba contamos con la clase denominada TestCase, mientras que para escribir suites tenemos otra llamada TestSuite. La creación y/o carga de fixtures se realiza a través de unmétodo especial (setUp) que pertenece a la clase TestCase. Por otrolado, si son necesarias realizar acciones al terminar cada caso deprueba, por ejemplo, el borrado de algún dato, estas pueden llevarse acabo gracias al método tearDown(), también implementado en TestCase(). Tanto setUp(), como tearDown(), deben ser sobrescritos ennuestra clase de prueba, en caso de que sean necesarias acciones deinicialización o finalización, respectivamente. Es importante saber quepara cada método de nuestra clase de prueba, serán ejecutados setUp() y tearDown(). El primero lo será justo antes de comenzar la ejecuciónde cada método y el segundo al terminar dicha ejecución.Así pues, para escribir un script que ejecute una o varias pruebasunitarias, comenzaremos creando una clase de prueba que herede de TestCase. Seguidamente, escribiremos una serie de métodos, uno porfuncionalidad y finalmente, invocaremos a la función main() delmódulo unittest, que se encargará de ejecutar las pruebas cuandonuestro script sea invocado. ¿Cómo comprobamos si la prueba tieneéxito o no? Para ello contamos con varios métodos, de la clase TestCase, que comienza con el prefijo assert. De esta forma, porejemplo, el método assertEqual() recibirá dos valores y, en caso de seriguales, devolverá un valor que indicará que el resultado de la pruebaes válido. Si deseamos comprobar que dos valores son diferentes,utilizaremos el método assertNoEqual. Otros métodos similares son assertTrue() y assertFalse() que comprueban si un valor es True o False, respectivamente. Interesantes son también los métodos assertIsInstance() y assertNotIsInstance(), útiles para comprobar si un
    • 347. objeto es instancia o no de una determinada clase.Para ilustrar el funcionamiento de unittest vamos a escribir unsencillo fichero en Python que contendrá una función para sumartodos los valores de una lista. Posteriormente, escribiremos un caso deprueba que comprobará si el valor obtenido es el esperado, en cuyocaso, la prueba habrá sido positiva. El código en cuestión para nuestrofichero (lista.py) de ejemplo sería el siguiente: def suma(li):total = 0for ele in li:total += elereturn total El siguiente paso será escribir un nuevo script al que llamaremos pruebas.py, cuyo contenido será el siguiente: import unittestimport lista class ListaTestCase(unittest.TestCase):def setUp(self):self.li = [1, 3, 5, 7]def test suma(self):total = lista.suma(self.li)self.assertEqual(total, 16)if __name__ == '__main__':unittest.main() Por convención, los nombres de los métodos de la clase de pruebascomenzarán por el prefijo test_, seguido del método o función que vana probar. En nuestro ejemplo solo tenemos un método llamado test_suma(). A través del método assertEqual() comprobaremos si elvalor obtenido al invocar a la función suma() es 16, que es elcorrespondiente a la suma de todos los valores de nuestra lista deejemplo, cuyo valor hemos prefijado, como un atributo de instancia, enel método de inicialización setUp(). Al ejecutar nuestro script por líneade comandos, la función main() será invocada, procediendo allanzamiento de las pruebas. Ahora solo nos queda lanzar el script de lasiguiente forma: python pruebas.py Dado que el valor es correcto, obtendremos el siguiente resultadopor la salida estándar:
    • 348. .. Ran 1 test in 0.002sOK La información obtenida hace referencia al número de pruebasejecutadas y la cadena de texto string significa que la prueba ha sidopasada correctamente, asegurando así que nuestra función suma() funciona correctamente.Hasta aquí la funcionalidad básica del módulo unittest, los lectoresinteresados pueden completar la información recibida con ladocumentación existente en la página web oficial (ver referencias) delmencionado módulo.
    • 349. DOCTEST Además de unittest, Python nos ofrece en su librería estándar otromódulo para escribir y realizar pruebas unitarias. Su nombre es doctest y permite escribir pruebas en línea, es decir, como parte de ladocumentación de una función y método, podemos escribirdirectamente el caso de prueba. Para comprobar el funcionamiento deeste módulo vamos a modificar la función suma() del ejemplo anterior,añadiendo nuestro caso de prueba como parte del comentarlo de ladocumentación de la función. De esta forma, la nueva función suma() del módulo lista quedaría como sigue: def suma(li) :"""Devuelve la suma de todos los elementos de la lista>>> suma([1, 2, 3])6"""total = 0for ele in li:total += elereturn total Asimismo, para poder ejecutar el nuevo caso de prueba, seránecesario añadir un par de líneas, las cuales serán ejecutadas cuandose invoque a lista.py, directamente desde la línea de comandos. Laslíneas de código en cuestión serán las siguientes: if __name__ == '__main__ :import doctestdoctest.testmod() Si ahora lanzamos el siguiente comando, comenzará la ejecución denuestro caso de prueba: python lista.py Como resultado de la ejecución del comando anterior noobtendremos ninguna respuesta, ello significará que el test hafuncionado correctamente. No obstante, si quisiéramos ver un registrode las acciones llevadas a cabo, podríamos recurrir al parámetro -v , taly como muestra el siguiente ejemplo:
    • 350. python lista.py -v Por otro lado, si la prueba fallara, obtendríamos información alrespecto. Si cambiamos el valor que debe ser obtenido a otrocualquiera, la prueba dará un resultado erróneo, tal y como podemoscomprobar en la salida que obtendríamos: ************************************************************ File "lista.py", line 3, in __main__.sumaFailed example:suma([1, 2, 3])Expected:7Got: 6 ************************************************************ 1 ítems had failures:of 1 in __main__.suma***Test Failed*** 1 failures. A pesar de que la funcionalidad y flexibilidad ofrecidas por doctest es bastante limitada con respecto a unittest, el primero es un móduloque nos permite escribir pruebas unitarias de una forma muy sencilla.En función de la complejidad que necesitemos podemos emplear unou otro módulo.
    • 351. OTROS FRAMEWORKS Aparte de doctest y unittest existen otros frameworks para escribirpruebas unitarias en Python. Entre ellos, por ejemplo, se encuentran nose, unittest2 y pytest. Ninguno de estos forma parte de la libreríaestándar, por lo que deben ser instalados, por ejemplo, a través de ungestor de paquetes como pip .El módulo unittest2 fue desarrollado con la idea de implementar lasnuevas funcionalidades, que fueron añadidas a unirttest en la versión2.7 de Python, para hacerlo compatible con las versiones de Pythondesde la 2.3 a la 2.6. Además, existe también una versión específica de unittest2 para Python 3, de forma que aquellos que han escrito suspruebas con unittest, puedan migrar su código a Python 3 sinproblemas. pytest incorpora una serie de componentes para escribir complejoscasos de prueba. Entre sus funciones encontramos aquellas quepermiten obtener información sobre los tests que tardan más de untiempo determinado, continuar con la ejecución de todas las pruebasaunque algunas fallen y la opción es escribir plugins para personalizarnuevas pruebas funcionales que nos sean necesarias.Por último, nose está basado en unittest solo que se han añadidouna serie de funcionalidades para lograr que sea más sencillo escribir yejecutar casos de prueba. Entre ellas se encuentra la opción de pasaruna serie de argumentos, por línea de comandos, para ejecutar unalista de test concreta o para indicar un directorio por defecto donde seencuentran todos los scripts de prueba.
    • 352. AEL ZEN DE PYTHON TRADUCCIÓN DE "EL ZEN DE PYTHON" Bonito es mejor que feo.Explícito es mejor que implícito.Simple es mejor que complejo.Complejo es mejor que complicado.Sencillo es mejor que anidado.Disperso es mejor que denso.La legibilidad cuenta.Los casos especiales no son lo suficientemente especiales pararomper las reglas. La practicidad bate a la pureza.Los errores no deben pasar inadvertidos.A menos que sean explícitamente silenciados.En caso de ambigüedad, rechazar la tentación de adivinar.Debe haber una -y preferiblemente únicaforma obvia de hacerlo.Aunque esa única forma no sea obvia en un primer momento, a noser que seas holandés.Ahora es mejor que nunca.Aunque nunca es mejor que *ahora* mismo.Si la implementación es difícil de explicar, es una mala idea.Si la implementación es fácil de explicar, debe ser una buena idea.Los espacios de nombres son una idea genial -¡hay que hacer másde esos! El original puede leerse desde el intérprete interactivo, ejecutando la
    • 353. siguiente línea: >>> import this
    • 354. BCÓDIGO DE BUENASPRÁCTICAS REGLAS Es importante estructurar correctamente un proyecto. Crear unaadecuada jerarquía de directorios y ficheros en función de lasnecesidades de cada aplicación.Utilizar una guía de estilo para la codificación. PEP 8 ( http://www.python.org/dev/peps/pep-0008/ ) define una completaguía de estilo, considerada como un estándar para código Python.El módulo pep8 puede comprobar automáticamente si un script escompatible con las reglas fijadas en PEP 8. Aplicar El Zen de Python. Documentar siempre el código. Unas simples líneas dedocumentación pueden ahorrar horas de trabajo en el futuro.Herramientas como Sphinx ( http://sphinx.pocoo.org/ ) y formatoscomo reStructuredText ( http://docutils.sourceforge.net/rst.html ) pueden ayudarnos mucho.Los controles de versiones son una excelente herramienta para eldesarrollo de software. Cualquier proyecto debería hacer uso deuno de ellos.Escribir pruebas unitarias. Las pruebas de integración y funcionalestambién son recomendables.
    • 355. REFERENCIAS CAPÍTULO 1. PRIMEROS PASOS Sitio web oficial de Python: http://www.python.org/ Página web de descargas de Python: http://www.python.org/getit/ Página web de descargas para Python 3.2.2: http://www.python.Org/getit/releases/3.2.2/ Página web oficial sobre IDLE: http://docs.python.org/library/idle.html Página web sobre la comunidad de Python: http://www.python.org/community/ Utilidad py2exe: http://www.py2exe.org Información para configurar Emacs para desarrollo con Python:http://www.emacswiki.org/emacs/? action=browse;old¡d=PythonMode;id=PythonProgramminglnEmacsScripts y plugins de Python para Vim\ http://vim.wikia.eom/wiki/Category:Python Sitio web de TextMate : http://www.macromates.com Sitio web de gedit: http://projects.gnome.org/gedit/ Sitio web de Notepad++: http://notepadplus-plus.org/ Sitio web de Eclipse : http://www.eclipse.org/ PyDev: Python + Eclipse: http://pydev.org/ Sitio web de NetBeans: http://netbeans.org Soporte para Python en NetBeans: http://wiki.netbeans.org/Python fríe IDE: http://eric-ide.python-projects.org PyCharm IDE: http://www.jetbrains.com/pycharm/ Wingware Python IDE: http://wingware.com Documentación oficial de IPython: http://ipython.org/ipython-doc/stable/index.html Documentación y comandos de pdb: http://docs.python.org/library/pdb.html
    • 356. Página web dedicada a 2to3, la herramienta para migrar de Python2.x a Python 3: http://docs.python.org/library/2to3.html Página oficial sobre las novedades de Python 3: http://docs.python.Org/py3k/whatsnew/3.0.html
    • 357. CAPÍTULO 3. SENTENCIAS DE CONTROL, MÓDULOS Y FUNCIONES Página web oficial de la librería estándar de Python: http://docs.python.org/py3k/library/index.html
    • 358. CAPÍTULO 4. ORIENTACIÓN A OBJETOS Página de Wikipedia sobre orientación a objetos: http://es.wikipedia.org/wiki/Programacion_orientada_a_objetos Principio de abstracción: http://es.wikipedia.org/wiki/Abstracción_(informática )
    • 359. CAPÍTULO 5. PROGRAMACIÓN AVANZADA Modelo NFA tradicional: http://en.wikipedia.org/wiki/Nondeterministic_finite_automata
    • 360. CAPÍTULO 6. FICHEROS Página oficial sobre el módulo csv: http://docs.python.org/py3k/library/csv.html Documentación oficial sobre el módulo zipfile: http://docs.python.org/py3k/library/zipfile.html Documentación oficial sobre el módulo tarfile: http://docs.python.org/py3k/library/tarfile.html Documentación oficial sobre el módulo bz2: http://docs.python.org/py3k/library/bz2.html Documentación oficial sobre el módulo gzip: http://docs.python.org/py3k/library/gzip.html Estructura para ficheros INI: http://docs.python.org/py3k/library/configparser Documentación sobre analizadores sintácticos de XML y HTML: http://docs.python.org/py3k/library/markup.html Sitio web de PyYAML: http://pyyaml.org/wiki/PyYAML URL para la descarga de la versión 3.10 de PyYAML: http://pyyaml.org/download/pyyaml/PyYAML-3.10.zip Sitio web oficial de JSON: http://www.jsong.org/ Página web sobre el módulo simplejson: http://pypi.python.org/pypi/simplejson/
    • 361. CAPÍTULO 7. BASES DE DATOS Sitio web de MySQL: http://www.mysql.com Documentación de referencia de MySQLdb: http://mysqlpython.sourceforge.net/MySQLdb.html#mysqldb Sitio web de psycopg2 : http://initd.org/psycopg/ Documentación oficial de psycopg2: http://initd.org/psycopg/docs/ Sitio web de PostgreSQL: http://www.postgresql.org Sitio web de Oracle: http://www.oracle.com/technetwork/database/enterpriseedition/overview/index.html Sitio web de cx_Oracle: http://cxoracle.sourceforge.net/ Sitio web de SQLAlchemy: http://www.SQLAlchemy.org Documentación oficial de SQLAlchemy : http://docs.SQLAlchemy.org/en/latest/index.html Sitio web de SQLObject : http://www.sqlobject.org Documentación oficial sobre SQLObject : http://sqlobject.org/SQLObject.html Sitio web de Cassandra : http://cassandra.apache.org Documentación oficial sobre pycassa : http://pycassa.github.com/pycassa/api/index.html Sitio web de Redis : http://redis.io Sitio web de redis-py en github: https://github.com/andymccurdy/redis-py Sitio web de MongoDB: http://www.mongodb.org Documentación oficial sobre PyMongo: http://api.mongodb.org/python/current/
    • 362. CAPÍTULO 8. INTERNET Sitio web de WSGI: http://www.wsgi.org Información de referencia del módulo lxml.html: http://lxml.de/lxmlhtml.html Sitio web de Django: http://www.djangoproject.com Sitio web oficial de Pyramid: http://www.pylonsproject.org Sitio web oficial de Pylatte: http://www.pylatte.org Página web de descarga de Pylatte: http://www.pylatte.org/subpage/download.html
    • 363. CAPÍTULO 9. INSTALACIÓN Y DISTRIBUCIÓN DE PAQUETES Sitio web de PyPi: http://pypi.python.org/pypi Página web de descargas del módulo distribute: http://python-distribute.org/ Página web de distribute en PyPi: http://pypi.python.org/pypi/distribute Página web de pip en PyPi: http://pypi.python.org/pypi/pip Página web de virtualenv en PyPi: http://pypi.python.org/pypi/virtualenv Página web de virtualenvwrapper en PyPi: http://pypi.python.org/pypi/virtualenvwrapper Sitio web de virtualenvwrapper: http://www.doughellmann.com/projects/virtualenvwrapper/ Listado completo de clasificadores para setup.py: http://pypi.python.org/pypi7%3AactionHist_classifiers Formulario de registro para registrar y subir paquetes a PyPi : http://pypi.python.org/pypi? %3Aaction=register_form
    • 364. CAPÍTULO 10. PRUEBAS UNITARIAS Documentación oficial del módulo unittest: http:// http://docs.python.org/py3k/library/unittest.html Página de PyPi sobre unittest2: http://pypi.python.org/pypi/unittest2 Sitio web de pytest: http://pytest.org/latest/ Documentación oficial de nose: http://readthedocs.org/docs/nose/en/latest/
    • 365. ÍNDICE ALFABÉTICO - __call__() 113, 115__iter__() 105__name__() 113, 114, 116, 210, 245, 246__next__() 21, 105 A append() 41 array dinámico 40ASCII 33atributos 76, 78, 79, 80, 81, 82, 83, 84, 90, 94, 95, 96, 97, 100, 102, 165,174, 176 B BDD 242 break 53, 54 bytearray 33, 34 bytecode 14, 67 C callable() 101 caso de prueba 242, 243, 244, 245, 246Cassandra 163, 164, 180, 181, 184, 256CGI 188, 206, 207, 208 classmethod 84, 86 closure 110, 114 comprensión 21, 44, 45, 49, 108 configparser 153, 255conjuntos matemáticos 31 CORBA 140 count() 40, 184CPython 6, 232
    • 366. CSV 6, 134, 150, 151, 152 D decode() 34decorador 80, 81, 84, 86, 111, 112, 113, 114, 115, 116, 117 decorator 80, 81, 84, 111, 116, 117 del() 42, 48, 89 desempaquetado de argumentos 61, 115 diccionario 21, 23, 46, 47, 48, 49, 60, 61, 102, 142, 148, 149, 150, 153,154, 183, 185 dir() 99, 100 distutils 221, 223, 233, 235DOM 143,144 E easy_install 221, 225, 226, 227, 228, 229, 230, 231, 232, 234, 236 encode() 34entornos virtuales 15, 222, 236, 237, 238, 239, 240 estructura de datos 24, 38, 46, 76, 102, 131, 134, 140 excepciones 3, 22, 51, 71, 72, 73, 74, 202expresiones regulares 16, 69, 103, 120, 122, 123, 126, 127, 128 F fixture 242 flush() 137formato INI 134,152 frameworks web 75, 174, 188, 206, 215, 216, 218, 219FTP 69, 187, 188, 191, 192, 193, 194, 234 función 7, 10, 11, 20, 21, 25, 26, 28, 31, 33, 34, 35, 37, 40, 42, 43, 44, 46,47, 48, 49, 53, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 74,76, 79, 80, 81, 87, 89, 90, 91, 92, 93, 98, 99, 100,101, 102, 105, 106,107, 108,109, 110, 111, 112, 113, 114, 115, 116,117, 118, 119, 120,123, 124, 125, 126,127, 129, 130, 131, 132, 134, 135, 136,142, 145,146, 150, 156, 158, 165, 166,171, 175, 176, 195, 196, 201, 202, 209,211, 214, 217, 218, 223, 225, 234, 241, 244, 245, 247, 251función anónima 62funciones lambda 62, 63, 118, 132
    • 367. G garbage collector 6, 89 generators 103, 107, 118, 119Guido van Rossum 2, 3gzip 134, 155, 157, 158, 159, 160, 255 H hasattr() 100, 101 herencia 5, 73, 76, 83, 94, 95, 97, 98, 100, 111 I IDLE 8, 9, 10, 12, 13, 14, 253IMAP4 187, 198, 203, 205 index() 40inmutable 32, 38, 39, 57, 64 insert() 42,185 instrospección 99, 102 IPython 17, 18, 254 isinstance() 100, 101 ítems() 21, 47 iterator 21, 104, 105, 106, 107, 119, 138 iterators 103, 106, 107, 118, 120 J join() 36, 135, 136JSON 133, 143, 146, 147, 148, 183, 217, 255 K keys() 21, 47, 48, 102 L len() 40, 42, 48, 130 librería estándar VII, 5, 19, 20, 22, 68, 69, 73, 74, 120, 133, 134, 135, 140,144, 147, 148, 149, 151, 153, 155, 166, 169, 171, 173, 175, 184, 189,191, 194, 197, 198, 201, 206, 209, 211, 213, 214, 221, 223, 226, 233,243, 245, 247, 254 list() 41, 48, 106, 118, 119, 161, 199, 200
    • 368. lista 21, 22, 26, 30, 36, 40, 41, 42, 43, 44, 45, 48, 49, 53, 56, 57, 58, 63,64, 65, 68, 71, 97, 100, 102,104, 106, 108, 118, 119, 120, 125,126,127, 129, 130, 131, 132, 137, 151,152, 153, 156, 161, 172, 183, 184,192,197, 199, 204, 209, 212, 234, 244, 245, 246, 247 lower() 36, 118, 119 lstrip() 35, 36 M macros 111, 112 map() 63, 106, 118, 119 marshalling 140 matrices 45método de instancia 79, 85 métodos de clase 84, 85, 86métodos especiales 87, 92, 93, 105 módulos 6, 17, 22, 24, 51, 65, 67, 69, 70, 89, 133, 134, 140, 144, 148,155,164, 171, 178, 181, 184, 188, 189, 194, 198, 206, 211, 216, 221,222, 224, 225, 226, 230, 232, 233, 234, 236, 257MongoDB 163, 164, 181, 182, 183, 184, 256mutable 33, 40, 46, 57, 58, 64MySQL 163, 166, 168, 169, 170, 171,174, 175, 178, 219, 256 MySQLdb 166, 168, 169, 170, 171, 172, 219, 256 N name mangling 83, 84 None 24, 65, 124, 141, 172, 204 NoSQL 163, 164, 180, 185, 191número indefinido de parámetros 59 O objeto iterable 39, 104OOP 23, 75, 76Oracle 163, 171, 172, 173, 174, 175, 231, 256 ORM 164, 172, 174, 175, 178, 180, 216, 219 P paquetes 11, 12, 17, 51, 65, 70, 166, 171, 189, 214, 217, 221, 222, 223,225, 226, 227, 228, 229, 230, 231, 232, 233, 235, 236, 237, 238, 239,
    • 369. 240, 247, 258paso de parámetros 56, 57, 58, 61, 115, 116pdb 18, 254 pickle 140, 141, 142 pickling 140 pip 17, 166, 170, 171, 175, 178, 181, 182, 184, 214, 217, 221, 222, 225,226, 229, 230, 231, 232, 234, 235, 236, 237, 238, 239, 240, 247, 257 polimorfismo 76, 98, 99 pop() 43, 48POP3 187, 191, 198, 199, 200, 201, 203, 205PostgreSQL 163, 169, 171, 173, 174, 175, 178, 256precedencia de operadores 29 profiler 19programación funcional 5, 51, 103, 118, 120programación orientada a objetos VIII, 5, 23, 73, 87, 94programación procedural VII, 51,118prompt del intérprete 13 property() 81 psycopg2 169, 170, 171, 172, 256 Pylatte 215, 218, 219, 257PyPi 225, 226, 227, 228, 231, 233, 234, 235, 236, 257, 258 Pyramid 215, 216, 217, 218, 257 pytest 247, 258 Python Software Foundation 3,4 PYTHONPATH 68 R RAR 134RDBMS 163 read() 137, 138, 139, 211 readlines() 137Redis 164, 180, 181, 182, 256 remove() 32, 42, 43 replace() 35 repr() 90, 91 reverse() 44rstrip() 35, 36
    • 370. S SAX 143, 144, 145, 146 Scripts 4, 65, 68, 69, 181, 194, 206, 207, 224, 230, 237, 247 seek() 138, 139sentencias de control VIII, 9, 51, 52 serialización 133, 140, 142, 143, 146, 148 setUp() 244, 245sistema operativo 1, 52SMTP 187, 191, 198, 201, 202, 203 sort() 21, 43, 44, 49, 130 sorted() 21, 43, 44, 49, 129, 130, 131, 132 split() 36, 127, 205 SQLAchemy 164 SQLAlchemy 174, 175, 176, 177, 178, 180, 256SQLite3 163, 173 SQLObject 164, 174, 177, 178, 180, 256 staticmethod 86 strings 5, 20, 22, 32, 34, 35, 37, 88, 100, 104, 127 strip() 35, 36 T TDD 242 tearDown() 244TELNET 187, 188, 189, 191 terminal 5, 11, 12, 166, 175, 181, 188, 189, 190, 198, 210, 223, 226, 238 test runner 243 test suite 243 tipado dinámico 5, 24, 25TLS 203 tupla 21, 38, 39, 40, 41, 42, 53, 58, 59, 61, 74, 126, 131, 157, 167, 169,171, 195, 204, 209 type() 26, 34, 101 U Unicode 20, 33, 127 unittest2 247, 258 unpickling 140
    • 371. upper() 36 V values() 21, 47, 48variable de entorno PATH 14, 224, 227variables de instancia 78, 80 W web scraping 188, 206, 210, 211, 213, 214 write() 137, 154, 158, 190 writelines() 137, 158WSGI 188, 206, 208, 209, 210, 215, 218, 257 X XML 69, 133, 143, 144, 145, 146, 147, 148, 187, 194, 195, 197, 198, 213,219, 231, 232, 255 Y YAML 133, 143, 148, 149, 150 yield() 107 Z ZIP 6, 134, 155, 156, 157, 159, 223, 233, 234
    • 373. Ta bleofContents page-001 page-002 page-003 page-004 page-005 page-006 page-007 page-008 page-009 page-010 page-011 page-012 page-013 page-014 page-015 page-016 page-017 page-018 page-019 page-020 page-021 page-022 page-023 page-024 page-025 page-026 page-027 page-028 page-029 page-030 page-031 page-032 page-033 page-034
    • 374. page-035 page-036 page-037 page-038 page-039 page-040 page-041 page-042 page-043 page-044 page-045 page-046 page-047 page-048 page-049 page-050 page-051 page-052 page-053 page-054 page-055 page-056 page-057 page-058 page-059 page-060 page-061 page-062 page-063 page-064 page-065 page-066 page-067 page-068 page-069 page-070 page-071
    • 375. page-072 page-073 page-074 page-075 page-076 page-077 page-078 page-079 page-080 page-081 page-082 page-083 page-084 page-085 page-086 page-087 page-088 page-089 page-090 page-091 page-092 page-093 page-094 page-095 page-096 page-097 page-098 page-099 page-100 page-101 page-102 page-103 page-104 page-105 page-106 page-107 page-108
    • 376. page-109 page-110 page-111 page-112 page-113 page-114 page-115 page-116 page-117 page-118 page-119 page-120 page-121 page-122 page-123 page-124 page-125 page-126 page-127 page-128 page-129 page-130 page-131 page-132 page-133 page-134 page-135 page-136 page-137 page-138 page-139 page-140 page-141 page-142 page-143 page-144 page-145
    • 377. page-146 page-147 page-148 page-149 page-150 page-151 page-152 page-153 page-154 page-155 page-156 page-157 page-158 page-159 page-160 page-161 page-162 page-163 page-164 page-165 page-166 page-167 page-168 page-169 page-170 page-171 page-172 page-173 page-174 page-175 page-176 page-177 page-178 page-179 page-180 page-181 page-182
    • 378. page-183 page-184 page-185 page-186 page-187 page-188 page-189 page-190 page-191 page-192 page-193 page-194 page-195 page-196 page-197 page-198 page-199 page-200 page-201 page-202 page-203 page-204 page-205 page-206 page-207 page-208 page-209 page-210 page-211 page-212 page-213 page-214 page-215 page-216 page-217 page-218 page-219
    • 379. page-220 page-221 page-222 page-223 page-224 page-225 page-226 page-227 page-228 page-229 page-230 page-231 page-232 page-233 page-234 page-235 page-236 page-237 page-238 page-239 page-240 page-241 page-242 page-243 page-244 page-245 page-246 page-247 page-248 page-249 page-250 page-251 page-252 page-253 page-254 page-255 page-256
    • 380. page-257 page-258 page-259 page-260 page-261 page-262 page-263 page-264 page-265 page-266 page-267 page-268 page-269 page-270 page-271 page-272 page-273 page-274 page-275 page-276 page-277 page-278 page-279 page-280 page-281 page-282 page-283 page-284 page-285 page-286 page-287 page-288 page-289 page-290 page-291 page-292 page-293
    • 381. page-294 page-295 page-296 page-297 page-298 page-299 page-300 page-301 page-302 page-303 page-304 page-305 page-306 page-307 page-308 page-309 page-310 page-311 page-312 page-313 page-314 page-315 page-316 page-317 page-318 page-319 page-320 page-321 page-322 page-323 page-324 page-325 page-326 page-327


    • Previous
    • Next
    • f Fullscreen
    • esc Exit Fullscreen