Python 3 al descubierto

Python 3 al descubierto

@ProfGastonPerez
@ProfGastonPerez
1 Follower
1 month ago 63
AI Summary
Bulleted
Text
Key Insights
#PythonProgramming #DataStructures #WebDevelopment #SoftwareDevelopment #TechEducation
1/381
2/381
3/381
4/381
Python 3 al descubierto Arturo Fernández MontoroISBN: 978-84-939450-
4-6 edición original publicad…
5/381
EDITOR, S.A. de C.V. no será jurídicamenteresponsable por: errores u
omisiones; daños y perjuicios…
6/381
Impreso en México. Printed in México.
Empresas del grupo:México: Alfaomega Grupo Editor, S.A. de C…
7/381
PRÓLOGO
En la actualidad Python es uno de los lenguajes de programacióncon mayor
proyección. Su f…
8/381
programación orientada a objetos y detalles más avanzados sobre
ellenguaje. Los siguientes capítul…
9/381
ÍNDICE
PRÓLOGO
CAPÍTULO 1. PRIMEROS PASOS Introducción ¿Qué es Python? Un
poco de historia Princ…
10/381
Operadores Funciones matemáticas Conjuntos Cadenas de textoTipos
Principales funciones y métodos O…
11/381
Funcionamiento de la importación Path de búsqueda Librería
estándarPaquetes Comentarios Excepcione…
12/381
Iterators Funciones integradas Generators Closures DecoratorsPatrón
decorator, macros y Python dec…
13/381
Ficheros CSV Analizador de ficheros de configuración Compresión y
descompresión de ficheros Format…
14/381
CGI WSGI Web scraping urllib.request lxml Frameworks pyramid pylatte
CAPÍTULO 9. INSTALACIÓN Y DIS…
15/381
ÍNDICE ALFABÉTICO
16/381
PRIMEROS PASOS
INTRODUCCIÓN
Este primer capítulo será nuestra primera toma de contacto
conPython…
17/381
¿QUÉ ES PYTHON?
Básicamente, Python es un lenguaje de programación de alto nivel,
interpretado y …
18/381
con una sintaxis clara y concisa. Además, no requiere dedicar tiempo asu
compilación debido a que …
19/381
momento de escribir estas líneas, la versión 3 cuenta con laactualización
3.2, liberada en febrero…
20/381
Foundation invita, a cualquiera que quiera hacerlo, a contribuir aldesarrollo
y promoción de este …
21/381
lenguaje.La interacción con el intérprete del lenguaje se puede
hacerdirectamente a través de la c…
22/381
en una línea diferente. Por otro lado, tampoco se hace uso de las llaves ({})
para indicar el prin…
23/381
e IronPython, que permite la ejecución en la plataforma .NET deMicrosoft.
El siguiente apartado lo…
24/381
INSTALACIÓN
A continuación, nos centraremos en la instalación del intérprete dePython y
sus herra…
25/381
Figura 1-2. Selección de la instalación de Python para todos losusuarios o
solo para el actual
Al…
26/381
Figura 1-3. Selección del directorio base para de la instalaciónde Python
Avanzamos un paso más y …
27/381
Figura 1-4. Personalización de la instalación de Python
La interfaz gráfica presenta algunas venta…
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 i…
30/381
continuar a través del botón Continue. En el siguiente paso del asistente se
nos solicita que indi…
31/381
Figura 1-7. Selección del disco para la instalación de Python
Linux
La mayoría de las distribucio…
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, par…
34/381
HOLA MUNDO
La primera toma de contacto práctica con el lenguaje larealizaremos a
través del famos…
35/381
Veremos, entonces, cómo aparece el mencionado mensaje en lasiguiente
línea y después volverá a apa…
36/381
Código fuente y bytecode
Hasta ahora solo hemos hablado de los ficheros de código Python,que
util…
37/381
HERRAMIENTAS DE DESARROLLO
Uno de los factores importantes a tener en cuenta, a la hora deabordar …
38/381
En la actualidad existen multitud de editores de texto queincorporan otras
muchas funcionalidades,…
39/381
ofrecen funcionalidades específicas para él mismo. Entre las ventajasde
estos dos IDE caben destac…
40/381
La instalación de IPython puede realizarse como si de un módulo dePython
más se tratara, siendo, p…
41/381
ejecución del programa paso a paso. El lanzamiento de pdb paranuestro
script de ejemplo se haría d…
42/381
Hola Mundo8 function calls in 0.000 secondsOrdered by: standard
namencalls tottime percall cumtime…
43/381
NOVEDADES EN PYTHON 3
La última versión de Python trae consigo una serie de clarasnovedades y
dif…
44/381
>>> 7 / 2
45/381
Por otro lado, para la división entera, en Python 3, ejecutaríamos elsiguiente
comando, siendo el …
46/381
que debemos tenerlo en cuenta a la hora deutilizar la sentencia import. Por
ejemplo, el módulo Coo…
47/381
encuentra dentro de http y se llama client (import http.Client) .Las
excepciones que capturan un o…
48/381
ESTRUCTURAS Y TIPOS DE DATOSBÁSICOS
INTRODUCCIÓN
Realizada una primera toma de contacto con Pytho…
49/381
CONCEPTOS BÁSICOS
Uno de los conceptos básicos y principales en Python es el objeto.
Básicamente,…
50/381
ficheros, de los que se ocupa el capítulo 7.Antes de comenzar a descubrir
los objetos built-in de …
51/381
referencias que se van asignando entre objetos y variables. En funciónde un
algoritmo determinado,…
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 comprob…
54/381
Ahora modificamos el segundo elemento de la primera lista,ejecutando la
siguiente sentencia:
>>> …
55/381
Como resultado, ambas listas habrán sido modificadas y su valorserá el
mismo. ¿Por qué se da esta …
56/381
NÚMEROS
Como en cualquier lenguaje de programación, en Python, larepresentación
y el manejo de nú…
57/381
imaginaria aparece representada por la letra j, siendo también
posibleemplear la misma letra en ma…
58/381
Sistemas de representación
Tal y como hemos adelantado previamente, Python puederepresentar los
n…
59/381
existe la precedencia de operadores, lo que deberemos tener encuenta a la
hora de escribir expresi…
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 …
61/381
matemáticas. Entre ellas, tenemos algunas como el valor absoluto, laraíz
cuadrada, el cálculo del …
62/381
Definir y operar con conjuntos matemáticos también es posible enPython.
La función para crear un c…
63/381
CADENAS DE TEXTO
No cabe duda de que, a parte de los números, las cadenas de texto( strings )
son…
64/381
Otra de las novedades de Python 3 con referencia a las cadenas detexto es el
tipo de estas que sop…
65/381
>>> cad. encode()b'es de tipo str'
La función decode() realiza el paso inverso, es decir, conviert…
66/381
Respecto a los métodos con los que cuentan los objetos de tipostring,
Python incorpora varios de e…
67/381
" cadena con espacios en blanco "
Relacionados con upper() y lower() encontramos otro métodollamad…
68/381
format(). Este método admite emplear, dentro de la cadena de texto,los
caracteres {}, entre los qu…
69/381
del ejemplo anterior podemos imprimir solo los tres primeroscaracteres:
>>> print(cad[:3])Cad
En …
70/381
TUPLAS
En Python una tupla es una estructura de datos que representa unacolección
de objetos, pud…
71/381
('a', 3)5.6
Concatenar dos tuplas es sencillo, se puede hacer directamente através del
operador +…
72/381
LISTAS
Básicamente, una lista es una colección ordenada de objetos,similar al
array dinámico empl…
73/381
True
Existen dos funciones integradas que relacionan las listas con lastuplas:
list() y tuple(). …
74/381
>>> li.insert(3, 'c')>>> li.insert(12, 'c')
Por el contrario, podemos insertar un elemento en una …
75/381
Ordenación
Los elementos de una lista pueden ser ordenados a través delmétodo sort()
o utilizando…
76/381
adicional y observaremos que el criterio de ordenación que utiliza
elintérprete, por defecto, es o…
77/381
Veamos un ejemplo prácticoconstrucción sintáctica en Python:
para
utilizar
la
mencionada
>>> l…
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
utilizand…
81/381
Como hemos visto previamente, para acceder a los elementos delas listas y
las tuplas, hemos utiliz…
82/381
...valor=1valor=2valor=3
Por defecto, si iteramos sobre un diccionario con un bucle for, obtendrem…
83/381
>>> 'x' in diccionarioFalse
Comprensión
De forma similar a las listas, los diccionarios pueden ta…
84/381
SENTENCIAS DE CONTROL,MÓDULOS Y FUNCIONES
INTRODUCCIÓN
Las sentencias de control es uno de los pr…
85/381
PRINCIPALES SENTENCIAS DE CONTROL
Al igual que otros lenguajes de programación, Python incorporaun…
86/381
Como el lector habrá podido observar y a diferencia de otroslenguajes de
programación, los parénte…
87/381
ejecutadas las sentencias que pertenecen al else al finalizar el bucle.
Acontinuación, veamos un e…
88/381
comprobaremos cómo la última sentencia print es ejecutada.Además de
break, otra sentencia asociada…
89/381
operación siempre es recomendable cerrar el fichero. Gracias a with esto
ocurrirá automáticamente,…
90/381
FUNCIONES
En programación estructurada, las funciones son uno de loselementos
básicos. Una funció…
91/381
Una vez que salvemos el fichero con el código, ejecutaremos elprograma
desde la línea de comandos …
92/381
Efectivamente, al pasar como argumento una lista, que es demutable, y
modificar uno de sus valores…
93/381
A pesar de ser el comportamiento por defecto, es posible nomodificar el
parámetro mutable pasado c…
94/381
Dada la anterior función, las siguientes sentencias son válidas:
>>> fun(1, 2, 4)>>> fun(a=1, b=2,…
95/381
tratados como tal. El siguiente ejemplo nos muestra cómo empleareste
operador en la cabecera de un…
96/381
1 2 3
En lugar de una tupla, el operador ** se basa en el uso de undiccionario.
Tomando como ejem…
97/381
>>> fun(1, 3)Traceback (most recent cali last):File "<stdin>", line 1, in
<module>TypeError: fun()…
98/381
En la práctica, la utilidad de las funciones lambda es que nospermite definir
una función directam…
99/381
>>> new_li = map(lambda x: x+2, li)>>> for item in new_li:
print(item)...34
5
Tipos mutables com…
100/381
consistente en utilizar el valor None por defecto para nuestroparámetro y en
crear una lista vacía…
101/381
MÓDULOS Y PAQUETES
La organización del código es imprescindible para su
eficientemantenimiento y …
102/381
first.say_hello()
Ahora utilizaremos el intérprete para ejecutar este último scriptcreado.
Desde …
103/381
from first import say_hello, say_bye
Adicionalmente, un módulo puede ser importado para serreferen…
104/381
ejecución del código del módulo importado. Esta es la razón por lacual se
ejecuta la sentencia pri…
105/381
>>> import sys>>>
sys.path['','C:\\Windows\\system32\\python32.zip','C:\\Python32\\DLLs',
'C:\\Py…
106/381
del mismo nuestros scripts first.py y second.py. Por último, crearemosun
fichero vacío llamado __i…
107/381
entender el funcionamiento del código, tanto para otrosprogramadores,
como para nosotros mismos. E…
108/381
de una lista que contiene dos elementos. Si intentamos acceder a lalista
utilizando el valor de ín…
109/381
try:
<li [2]except:<print("Error: índice no válido")else:<print("Sin
error")finally:<print("Bloqu…
110/381
cómo implementar una clase y cómo funciona la herencia. El
siguientecapítulo está dedicado a cómo …
111/381
Python nos permite trabajar con la información generada cuando seproduce
una excepción. Esto puede…
112/381
ORIENTACION A OBJETOS
INTRODUCCIÓN
Entre los paradigmas de programación soportados por Pythondest…
113/381
que presenta Python con respecto a otros lenguajes a la hora detrabajar con
el paradigma de la OOP…
114/381
CLASES Y OBJETOS
Hasta ahora hemos utilizado indistintamente el concepto de objetocomo
tipo o est…
115/381
utilizando la siguiente línea de código:
a = First ()
Al igual que otros lenguajes de programació…
116/381
mencionada referencia. Como ejemplo, modifiquemos nuestra
claseañadiendo el siguiente método:
def…
117/381
nombres del método en cuestión. Para ilustrar este
comportamiento,añadamos el siguiente nuevo méto…
118/381
Relacionados con los atributos, también existen en Python lasvariables de
clase. Estas se caracter…
119/381
estos atributos requieren de un procesamiento inicial en el momentode ser
accedidos. Para implemen…
120/381
Seguro que los programadores de Java están habituados a utilizarmétodos
setters y getters para acc…
121/381
@radio.setterdef radio(self, radio):self. radio = radio
Ahora veremos cómo emplear nuestra clase c…
122/381
no impide su acceso. Trabajemos con un sencillo ejemplo, declarandouna
clase y su correspondiente …
123/381
La técnica del name mangling se diseñó en Python para asegurarque una
clase hija no sobrescribe ac…
124/381
se pueden utilizar parámetros adicionales para el método de clase. Loque sí
que es importante que …
125/381
El comportamiento anteriormente explicado tiene sentido, ya que,en
realidad, Python no tiene modif…
126/381
>>> t = Test()>>> t.metodo_ estático (33)Valor: 33
Echando un vistazo al código anterior, podemos …
127/381
que, por defecto, contendrán todas las clases que creemos. Enrealidad,
cualquier clase que creemos…
128/381
El método __new__() fue diseñado para permitir la personalizaciónen la
creación de instancias de c…
129/381
def __init__(self, x):self.x = x
La invocación a la creación de un objeto de la clase que contendr…
130/381
constructor. Sin embargo, esta situación varía si empleamos la consolade
comandos y creamos la ins…
131/381
Con la información devuelta por la función repr podríamos crear unnuevo
objeto con los mismos valo…
132/381
Es decir, podemos indicar, valores como, por ejemplo, la alineación dela
cadena de texto producida…
133/381
esta simple afirmación, escribiremos el código del método que seencargará
de realizar la comprobac…
134/381
>>> t.__hash__()39175112>>> hash(t)39175112>>> x = TestHash()>>>
x__hash__()2448448>>> hash(x)2448…
135/381
HERENCIA
El concepto de herencia es uno de los más importantes en laprogramación
orientada a obje…
136/381
apartados.
Simple
La herencia simple consiste en que una clase hereda únicamente deotra.
Como he…
137/381
def __init__(self):print("Constructor hija")>> z = Hija()Constructor hija
Pero no solo los métodos…
138/381
Múltiple
Como hemos comentado previamente, Python soporta la herenciamúltiple,
del mismo modo que…
139/381
Teniendo en cuenta esta figura, Python crearía la lista de clases D, B,A, C,
A. Después eliminarí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 …
142/381
POLIMORFISMO
En el ámbito de la orientación a objetos el polimorfismo hacereferencia a la
habilid…
143/381
permitir y facilitar el uso del polimorfismo. Por ejemplo, Java cuentacon las
clases abstractas, q…
144/381
INTROSPECCIÓN
La instrospección o inspección interna es la habilidad que tiene
uncomponente, como…
145/381
isinstance() y hasattr(). La primera de ellas nos sirve para comprobar siuna
variable es instancia…
146/381
Obviamente, aunque las clases Intro e Hijo estén relacionadas
sondiferentes. Sin embargo, la funci…
147/381
pasamos como primer argumento una instancia de cualquier de ellas ycomo
segundo el nombre de una d…
148/381
de instancia, seguido de un punto y del nombre del atributo encuestión. Sin
embargo, los métodos _…
149/381
PROGRAMACIÓN AVANZADA
INTRODUCCIÓN
En este capítulo nos adentraremos en una serie de aspectosavan…
150/381
ITERATORS Y GENERATORS
Python cuenta con dos mecanismos específicos que nos permiteniterar
sobre …
151/381
comentado previamente. Aunque contamos con un capítulo dedicadoa cómo
trabajar con distintos tipos…
152/381
inferior a cero deberemos detener la iteración. Para ello, contamos conla
excepción StopIteration,…
153/381
A diferencia de las versiones 2.x de Python, la serie 3 de estelenguaje
cuenta con una serie de fu…
154/381
este hecho, vamos a reescribir nuestro primer ejemplo de iterator
empleando una simple función que…
155/381
A diferencia de otros objetos, la iteración sobre un generator solo sepuede
hacer una única vez. S…
156/381
CLOSURES
En Python es posible anidar una función dentro de otra. Unafunción no es
más que un obje…
157/381
mente, podemos invocar a la función principal() y asignar su resultadoa una
variable:
>>> res = p…
158/381
enfoque. Además, necesitamos realizar una serie de acciones enfunción del
texto que la caja conten…
159/381
DECORATORS
En el capítulo anterior, hemos descubierto cómo utilizar un decorator, para
por ejempl…
160/381
que las funciones implementadas pueden ser utilizadas comodecoradores en
otras funciones de nuestr…
161/381
Decorators en clases
Para ilustrar el funcionamiento de la aplicación de decoradoresutilizando
cl…
162/381
esto es simplemente lo que ocurre. Continuando con la ejecución de
__call__() , se imprime el últi…
163/381
def nueva():print("ini", f.__name__)f()print("fin", f.__name__)return nueva
El siguiente paso es d…
164/381
invocar a una función decorada pasando a su vez un númerodeterminado de
parámetros. Es más, se pue…
165/381
sencillo. Simplemente hemos tenido en cuenta los mismos al igual queen
una función convencional.
…
166/381
ini wrapped_fdecorator args: 1 2 3fun args: p1 p2fin wrapped_f
En la salida anterior comprobaremos…
167/381
PROGRAMACIÓN FUNCIONAL
Tanto la orientación a objetos como la programación procedural,son dos de
…
168/381
["hola", "mundo", "adios"]
El mismo ejemplo podría haber sido realizado utilizando lacomprehensión…
169/381
Los números en negrita representan los elementos de la lista y elcódigo
completo para realizar la …
170/381
EXPRESIONES REGULARES
Las expresiones regulares definen una serie de patrones que seutilizan para
…
171/381
Patrón Significado
 Cualquier carácter excepto el que representa una nueva línea
\n Nueva línea
…
172/381
^ Inicio de cadena
$ Fin de cadena
\ Escape para caracteres especiales
[] Rango. Cualquier carác…
173/381
Supongamos que estamos buscando en una cadena de texto unpatrón que
corresponde a un determinado n…
174/381
La función básica de la que dispone el módulo re de Python es search().
Esta función recibe como a…
175/381
<_sre.SRE_Match object at 0x00000000021A15E0>
Efectivamente, la simple cadena de texto "o" es por …
176/381
¿Qué ocurre si el patrón no coincide con ninguna parte de lacadena donde
se realiza la búsqueda? S…
177/381
>>> re.search(r" ^hola$", "adiosholaadios")>>> re.search(r"hola",
"adiosholaadios")<_sre.SRE_Match…
178/381
número de ocurrencias que deben ser reemplazadas, por defecto
sereemplazarán todas las que se encu…
179/381
Separaciones
Interesante es la también función split() ofrecida por el módulo
paraexpresiones reg…
180/381
Para ver cómo funcionan las búsquedas utilizando el modificador
MULTILINE, buscaremos un determina…
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}$
correoel…
183/381
ORDENACIÓN DE DATOS
En el presente apartado nos centraremos en aprender cómoordenar datos. La
fun…
184/381
que debe emplear la función sort(), podremos hacerlo gracias alparámetro
key que acepta esta funci…
185/381
>>> lista = [("Madrid", 4), ("Barcelona", 1), ("Sevilla", 5),("Valencia", 3)]
[("Madrid", 4), ("Ba…
186/381
class Empleado:def __init__(self, apellidos, puesto, edad):self.apellidos =
apellidosself.puesto =…
187/381
FICHEROS
INTRODUCCIÓN
Los sistemas operativos almacenan los datos de forma estructuradaen unas
u…
188/381
ser empleado para guardar información relativa a una
determinadaconfiguración. También en este cap…
189/381
OPERACIONES BÁSICAS
Python incorpora en su librería estándar una estructura de datosespecífica
pa…
190/381
>>> fich = open("fichero.txt")
En nuestro ejemplo, hemos supuesto que el fichero creado seencuentr…
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…
193/381
aprender cómo leer y escribir datos.
Lectura y escritura
La operación básica de escritura en un f…
194/381
ello, Python nos ofrece tres métodos diferentes. Elprimero de ellos es
read(), el cual lee el cont…
195/381
devuelve un única cadena de texto. El segundo en cuestión es readline() que
se ocupa de leer línea…
196/381
... print (fich.read())...Primera líneaSegunda línea
Hasta el momento, nuestros ejemplos se han re…
197/381
valores: 0 (comienzo del fichero), 1 (posición actual) y 2 (fin de
fichero).Por defecto, si no se …
198/381
SERIALIZACIÓN
La serialización es un proceso mediante el cual una estructura dedatos es
codificad…
199/381
posible deserializar aquellos objetos señalizados con este móduloempleando
otros lenguajes de prog…
200/381
Ejemplo práctico
Básicamente, para serializar objetos en Python, a través del módulo pickle,
empl…
201/381
Para llevar a cabo el proceso inverso, es decir, la deseríalízacíón,
esnecesario invocar al método…
202/381
FICHEROS XML, JSON Y YAML
Con la serialización se encuentran relacionados una serie deformatos de
…
203/381
for XML). La primera de ellas se basa en utilizar y tratar los datosbasándose
en una estructura de…
204/381
mostrar la información relativa a los diferentes títulos que
contiene,empleando para ello los dife…
205/381
El último de nuestros ejemplos muestra cómo emplear el métodoSAX para
recorrer nuestro fichero e i…
206/381
procesamiento y memoria requerida para su análisis.
JSON
El formato JSON se ha convertido en uno …
207/381
funciones de ficheros de Python podemos leer y escribir ficheros
quecontengan datos en este format…
208/381
'w')new_fich.writeline(cad)new_fich.close()
Si abrimos el nuevo fichero albums_new.json y echamos …
209/381
su contenido, encontraremos el siguiente texto en formato JSON:
{"albums": {"títulos": ["Achtung B…
210/381
puerto: 8003
Python no dispone de ningún módulo en su librería estándar paratrabajar
con ficheros…
211/381
>>> data['producción'] ['puerto']8003
212/381
Por otro lado, la estructura puede ser modificada y crear con ella unnuevo
fichero YAML. Supongamo…
213/381
FICHEROS CSV
El formato CSV (Comma Separated Values) es uno de los máscomunes y
sencillos para al…
214/381
... print("Apellido: {O}; Nombre: {1}; Departamento: {2};Ciudad:
{3}".format(row[0], row[1], row[2…
215/381
Martinez$Juan$Administracion$BarcelonaLopez$Maria$Finanzas$Valenci
a
Finalizamos en este punto nu…
216/381
ANALIZADOR DE FICHEROS DE CONFIGURACIÓN
En los sistemas Windows es muy popular el formato INI, que…
217/381
>>> config.read("config.ini")
Ahora podemos, por ejemplo, obtener una lista con todas lassecciones…
218/381
Si ahora abrimos nuestro fichero INI con cualquier editor de
textos,comprobaremos cómo contiene la…
219/381
Formato ZIP
El nombre del módulo de la librería estándar que nos permitetrabajar con
este formato…
220/381
Filenameempleados.csvfich.txttest.dat
Modified2012-01-07 21:47:142012-02-07 22:32:022012-03-07 20:…
221/381
reside en que el primer método lo hace sobre todos y cadauno de los
ficheros que forman parte del …
222/381
recibir como parámetro uno de los ficheros para devolver lainformación
relativa al mismo. Entre la…
223/381
Para crear un fichero comprimido a partir de un fichero original,basta con
abrir el fichero origin…
224/381
Esta representa a un fichero con este tipo de compresión y dispone
demétodos para crear, leer, com…
225/381
añadido compresión empleando gzip o bz2. En realidad, tar nocomprime
por sí mismo, simplemente agr…
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/…
227/381
BASES DE DATOS
INTRODUCCIÓN
No cabe duda de que las bases de datos juegan un papel muyimportante …
228/381
interactuar con una base de datos con independencia de cuál sea sumotor.
Esto permite desarrollar …
229/381
RELACIONALES
Previamente a descubrir cómo interactuar con los gestores de basesde datos
relaciona…
230/381
Tabla 7-1. Tabla con información sobre países
Antes de continuar, es conveniente crear una nueva b…
231/381
'moneda' VARCHAR(30) NOT NULL,PRIMARY KEY ('id’))
Debemos tener en cuenta que la anterior sentenci…
232/381
La función connect() recibe como parámetros el nombre de lamáquina que
está ejecutando el gestor d…
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 asign…
235/381
Si el método fetchone() es lanzado sobre un cursor que devuelvemás de un
resultado, solo el primer…
236/381
estaremos consumiendo recursos innecesariamente y el rendimientode la
aplicación bajará considerab…
237/381
Para lanzar una sentencia SQL, también necesitamos contar con elobjeto
cursor, así pues, obtendrem…
238/381
>>> cursor = con.cursor()
El método execute() es el encargado de lanzar las sentencias SQL,sirva
…
239/381
correspondiente licencia de software.Desde Python también podemos
trabajar con bases de datosgesti…
240/381
A la hora de utilizar prepared statements, el módulo cxOracle lohace de
forma diferente a MySQLdb …
241/381
para pasar los parámetros necesarios. De esta forma, el siguienteejemplo
muestra cómo parametrizar…
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 conectarno…
244/381
Dos son los ORM más utilizados en Python: SQLAlchemy y SQLObject.
Ambos son similares en funcional…
245/381
estableciendo relaciones entre ellas que luego serán traducidas en labase de
datos empleando clave…
246/381
>>> cad_con = 'mysql://usuario:password@localhost/prueba'>>> engine =
create_engine(cad_con)
Las …
247/381
Conectándonos a la base de datos podremos comprobar que,efectivamente,
la nueva tabla ha sido crea…
248/381
operaciones para interactuar con ella deben ser ejecutadas a través deuna
instancia específica de …
249/381
instancia e invocar al método commit(), tal y como muestrael siguiente
código:
>>> p.habitantes =…
250/381
>>> session.commit()
Una vez aprendido lo básico sobre la utilización de SQLAlchemy, eshora de
co…
251/381
de la clase en cuestión es como sigue:
252/381
>>> class Pais(sqlobject.SQLObject):... nombre =
sqlobject.StringCol(length=100)... continente =
…
253/381
líneas:
254/381
>>> p.set(habitantes=67)>>> p = Pais.get(1)>>> p.habitantes67
La consulta de registros puede ser l…
255/381
Facebook o Twitter.Básicamente, una base de datos NoSQL almacena una
serie depares claves:valor y,…
256/381
>>> con = redis.StrictRedis(host='localhost', port=6379, db=0)
Los tres parámetros pasados al cons…
257/381
Aprendido lo básico para interactuar con una base de datos Redis desde
Python, continuaremos descu…
258/381
MongoDB
El módulo más popular para trabajar con MongoDB desde Pythones el
llamado PyMongo. Este p…
259/381
Para comprobar que el documento ha sido insertadocorrectamente, basta
con invocar al método find_o…
260/381
conceptos de keyspace y column familiy para almacenar la informaciónde
forma estructurada. Al fin …
261/381
Ahora ya estamos listos para insertar nuestro primer registro; paraello,
emplearemos el método ins…
262/381
clave:valor que deseamos insertar. Veamos cómo el siguiente códigonos
servirá para insertar un nue…
263/381
INTERNET
INTRODUCCIÓN
En este capítulo nos ocuparemos de aquellos aspectos másrelevantes
relacio…
264/381
realizar un exhaustivo análisis de todos los módulos, herramientas
ycomponentes existentes y relac…
265/381
TELNET Y FTP
Dos de los más utilizados protocolos en Internet para interactuarcon
servidores son …
266/381
al puerto estándar (23) para TELNET.Antes de comenzar a ver los ejemplos
de código de utilización …
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 c…
269/381
>>> tel. read_all ()'\r\nLast login: Mon Feb 20 17:10:37 on
ttys003\r\n[arturo@yoda
~\xlb[0;32m]\…
270/381
top\t\t\t\tftdetect\r\nDocuments\t\t\tftplugin[arturo@yoda~\x1b[0;32m]\x1b[
0;37m $\r [arturo@yoda…
271/381
>>> from ftplib import FTP>>> con = FTP('ftp.miservidor.com')
En cuanto tengamos creada la instanc…
272/381
>>> fich.close()
Efectivamente, para abrir nuestro fichero hemos empleado b como flag para
indica…
273/381
Para trabajar con directorios dentro del servidor existen diferentesmétodos,
como son cwd(), pwd()…
274/381
XML-RPC
El protocolo XML-RPC se basa en la utilización de la invocaciónremota a
funciones o métod…
275/381
cual recibirá como parámetro una cadena de texto y devolverá unsencillo
mensaje, concatenando la c…
276/381
el método register_function(). Si no registramos la función,no será posible
su invocación. No olvi…
277/381
importante. A continuación, el código necesario que define nuestrafunción
y que la registra en el …
278/381
estos clientes en Python de estaforma, descubriremos cómo el módulo
xmlrpc.client puede ayudarnosa…
279/381
xmlrpc.client
En Python el módulo xmlrpc.client de su librería estándar nos ofreceuna
serie de fu…
280/381
>>> cli.say_bye('Lucas')Bye, bye Lucas
Si en lugar de emplear el intérprete de Python desde la lín…
281/381
CORREO ELECTRÓNICO
De los servicios utilizados a través de Internet, sin duda el correoelectrónico…
282/381
un buzón y, cómo no, obtener los correos recibidos.Para comenzar a trabajar
con poplib, lo primero…
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 la…
285/381
No olvidemos que GMail emplea como usuario de correo elnombre seguido
de la arroba más el dominio.…
286/381
Una vez que tenemos la conexión realizada, si el servidor así lorequiere,
será necesario pasar el …
287/381
Otro método análogo al set debuglevel() de poplib es el homónimoque
existe en smtplib. La invocaci…
288/381
Layer Security) para aquellos servidores que lo requieran.Adicionalmente,
smtplib cuenta también c…
289/381
podemos pasar al mencionado constructor diferentes valores paraestos
parámetros, siendo el primero…
290/381
>>> tip, data = con.search(None, ’FROM','"prueba@miservidor.com"')
Volviendo a nuestra llamada ant…
291/381
WEB
La web es uno de los servicios que más peso tiene en Internet, juntocon el
correo electrónico…
292/381
como es el caso de los servlets de Java, Rack para Ruby, mod_php paraPHP
y WSGI para Python.En oca…
293/381
vars = cgi.FieldStorage()nombre = vars.getvalue('nombre')
print("Content-type: text/html")print()p…
294/381
ejecuta para llevar a cabo una serie de inicializaciones necesarias
paratrabajar con CGI. Con la p…
295/381
responder a las mismas generando contenido que pueda ser leído porun
navegador web. Nuestro sencil…
296/381
$python servidor_wsgi.pyLanzando servidor en puerto 8080...
Si abrimos nuestro navegador y nos con…
297/381
un documento HTML y ser capaces de extraer información analizandolas
distintas etiquetas que conti…
298/381
con invocar al método open() del objeto HTTPResponse, que a su vezfue
devuelto por la función urlo…
299/381
versión 10.6.8 de Mac OS X.El código Python necesario para ello sería el
siguiente:
>>> opener = …
300/381
Mozilla/5.0 (Macintosh; U; Intel Mac
OS X 10_6_8) ')])>>> opener.open(url)
Efectivamente, al méto…
301/381
módulo es un binding para Python de las populareslibrerías libxml2 y
libxslt, escritas ambas en el…
302/381
programación C. Haciendo uso de la estructura ElementTree y
lascorrespondientes clases y funciones…
303/381
>>> from lxml.html import fromstring>>> doc = fromstring(pagina)>>>
ele = doc.find_class('fiveday-…
304/381
ellos, nos dedicaremos a ver qué puede ofrecernos cada uno en
líneasgenerales.
PYRAMID
El proyec…
305/381
haciendo gala de su carácter minimalista, la creación de aplicacionesweb
utilizando un único fiche…
306/381
será necesario llevar a cabo ninguna acción adicional.En cuanto finalice la
instalación podremos c…
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 …
309/381
relacionales.Componentes incluidos el framework facilitan la obtención
deparámetros recibidos por …
310/381
INSTALACIÓN YDISTRIBUCIÓN DE PAQUETES
INTRODUCCIÓN
En capítulos anteriores nos hemos valido de ci…
311/381
puede ser un simple script escrito en este lenguaje, mientras que un paquete
es un conjunto de fic…
312/381
INSTALACIÓN DE PAQUETES
La instalación en nuestro sistema de paquetes Python desarrolladospor
ter…
313/381
obtener el fichero en el que está siendo distribuido, descomprimir elmismo
y ejecuta un comando de…
314/381
transparentemente el script setup.py. Sin embargo, existe unargumento que
puede ser pasado al menc…
315/381
$ python setup.py --install-scripts=/usr/local/bin/
Otro parámetro interesante, que también acepta…
316/381
servicio está accesible a través de Internet y es considerado elrepositorio
oficial de paquetes pa…
317/381
python distribute_setup.py
Dado que distribute es un módulo empaquetado siguiendo elestándar para
…
318/381
operativo que estemos utilizando, esinteresante que el script easy_install
sea accesible a través …
319/381
variable de entorno PATH. Si realizamos esta configuración, tanto enMac
OS X, como en Linux y Wind…
320/381
paquetes es la opción de Indicar el número de versión que deseamosinstalar
de un determinado paque…
321/381
PIP
322/381
El gestor de paquetes pip nació con la idea de ofrecer unaalternativa a
easy_install, que además, …
323/381
$ cd pip-1.0$ python setup.py install
La instalación de módulos en sistemas UNIX se realiza, por d…
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 in…
326/381
La desinstalación de módulos puede ser ejecutada fácilmentegracias al
comando uninstall, el cual s…
327/381
En este punto termina nuestro recorrido por la instalación depaquetes, en el
siguiente apartado tr…
328/381
DISTRIBUCIÓN
Para que otros desarrolladores puedan instalar y utilizar nuestrosmódulos de
Python,…
329/381
las siguientes líneas:
from distutils.core import setupsetup(name='hola', version='1.0',
py_modul…
330/381
creado el fichero de distribución, en nuestro caso, se llama hola-1.O.tar.gz.
Ya estamos preparado…
331/381
ejecutando las siguientes líneas de código:
>>> import hola>>> h = hola.Hola()>>> h.say_hello()Hol…
332/381
ENTORNOS VIRTUALES
Hasta el momento hemos aprendido a instalar paquetes en eldirectorio
asignado …
333/381
virtualenv
Para la gestión de entornos virtuales en Python, necesitamosinstalar un
módulo llamado…
334/381
pasoserá activarlo para poder comenzar a trabajar con él. Esta acción
selleva a cabo a través del …
335/381
directorio bin. En Linux, ejecutaríamos los siguientes comandos paraactivar
nuestro entorno recién…
336/381
mismo directorio del sistema de ficheros, activar un entorno a travésde un
nombre dado, permitir f…
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 …
339/381
Tanto los gestores de paquetes, como los entornos virtuales,
sonherramientas muy útiles para el de…
340/381
10
341/381
PRUEBAS UNITARIAS
INTRODUCCIÓN
Una de las fases más importantes en el proceso del desarrollo deso…
342/381
implementación.En los últimos años, la amplia aceptación de metodologías
dedesarrollo como TDD ( T…
343/381
CONCEPTOS BÁSICOS
Para llevar a cabo las pruebas unitarias, es importante estarfamiliarizado
con …
344/381
pequeño módulo Python que contiene tres funciones diferentes. Eneste caso
se debería escribir un c…
345/381
UNITTEST
Python incorpora un módulo en su librería estándar que permiteescribir y
ejecutar prueba…
346/381
objeto es instancia o no de una determinada clase.Para ilustrar el
funcionamiento de unittest vamo…
347/381
..
Ran 1 test in 0.002sOK
La información obtenida hace referencia al número de pruebasejecutadas …
348/381
DOCTEST
Además de unittest, Python nos ofrece en su librería estándar otromódulo
para escribir y …
349/381
python lista.py -v
Por otro lado, si la prueba fallara, obtendríamos información alrespecto. Si
c…
350/381
OTROS FRAMEWORKS
Aparte de doctest y unittest existen otros frameworks para escribirpruebas
unita…
351/381
AEL ZEN DE PYTHON
TRADUCCIÓN DE "EL ZEN DE PYTHON"
Bonito es mejor que feo.Explícito es mejor que…
352/381
siguiente línea:
>>> import this
353/381
BCÓDIGO DE BUENASPRÁCTICAS
REGLAS
Es importante estructurar correctamente un proyecto. Crear unaa…
354/381
REFERENCIAS
CAPÍTULO 1. PRIMEROS PASOS
Sitio web oficial de Python: http://www.python.org/ Página…
355/381
Página web dedicada a 2to3, la herramienta para migrar de Python2.x a
Python 3: http://docs.python…
356/381
CAPÍTULO 3. SENTENCIAS DE CONTROL, MÓDULOS Y
FUNCIONES
Página web oficial de la librería estándar…
357/381
CAPÍTULO 4. ORIENTACIÓN A OBJETOS
Página de Wikipedia sobre orientación a objetos:
http://es.wiki…
358/381
CAPÍTULO 5. PROGRAMACIÓN AVANZADA
Modelo NFA tradicional:
http://en.wikipedia.org/wiki/Nondetermi…
359/381
CAPÍTULO 6. FICHEROS
Página oficial sobre el módulo csv:
http://docs.python.org/py3k/library/csv.…
360/381
CAPÍTULO 7. BASES DE DATOS
Sitio web de MySQL: http://www.mysql.com Documentación de referencia
d…
361/381
CAPÍTULO 8. INTERNET
Sitio web de WSGI: http://www.wsgi.org Información de referencia del
módulo …
362/381
CAPÍTULO 9. INSTALACIÓN Y DISTRIBUCIÓN DE PAQUETES
Sitio web de PyPi: http://pypi.python.org/pypi …
363/381
CAPÍTULO 10. PRUEBAS UNITARIAS
Documentación oficial del módulo unittest: http://
http://docs.pyt…
364/381
ÍNDICE ALFABÉTICO
-
__call__() 113, 115__iter__() 105__name__() 113, 114, 116, 210, 245,
246__ne…
365/381
CSV 6, 134, 150, 151, 152
D
decode() 34decorador 80, 81, 84, 86, 111, 112, 113, 114, 115, 116, 11…
366/381
G
garbage collector 6, 89 generators 103, 107, 118, 119Guido van Rossum 2,
3gzip 134, 155, 157, 1…
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, 1…
368/381
240, 247, 258paso de parámetros 56, 57, 58, 61, 115, 116pdb 18, 254 pickle
140, 141, 142 pickling …
369/381
S
SAX 143, 144, 145, 146 Scripts 4, 65, 68, 69, 181, 194, 206, 207, 224, 230,
237, 247 seek() 138…
370/381
upper() 36
V
values() 21, 47, 48variable de entorno PATH 14, 224, 227variables de
instancia 78, …
371/381
372/381
Ta
bleofContents
page-001
page-002
page-003
page-004
page-005
page-006
page-007
page-008
…
373/381
page-035
page-036
page-037
page-038
page-039
page-040
page-041
page-042
page-043
page-044
…
374/381
page-072
page-073
page-074
page-075
page-076
page-077
page-078
page-079
page-080
page-081
…
375/381
page-109
page-110
page-111
page-112
page-113
page-114
page-115
page-116
page-117
page-118
…
376/381
page-146
page-147
page-148
page-149
page-150
page-151
page-152
page-153
page-154
page-155
…
377/381
page-183
page-184
page-185
page-186
page-187
page-188
page-189
page-190
page-191
page-192
…
378/381
page-220
page-221
page-222
page-223
page-224
page-225
page-226
page-227
page-228
page-229
…
379/381
page-257
page-258
page-259
page-260
page-261
page-262
page-263
page-264
page-265
page-266
…
380/381
page-294
page-295
page-296
page-297
page-298
page-299
page-300
page-301
page-302
page-303
…
381/381

Python 3 al descubierto

  • 1.
  • 2.
  • 3.
  • 4.
  • 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
  • 372.
  • 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
@ProfGastonPerez

Share

Python 3 al descubierto

Embed code

Report Inappropriate Content on Jaunt

Choose the reason you are reporting: Python 3 al descubierto as inappropriate to Jaunt's content team.


Swipe LEFT
to view Related

Scroll DOWN
to read doc

Cookies to automatically collect, record, and share information about your interactions with our site for analytics purposes.
Cookies used to enable advertising on our site.

Login

OR

Forgot password?

Don't have an account? Sign Up