Pythonlearn PDF
Pythonlearn PDF
Pythonlearn PDF
Charles R. Severance
Créditos
Historial de impresión
Detalles de Copyright
Prólogo
web, análisis de datos en formato XML y JSON, creación y uso de bases de datos
usando el Lenguaje de Consultas Estructurado (SQL), y la visualización de datos.
El objetivo final de todos estos cambios es variar la orientación, desde una dirigida
a las Ciencias de la Computación hacia otra puramente informática, que trate
sólo temas adecuados para una clase de tecnología para principiantes, que puedan
resultarles útiles incluso si eligen no ser programadores profesionales.
Los estudiantes que encuentren este libro interesante y quieran ir más allá, de-
berían echar un vistazo al libro Think Python de Allen B. Downey’s. Como ambos
libros comparten un montón de materia, los estudiantes adquirirán rápidamente
habilidades en las áreas adicionales de la programación técnica y pensamiento al-
gorítmico que se tratan en Think Python. Y dado que ambos libros comparten un
estilo de escritura similar, deberían ser capaces de avanzar rápidamente a través
del contenido de Think Python con un esfuerzo mínimo.
Como propietario del copyright de Think Python, Allen me ha dado permiso para
cambiar la licencia del contenido de su libro que se utiliza en éste, y que original-
mente poseía una GNU Free Documentation License a otra más actual, Creative
Commons Attribution — Share Alike license. Así se sigue una tendencia general
en las licencias de documentación abierta, que están pasando desde la GFDL a la
CC-BY-SA (por ejemplo, Wikipedia). El uso de la licencia CC-BY-SA mantiene
la arraigada tradición copyleft del libro, a la vez que hacen más sencillo para los
autores nuevos la reutilización de ese material a su conveniencia.
Personalmente creo que este libro sirve como ejemplo de por qué los contenidos
libres son tan importantes para el futuro de la educación, y quiero agradecer a
Allen B. Downey y a la Cambridge University Press por su amplitud de miras
a la hora de distribuir el libro bajo un copyright abierto. Espero que se sientan
satisfechos con el resultado de mis esfuerzos y deseo que tú como lector también
te sientas satisfecho de nuestros esfuerzos colectivos.
Quiero agradecer a Allen B. Downey y Lauren Cowles su ayuda, paciencia y ori-
entación a la hora de tratar y resolver los problemas de copyright referentes a este
libro.
Charles Severance
www.dr-chuck.com
Ann Arbor, MI, USA
9 de Septiembre, 2013
Charles Severance es Profesor Clínico Adjunto en la Escuela de Información (School
of Information) de la Universidad de Michigan.
Contents
v
vi CONTENTS
Chapter 1
1
2CHAPTER 1. ¿POR QUÉ DEBERÍAS APRENDER A ESCRIBIR PROGRAMAS?
Por ejemplo, mira los primeros tres párrafos de este capítulos y dime cuál es la pal-
abra que más se repite, y cuántas veces se ha utilizado. Aunque seas capaz de leer
y comprender las palabras en pocos segundos, contarlas te resultará casi doloroso,
porque la mente humana no fue diseñada para resolver ese tipo de problemas. Para
una computadora es justo al revés, leer y comprender texto de un trozo de papel
le sería difícil, pero contar las palabras y decirte cuántas veces se ha repetido la
más utilizada le resulta muy sencillo:
python words.py
Enter file:words.txt
to 16
Antes de que empecemos a aprender el lenguaje que deberemos hablar para darle
instrucciones a las computadoras para desarrollar software, tendremos que apren-
der un poco acerca de cómo están construidos esas máquinas. Si desmontaras tu
computadora o smartphone y mirases dentro con atención, encontrarías los sigu-
ientes componentes:
¿Qué hago a
Software continuación?
Dispositivos Unidad
Entrada Central Red
Salida Procesamiento
Memoria Memoria
Principal Secundaria
¿Qué hago a
Software continuación?
Dispositivos Unidad
Entrada Central Red
Salida Procesamiento
Memoria Memoria
Principal Secundaria
Las palabras reservadas en el lenguaje que utilizan los humanos para hablar con
Python son, entre otras, las siguientes:
print('¡Hola, mundo!')
Esto no se ve bien. A menos que pienses en algo rápidamente, los habitantes del
planeta sacarán sus lanzas, te ensartarán, te asarán sobre el fuego y al final les
servirás de cena.
Por suerte compraste una copia de este libro durante tus viajes, así que lo abres
precisamente por esta página y pruebas de nuevo:
Esto tiene mejor aspecto, de modo que intentas comunicarte un poco más:
>>> print('Usted debe ser el dios legendario que viene del cielo')
Usted debe ser el dios legendario que viene del cielo
>>> print('Hemos estado esperándole durante mucho tiempo')
Hemos estado esperándole durante mucho tiempo
>>> print('La leyenda dice que debe estar usted muy rico con mostaza')
La leyenda dice que debe estar usted muy rico con mostaza
>>> print 'Tendremos un festín esta noche a menos que diga
File "<stdin>", line 1
print 'Tendremos un festín esta noche a menos que diga
^
SyntaxError: Missing parentheses in call to 'print'
>>>
La conversación fue bien durante un rato, pero en cuanto cometiste el más mínimo
fallo al utilizar el lenguaje Python, Python volvió a sacar las lanzas.
En este momento, te habrás dado cuenta que a pesar de que Python es tremen-
damente complejo y poderoso, y muy estricto en cuanto a la sintaxis que debes
usar para comunicarte con él, Python no es inteligente. En realidad estás sola-
mente manteniendo una conversación contigo mismo; eso sí, usando una sintaxis
adecuada.
En cierto modo, cuando utilizas un programa escrito por otra persona, la conver-
sación se mantiene entre tú y el programador, con Python actuando meramente de
8CHAPTER 1. ¿POR QUÉ DEBERÍAS APRENDER A ESCRIBIR PROGRAMAS?
>>> adiós
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name 'adiós' is not defined
>>> if you don't mind, I need to leave\footnote{si no te importa, tengo que marcha
File "<stdin>", line 1
if you don't mind, I need to leave
^
SyntaxError: invalid syntax
>>> quit()
Te habrás fijado en que el error es diferente en cada uno de los dos primeros intentos.
El segundo error es diferente porque if es una palabra reservada, y cuando Python
la ve, cree que estamos intentando decirle algo, pero encuentra la sintaxis de la
frase incorrecta.
La forma correcta de decirle “adiós” a Python es introducir quit() en el símbolo
indicador del sistema >>>. Seguramente te hubiera llevado un buen rato adivinarlo,
así que tener este libro a mano probablemente te haya resultado útil.
001010001110100100101010000001111
11100110000011101010010101101101
...
El código máquina parece bastante sencillo a simple vista, dado que sólo contiene
ceros y unos, pero su sintaxis es incluso más compleja y mucho más enrevesada
que la de Python, razón por la cual muy pocos programadores escriben en código
máquina. En vez de eso, se han creado varios programas traductores para permitir
a los programadores escribir en lenguajes de alto nivel como Python o Javascript,
1.6. TERMINOLOGÍA: INTÉRPRETE Y COMPILADOR 9
y son esos traductores quienes convierten los programas a código máquina, que es
lo que ejecuta en realidad la CPU.
Dado que el código máquina está ligado al hardware de la máquina que lo ejecuta,
ese código no es portable (trasladable) entre equipos de diferente tipo. Los pro-
gramas escritos en lenguajes de alto nivel pueden ser trasladados entre distintas
máquinas usando un intérprete diferente en cada una de ellas, o recompilando el
código para crear una versión diferente del código máquina del programa para cada
uno de los tipos de equipo.
Esos traductores de lenguajes de programación forman dos categorías generales:
(1) intérpretes y (2) compiladores.
Un intérprete lee el código fuente de los programas tal y como ha sido escrito por
el programador, lo analiza, e interpreta sus instrucciones sobre la marcha. Python
es un intérprete y cuando lo estamos ejecutando de forma interactiva, podemos
escribir una línea de Python (una frase), y este la procesa de forma inmediata,
quedando listo para que podamos escribir otra línea.
Algunas de esas líneas le indican a Python que tú quieres que recuerde cierto
valor para utilizarlo más tarde. Tenemos que escoger un nombre para que ese
valor sea recordado y usaremos ese nombre simbólico para recuperar el valor más
tarde. Utilizamos el término variable para denominar las etiquetas que usamos
para referirnos a esos datos almacenados.
>>> x = 6
>>> print(x)
6
>>> y = x * 7
>>> print(y)
42
>>>
En este ejemplo, le pedimos a Python que recuerde el valor seis y use la etiqueta
x para que podamos recuperar el valor más tarde. Comprobamos que Python ha
guardado de verdad el valor usando print. Luego le pedimos a Python que recupere
x, lo multiplique por siete y guarde el valor calculado en y. Finalmente, le pedimos
a Python que escriba el valor actual de y.
A pesar de que estamos escribiendo estos comandos en Python línea a línea, Python
los está tratando como una secuencia ordenada de sentencias, en la cual las últimas
frases son capaces de obtener datos creados en las anteriores. Estamos, por tanto,
escribiendo nuestro primer párrafo sencillo con cuatro frases en un orden lógico y
útil.
La esencia de un intérprete consiste en ser capaz de mantener una conversación
interactiva como la mostrada más arriba. Un compilador necesita que le entreguen
el programa completo en un fichero, y luego ejecuta un proceso para traducir el
código fuente de alto nivel a código máquina, tras lo cual coloca ese código máquina
resultante dentro de otro fichero para su ejecución posterior.
En sistemas Windows, a menudo esos ejecutables en código máquina tienen un
sufijo o extensión como “.exe” o “.dll”, que significan “ejecutable” y “librería de
10CHAPTER 1. ¿POR QUÉ DEBERÍAS APRENDER A ESCRIBIR PROGRAMAS?
^?ELF^A^A^A^@^@^@^@^@^@^@^@^@^B^@^C^@^A^@^@^@\xa0\x82
^D^H4^@^@^@\x90^]^@^@^@^@^@^@4^@ ^@^G^@(^@$^@!^@^F^@
^@^@4^@^@^@4\x80^D^H4\x80^D^H\xe0^@^@^@\xe0^@^@^@^E
^@^@^@^D^@^@^@^C^@^@^@^T^A^@^@^T\x81^D^H^T\x81^D^H^S
^@^@^@^S^@^@^@^D^@^@^@^A^@^@^@^A\^D^HQVhT\x83^D^H\xe8
....
C:\Python35\python.exe
Ahora imagina que haces lo mismo pero buscando a través de millones de líneas
de texto. Francamente, tardarías menos aprendiendo Python y escribiendo un
programa en ese lenguaje para contar las palabras que si tuvieras que ir revisando
todas ellas una a una.
Pero hay una noticia aún mejor, y es que se me ha ocurrido un programa sencillo
para encontrar cuál es la palabra más común dentro de un fichero de texto. Ya
lo escribí, lo probé, y ahora te lo regalo para que lo puedas utilizar y ahorrarte
mucho tiempo.
12CHAPTER 1. ¿POR QUÉ DEBERÍAS APRENDER A ESCRIBIR PROGRAMAS?
bigcount = None
bigword = None
for word, count in list(counts.items()):
if bigcount is None or count > bigcount:
bigword = word
bigcount = count
print(bigword, bigcount)
# Code: http://www.py4e.com/code3/words.py
No necesitas ni siquiera saber Python para usar este programa. Tendrás que llegar
hasta el capítulo 10 de este libro para entender por completo las impresionantes
técnicas de Python que se han utilizado para crearlo. Ahora eres el usuario final,
sólo tienes que usar el programa y sorprenderte de sus habilidades y de cómo te
permite ahorrar un montón de esfuerzo. Tan sólo tienes que escribir el código
dentro de un fichero llamado words.py y ejecutarlo, o puedes descargar el código
fuente directamente desde https://es.py4e.com/code3/ y ejecutarlo.
Este es un buen ejemplo de cómo Python y el lenguaje Python actúan como un
intermediario entre tú (el usuario final) y yo (el programador). Python es un medio
para que intercambiemos secuencias de instrucciones útiles (es decir, programas)
en un lenguaje común que puede ser usado por cualquiera que instale Python en
su computadora. Así que ninguno de nosotros está hablando con Python, sino que
estamos comunicándonos uno con el otro a través de Python.
entrada Obtener datos del “mundo exterior”. Puede consistir en leer datos desde
un fichero, o incluso desde algún tipo de sensor, como un micrófono o un GPS.
1.10. ¿QUÉ ES POSIBLE QUE VAYA MAL? 13
En nuestros primeros programas, las entradas van a provenir del usuario, que
introducirá los datos a través del teclado.
salida Mostrar los resultados del programa en una pantalla, almacenarlos en un
fichero o incluso es posible enviarlos a un dispositivo como un altavoz para
reproducir música o leer un texto.
ejecución secuencial Ejecutar una sentencia tras otra en el mismo orden en que
se van encontrando en el script.
ejecución condicional Comprobar ciertas condiciones y luego ejecutar u omitir
una secuencia de sentencias.
ejecución repetida Ejecutar un conjunto de sentencias varias veces, normal-
mente con algún tipo de variación.
reutilización Escribir un conjunto de instrucciones una vez, darles un nombre
y así poder reutilizarlas luego cuando se necesiten en cualquier punto de tu
programa.
Parece demasiado simple para ser cierto, y por supuesto nunca es tan sencillo.
Es como si dijéramos que andar es simplemente “poner un pie delante del otro”.
El “arte” de escribir un programa es componer y entrelazar juntos esos elementos
básicos muchas veces hasta conseguir al final algo que resulte útil para sus usuarios.
El programa para contar palabras que vimos antes utiliza al mismo tiempo todos
esos patrones excepto uno.
Errores de sintaxis (Syntax errors) Estos son los primeros errores que come-
terás y también los más fáciles de solucionar. Un error de sintaxis significa
que has violado las reglas “gramaticales” de Python. Python hace todo lo
que puede para señalar el punto exacto, la línea y el carácter donde ha de-
tectado el fallo. Lo único complicado de los errores de sintaxis es que a veces
el error que debe corregirse está en realidad en una línea anterior a la cual
Python detectó ese fallo. De modo que la línea y el carácter que Python
indica en un error de sintaxis pueden ser tan sólo un punto de partida para
tu investigación.
Errores lógicos Se produce un error lógico cuando un programa tiene una sin-
taxis correcta, pero existe un error en el orden de las sentencias o en la forma
en que están relacionadas unas con otras. Un buen ejemplo de un error lógico
sería: “toma un trago de tu botella de agua, ponla en tu mochila, camina
hasta la biblioteca y luego vuelve a enroscar la tapa en la botella.”
Errores semánticos Un error semántico ocurre cuando la descripción que has
brindado de los pasos a seguir es sintácticamente perfecta y está en el orden
correcto, pero sencillamente hay un error en el programa. El programa es
correcto, pero no hace lo que tú pretendías que hiciera. Un ejemplo podría
ser cuando le das indicaciones a alguien sobre cómo llegar a un restaurante,
y le dices “. . . cuando llegues a la intersección con la gasolinera, gira a la
izquierda, continúa durante otro kilómetro y el restaurante es el edificio rojo
que encontrarás a tu izquierda.”. Tu amigo se retrasa y te llama para decirte
que está en una granja dando vueltas alrededor de un granero, sin rastro
alguno de un restaurante. Entonces le preguntas “¿giraste a la izquierda
o la derecha?”, y te responde “Seguí tus indicaciones al pie de la letra, di-
jiste que girara a la izquierda y continuar un kilómetro desde la gasolinera.”,
entonces le respondes “Lo siento mucho, porque a pesar de que mis indica-
1.11. DEPURANDO LOS PROGRAMAS 15
Insisto en que, ante cualquiera de estos tres tipos de errores, Python únicamente
hace lo que está a su alcance por seguir al pie de la letra lo que tú le has pedido
que haga.
leer Revisar tu código, leerlo de nuevo, y asegurarte de que ahí está expresado de
forma correcta lo que quieres decir.
ejecutar Prueba haciendo cambios y ejecutando diferentes versiones. Con frecuen-
cia, si muestras en tu programa lo correcto en el lugar indicado, el problema
se vuelve obvio, pero en ocasiones debes invertir algo de tiempo hasta con-
seguirlo.
pensar detenidamente ¡Toma tu tiempo para pensar!, ¿A qué tipo de error
corresponde: sintaxis, en tiempo de ejecución, semántico?, ¿Qué información
puedes obtener de los mensajes de error, o de la salida del programa?, ¿Qué
tipo de errores podría generar el problema que estás abordando?, ¿Cuál fue
el último cambio que hiciste, antes de que se presentara el problema?
retroceder En algún momento, lo mejor que podrás hacer es dar marcha atrás,
deshacer los cambios recientes hasta obtener de nuevo un programa que fun-
cione y puedas entender. Llegado a ese punto, podrás continuar con tu
trabajo.
Pero incluso las mejores técnicas de depuración fallarán si hay demasiados errores,
o si el código que intentas mejorar es demasiado extenso y complicado. Algunas
veces la mejor opción es retroceder, y simplificar el programa hasta que obtengas
algo que funcione y puedas entender.
Por lo general, los programadores novatos son reacios a retroceder porque no so-
portan tener que borrar una línea de código (incluso si está mal). Si te hace sentir
mejor, prueba a copiar tu programa en otro archivo antes de empezar a modifi-
carlo. De esa manera, puedes recuperar poco a poco pequeñas piezas de código
que necesites.
Según vayas avanzando por el resto del libro, no te asustes si los conceptos no
parecen encajar bien unos con otros al principio. Cuando estabas aprendiendo a
hablar, no supuso un problema que durante los primeros años solo pudieras emitir
lindos balbuceos. Y también fue normal que te llevara seis meses pasar de un
vocabulario simple a frases simples, y que te llevara 5-6 años más pasar de frases a
párrafos, y que todavía tuvieran que transcurrir unos cuantos años más hasta que
fuiste capaz de escribir tu propia historia corta interesante.
1.13 Glosario
1.14 Ejercicios
Ejercicio 6: ¿En qué lugar del computador queda almacenada una variable, como
en este caso “X”, después de ejecutar la siguiente línea de Python?:
x = 123
x = 43
x = x + 1
print(x)
a) 43
b) 44
c) x + 1
d) Error, porque x = x + 1 no es posible matemáticamente.
Ejercicio 8: Explica cada uno de los siguientes conceptos usando un ejemplo de
una capacidad humana: (1) Unidad central de procesamiento, (2) Memoria prin-
cipal, (3) Memoria secundaria, (4) Dispositivos de entrada, y (5) Dispositivos de
salida. Por ejemplo, “¿Cuál sería el equivalente humano de la Unidad central de
procesamiento?”.
Ejercicio 9: ¿Cómo puedes corregir un “Error de sintaxis”?.
Chapter 2
Variables, expresiones y
sentencias
python
>>> print(4)
4
Not surprisingly, strings belong to the type str and integers belong to the type
int. Less obviously, numbers with a decimal point belong to a type called float,
because these numbers are represented in a format called floating point.
>>> type(3.2)
<class 'float'>
19
20 CHAPTER 2. VARIABLES, EXPRESIONES Y SENTENCIAS
¿Qué ocurre con valores como “17” y “3.2”? Parecen números, pero van entre
comillas como las cadenas.
>>> type('17')
<class 'str'>
>>> type('3.2')
<class 'str'>
Son cadenas.
Cuando escribes un entero grande, puede que te sientas tentado a usar comas o
puntos para separarlo en grupos de tres dígitos, como en 1,000,000 1 . Eso no es
un entero válido en Python, pero en cambio sí que resulta válido algo como:
>>> print(1,000,000)
1 0 0
2.2 Variables
Una de las características más potentes de un lenguaje de programación es la
capacidad de manipular variables. Una variable es un nombre que se refiere a un
valor.
Una sentencia de asignación crea variables nuevas y las da valores:
Este ejemplo hace tres asignaciones. La primera asigna una cadena a una variable
nueva llamada mensaje; la segunda asigna el entero 17 a n; la tercera asigna el
valor (aproximado) de π a pi.
Para mostrar el valor de una variable, se puede usar la sentencia print:
>>> print(n)
17
>>> print(pi)
3.141592653589793
>>> type(mensaje)
<class 'str'>
>>> type(n)
<class 'int'>
>>> type(pi)
<class 'float'>
Puede que quieras tener esta lista a mano. Si el intérprete se queja por el nombre
de una de tus variables y no sabes por qué, comprueba si ese nombre está en esta
lista.
22 CHAPTER 2. VARIABLES, EXPRESIONES Y SENTENCIAS
2.4 Sentencias
Una sentencia es una unidad de código que el intérprete de Python puede ejecutar.
Hemos visto hasta ahora dos tipos de sentencia: print y las asignaciones.
Cuando escribes una sentencia en modo interactivo, el intérprete la ejecuta y mues-
tra el resultado, si es que lo hay.
Un script normalmente contiene una secuencia de sentencias. Si hay más de una
sentencia, los resultados aparecen de uno en uno según se van ejecutando las sen-
tencias.
Por ejemplo, el script
print(1)
x = 2
print(x)
produce la salida
1
2
20+32
hour-1
hour*60+minute
minute/60
5**2
(5+9)*(15-7)
>>> minute = 59
>>> minute/60
0.9833333333333333
2.6. EXPRESIONES 23
>>> minute = 59
>>> minute/60
0
Para obtener la misma respuesta en Python 3.0 use división dividida (// integer).
>>> minute = 59
>>> minute//60
0
2.6 Expresiones
17
x
x + 17
>>> 1 + 1
2
Sin embargo, en un script, ¡una expresión por si misma no hace nada! Esto a
menudo puede producir confusión entre los principiantes.
5
x = 5
x + 1
24 CHAPTER 2. VARIABLES, EXPRESIONES Y SENTENCIAS
>>> quotient = 7 // 3
>>> print(quotient)
2
>>> remainder = 7 % 3
>>> print(remainder)
1
>>> primero = 10
>>> segundo = 15
>>> print(primero+segundo)
25
>>> primero = '100'
>>> segundo = '150'
>>> print(primero + segundo)
100150
Antes de recibir cualquier dato desde el usuario, es buena idea escribir un mensaje
explicándole qué debe introducir. Se puede pasar una cadena a input, que será
mostrada al usuario antes de que el programa se detenga para recibir su entrada:
Pero si el usuario escribe algo que no sea una cadena de dígitos, obtendrás un error:
2.11 Comentarios
A medida que los programas se van volviendo más grandes y complicados, se vuel-
ven más difíciles de leer. Los lenguajes formales son densos, y a menudo es com-
plicado mirar un trozo de código e imaginarse qué es lo que hace, o por qué.
Por eso es buena idea añadir notas a tus programas, para explicar en un lenguaje
normal qué es lo que el programa está haciendo. Estas notas reciben el nombre de
comentarios, y en Python comienzan con el símbolo #:
En este caso, el comentario aparece como una línea completa. Pero también puedes
poner comentarios al final de una línea
Todo lo que va desde # hasta el final de la línea es ignorado—no afecta para nada
al programa.
Las comentarios son más útiles cuando documentan características del código que
no resultan obvias. Es razonable asumir que el lector puede descifrar qué es lo que
el código hace; es mucho más útil explicarle por qué.
Este comentario es redundante con el código e inútil:
2.12. ELECCIÓN DE NOMBRES DE VARIABLES MNEMÓNICOS 27
v = 5 # asigna 5 a v
v = 5 # velocidad en metros/segundo.
Elegir nombres adecuados para las variables puede reducir la necesidad de co-
mentarios, pero los nombres largos también pueden ocasionar que las expresiones
complejas sean difíciles de leer, así que hay que conseguir una solución de compro-
miso.
a = 35.0
b = 12.50
c = a * b
print(c)
horas = 35.0
tarifa = 12.50
salario = horas * tarifa
print(salario)
x1q3z9ahd = 35.0
x1q3z9afd = 12.50
x1q3p9afd = x1q3z9ahd * x1q3z9afd
print(x1q3p9afd)
de la palabra “mnemónico”.
28 CHAPTER 2. VARIABLES, EXPRESIONES Y SENTENCIAS
¿Qué ocurre aquí? ¿Cuáles de las piezas (for, word, in, etc.) son palabras reser-
vadas y cuáles son simplemente nombres de variables? ¿Acaso Python comprende
de un modo básico la noción de palabras (words)? Los programadores novatos
tienen problemas separando qué parte del código debe mantenerse tal como está
en este ejemplo y qué partes son simplemente elección del programador.
El código siguiente es equivalente al de arriba:
Para los principiantes es más fácil estudiar este código y saber qué partes son
palabras reservadas definidas por Python y qué partes son simplemente nombres
de variables elegidas por el programador. Está bastante claro que Python no
entiende nada de pizza ni de porciones, ni del hecho de que una pizza consiste en
un conjunto de una o más porciones.
Pero si nuestro programa lo que realmente va a hacer es leer datos y buscar palabras
en ellos, pizza y porción son nombres muy poco mnemónicos. Elegirlos como
nombres de variables distrae del propósito real del programa.
Dentro de muy poco tiempo, conocerás las palabras reservadas más comunes, y
empezarás a ver cómo esas palabras reservadas resaltan sobre las demás:
Las partes del código que están definidas por Python (for, in, print, y :) están
en negrita, mientras que las variables elegidas por el programador (word y words)
no lo están. Muchos editores de texto son conscientes de la sintaxis de Python
y colorearán las palabras reservadas de forma diferente para darte pistas que te
permitan mantener tus variables y las palabras reservadas separados. Dentro de
poco empezarás a leer Python y podrás determinar rápidamente qué es una variable
y qué es una palabra reservada.
3 El párrafo anterior se refiere más bien a quienes eligen nombres de variables en inglés, ya que
todas las palabras reservadas de Python coinciden con palabras propias de ese idioma (Nota del
trad.)
2.13. DEPURACIÓN 29
2.13 Depuración
En este punto, el error de sintaxis que es más probable que cometas será intentar
utilizar nombres de variables no válidos, como class y yield, que son palabras
clave, o odd~job y US$, que contienen caracteres no válidos.
Si pones un espacio en un nombre de variable, Python cree que se trata de dos
operandos sin ningún operador:
>>> month = 09
File "<stdin>", line 1
month = 09
^
SyntaxError: invalid token
Los nombres de las variables son sensibles a mayúsculas, así que LaTeX no es lo
mismo que latex.
En este punto, la causa más probable de un error semántico es el orden de las
operaciones. Por ejemplo, para evaluar 2π
1
, puedes sentirte tentado a escribir
Pero la división se evalúa antes, ¡así que obtendrás π/2, que no es lo mismo! No
hay forma de que Python sepa qué es lo que querías escribir exactamente, así que
en este caso no obtienes un mensaje de error; simplemente obtienes una respuesta
incorrecta.
2.14 Glosario
asignación Una sentencia que asigna un valor a una variable.
30 CHAPTER 2. VARIABLES, EXPRESIONES Y SENTENCIAS
división entera La operación que divide dos números y trunca la parte frac-
cionaria.
palabra clave Una palabra reservada que es usada por el compilador para
analizar un programa; no se pueden usar palabres clave como if, def, y
while como nombres de variables.
operador Un símbolo especial que representa un cálculo simple, como suma, mul-
tiplicación o concatenación de cadenas.
sentencia Una sección del código que representa un comando o acción. Hasta
ahora, las únicas sentencias que hemos visto son asignaciones y sentencias
print.
tipo Una categoría de valores. Los tipos que hemos visto hasta ahora son enteros
(tipo int), números en punto flotante (tipo float), y cadenas (tipo str).
valor Una de las unidades básicas de datos, como un número o una cadena, que
un programa manipula.
2.15 Ejercicios
Ejercicio 2: Escribe un programa que use input para pedirle al usuario
su nombre y luego darle la bienvenida.
Introduzca Horas: 35
Introduzca Tarifa: 2.75
Salario: 96.25
ancho = 17
alto = 12.0
1. ancho/2
2. ancho/2.0
3. alto/3
4. 1 + 2 * 5
Ejecución condicional
>>> 5 == 5
True
>>> 5 == 6
False
True y False son valores especiales que pertenecen al tipo bool (booleano); no
son cadenas:
>>> type(True)
<class 'bool'>
>>> type(False)
<class 'bool'>
x != y # x es distinto de y
x > y # x es mayor que y
x < y # x es menor que y
x >= y # x es mayor o igual que y
x <= y # x es menor o igual que y
x is y # x es lo mismo que y
x is not y # x no es lo mismo que y
33
34 CHAPTER 3. EJECUCIÓN CONDICIONAL
Esta flexibilidad puede ser útil, pero existen ciertas sutilezas en ese tipo de uso
que pueden resultar confusas. Es posible que prefieras evitar usarlo de este modo
hasta que estés bien seguro de lo que estás haciendo.
if x > 0 :
print('x es positivo')
suele decir que las líneas van “indentadas” (Nota del trad.)
2 Estudiaremos las funciones en el capítulo 4 y los bucles en el capítulo 5.
3.3. EJECUCIÓN CONDICIONAL 35
sí
x>0
print(‘x es positivo’)
if x < 0 :
pass # ¡necesito gestionar los valores negativos!
>>> x = 3
>>> if x < 10:
... print('Pequeño')
...
Pequeño
>>>
>>> x = 3
>>> if x < 10:
... print('Pequeño')
... print('Hecho')
File "<stdin>", line 3
print('Hecho')
^
SyntaxError: invalid syntax
if x%2 == 0 :
print('x es par')
else :
print('x es impar')
no sí
x%2 == 0
Dado que la condición debe ser obligatoriamente verdadera o falsa, solamente una
de las alternativas será ejecutada. Las alternativas reciben el nombre de ramas,
dado que se trata de ramificaciones en el flujo de la ejecución.
if x < y:
print('x es menor que y')
elif x > y:
print('x es mayor que y')
else:
print('x e y son iguales')
elif es una abreviatura para “else if”. En este caso también será ejecutada única-
mente una de las ramas.
No hay un límite para el número de sentencias elif. Si hay una clausula else,
debe ir al final, pero tampoco es obligatorio que ésta exista.
3.6. CONDICIONALES ANIDADOS 37
sí
x<y print(‘menor’)
sí
x>y print (‘mayor’)
print(‘igual’)
if choice == 'a':
print('Respuesta incorrecta')
elif choice == 'b':
print('Respuesta correcta')
elif choice == 'c':
print('Casi, pero no es correcto')
if x == y:
print('x e y son iguales')
else:
if x < y:
print('x es menor que y')
else:
print('x es mayor que y')
El condicional exterior contiene dos ramas. La primera rama ejecuta una sentencia
simple. La segunda contiene otra sentencia if, que tiene a su vez sus propias dos
ramas. Esas dos ramas son ambas sentencias simples, pero podrían haber sido
sentencias condicionales también.
A pesar de que el indentado de las sentencias hace que la estructura esté clara, los
condicionales anidados pueden volverse difíciles de leer rápidamente. En general,
es buena idea evitarlos si se puede.
38 CHAPTER 3. EJECUCIÓN CONDICIONAL
sí No
x == y
sí No
x<y
print(‘igual’)
print(‘menor’) print(‘mayor’)
if 0 < x:
if x < 10:
print('x es un número positivo con un sólo dígito.')
# Code: http://www.py4e.com/code3/fahren.py
python fahren.py
Introduzca la Temperatura Fahrenheit:72
22.2222222222
python fahren.py
Introduzca la Temperatura Fahrenheit:fred
Traceback (most recent call last):
File "fahren.py", line 2, in <module>
fahr = float(ent)
ValueError: invalid literal for float(): fred
# Code: http://www.py4e.com/code3/fahren2.py
python fahren2.py
Introduzca la Temperatura Fahrenheit:72
22.2222222222
python fahren2.py
Introduzca la Temperatura Fahrenheit:fred
Por favor, introduzca un número
Gestionar una excepción con una sentencia try recibe el nombre de capturar una
excepción. En este ejemplo, la clausula except muestra un mensaje de error. En
general, capturar una excepción te da la oportunidad de corregir el problema,
volverlo a intentar o, al menos, terminar el programa con elegancia.
>>> x = 6
>>> y = 2
>>> x >= 2 and (x/y) > 2
True
>>> x = 1
>>> y = 0
>>> x >= 2 and (x/y) > 2
False
>>> x = 6
>>> y = 0
>>> x >= 2 and (x/y) > 2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>
La tercera operación ha fallado porque Python intentó evaluar (x/y) e y era cero,
lo cual provoca un runtime error (error en tiempo de ejecución). Pero el segundo
3.9. DEPURACIÓN 41
>>> x = 1
>>> y = 0
>>> x >= 2 and y != 0 and (x/y) > 2
False
>>> x = 6
>>> y = 0
>>> x >= 2 and y != 0 and (x/y) > 2
False
>>> x >= 2 and (x/y) > 2 and y != 0
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ZeroDivisionError: division by zero
>>>
3.9 Depuración
Los “traceback” que Python muestra cuando se produce un error contienen un
montón de información, pero pueden resultar abrumadores. Las partes más útiles
normalmente son:
Los errores de sintaxis (syntax errors), normalmente son fáciles de localizar, pero
a veces tienen trampa. Los errores debido a espacios en blanco pueden ser compli-
cados, ya que los espacios y las tabulaciones son invisibles, y solemos ignorarlos.
>>> x = 5
>>> y = 6
File "<stdin>", line 1
y = 6
^
IndentationError: unexpected indent
42 CHAPTER 3. EJECUCIÓN CONDICIONAL
En este ejemplo, el problema es que la segunda línea está indentada por un espacio.
Pero el mensaje de error apunta a y, lo cual resulta engañoso. En general, los
mensajes de error indican dónde se ha descubierto el problema, pero el error real
podría estar en el código previo, a veces en alguna línea anterior.
Ocurre lo mismo con los errores en tiempo de ejecución (runtime errors). Supón
que estás tratando de calcular una relación señal-ruido en decibelios. La fórmula
es SN Rdb = 10 log10 (Psenal /Pruido ). En Python, podrías escribir algo como esto:
import math
int_senal = 9
int_ruido = 10
relacion = int_senal / int_ruido
decibelios = 10 * math.log10(relacion)
print(decibelios)
# Code: http://www.py4e.com/code3/snr.py
El mensaje de error apunta a la línea 5, pero no hay nada incorrecto en ese línea.
Para encontrar el error real, puede resultar útil mostrar en pantalla el valor de
relacion, que resulta ser 0. El problema está en la línea 4, ya que al dividir dos
enteros se realiza una división entera. La solución es representar la intensidad de
la señal y la intensidad del ruido con valores en punto flotante.
En general, los mensajes de error te dicen dónde se ha descubierto el problema,
pero a menudo no es ahí exactamente donde se ha producido.
3.10 Glosario
condición La expresión booleana en una sentencia condicional que determina qué
rama será ejecutada.
condicional anidado Una sentencia condicional que aparece en una de las ramas
de otra sentencia condicional.
condicional encadenado Una sentencia condicional con una serie de ramas al-
ternativas.
expresión booleana Un expresión cuyo valor puede ser o bien Verdadero o bien
Falso.
traceback Una lista de las funciones que se están ejecutando, que se muestra en
pantalla cuando se produce una excepción.
3.11 Ejercicios
Ejercicio 1: Reescribe el programa del cálculo del salario para darle al empleado
1.5 veces la tarifa horaria para todas las horas trabajadas que excedan de 40.
Ejercicio 2: Reescribe el programa del salario usando try y except, de modo que el
programa sea capaz de gestionar entradas no numéricas con elegancia, mostrando
un mensaje y saliendo del programa. A continuación se muestran dos ejecuciones
del programa:
Ejercicio 3: Escribe un programa que solicite una puntuación entre 0.0 y 1.0. Si la
puntuación está fuera de ese rango, muestra un mensaje de error. Si la puntuación
está entre 0.0 y 1.0, muestra la calificación usando la tabla siguiente:
Puntuación Calificación
>= 0.9 Sobresaliente
>= 0.8 Notable
>= 0.7 Bien
>= 0.6 Suficiente
< 0.6 Insuficiente
Ejecuta el programa repetidamente, como se muestra arriba, para probar con varios
valores de entrada diferentes.
Chapter 4
Funciones
>>> type(32)
<class 'int'>
45
46 CHAPTER 4. FUNCIONES
La función max nos dice cuál es el “carácter más grande” de la cadena (que resulta
ser la letra “u”), mientras que la función min nos muestra el carácter más pequeño
(que en ese caso es un espacio).
Otra función interna muy común es len, que nos dice cuántos elementos hay en
su argumento. Si el argumento de len es una cadena, nos devuelve el número de
caracteres que hay en la cadena.
>>> int('32')
32
>>> int('Hola')
ValueError: invalid literal for int() with base 10: 'Hola'
int puede convertir valores en punto flotante a enteros, pero no los redondea;
simplemente corta y descarta la parte decimal:
>>> int(3.99999)
3
>>> int(-2.3)
-2
>>> float(32)
32.0
>>> float('3.14159')
3.14159
>>> str(32)
'32'
>>> str(3.14159)
'3.14159'
4.4. FUNCIONES MATEMÁTICAS 47
>>> print(math)
<module 'math' (built-in)>
>>> grados = 45
>>> radianes = grados / 360.0 * 2 * math.pi
>>> math.sin(radianes)
0.7071067811865476
La expresión math.pi toma la variable pi del módulo math. El valor de esa variable
es una aproximación de π, con una precisión de unos 15 dígitos.
Si sabes de trigonometría, puedes comprobar el resultado anterior, comparándolo
con la raíz cuadrada de dos dividida por dos:
import random
for i in range(10):
x = random.random()
print(x)
Este programa produce la siguiente lista de 10 números aleatorios entre 0.0 y hasta
(pero no incluyendo) 1.0.
0.11132867921152356
0.5950949227890241
0.04820265884996877
0.841003109276478
0.997914947094958
0.04842330803368111
0.7416295948208405
0.510535245390327
0.27447040171978143
0.028511805472785867
>>> t = [1, 2, 3]
>>> random.choice(t)
2
>>> random.choice(t)
3
Hasta ahora, sólo hemos estado usando las funciones que vienen incorporadas en
Python, pero es posible añadir también funciones nuevas. Una definición de fun-
ción especifica el nombre de una función nueva y la secuencia de sentencias que se
ejecutan cuando esa función es llamada. Una vez definida una función, se puede
reutilizar una y otra vez a lo largo de todo el programa.
He aquí un ejemplo:
def muestra_estribillo():
print('Soy un leñador, qué alegría.')
print('Duermo toda la noche y trabajo todo el día.')
def es una palabra clave que indica que se trata de una definición de función. El
nombre de la función es muestra_estribillo. Las reglas para los nombres de las
funciones son los mismos que para las variables: se pueden usar letras, números y
algunos signos de puntuación, pero el primer carácter no puede ser un número. No
se puede usar una palabra clave como nombre de una función, y se debería evitar
también tener una variable y una función con el mismo nombre.
Los paréntesis vacíos después del nombre indican que esta función no toma ningún
argumento. Más tarde construiremos funciones que reciban argumentos de entrada.
La primera línea de la definición de la función es llamada la cabecera; el resto se
llama el cuerpo. La cabecera debe terminar con dos-puntos (:), y el cuerpo debe ir
indentado. Por convención, el indentado es siempre de cuatro espacios. El cuerpo
puede contener cualquier número de sentencias.
Las cadenas en la sentencia print están encerradas entre comillas. Da igual uti-
lizar comillas simples que dobles; la mayoría de la gente prefiere comillas simples,
excepto en aquellos casos en los que una comilla simple (que también se usa como
apostrofe) aparece en medio de la cadena.
Si escribes una definición de función en modo interactivo, el intérprete mostrará
puntos suspensivos (. . . ) para informarte de que la definición no está completa:
50 CHAPTER 4. FUNCIONES
Para finalizar la función, debes introducir una línea vacía (esto no es necesario en
un script).
Al definir una función se crea una variable con el mismo nombre.
>>> print(muestra_estribillo)
<function muestra_estribillo at 0xb7e99e9c>
>>> print(type(muestra_estribillo))
<type 'function'>
>>> muestra_estribillo()
Soy un leñador, qué alegría.
Duermo toda la noche y trabajo todo el día.
Una vez que se ha definido una función, puede usarse dentro de otra. Por ejem-
plo, para repetir el estribillo anterior, podríamos escribir una función llamada
repite_estribillo:
def repite_estribillo():
muestra_estribillo()
muestra_estribillo()
>>> repite_estribillo()
Soy un leñador, qué alegría.
Duermo toda la noche y trabajo todo el día.
Soy un leñador, qué alegría.
Duermo toda la noche y trabajo todo el día.
def muestra_estribillo():
print('Soy un leñador, que alegría.')
print('Duermo toda la noche y trabajo todo el día.')
def repite_estribillo():
muestra_estribillo()
muestra_estribillo()
repite_estribillo()
# Code: http://www.py4e.com/code3/lyrics.py
def muestra_dos_veces(bruce):
print(bruce)
print(bruce)
>>> muestra_dos_veces('Spam')
Spam
Spam
>>> muestra_dos_veces(17)
17
17
>>> muestra_dos_veces(math.pi)
3.14159265359
3.14159265359
Las mismas reglas de composición que se aplican a las funciones internas, también
se aplican a las funciones definidas por el usuario, de modo que podemos usar
cualquier tipo de expresión como argumento para muestra_dos_veces:
El argumento es evaluado antes de que la función sea llamada, así que en los
ejemplos, la expresión Spam *4 y math.cos(math.pi) son evaluadas sólo una vez.
También se puede usar una variable como argumento:
x = math.cos(radians)
aurea = (math.sqrt(5) + 1) / 2
>>> math.sqrt(5)
2.23606797749979
math.sqrt(5)
Este script calcula la raíz cuadrada de 5, pero dado que no almacena el resultado
en una variable ni lo muestra, no resulta en realidad muy útil.
Las funciones estériles pueden mostrar algo en la pantalla o tener cualquier otro
efecto, pero no devuelven un valor. Si intentas asignar el resultado a una variable,
obtendrás un valor especial llamado None (nada).
El valor None no es el mismo que la cadena “None”. Es un valor especial que tiene
su propio tipo:
>>> print(type(None))
<class 'NoneType'>
Para devolver un resultado desde una función, usamos la sentencia return dentro
de ella. Por ejemplo, podemos crear una función muy simple llamada sumados,
que suma dos números y devuelve el resultado.
x = sumados(3, 5)
print(x)
# Code: http://www.py4e.com/code3/addtwo.py
Cuando se ejecuta este script, la sentencia print mostrará “8”, ya que la función
sumados ha sido llamada con 3 y 5 como argumentos. Dentro de la función, los
parámetros a y b equivaldrán a 3 y a 5 respectivamente. La función calculó la
suma de ambos número y la guardó en una variable local a la función llamada
suma. Después usó la sentencia return para enviar el valor calculado de vuelta al
código de llamada como resultado de la función, que fue asignado a la variable x
y mostrado en pantalla.
A lo largo del resto del libro, a menudo usaremos una definición de función para
explicar un concepto. Parte de la habilidad de crear y usar funciones consiste en
llegar a tener una función que represente correctamente una idea, como “encontrar
el valor más pequeño en una lista de valores”. Más adelante te mostraremos el
código para encontrar el valor más pequeño de una lista de valores y te lo pre-
sentaremos como una función llamada min, que toma una lista de valores como
argumento y devuelve el menor valor de esa lista.
4.12. DEPURACIÓN 55
4.12 Depuración
Si estás usando un editor de texto para escribir tus propios scripts, puede que tengas
problemas con los espacios y tabulaciones. El mejor modo de evitar esos problemas
es usar espacios exclusivamente (no tabulaciones). La mayoría de los editores de
texto que reconocen Python lo hacen así por defecto, aunque hay algunos que no.
Las tabulaciones y los espacios normalmente son invisibles, lo cual hace que sea
difícil depurar los errores que se pueden producir, así que mejor busca un editor
que gestione el indentado por ti.
Tampoco te olvides de guardar tu programa antes de hacerlo funcionar. Algunos
entornos de desarrollo lo hacen automáticamente, pero otros no. En ese caso, el
programa que estás viendo en el editor de texto puede no ser el mismo que estás
ejecutando en realidad.
¡La depuración puede llevar mucho tiempo si estás haciendo funcionar el mismo
programa con errores una y otra vez!
Asegúrate de que el código que estás examinando es el mismo que estás ejecutando.
Si no estás seguro, pon algo como print("hola") al principio del programa y hazlo
funcionar de nuevo. Si no ves hola en la pantalla, ¡es que no estás ejecutando el
programa correcto!
4.13 Glosario
algoritmo Un proceso general para resolver una categoría de problemas.
composición Uso de una expresión o sentencia como parte de otra más larga,
definición de función Una sentencia que crea una función nueva, especificando
su nombre, parámetros, y las sentencias que ejecuta.
función Una secuencia de sentencias con un nombre que realizan alguna operación
útil. Las funciones pueden tomar argumentos o no, y pueden producir un
resultado o no.
función estéril (void function) Una función que no devuelve ningún valor.
llamada a función Una sentencia que ejecuta una función. Consiste en el nom-
bre de la función seguido por una lista de argumentos.
notación punto La sintaxis para llamar a una función de otro módulo, especifi-
cando el nombre del módulo seguido por un punto y el nombre de la función.
objeto módulo Un valor creado por una sentencia import, que proporciona ac-
ceso a los datos y código definidos en un módulo.
parámetro Un nombre usado dentro de una función para referirse al valor pasado
como argumento.
sentencia import Una sentencia que lee un archivo módulo y crea un objeto
módulo.
4.14 Ejercicios
def fred():
print("Zap")
def jane():
print("ABC")
jane()
fred()
jane()
Introduzca Horas: 45
Introduzca Tarifa: 10
Salario: 475.0
Puntuación Calificación
> 0.9 Sobresaliente
> 0.8 Notable
> 0.7 Bien
> 0.6 Suficiente
<= 0.6 Insuficiente
Ejecuta el programa repetidamente para probar con varios valores de entrada difer-
entes.
Chapter 5
Iteración
x = x + 1
Esto quiere decir “‘toma el valor actual de x, añádele 1, y luego actualiza x con el
nuevo valor”.
Si intentas actualizar una variable que no existe, obtendrás un error, ya que Python
evalúa el lado derecho antes de asignar el valor a x:
>>> x = x + 1
NameError: name 'x' is not defined
Antes de que puedas actualizar una variable, debes inicializarla, normalmente me-
diante una simple asignación:
>>> x = 0
>>> x = x + 1
59
60 CHAPTER 5. ITERACIÓN
n = 5
while n > 0:
print(n)
n = n - 1
print('¡Despegue!')
Casi se puede leer la sentencia while como si estuviera escrita en inglés. Significa,
“Mientras n sea mayor que 0, muestra el valor de n y luego reduce el valor de n
en 1 unidad. Cuando llegues a 0, sal de la sentencia while y muestra la palabra
¡Despegue!”
Éste es el flujo de ejecución de la sentencia while, explicado de un modo más
formal:
Este tipo de flujo recibe el nombre de bucle, ya que el tercer paso enlaza de nuevo
con el primero. Cada vez que se ejecuta el cuerpo del bucle se dice que realizamos
una iteración. Para el bucle anterior, podríamos decir que “ha tenido cinco itera-
ciones”, lo que significa que el cuerpo del bucle se ha ejecutado cinco veces.
El cuerpo del bucle debe cambiar el valor de una o más variables, de modo que
la condición pueda en algún momento evaluarse como falsa y el bucle termine. La
variable que cambia cada vez que el bucle se ejecuta y controla cuándo termina
éste, recibe el nombre de variable de iteración. Si no hay variable de iteración, el
bucle se repetirá para siempre, resultando así un bucle infinito.
n = 10
while True:
print(n, end=' ')
n = n - 1
print('¡Terminado!')
while True:
linea = input('> ')
if linea == 'fin':
break
print(linea)
print('¡Terminado!')
# Code: http://www.py4e.com/code3/copytildone1.py
La condición del bucle es True, lo cual es verdadero siempre, así que el bucle se
repetirá hasta que se ejecute la sentencia break.
Cada vez que se entre en el bucle, se pedirá una entrada al usuario. Si el usuario
escribe fin, la sentencia break hará que se salga del bucle. En cualquier otro caso,
el programa repetirá cualquier cosa que el usuario escriba y volverá al principio
del bucle. Éste es un ejemplo de su funcionamiento:
Este modo de escribir bucles while es habitual, ya que así se puede comprobar la
condición en cualquier punto del bucle (no sólo al principio), y se puede expresar
la condición de parada afirmativamente (“detente cuando ocurra. . . ”), en vez de
tener que hacerlo con lógica negativa (“sigue haciéndolo hasta que ocurra. . . ”).
while True:
linea = input('> ')
if linea[0] == '#' :
continue
if linea == 'fin':
break
print(linea)
print('¡Terminado!')
# Code: http://www.py4e.com/code3/copytildone2.py
He aquí una ejecución de ejemplo de ese nuevo programa con la sentencia continue
añadida.
Todas las líneas se imprimen en pantalla, excepto la que comienza con el símbolo
de almohadilla, ya que en ese caso se ejecuta continue, finaliza la iteración actual
y salta de vuelta a la sentencia while para comenzar la siguiente iteración, de
modo que que se omite la sentencia print.
tiene una lista de cosas para recorrer, se puede construir un bucle definido usando
una sentencia for. A la sentencia while se la llama un bucle indefinido, porque
simplemente se repite hasta que cierta condición se hace Falsa, mientras que el
bucle for se repite a través de un conjunto conocido de elementos, de modo que
ejecuta tantas iteraciones como elementos hay en el conjunto.
La sintaxis de un bucle for es similar a la del bucle while, en ella hay una sentencia
for y un cuerpo que se repite:
• Se realiza alguna operación con cada elemento en el cuerpo del bucle, posi-
blemente cambiando las variables dentro de ese cuerpo.
Usaremos ahora una lista de números para demostrar los conceptos y construcción
de estos diseños de bucles.
Por ejemplo, para contar el número de elementos en una lista, podemos escribir el
siguiente bucle for:
contador = 0
for valor in [3, 41, 12, 9, 74, 15]:
contador = contador + 1
print('Num. elementos: ', contador)
total = 0
for valor in [3, 41, 12, 9, 74, 15]:
total = total + valor
print('Total: ', total)
Ni el bucle que cuenta los elementos ni el que los suma resultan particularmente
útiles en la práctica, dado que existen las funciones internas len() y sum() que
cuentan el número de elementos de una lista y el total de elementos en la misma
respectivamente.
Para encontrar el valor mayor de una lista o secuencia, construimos el bucle sigu-
iente:
mayor = None
print('Antes:', mayor)
for valor in [3, 41, 12, 9, 74, 15]:
if mayor is None or valor > mayor :
mayor = valor
print('Bucle:', valor, mayor)
print('Mayor:', mayor)
Antes: None
Bucle: 3 3
Bucle: 41 41
Bucle: 12 41
Bucle: 9 41
Bucle: 74 74
Bucle: 15 74
Mayor: 74
Debemos pensar en la variable mayor como el “mayor valor visto hasta ese mo-
mento”. Antes del bucle, asignamos a mayor el valor None. None es un valor
constante especial que se puede almacenar en una variable para indicar que la
variable está “vacía”.
Antes de que el bucle comience, el mayor valor visto hasta entonces es None, dado
que no se ha visto aún ningún valor. Durante la ejecución del bucle, si mayor es
None, entonces tomamos el primer valor que tenemos como el mayor hasta entonces.
Se puede ver en la primera iteración, cuando el valor de valor es 3, mientras que
mayor es None, inmediatamente hacemos que mayor pase a ser 3.
Tras la primera iteración, mayor ya no es None, así que la segunda parte de la ex-
presión lógica compuesta que comprueba si valor > mayor se activará sólo cuando
encontremos un valor que sea mayor que el “mayor hasta ese momento”. Cuando
encontramos un nuevo valor “mayor aún”, tomamos ese nuevo valor para mayor.
Se puede ver en la salida del programa que mayor pasa desde 3 a 41 y luego a 74.
Al final del bucle, se habrán revisado todos los valores y la variable mayor contendrá
entonces el mayor valor de la lista.
Para calcular el número más pequeño, el código es muy similar con un pequeño
cambio:
66 CHAPTER 5. ITERACIÓN
print('Antes:', menor)
for valor in [3, 41, 12, 9, 74, 15]:
if menor is None or valor < menor:
menor = valor
print('Bucle:', valor, menor)
print('Menor:', menor)
De nuevo, menor es el “menor hasta ese momento” antes, durante y después de que
el bucle se ejecute. Cuando el bucle se ha completado, menor contendrá el valor
mínimo de la lista
También como en el caso del número de elementos y de la suma, las funciones
internas max() y min() convierten la escritura de este tipo de bucles en innecesaria.
Lo siguiente es una versión simple de la función interna de Python min():
def min(valores):
menor = None
for valor in valores:
if menor is None or valor < menor:
menor = valor
return menor
En esta versión de la función para calcular el mínimo, hemos eliminado las senten-
cias print, de modo que sea equivalente a la función min, que ya está incorporada
dentro de Python.
5.8 Depuración
A medida que vayas escribiendo programas más grandes, puede que notes que vas
necesitando emplear cada vez más tiempo en depurarlos. Más código significa más
oportunidades de cometer un error y más lugares donde los bugs pueden esconderse.
Un método para acortar el tiempo de depuración es “depurar por bisección”. Por
ejemplo, si hay 100 líneas en tu programa y las compruebas de una en una, te
llevará 100 pasos.
En lugar de eso, intenta partir el problema por la mitad. Busca en medio del
programa, o cerca de ahí, un valor intermedio que puedas comprobar. Añade
una sentencia print (o alguna otra cosa que tenga un efecto verificable), y haz
funcionar el programa.
Si en el punto medio la verificación es incorrecta, el problema debería estar en la
primera mitad del programa. Si ésta es correcta, el problema estará en la segunda
mitad.
Cada vez que realices una comprobación como esta, reduces a la mitad el número
de líneas en las que buscar. Después de seis pasos (que son muchos menos de 100),
lo habrás reducido a una o dos líneas de código, al menos en teoría.
En la práctica no siempre está claro qué es “en medio del programa”, y no siempre
es posible colocar ahí una verificación. No tiene sentido contar las líneas y encontrar
5.9. GLOSARIO 67
el punto medio exacto. En lugar de eso, piensa en lugares del programa en los cuales
pueda haber errores y en lugares donde resulte fácil colocar una comprobación.
Luego elige un sitio donde estimes que las oportunidades de que el bug esté por
delante y las de que esté por detrás de esa comprobación son más o menos las
mismas.
5.9 Glosario
acumulador Una variable usada en un bucle para sumar o acumular un resultado.
contador Una variable usada en un bucle para contar el número de veces que algo
sucede. Inicializamos el contador a cero y luego lo vamos incrementando cada
vez que queramos que “cuente” algo.
inicializar Una asignación que da un valor inicial a una variable que va a ser
después actualizada.
iteración Ejecución repetida de una serie de sentencias usando bien una función
que se llama a si misma o bien un bucle.
5.10 Ejercicios
Ejercicio 1: Escribe un programa que lea repetidamente números hasta
que el usuario introduzca “fin”. Una vez se haya introducido “fin”,
muestra por pantalla el total, la cantidad de números y la media de
esos números. Si el usuario introduce cualquier otra cosa que no sea un
número, detecta su fallo usando try y except, muestra un mensaje de
error y pasa al número siguiente.
Introduzca un número: 4
Introduzca un número: 5
Introduzca un número: dato erróneo
Entrada inválida
Introduzca un número: 7
Introduzca un número: fin
16 3 5.33333333333
68 CHAPTER 5. ITERACIÓN
Ejercicio 2: Escribe otro programa que pida una lista de números como
la anterior y al final muestre por pantalla el máximo y mínimo de los
números, en vez de la media.
Chapter 6
Cadenas
>>> print(letra)
a
Para la mayoría de las personas, la primer letra de “banana” es “b”, no “a”. Pero
en Python, el índice es un desfase desde el inicio de la cadena, y el desfase de la
primera letra es cero.
Así que “b” es la letra 0 (“cero”) de “banana”, “a” es la letra con índice 1, y “n”
es la que tiene índice 2, etc.
Puedes usar cualquier expresión, incluyendo variables y operadores, como un índice,
pero el valor del índice tiene que ser un entero. De otro modo obtendrás:
69
70 CHAPTER 6. CADENAS
b a n a n a
[0] [1] [2] [3] [4] [5]
Para obtener la última letra de una cadena, podrías estar tentado a probar algo
como esto:
La razón de que haya un IndexError es que ahí no hay ninguna letra en “banana”
con el índice 6. Puesto que empezamos a contar desde cero, las seis letras están
enumeradas desde 0 hasta 5. Para obtener el último carácter, tienes que restar 1
a length:
Alternativamente, puedes usar índices negativos, los cuales cuentan hacia atrás
desde el final de la cadena. La expresión fruta[-1] devuelve la última letra,
fruta[-2] la penúltima letra, y así sucesivamente.
indice = 0
while indice < len(fruta):
letra = fruta[indice]
print(letra)
indice = indice + 1
6.4. REBANADO DE UNA CADENA 71
Este bucle recorre la cadena e imprime cada letra en una línea cada una. La
condición del bucle es indice < len(fruta), así que cuando indice es igual al
tamaño de la cadena, la condición es falsa, y el código del bucle no se ejecuta. El
último carácter accedido es el que tiene el índice len(fruta)-1, el cual es el último
carácter en la cadena.
Ejercicio 1: Escribe un bucle while que comience con el último carácter
en la cadena y haga un recorrido hacia atrás hasta el primer carácter
en la cadena, imprimiendo cada letra en una línea independiente.
Otra forma de escribir un recorrido es con un bucle for:
Si el primer índice es mayor que o igual que el segundo, el resultado es una cadena
vacía, representado por dos comillas:
Este ejemplo concatena una nueva letra a una parte de saludo. Esto no tiene
efecto sobre la cadena original.
El siguiente programa cuenta el número de veces que la letra “a” aparece en una
cadena:
palabra = 'banana'
contador = 0
for letra in palabra:
if letra == 'a':
contador = contador + 1
print(contador)
6.7 El operador in
La palabra in es un operador booleano que toma dos cadenas y regresa True si la
primera cadena aparece como una subcadena de la segunda:
if palabra == 'banana':
print('Muy bien, bananas.')
Otras operaciones de comparación son útiles para poner palabras en orden al-
fabético:
capitalize(...)
S.capitalize() -> str
Aunque la función dir lista los métodos y puedes usar la función help
para obtener una breve documentación de un método, una mejor fuente
de documentación para los métodos de cadenas se puede encontrar en
https://docs.python.org/library/stdtypes.html#string-methods.
Llamar a un método es similar a llamar una función (esta toma argumentos y
devuelve un valor) pero la sintaxis es diferente. Llamamos a un método uniendo
el nombre del método al de la variable, usando un punto como delimitador.
Por ejemplo, el método upper toma una cadena y devuelve una nueva cadena con
todas las letras en mayúscula:
En vez de la sintaxis de función upper(word), éste utiliza la sintaxis de método
word.upper().
Esta forma de notación con punto especifica el nombre del método, upper, y el
nombre de la cadena al que se le aplicará el método, palabra. Los paréntesis vacíos
indican que el método no toma argumentos.
Una llamada a un método es conocida como una invocación; en este caso, diríamos
que estamos invocando upper en palabra.
Por ejemplo, existe un método de cadena llamado find que busca la posición de
una cadena dentro de otra:
6.9. MÉTODOS DE CADENAS 75
>>> palabra.find('na')
2
También puede tomar como un segundo argumento el índice desde donde debe
empezar:
>>> palabra.find('na', 3)
4
Una tarea común es eliminar los espacios en blanco (espacios, tabs, o nuevas líneas)
en el inicio y el final de una cadena usando el método strip:
Utilizamos una versión del método find que nos permite especificar la posición en
la cadena desde donde queremos que find comience a buscar. Cuando recortamos
una parte de una cadena, extraemos los caracteres desde “uno después de la arroba
hasta, pero no incluyendo, el carácter de espacio”.
La documentación del método find está disponible en
https://docs.python.org/library/stdtypes.html#string-methods.
6.11. EL OPERADOR DE FORMATO 77
>>> camellos = 42
>>> '%d' % camellos
'42'
El resultado es la cadena ‘42’, el cual no debe ser confundido con el valor entero
42.
Una secuencia de formato puede aparecer en cualquier lugar en la cadena, así que
puedes meter un valor en una frase:
>>> camellos = 42
>>> 'Yo he visto %d camellos.' % camellos
'Yo he visto 42 camellos.'
6.12 Depuración
Una habilidad que debes desarrollar cuando programas es siempre preguntarte a ti
mismo, “¿Qué podría fallar aquí?” o alternativamente, “¿Qué cosa ilógica podría
hacer un usuario para hacer fallar nuestro (aparentemente) perfecto programa?”
Por ejemplo, observa el programa que utilizamos para demostrar el bucle while
en el capítulo de iteraciones:
while True:
linea = input('> ')
if linea[0] == '#' :
continue
if linea == 'fin':
break
print(linea)
print('¡Terminado!')
# Code: http://www.py4e.com/code3/copytildone2.py
Mira lo que pasa cuando el usuario introduce una línea vacía como entrada:
El código funciona bien hasta que se presenta una línea vacía. En ese momento no
hay un carácter cero, por lo que obtenemos una traza de error (traceback). Existen
dos soluciones a esto para convertir la línea tres en “segura”, incluso si la línea está
vacía.
Una posibilidad es simplemente usar el método startswith que devuelve False si
la cadena está vacía.
if line.startswith('#'):
6.13. GLOSARIO 79
6.13 Glosario
bandera Una variable booleana utilizada para indicar si una condición es ver-
dadera o falsa.
búsqueda Un patrón de recorrido que se detiene cuando encuentra lo que está
buscando.
cadena a formatear Una cadena, usado con el operador de formato, que con-
tiene secuencias de formato.
cadena vacía una cadena sin caracteres y de tamaño 0, representada por dos
comillas sencillas.
contador Una variable utilizada para contar algo, usualmente inicializada a cero
y luego incrementada.
índice Un valor entero utilizado para seleccionar un ítem en una secuencia, tal
como un carácter en una cadena.
inmutable La propiedad de una secuencia cuyos elementos no pueden ser asigna-
dos.
invocación Una sentencia que llama un método.
ítem Uno de los valores en una secuencia.
método Una función que está asociada a un objeto y es llamada utilizando la
notación de punto.
objeto Algo a lo que una variable puede referirse. Por ahora, puedes usar “objeto”
y “valor” indistintamente.
operador de formato Un operador, %, que toma una cadena de formato y una
tupla y genera una cadena que incluye los elementos de la tupla formateados
como se especifica en la cadena de formato.
rebanado Una parte de una cadena especificado por un rango de índices.
recorrido Iterar a través de los ítems de una secuencia, ejecutando una operación
similar en cada uno.
secuencia Un conjunto ordenado; esto es, un conjunto de valores donde cada valor
es identificado por un índice entero.
secuencia de formato Una secuencia de caracteres en una cadena a formatear,
como %d, que especifica cómo un valor debe ser formateado.
6.14 Ejercicios
Ejercicio 5: Toma el siguiente código en Python que almacena una ca-
dena:
str = 'X-DSPAM-Confidence:0.8475'
Utiliza find y una parte de la cadena para extraer la porción de la cadena
después del carácter dos puntos y después utiliza la función float para
convertir la cadena extraída en un número de punto flotante.
80 CHAPTER 6. CADENAS
Archivos
7.1 Persistencia
Hasta ahora, hemos aprendido cómo escribir programas y comunicar nuestras inten-
ciones a la Unidad Central de Procesamiento utilizando ejecuciones condicionales,
funciones, e iteraciones. Hemos aprendido como crear y usar estructuras de datos
en la Memoria Principal. La CPU y la memoria son los lugares donde nuestro
software funciona y se ejecuta. Es donde toda la inteligencia ocurre.
Pero si recuerdas nuestras discusiones de arquitectura de hardware, una vez que
la corriente se interrumpe, cualquier cosa almacenada ya sea en la CPU o en la
memoria es eliminada. Así que hasta ahora nuestros programas han sido sólo una
diversión pasajera para aprender Python.
¿Qué hago a
Software continuación?
Dispositivos Unidad
Entrada Central Red
Salida Procesamiento
Memoria Memoria
Principal Secundaria
81
82 CHAPTER 7. ARCHIVOS
desde nuestros programas pueden ser retirados del sistema y transportados a otro
sistema.
Nos vamos a enfocar principalmente en leer y escribir archivos como los que creamos
en un editor de texto. Más adelante veremos cómo trabajar con archivos de bases
de datos, que son archivos binarios diseñados específicamente para ser leídos y
escritos a través de software para manejo de bases de datos.
open H
A
close From stephen.m..
N Return-Path: <p..
read D Date: Sat, 5 Jan ..
L To: source@coll..
write E From: stephen...
Subject: [sakai]...
Details: http:/...
Tu …
Programa
Más adelante vamos a utilizar try y except para controlar de mejor manera la
situación donde tratamos de abrir un archivo que no existe.
Mundo!
>>> cosa = 'X\nY'
>>> print(cosa)
X
Y
>>> len(cosa)
3
También puedes ver que el tamaño de la cadena X\nY es tres caracteres debido a
que el separador de línea es un solo carácter.
Por tanto, cuando vemos las líneas en un archivo, necesitamos imaginar que ahí
hay un carácter invisible llamado separador de línea al final de cada línea, el cual
marca el final de la misma.
De modo que el separador de línea separa los caracteres del archivo en líneas.
fhand = open('mbox-short.txt')
count = 0
for line in fhand:
count = count + 1
print('Line Count:', count)
# Code: http://www.py4e.com/code3/open.py
Podemos usar el manejador de archivos como una secuencia en nuestro bucle for.
Nuestro bucle for simplemente cuenta el número de líneas en el archivo y las
imprime. La traducción aproximada de ese bucle al español es, “para cada línea
en el archivo representado por el manejador de archivo, suma uno a la variable
count.”
La razón por la cual la función open no lee el archivo completo es porque el archivo
puede ser muy grande, incluso con muchos gigabytes de datos. La sentencia open
emplea la misma cantidad de tiempo sin importar el tamaño del archivo. De hecho,
es el bucle for el que hace que los datos sean leídos desde el archivo.
Cuando el archivo es leído usando un bucle for de esta manera, Python se encarga
de dividir los datos del archivo en líneas separadas utilizando el separador de línea.
Python lee cada línea hasta el separador e incluye el separador como el último
carácter en la variable line para cada iteración del bucle for.
Debido a que el bucle for lee los datos línea a línea, éste puede leer eficientemente
y contar las líneas en archivos muy grandes sin quedarse sin memoria principal
para almacenar los datos. El programa previo puede contar las líneas de cualquier
7.5. BÚSQUEDA A TRAVÉS DE UN ARCHIVO 85
tamaño de archivo utilizando poca memoria, puesto que cada línea es leída, con-
tada, y después descartada.
Si sabes que el archivo es relativamente pequeño comparado al tamaño de tu memo-
ria principal, puedes leer el archivo completo en una sola cadena utilizando el
método read en el manejador de archivos.
En este ejemplo, el contenido completo (todos los 94626 caracteres) del archivo
mbox-short.txt son leídos directamente en la variable inp. Utilizamos el troceado de
cadenas para imprimir los primeros 20 caracteres de la cadena de datos almacenada
en inp.
Cuando el archivo es leído de esta forma, todos los caracteres incluyendo los saltos
de línea son una cadena gigante en la variable inp. Es una buena idea almacenar
la salida de read como una variable porque cada llamada a read vacía el contenido
por completo:
Recuerda que esta forma de la función open solo debe ser utilizada si los datos del
archivo son apropiados para la memoria principal del sistema. Si el archivo es muy
grande para caber en la memoria principal, deberías escribir tu programa para leer
el archivo en bloques utilizando un bucle for o while.
fhand = open('mbox-short.txt')
count = 0
for line in fhand:
86 CHAPTER 7. ARCHIVOS
if line.startswith('From:'):
print(line)
# Code: http://www.py4e.com/code3/search1.py
From: stephen.marquard@uct.ac.za
From: louis@media.berkeley.edu
From: zqian@umich.edu
From: rjlowe@iupui.edu
...
La salida parece correcta puesto que las líneas que estamos buscando son aquellas
que comienzan con “From:”, pero ¿por qué estamos viendo las líneas vacías extras?
Esto es debido al carácter invisible salto de línea. Cada una de las líneas leídas
termina con un salto de línea, así que la sentencia print imprime la cadena alma-
cenada en la variable line, la cual incluye ese salto de línea, y después print agrega
otro salto de línea, resultando en el efecto de doble salto de línea que observamos.
Podemos usar troceado de líneas para imprimir todos los caracteres excepto el
último, pero una forma más sencilla es usar el método rstrip, el cual elimina los
espacios en blanco del lado derecho de una cadena, tal como:
fhand = open('mbox-short.txt')
for line in fhand:
line = line.rstrip()
if line.startswith('From:'):
print(line)
# Code: http://www.py4e.com/code3/search2.py
From: stephen.marquard@uct.ac.za
From: louis@media.berkeley.edu
From: zqian@umich.edu
From: rjlowe@iupui.edu
From: zqian@umich.edu
From: rjlowe@iupui.edu
From: cwen@iupui.edu
...
Podemos estructurar el bucle para seguir el patrón de ignorar las líneas no intere-
santes así:
fhand = open('mbox-short.txt')
for line in fhand:
line = line.rstrip()
# Skip 'uninteresting lines'
if not line.startswith('From:'):
continue
# Process our 'interesting' line
print(line)
# Code: http://www.py4e.com/code3/search3.py
fhand = open('mbox-short.txt')
for line in fhand:
line = line.rstrip()
if line.find('@uct.ac.za') == -1: continue
print(line)
# Code: http://www.py4e.com/code3/search4.py
# Code: http://www.py4e.com/code3/search6.py
python search6.py
Enter the file name: mbox.txt
There were 1797 subject lines in mbox.txt
python search6.py
Enter the file name: mbox-short.txt
There were 27 subject lines in mbox-short.txt
python search6.py
Enter the file name: missing.txt
Traceback (most recent call last):
File "search6.py", line 2, in <module>
7.7. UTILIZANDO TRY, EXCEPT, Y OPEN 89
fhand = open(fname)
FileNotFoundError: [Errno 2] No such file or directory: 'missing.txt'
python search6.py
Enter the file name: na na boo boo
Traceback (most recent call last):
File "search6.py", line 2, in <module>
fhand = open(fname)
FileNotFoundError: [Errno 2] No such file or directory: 'na na boo boo'
No te rías. Los usuarios eventualmente harán cualquier cosa que puedan para
estropear tus programas, sea a propósito o sin intenciones maliciosas. De hecho,
una parte importante de cualquier equipo de desarrollo de software es una persona
o grupo llamado Quality Assurance (Control de Calidad) (o QA en inglés) cuyo
trabajo es probar las cosas más locas posibles en un intento de hacer fallar el
software que el programador ha creado.
El equipo de QA (Control de Calidad) es responsable de encontrar los fallos en
los programas antes de éstos sean entregados a los usuarios finales, que podrían
comprar nuestro software o pagar nuestro salario por escribirlo. Así que el equipo
de QA es el mejor amigo de un programador.
Ahora que vemos el defecto en el programa, podemos arreglarlo de forma elegante
utilizando la estructura try/except. Necesitamos asumir que la llamada a open
podría fallar y agregar código de recuperación para ese fallo, así:
# Code: http://www.py4e.com/code3/search7.py
La función exit termina el programa. Es una función que llamamos que nunca
retorna. Ahora cuando nuestro usuario (o el equipo de QA) introduzca algo sin
sentido o un nombre de archivo incorrecto, vamos a “capturarlo” y recuperarnos
de forma elegante:
python search7.py
Enter the file name: mbox.txt
There were 1797 subject lines in mbox.txt
python search7.py
Enter the file name: na na boo boo
File cannot be opened: na na boo boo
90 CHAPTER 7. ARCHIVOS
Proteger la llamada a open es un buen ejemplo del uso correcto de try y except
en un programa de Python. Utilizamos el término “Pythónico” cuando estamos
haciendo algo según el “estilo de Python”. Podríamos decir que el ejemplo anterior
es una forma Pythónica de abrir un archivo.
Una vez que estés más familiarizado con Python, puedes intercambiar opiniones con
otros programadores de Python para decidir cuál de entre dos soluciones equiva-
lentes a un problema es “más Pythónica”. El objetivo de ser “más Pythónico”
engloba la noción de que programar es en parte ingeniería y en parte arte. No
siempre estamos interesados sólo en hacer que algo funcione, también queremos
que nuestra solución sea elegante y que sea apreciada como elegante por nuestros
compañeros.
Cuando terminas de escribir, tienes que cerrar el archivo para asegurarte que la
última parte de los datos es escrita físicamente en el disco duro, de modo que no
se pierdan los datos si la corriente eléctrica se interrumpe.
7.9. DEPURACIÓN 91
>>> fout.close()
Podríamos cerrar los archivos abiertos para lectura también, pero podemos ser
menos rigurosos si sólo estamos abriendo unos pocos archivos puesto que Python
se asegura de que todos los archivos abiertos sean cerrados cuando termina el
programa. En cambio, cuando estamos escribiendo archivos debemos cerrarlos de
forma explícita para no dejar nada al azar.
7.9 Depuración
Cuando estás leyendo y escribiendo archivos, puedes tener problemas con los es-
pacios en blanco. Esos errores pueden ser difíciles de depurar debido a que los
espacios, tabuladores, y saltos de línea son invisibles normalmente:
La función nativa repr puede ayudarte. Recibe cualquier objeto como argumento
y devuelve una representación del objeto como una cadena. En el caso de las
cadenas, representa los espacios en blanco con secuencias de barras invertidas:
>>> print(repr(s))
'1 2\t 3\n 4'
7.10 Glosario
7.11 Ejercicios
Ejercicio 1: Escribe un programa que lea un archivo e imprima su con-
tenido (línea por línea), todo en mayúsculas. Al ejecutar el programa,
debería parecerse a esto:
python shout.py
Ingresa un nombre de archivo: mbox-short.txt
FROM STEPHEN.MARQUARD@UCT.AC.ZA SAT JAN 5 09:14:16 2008
RETURN-PATH: <POSTMASTER@COLLAB.SAKAIPROJECT.ORG>
RECEIVED: FROM MURDER (MAIL.UMICH.EDU [141.211.14.90])
BY FRANKENSTEIN.MAIL.UMICH.EDU (CYRUS V2.3.8) WITH LMTPA;
SAT, 05 JAN 2008 09:14:16 -0500
X-DSPAM-Confidence: 0.8475
python huevo.py
Ingresa un nombre de archivo: mbox.txt
Hay 1797 líneas subject en mbox.txt
python huevo.py
Ingresa un nombre de archivo: inexistente.tyxt
El archivo no puede ser abierto: inexistente.tyxt
python huevo.py
Ingresa un nombre de archivo: na na boo boo
NA NA BOO BOO PARA TI - Te he atrapado!
Listas
Así como una cadena, una lista es una secuencia de valores. En una cadena, los
valores son caracteres; en una lista, pueden ser cualquier tipo. Los valores en una
lista son llamados elementos o a veces ítems.
Hay varias formas de crear una nueva lista; la más simple es encerrar los elementos
en corchetes (“[" y “]”):
El primer ejemplo es una lista de 4 enteros. La segunda es una lista de tres cadenas.
Los elementos de una lista no tienen que ser del mismo tipo. La siguiente lista
contiene una cadena, un flotante, un entero, y (¡mira!) otra lista:
95
96 CHAPTER 8. LISTAS
>>> print(quesos[0])
Cheddar
A diferencia de las cadenas, las listas son mutables porque pueden cambiar el
orden de los elementos en una lista o reasignar un elemento en una lista. Cuando
el operador corchete aparece en el lado izquierdo de una asignación, éste identifica
el elemento de la lista que será asignado.
• Si un índice tiene un valor negativo, éste cuenta hacia atrás desde el final de
la lista.
Esto funciona bien si solamente necesitas leer los elementos de la lista. Pero si
quieres escribir o actualizar los elementos, necesitas los índices. Una forma común
de hacer eso es combinando las funciones range y len:
for i in range(len(numeros)):
numeros[i] = numeros[i] * 2
Este bucle recorre la lista y actualiza cada elemento. len regresa el número de
elementos en una lista. range regresa una lista de índices desde 0 hasta n − 1,
donde n es la longitud de la lista. Cada vez que pasa a través del recorrido, i
obtiene el índice del siguiente elemento. La sentencia de asignación dentro del
bucle utiliza i para leer el valor original del elemento y asignar un nuevo valor.
Un bucle for a través de una lista vacía nunca ejecuta el código contenido en el
cuerpo:
for x in vacia:
print('Esto nunca sucede.')
Aunque una lista puede contener otra lista, las listas anidadas siguen contando
como un solo elemento. El tamaño de esta lista es cuatro:
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = a + b
>>> print(c)
[1, 2, 3, 4, 5, 6]
>>> [0] * 4
[0, 0, 0, 0]
>>> [1, 2, 3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
>>> t[:]
['a', 'b', 'c', 'd', 'e', 'f']
Como las listas son mutables, a veces es útil hacer una copia antes de hacer opera-
ciones que doblan, pegan, o cortan listas.
Un operador de rebanado al lado izquierdo de una asignación puede actualizar
múltiples elementos:
extend toma una lista como argumento y agrega todos los elementos:
Si sabes qué elemento quieres remover (pero no sabes el índice), puedes usar
remove:
Como siempre, el rebanado selecciona todos los elementos hasta, pero excluyendo,
el segundo índice.
100 CHAPTER 8. LISTAS
La función sum() solamente funciona cuando los elementos de la lista son números.
Las otras funciones (max(), len(), etc.) funcionan con listas de cadenas y otros
tipos que pueden ser comparados entre sí.
Podríamos reescribir un programa anterior que calculaba el promedio de una lista
de números ingresados por el usuario utilizando una lista.
Primero, el programa para calcular un promedio sin una lista:
total = 0
count = 0
while (True):
inp = input('Enter a number: ')
if inp == 'done': break
value = float(inp)
total = total + value
count = count + 1
# Code: http://www.py4e.com/code3/avenum.py
En este programa, tenemos las variables count y total para almacenar la cantidad
y el total actual de los números del usuario según el usuario va ingresando los
números repetidamente.
Podríamos simplemente recordar cada número como el número lo ingresó, y utilizar
funciones internas para calcular la suma y el total de números al final.
numlist = list()
while (True):
inp = input('Enter a number: ')
if inp == 'done': break
8.9. LISTAS Y CADENAS 101
value = float(inp)
numlist.append(value)
# Code: http://www.py4e.com/code3/avelist.py
Creamos una lista vacía antes de que comience el bucle, y luego cada vez que
tengamos un número, lo agregamos a la lista. Al final del programa, simplemente
calculamos la suma de los números en la lista y la dividimos por el total de números
en la lista para obtener el promedio.
>>> s = 'spam'
>>> t = list(s)
>>> print(t)
['s', 'p', 'a', 'm']
Debido a que list es el nombre de una función interna, debes evitar usarla como
un nombre de variable. Yo trato de evitar también la letra “l” porque se parece
mucho al número “1”. Así que por eso utilizo “t”.
La función list divide una cadena en letras individuales. Si quieres dividir una
cadena en palabras, puedes utilizar el método split:
Una vez que hayas utilizado split para dividir una cadena en una lista de palabras,
puedes utilizar el operador índice (corchetes) para ver una palabra en particular
en la lista.
Puedes llamar split con un argumento opcional llamado delimitador que especifica
qué caracteres usar para delimitar las palabras. El siguiente ejemplo utiliza un
guión medio como delimitador:
>>> s = 'spam-spam-spam'
>>> delimiter = '-'
>>> s.split(delimiter)
['spam', 'spam', 'spam']
102 CHAPTER 8. LISTAS
join es el inverso de split. Este toma una lista de cadenas y concatena los
elementos. join es un método de cadenas, así que tienes que invocarlo en el
delimitador y pasar la lista como un parámetro:
El método split es muy efectivo cuando nos encontramos este tipo de problemas.
Podemos escribir un pequeño programa que busca líneas donde la línea comienza
con “From”, split (dividir) esas líneas, y finalmente imprimir la tercer palabra de
la línea:
fhand = open('mbox-short.txt')
for line in fhand:
line = line.rstrip()
if not line.startswith('From '): continue
words = line.split()
print(words[2])
# Code: http://www.py4e.com/code3/search5.py
Sat
Fri
Fri
Fri
...
Más tarde, aprenderemos técnicas muy sofisticadas para obtener las líneas que
queremos para trajar sobre ellas y cómo sacar el fragmento exacto de información
que estamos buscando.
8.11. OBJETOS Y VALORES 103
a = 'banana'
b = 'banana'
a ‘banana’ a
‘banana’
b ‘banana’ b
Por un lado, a y b se refieren a dos objetos diferentes que tienen el mismo valor.
Por otro lado, apuntan al mismo objeto.
Para revisar si dos variables apuntan al mismo objeto, puedes utilizar el operador
is.
>>> a = 'banana'
>>> b = 'banana'
>>> a is b
True
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> a is b
False
En este caso podríamos decir que las dos listas son equivalentes, porque tienen
los mismos elementos, pero no idénticas, porque no son el mismo objeto. Si dos
objetos son idénticos, son también equivalentes, pero si son equivalentes, no son
necesariamente idénticos.
8.12 Alias
Si a se refiere a un objecto y tu asignasb = a, entonces ambas variables se refieren
al mismo objeto:
>>> a = [1, 2, 3]
>>> b = a
>>> b is a
True
>>> b[0] = 17
>>> print(a)
[17, 2, 3]
a = 'banana'
b = 'banana'
def remover_primero(t):
del t[0]
>>> t1 = [1, 2]
>>> t2 = t1.append(3)
>>> print(t1)
[1, 2, 3]
>>> print(t2)
None
>>> t3 = t1 + [3]
>>> print(t3)
[1, 2, 3]
>>> t2 is t3
False
def mal_eliminar_primero(t):
t = t[1:] # ¡EQUIVOCADO!
El operador de rebanado crea una nueva lista y el asignamiento hace que t apunte
a la lista, pero nada de esto tiene efecto en la lista que fue pasada como argumento.
Una alternativa es escribir una función que cree y regrese una nueva lista. Por
ejemplo, cola regresa todo excepto el primer elemento de una lista:
def cola(t):
return t[1:]
Esta función deja la lista original sin modificar. Aquí está como es que se usa:
**Ejercicio 1: Escribe una función llamada recortar que toma una lista y la modi-
fica, removiendo el primer y último elemento, y regresa None. Después escribe una
función llamada medio que toma una lista y regresa una nueva lista que contiene
todo excepto el primero y último elementos.
106 CHAPTER 8. LISTAS
8.14 Depuración
El uso descuidado de listas (y otros objetos mutables) puede llevar a largas horas
de depuración. Aquí están algumos de los errores más comunes y las formas de
evitarlos:
palabra = palabra.strip()
t = t.sort() # ¡EQUIVOCADO!
Debido a que sort regresa None, la siguiente operación que hagas con t es
probable que falle.
Antes de usar métodos y operadores de listas, deberías leer la documentación
cuidadosamente y después probarlos en modo interactivo. Los métodos y
operadores que las listas comparten con otras secuencias (como cadenas)
están documentados en:
docs.python.org/library/stdtypes.html#common-sequence-operations
Los métodos y operadores que solamente aplican a secuencias mutables están
documentados en:
docs.python.org/library/stdtypes.html#mutable-sequence-types
t.append(x)
t = t + [x]
t.append([x]) # ¡EQUIVOCADO!
t = t.append(x) # ¡EQUIVOCADO!
t + [x] # ¡EQUIVOCADO!
t = t + x # ¡EQUIVOCADO!
Prueba cada uno de esos ejemplos en modo interactivo para asegurarte que
entiendes lo que hacen. Nota que solamente la última provoca un error en
tiempo de ejecución (runtime error); los otros tres son válidos, pero hacen la
función equivocada.
8.14. DEPURACIÓN 107
orig = t[:]
t.sort()
manejador = open('mbox-short.txt')
for linea in manejador:
palabras = linea.split()
if palabras[0] != 'From' : continue
print(palabras[2])
python search8.py
Sat
Traceback (most recent call last):
File "search8.py", line 5, in <module>
if palabras[0] != 'From' : continue
IndexError: list index out of range
antes de la línea donde el programa falló, e imprimir los datos que parece
que causan la falla.
Ahora bien, este método podría generar muchas líneas de salida, pero al
menos tendrás inmediatamente alguna pista de cuál es el problema. Así
que agregamos un print a la variable palabras justo antes de la línea cinco.
Incluso podemos agregar un prefijo “Depuración:” a la línea de modo que
mantenemos nuestra salida regular separada de la salida de mensajes de
depuración.
X-DSPAM-Result: Innocent
X-DSPAM-Processed: Sat Jan 5 09:14:16 2008
X-DSPAM-Confidence: 0.8475
X-DSPAM-Probability: 0.0000
Details: http://source.sakaiproject.org/viewsvn/?view=rev&rev=39772
¡El error ocurre cuando nuestro programa encuentra una línea vacía! Por
supuesto, hay “cero palabras” en una lista vacía. ¿Por qué no pensamos en eso
cuando estábamos escribiendo el código? Cuando el código busca la primera
palabra (palabras[0]) para revisar si coincide con “From”, obtenemos un
error “index out of range” (índice fuera de rango).
Este es, por supuesto, el lugar perfecto para agregar algo de código guardián
para evitar revisar la primer palabra si la primer palabra no existe. Hay
muchas maneras de proteger este código; vamos a optar por revisar el número
de palabras que tenemos antes de mirar a la primer palabra:
manejador = open('mbox-short.txt')
contador = 0
8.15. GLOSARIO 109
8.15 Glosario
alias Una circunstancia donde dos o más variables apuntan al mismo objeto.
delimitador Un caracter o cadena utilizado para indicar dónde una cadena debe
ser dividida.
elemento Uno de los valores en una lista (u otra secuencia); también llamados
ítems.
equivalente Que tiene el mismo valor.
idéntico Ser el mismo objeto (lo cual implica equivalencia).
índice Un valor entero que indica un elemento en una lista.
lista Una secuencia de valores.
lista anidada Una lista que es uno de los elementos de otra lista.
objeto Algo a lo que una variable puede referirse. Un objeto tiene un tipo y un
valor.
recorrido de lista Acceso secuencial a cada elemento de una lista.
referencia La asociación entre una variable y su valor.
110 CHAPTER 8. LISTAS
8.16 Ejercicios
Ejercicio 4: Descargar una copia de un archivo www.py4e.com/code3/romeo.txt.
Escribir un programa para abrir el archivo romeo.txt y leerlo línea
por línea. Para cada línea, dividir la línea en una lista de palabras
utilizando la función split. Para cada palabra, revisar si la palabra ya
se encuentra previamente en la lista. Si la palabra no está en la lista,
agregarla a la lista. Cuando el programa termine, ordenar e imprimir
las palabras resultantes en orden alfabético.
python fromcuenta.py
Ingresa un nombre de archivo: mbox-short.txt
stephen.marquard@uct.ac.za
louis@media.berkeley.edu
zqian@umich.edu
ray@media.berkeley.edu
cwen@iupui.edu
cwen@iupui.edu
cwen@iupui.edu
Hay 27 lineas en el archivo con la palabra From al inicio
Ingresa un número: 6
Ingresa un número: 2
Ingresa un número: 9
Ingresa un número: 3
Ingresa un número: 5
Ingresa un número: hecho
Máximo: 9.0
Minimo: 2.0
112 CHAPTER 8. LISTAS
Chapter 9
Diccionarios
Un diccionario es como una lista, pero más general. En una lista, los índices de
posiciones tienen que ser enteros; en un diccionario, los índices pueden ser (casi)
cualquier tipo.
Puedes pensar en un diccionario como una asociación entre un conjunto de índices
(que son llamados claves) y un conjunto de valores. Cada clave apunta a un valor.
La asociación de una clave y un valor es llamada par clave-valor o a veces elemento.
Como ejemplo, vamos a construir un diccionario que asocia palabras de Inglés a
Español, así que todas las claves y los valores son cadenas.
La función dict crea un nuevo diccionario sin elementos. Debido a que dict es el
nombre de una función interna, deberías evitar usarlo como un nombre de variable.
Esta línea crea un elemento asociando a la clave 'one' el valor “uno”. Si imprimi-
mos el diccionario de nuevo, vamos a ver un par clave-valor con dos puntos entre
la clave y el valor:
>>> print(eng2sp)
{'one': 'uno'}
113
114 CHAPTER 9. DICCIONARIOS
>>> print(eng2sp['two'])
'dos'
La clave 'two' siempre se asocia al valor “dos”, así que el orden de los elementos
no importa.
Si la clave no está en el diccionario, obtendrás una excepción (exception):
>>> print(eng2sp['four'])
KeyError: 'four'
>>> len(eng2sp)
3
El operador in funciona en diccionarios; éste te dice si algo aparece como una clave
en el diccionario (aparecer como valor no es suficiente).
Para ver si algo aparece como valor en un diccionario, puedes usar el método
values, el cual retorna los valores como una lista, y después puedes usar el operador
in:
Supongamos que recibes una cadena y quieres contar cuántas veces aparece cada
letra. Hay varias formas en que puedes hacerlo:
1. Puedes crear 26 variables, una por cada letra del alfabeto. Luego puedes
recorrer la cadena, y para cada caracter, incrementar el contador correspon-
diente, probablemente utilizando varios condicionales.
2. Puedes crear una lista con 26 elementos. Después podrías convertir cada
caracter en un número (usando la función interna ord), usar el número como
índice dentro de la lista, e incrementar el contador correspondiente.
Cada una de esas opciones hace la misma operación computacional, pero cada una
de ellas implementa esa operación en forma diferente.
Una implementación es una forma de llevar a cabo una operación computacional;
algunas implementaciones son mejores que otras. Por ejemplo, una ventaja de la
implementación del diccionario es que no tenemos que saber con antelación qué
letras aparecen en la cadena y solamente necesitamos espacio para las letras que
sí aparecen.
Aquí está un ejemplo de como se vería ese código:
palabra = 'brontosaurio'
d = dict()
for c in palabra:
if c not in d:
d[c] = 1
else:
d[c] = d[c] + 1
print(d)
116 CHAPTER 9. DICCIONARIOS
El histograma indica que las letras “a” y “b” aparecen solo una vez; “o” aparece
dos, y así sucesivamente.
Los diccionarios tienen un método llamado get que toma una clave y un valor por
defecto. Si la clave aparece en el diccionario, get regresa el valor correspondiente;
si no, regresa el valor por defecto. Por ejemplo:
Podemos usar get para escribir nuestro bucle de histograma más conciso. Puesto
que el método get automáticamente maneja el caso en que una clave no está en el
diccionario, podemos reducir cuatro líneas a una y eliminar la sentencia if.
palabra = 'brontosaurio'
d = dict()
for c in palabra:
d[c] = d.get(c,0) + 1
print(d)
El uso del método get para simplificar este bucle contador termina siendo un
“idioma” muy utilizado en Python y vamos a utilizarlo muchas veces en el resto
del libro. Así que deberías tomar un momento para comparar el bucle utilizando
la sentencia if y el operador in con el bucle utilizando el método get. Ambos
hacen exactamente lo mismo, pero uno es más breve.
Vamos a escribir un programa de Python para leer a través de las líneas del archivo,
dividiendo cada línea en una lista de palabras, y después iterando a través de cada
una de las palabras en la línea y contando cada palabra utilizando un diccionario.
Verás que tenemos dos bucles for. El bucle externo está leyendo las líneas del
archivo y el bucle interno está iterando a través de cada una de las palabras en
esa línea en particular. Este es un ejemplo de un patrón llamado bucles anidados
porque uno de los bucles es el bucle externo y el otro bucle es el bucle interno.
Como el bucle interno ejecuta todas sus iteraciones cada vez que el bucle externo
hace una sola iteración, consideramos que el bucle interno itera “más rápido” y el
bucle externo itera más lento.
La combinación de los dos bucles anidados asegura que contemos cada palabra en
cada línea del archivo de entrada.
counts = dict()
for line in fhand:
words = line.split()
for word in words:
if word not in counts:
counts[word] = 1
else:
counts[word] += 1
print(counts)
# Code: http://www.py4e.com/code3/count1.py
python count1.py
118 CHAPTER 9. DICCIONARIOS
Es un poco inconveniente ver a través del diccionario para encontrar las palabras
más comunes y sus contadores, así que necesitamos agregar un poco más de código
para mostrar una salida que nos sirva más.
jan 100
chuck 1
annie 42
El bucle for itera a través de las claves del diccionario, así que debemos utilizar el
operador índice para obtener el valor correspondiente para cada clave. Aquí está
la salida del programa:
jan 100
annie 42
Primero se ve la lista de claves sin ordenar como la obtuvimos del método keys.
Después vemos los pares clave-valor en orden desde el bucle for.
Puesto que la función split en Python busca espacios y trata las palabras como
piezas separadas por esos espacios, trataríamos a las palabras “soft!” y “soft” como
diferentes palabras y crearíamos una entrada independiente para cada palabra en
el diccionario.
Además, como el archivo tiene letras mayúsculas, trataríamos “who” y “Who”
como diferentes palabras con diferentes contadores.
Podemos resolver ambos problemas utilizando los métodos de cadenas lower,
punctuation, y translate. El método translate es el más sutil de los métodos.
Aquí esta la documentación para translate:
line.translate(str.maketrans(fromstr, tostr, deletestr))
Reemplaza los caracteres en fromstr con el caracter en la misma posición en tostr
y elimina todos los caracteres que están en deletestr. Los parámetros fromstr y
tostr pueden ser cadenas vacías y el parámetro deletestr es opcional.
No vamos a especificar el valor de tostr pero vamos a utilizar el parámetro
deletestr para eliminar todos los signos de puntuación. Incluso vamos a dejar que
Python nos diga la lista de caracteres que considera como “signos de puntuación”:
120 CHAPTER 9. DICCIONARIOS
import string
counts = dict()
for line in fhand:
line = line.rstrip()
line = line.translate(line.maketrans('', '', string.punctuation))
line = line.lower()
words = line.split()
for word in words:
if word not in counts:
counts[word] = 1
else:
counts[word] += 1
print(counts)
# Code: http://www.py4e.com/code3/count2.py
Interpretar los datos a través de esta salida es aún difícil, y podemos utilizar
Python para darnos exactamente lo que estamos buscando, pero para que sea
así, necesitamos aprender acerca de las tuplas en Python. Vamos a retomar este
ejemplo una vez que aprendamos sobre tuplas.
9.5. DEPURACIÓN 121
9.5 Depuración
Conforme trabajes con conjuntos de datos más grandes puede ser complicado depu-
rar imprimiendo y revisando los datos a mano. Aquí hay algunas sugerencias para
depurar grandes conjuntos de datos:
De nuevo, el tiempo que inviertas haciendo una buena estructura puede reducir el
tiempo que inviertas en depurar.
9.6 Glosario
bucles anidados Cuando hay uno o más bucles “dentro” de otro bucle. Los
bucles internos terminan de ejecutar cada vez que el bucle externo ejecuta
una vez.
búsqueda Una operación de diccionario que toma una clave y encuentra su valor
correspondiente.
clave Un objeto que aparece en un diccionario como la primera parte de un par
clave-valor.
diccionario Una asociación de un conjunto de claves a sus valores correspondi-
entes.
122 CHAPTER 9. DICCIONARIOS
9.7 Ejercicios
Ejercicio 2: Escribir un programa que clasifica cada mensaje de correo
dependiendo del día de la semana en que se recibió. Para hacer esto
busca las líneas que comienzan con “From”, después busca por la tercer
palabra y mantén un contador para cada uno de los días de la semana.
Al final del programa imprime los contenidos de tu diccionario (el orden
no importa).
Línea de ejemplo:
Ejemplo de ejecución:
python dow.py
Ingresa un nombre de archivo: mbox-short.txt
{'Fri': 20, 'Thu': 6, 'Sat': 1}
python schoolcount.py
Ingresa un nombre de archivo: mbox-short.txt
{'media.berkeley.edu': 4, 'uct.ac.za': 6, 'umich.edu': 7,
'gmail.com': 1, 'caret.cam.ac.uk': 1, 'iupui.edu': 8}
124 CHAPTER 9. DICCIONARIOS
Chapter 10
Tuplas
Aunque no es necesario, es común encerrar las tuplas entre paréntesis para ayu-
darnos a identificarlas rápidamente cuando revisemos código de Python:
Para crear una tupla con un solo elemento, es necesario incluir una coma al final:
>>> t1 = ('a',)
>>> type(t1)
<type 'tuple'>
Sin la coma, Python considera ('a') como una expresión con una cadena entre
paréntesis que es evaluada como de tipo cadena (string):
>>> t2 = ('a')
>>> type(t2)
<type 'str'>
1 Dato curioso: La palabra “tuple” proviene de los nombres dados a secuencias de números de
distintas longitudes: simple, doble, triple, cuádruple, quíntuple, séxtuple, séptuple, etc.
125
126 CHAPTER 10. TUPLAS
Otra forma de construir una tupla es utilizando la función interna tuple. Sin
argumentos, ésta crea una tupla vacía:
>>> t = tuple()
>>> print(t)
()
>>> t = tuple('altramuces')
>>> print(t)
('a', 'l', 't', 'r', 'a', 'm', 'u', 'c', 'e', 's')
>>> print(t[1:3])
('b', 'c')
No se puede modificar los elementos de una tupla, pero sí se puede reemplazar una
tupla por otra:
Decorate (Decora) una secuencia, construyendo una lista de tuplas con uno o
más índices ordenados precediendo los elementos de la secuencia,
Sort (Ordena) la lista de tuplas utilizando la función interna sort, y
Undecorate (Quita la decoración) extrayendo los elementos ordenados de la
secuencia.
Por ejemplo, suponiendo una lista de palabras que se quieren ordenar de la más
larga a la más corta:
t.sort(reverse=True)
res = list()
for longitud, palabra in t:
res.append(palabra)
print(res)
# Code: http://www.py4e.com/code3/soft.py
El primer bucle genera una lista de tuplas, donde cada tupla es una palabra pre-
cedida por su longitud.
sort compara el primer elemento (longitud) primero, y solamente considera el
segundo elemento para desempatar. El argumento clave reverse=True indica a
sort que debe ir en orden decreciente.
El segundo bucle recorre la lista de tuplas y construye una lista de palabras en
orden descendente según la longitud. Las palabras de cuatro letras están ordenadas
en orden alfabético inverso, así que “deja” aparece antes que “allí” en la siguiente
lista.
La salida del programa es la siguiente:
>>> a, b = b, a
Ambos lados de la sentencia son tuplas, pero el lado izquierdo es una tupla de
variables; el lado derecho es una tupla de expresiones. Cada valor en el lado derecho
es asignado a su respectiva variable en el lado izquierdo. Todas las expresiones en
el lado derecho son evaluadas antes de realizar cualquier asignación.
El número de variables en el lado izquierdo y el número de valores en el lado
derecho deben ser iguales:
>>> a, b = 1, 2, 3
ValueError: too many values to unpack
Generalizando más, el lado derecho puede ser cualquier tipo de secuencia (cadena,
lista, o tupla). Por ejemplo, para dividir una dirección de e-mail en nombre de
usuario y dominio, se podría escribir:
El valor de retorno de split es una lista con dos elementos; el primer elemento es
asignado a nombreus, el segundo a dominio.
>>> print(nombreus)
monty
>>> print(dominio)
python.org
Este bucle tiene dos variables de iteración, debido a que items retorna una lista
de tuplas y clave, valor es una asignación en tupla que itera sucesivamente a
través de cada uno de los pares clave-valor del diccionario.
Para cada iteración a través del bucle, tanto clave y valor van pasando al siguiente
par clave-valor del diccionario (todavía en orden de dispersión).
La salida de este bucle es:
10 a
1 b
22 c
De nuevo, las claves están en orden de dispersión (es decir, ningún orden en par-
ticular).
Si se combinan esas dos técnicas, se puede imprimir el contenido de un diccionario
ordenado por el valor almacenado en cada par clave-valor.
Para hacer esto, primero se crea una lista de tuplas donde cada tupla es (valor,
clave). El método items dará una lista de tuplas (clave, valor), pero esta vez
se pretende ordenar por valor, no por clave. Una vez que se ha construido la lista
con las tuplas clave-valor, es sencillo ordenar la lista en orden inverso e imprimir
la nueva lista ordenada.
>>> l
[(10, 'a'), (1, 'b'), (22, 'c')]
>>> l.sort(reverse=True)
>>> l
[(22, 'c'), (10, 'a'), (1, 'b')]
>>>
import string
manejador = open('romeo-full.txt')
contadores = dict()
for linea in manejador:
linea = linea.translate(str.maketrans('', '', string.punctuation))
linea = linea.lower()
palabras = linea.split()
for palabra in palabras:
if palabra not in contadores:
contadores[palabra] = 1
else:
contadores[palabra] += 1
lst.sort(reverse=True)
# Code: http://www.py4e.com/code3/count3.py
clave), de forma que las tuplas cuyo valor es el mismo serán también ordenadas en
orden alfabético según su clave.
Al final escribimos un elegante bucle for que hace una iteración con asignación
múltiple e imprime las diez palabras más comunes, iterando a través de una parte
de la lista (lst[:10]).
Ahora la salida finalmente tiene el aspecto que queríamos para nuestro análisis de
frecuencia de palabras.
61 i
42 and
40 romeo
34 to
34 the
32 thou
32 juliet
30 that
29 my
24 thee
El hecho de que este complejo análisis y procesado de datos pueda ser realizado
con un programa de Python de 19 líneas fácil de entender, es una razón de por qué
Python es una buena elección como lenguaje para explorar información.
Dado que las tuplas son dispersables (hashable) y las listas no, si se quiere crear
una clave compuesta para usar en un diccionario, se debe utilizar una tupla como
clave.
Usaríamos por ejemplo una clave compuesta si quisiéramos crear un directorio
telefónico que mapea pares appellido, nombre con números telefónicos. Asumiendo
que hemos definido las variables apellido, nombre, y número, podríamos escribir
una sentencia de asignación de diccionario como sigue:
directorio[apellido,nombre] = numero
Este bucle recorre las claves en directorio, las cuales son tuplas. Asigna los
elementos de cada tupla a apellido y nombre, después imprime el nombre y el
número telefónico correspondiente.
10.8. SECUENCIAS: CADENAS, LISTAS, Y TUPLAS - ¡DIOS MÍO! 133
Me he enfocado en listas de tuplas, pero casi todos los ejemplos de este capítulo
funcionan también con listas de listas, tuplas de tuplas, y tuplas de listas. Para
evitar enumerar todas las combinaciones posibles, a veces es más sencillo hablar
de secuencias de secuencias.
Para comenzar con lo más obvio, las cadenas están más limitadas que otras secuen-
cias, debido a que los elementos tienen que ser caracteres. Además, son inmutables.
Si necesitas la capacidad de cambiar los caracteres en una cadena (en vez de crear
una nueva), quizá prefieras utilizar una lista de caracteres.
Las listas son más comunes que las tuplas, principalmente porque son mutables.
Pero hay algunos casos donde es preferible utilizar tuplas:
2. Si quieres utilizar una secuencia como una clave en un diccionario, debes usar
un tipo inmutable como una tupla o una cadena.
Dado que las tuplas son inmutables, no proporcionan métodos como sort y
reverse, que modifican listas ya existentes. Sin embargo, Python proporciona las
funciones internas sorted y reversed, que toman una secuencia como parámetro
y devuelven una secuencia nueva con los mismos elementos en un orden diferente.
10.9 Depuración
Las listas, diccionarios y tuplas son conocidas de forma genérica como estructuras
de datos; en este capítulo estamos comenzando a ver estructuras de datos compues-
tas, como listas de tuplas, y diccionarios que contienen tuplas como claves y listas
como valores. Las estructuras de datos compuestas son útiles, pero también son
propensas a lo que yo llamo errores de modelado; es decir, errores causados cuando
una estructura de datos tiene el tipo, tamaño o composición incorrecto, o quizás
al escribir una parte del código se nos olvidó cómo era el modelado de los datos
y se introdujo un error. Por ejemplo, si estás esperando una lista con un entero y
recibes un entero solamente (no en una lista), no funcionará.
134 CHAPTER 10. TUPLAS
10.10 Glosario
comparable Un tipo en el cual un valor puede ser revisado para ver si es mayor
que, menor que, o igual a otro valor del mismo tipo. Los tipos que son
comparables pueden ser puestos en una lista y ordenados.
estructura de datos Una collección de valores relacionados, normalmente orga-
nizados en listas, diccionarios, tuplas, etc.
DSU Abreviatura de “decorate-sort-undecorate (decorar-ordenar-quitar la deco-
ración)”, un patrón de diseño que implica construir una lista de tuplas, or-
denarlas, y extraer parte del resultado.
reunir La operación de tratar una secuencia como una lista de argumentos.
hashable (dispersable) Un tipo que tiene una función de dispersión. Los tipos
inmutables, como enteros, flotantes y cadenas son dispersables (hashables);
los tipos mutables como listas y diccionarios no lo son.
dispersar La operación de tratar una secuencia como una lista de argumentos.
modelado (de una estructura de datos) Un resumen del tipo, tamaño, y
composición de una estructura de datos.
singleton Una lista (u otra secuencia) con un único elemento.
tupla Una secuencia inmutable de elementos.
asignación por tuplas Una asignación con una secuencia en el lado derecho y
una tupla de variables en el izquierdo. El lado derecho es evaluado y luego
sus elementos son asignados a las variables en el lado izquierdo.
10.11 Ejercicios
Ejercicio 1: Revisa el programa anterior de este modo: Lee y analiza
las líneas “From” y extrae las direcciones de correo. Cuenta el número
de mensajes de cada persona utilizando un diccionario.
Después de que todos los datos hayan sido leídos, imprime la persona con
más envíos, creando una lista de tuplas (contador, email) del diccionario.
Después ordena la lista en orden inverso e imprime la persona que tiene
más envíos.
Línea de ejemplo:
From stephen.marquard@uct.ac.za Sat Jan 5 09:14:16 2008
python timeofday.py
Ingresa un nombre de archivo: mbox-short.txt
04 3
06 1
07 1
09 2
10 3
11 6
14 1
15 2
16 4
17 2
18 1
19 1
Expresiones regulares
Hasta ahora hemos leído archivos, buscando patrones y extrayendo varias secciones
de líneas que hemos encontrado interesantes. Hemos usado métodos de cadenas
como split y find, así como rebanado de listas y cadenas para extraer trozos de
las líneas.
Esta tarea de buscar y extraer es tan común que Python tiene una librería muy
poderosa llamada expresiones regulares que maneja varias de estas tareas de man-
era bastante elegante. La razón por la que no presentamos las expresiones regulares
antes se debe a que, aunque son muy poderosas, son un poco más complicadas y
toma algo de tiempo acostumbrarse a su sintaxis.
Las expresiones regulares casi son su propio lenguaje de programación en miniatura
para buscar y analizar cadenas. De hecho, se han escrito libros enteros sobre las
expresiones regulares. En este capítulo, solo cubriremos los aspectos básicos de las
expresiones regulares. Para más información al respecto, recomendamos ver:
https://es.wikipedia.org/wiki/Expresi%C3%B3n_regular
https://docs.python.org/library/re.html
Se debe importar la librería de expresiones regulares re a tu programa antes de que
puedas usarlas. La forma más simple de usar la librería de expresiones regulares
es la función search() (N. del T.: “search” significa búsqueda). El siguiente
programa demuestra una forma muy sencilla de usar esta función.
# Code: http://www.py4e.com/code3/re01.py
137
138 CHAPTER 11. EXPRESIONES REGULARES
toma ventaja del auténtico poder de las expresiones regulares, ya que podríamos
simplemente haber usado line.find() para lograr el mismo resultado.
El poder de las expresiones regulares se manifiesta cuando agregamos caracteres
especiales a la cadena de búsqueda que nos permite controlar de manera más precisa
qué líneas calzan con la cadena. Agregar estos caracteres especiales a nuestra
expresión regular nos permitirá buscar coincidencias y extraer datos usando unas
pocas líneas de código.
Por ejemplo, el signo de intercalación (N. del T.: “caret” en inglés, corresponde
al signo ˆ) se utiliza en expresiones regulares para encontrar “el comienzo” de una
lína. Podríamos cambiar nuestro programa para que solo retorne líneas en que
tengan “From:” al comienzo, de la siguiente manera:
# Code: http://www.py4e.com/code3/re02.py
Ahora solo retornará líneas que comiencen con la cadena “From:”. Este sigue siendo
un ejemplo muy sencillo que podríamos haber implementado usando el método
startswith() de la librería de cadenas. Pero sirve para presentar la idea de que
las expresiones regulares contienen caracteres especiales que nos dan mayor control
sobre qué coincidencias retornará la expresión regular.
# Code: http://www.py4e.com/code3/re03.py
11.2. EXTRAYENDO DATOS USANDO EXPRESIONES REGULARES 139
# Code: http://www.py4e.com/code3/re04.py
La cadena ˆFrom:.+@ retornará coincidencias con líneas que empiecen con “From:”,
seguidas de uno o más caracteres (.+), seguidas de un carácter @. Por lo tanto, la
siguiente línea coincidirá:
From: stephen.marquard@uct.ac.za
Puede considerarse que el comodín .+ se expande para abarcar todos los caracteres
entre los signos : y @.
From:.+@
Conviene considerar que los signos de suma y los asteriscos “empujan”. Por ejemplo,
la siguiente cadena marcaría una coincidencia con el último signo @, ya que el .+
“empujan” hacia afuera, como se muestra a continuación:
Es posible indicar a un asterisco o signo de suma que no debe ser tan “ambicioso”
agregando otro carácter. Revisa la documentación para obtener información sobre
cómo desactivar este comportamiento ambicioso.
import re
s = 'Un mensaje de csev@umich.edu para cwen@iupui.edu acerca de una junta @2PM'
lst = re.findall(r'\S+@\S+', s)
print(lst)
# Code: http://www.py4e.com/code3/re05.py
['csev@umich.edu', 'cwen@iupui.edu']
# Code: http://www.py4e.com/code3/re06.py
Con esto, leemos cada línea y luego extraemos las subcadenas que coincidan con
nuestra expresión regular. Dado que findall() retorna una lista, simplemente
11.2. EXTRAYENDO DATOS USANDO EXPRESIONES REGULARES 141
['wagnermr@iupui.edu']
['cwen@iupui.edu']
['<postmaster@collab.sakaiproject.org>']
['<200801032122.m03LMFo4005148@nakamura.uits.iupui.edu>']
['<source@collab.sakaiproject.org>;']
['<source@collab.sakaiproject.org>;']
['<source@collab.sakaiproject.org>;']
['apache@localhost)']
['source@collab.sakaiproject.org;']
Algunas de las direcciones tienen caracteres incorrectos como “<” o “;” al comienzo
o al final. Declaremos que solo estamos interesados en la parte de la cadena que
comience y termine con una letra o un número.
Para lograr esto, usamos otra característica de las expresiones regulares. Los
corchetes se usan para indicar un conjunto de caracteres que queremos aceptar
como coincidencias. La secuencia \S retornará el conjunto de “caracteres que no
sean un espacio en blanco”. Ahora seremos un poco más explícitos en cuanto a los
caracteres respecto de los cuales buscamos coincidencias.
Esta será nuestra nueva expresión regular:
[a-zA-Z0-9]\S*@\S*[a-zA-Z]
Esto se está complicando un poco; puedes ver por qué decimos que las expresiones
regulares son un lenguaje en sí mismas. Traduciendo esta expresión regular, esta-
mos buscando subcadenas que comiencen con una letra minúscula, letra mayúscula,
o número “[a-zA-Z0-9]”, seguida de cero o más caracteres que no sean un espacio
(\S*), seguidos de un signo @, seguido de cero o más caracteres que no sean espa-
cios en blanco (\S*), seguidos por una letra mayúscula o minúscula. Nótese que
hemos cambiado de + a * para indicar cero o más caracteres que no sean espacios,
ya que [a-zA-Z0-9] implica un carácter distinto de un espacio. Recuerda que el
* o + se aplica al carácter inmediatamente a la izquierda del signo de suma o del
asterisco.
Si usamos esta expresión en nuestro programas, nuestros datos quedarán mucho
más depurados:
# Code: http://www.py4e.com/code3/re07.py
142 CHAPTER 11. EXPRESIONES REGULARES
...
['wagnermr@iupui.edu']
['cwen@iupui.edu']
['postmaster@collab.sakaiproject.org']
['200801032122.m03LMFo4005148@nakamura.uits.iupui.edu']
['source@collab.sakaiproject.org']
['source@collab.sakaiproject.org']
['source@collab.sakaiproject.org']
['apache@localhost']
X-DSPAM-Confidence: 0.8475
X-DSPAM-Probability: 0.0000
^X-.*: [0-9.]+
Traduciendo esto, estamos diciendo que queremos líneas que empiecen con X-,
seguido por cero o más caracteres (.*), seguido por un carácter de dos puntos
(:) y luego un espacio. Después del espacio, buscamos uno o más caracteres que
sean, o bien un dígito (0-9), o bien un punto [0-9.]+. Nótese que al interior de
los corchetes el punto efectivamente corresponde a un punto (es decir, no funciona
como comodín entre corchetes).
La siguiente es una expresión bastante comprimida que solo retornará las líneas
que nos interesan:
# Búsqueda de líneas que comiencen con 'X' seguida de cualquier caracter que
# no sea espacio y ':' seguido de un espacio y cualquier número.
# El número incluye decimales.
11.3. COMBINANDO BÚSQUEDA Y EXTRACCIÓN 143
import re
man = open('mbox-short.txt')
for linea in man:
linea = linea.rstrip()
if re.search(r'^X\S*: [0-9.]+', linea):
print(linea)
# Code: http://www.py4e.com/code3/re10.py
Cuando ejecutamos el programa, vemos que los datos han sido procesados,
mostrando solo las líneas que buscamos.
X-DSPAM-Confidence: 0.8475
X-DSPAM-Probability: 0.0000
X-DSPAM-Confidence: 0.6178
X-DSPAM-Probability: 0.0000
Ahora, debemos resolver el problema de extraer los númueros. Aunque sería bas-
tante sencillo usar split, podemos echar mano a otra función de las expresiones
regulares para buscar y analizar la línea a la vez.
Los paréntesis son otros caracteres especiales en las expresiones regulares. Al agre-
gar paréntesis a una expresión regular, son ignorados a la hora de hacer coincidir
la cadena. Pero cuando se usa findall(), los paréntesis indican que, aunque se
quiere que toda la expresión coincida, solo interesa extraer una parte de la subca-
dena que coincida con la expresión regular.
Entonces, hacemos los siguientes cambios a nuestro programa:
# Búsqueda de líneas que comiencen con 'X' seguida de cualquier caracter que
# no sea espacio en blanco y ':' seguido de un espacio y un número.
# El número puede incluir decimales.
# Después imprimir el número si es mayor a cero.
import re
man = open('mbox-short.txt')
for linea in man:
linea = linea.rstrip()
x = re.findall(r'^X\S*: ([0-9.]+)', linea)
if len(x) > 0:
print(x)
# Code: http://www.py4e.com/code3/re11.py
['0.8475']
144 CHAPTER 11. EXPRESIONES REGULARES
['0.0000']
['0.6178']
['0.0000']
['0.6961']
['0.0000']
..
Los números siguen estando en una lista y deben ser convertidos de cadenas a
números de coma flotante, pero hemos usado las expresiones regulares para buscar
y extraer la información que consideramos interesante.
Otro ejemplo de esta técnica: si revisan este archivo, verán una serie de líneas en
el formulario:
Detalles: http://source.sakaiproject.org/viewsvn/?view=rev&rev=39772
Si quisiéramos extraer todos los números de revisión (el número entero al final de
esas líneas) usando la misma técnica del ejemplo anterior, podríamos escribir el
siguiente programa:
# Code: http://www.py4e.com/code3/re12.py
['39772']
['39771']
['39770']
['39769']
...
con la intención de extraer la hora del día en cada línea. Antes logramos esto
haciendo dos llamadas a split. Primero se dividía la línea en palabras y luego
tomábamos la quinta palabra y la dividíamos de nuevo en el carácter “:” para
obtener los dos caracteres que nos interesaban.
Aunque esta solución funciona, es el resultado de código bastante frágil, que de-
pende de asumir que las líneas tienen el formato adecuado. Si bien puedes agregar
chequeos (o un gran bloque de try/except) para asegurarte que el programa no
falle al encontrar líneas mal formateadas, esto hará que el programa aumente a 10
o 15 líneas de código, que además serán difíciles de leer.
Podemos lograr el resultado de forma mucho más simple utilizando la siguiente
expresión regular:
^From .* [0-9][0-9]:
La traducción de esta expresión regular sería que estamos buscando líneas que
empiecen con From (nótese el espacio), seguido de cualquier número de caracteres
(.*), seguidos de un espacio en blanco, seguido de dos dígitos [0-9][0-9], seguidos
de un carácter “:”. Esa es la definición de la clase de líneas que buscamos.
Para extraer solo la hora usando findall(), agregamos paréntesis alrededor de
los dos dígitos, de la siguiente manera:
^From .* ([0-9][0-9]):
# Code: http://www.py4e.com/code3/re13.py
['09']
['18']
['16']
['15']
...
import re
x = 'We just received $10.00 for cookies.'
y = re.findall('\$[0-9.]+',x)
Dado que antepusimos la barra invertida al signo “$”, encuentra una coincidencia
con el signo en la cadena en lugar de con el final de la línea, y el resto de la
expresión regular coincide con uno o más dígitos del carácter “.” Nota: dentro de
los corchetes, los caracteres no se consideran “especiales”. Por tanto, al escribir
[0-9.], efectivamente significa dígitos o un punto. Cuando no está entre corchetes,
el punto es el carácter “comodín” que coincide con cualquier carácter. Cuando está
dentro de corchetes, un punto es un punto.
11.5 Resumen
Aunque solo hemos escarbado la superficie de las expresiones regulares, hemos
aprendido un poco sobre su lenguaje. Son cadenas de búsqueda con caracteres es-
peciales en su interior, los que comunican tus deseos al sistema de expresiones reg-
ulares respecto de qué se considera una coincidencia y qué información es extraída
de las cadenas coincidentes. A continuación tenemos algunos de estos caracteres y
secuencias especiales:
ˆ Coincide con el comienzo de la línea.
$ Coincide con el final de la línea
. Coincide con cualquier carácter (un comodín).
\s Coincide con un espacio en blanco.
\S Coincide con un carácter que no sea un espacio en blanco (el opuesto a \s).
* Se aplica al carácter o caracteres inmediatamente anteriores, indicando que
pueden coincidir cero o más veces.
11.6. SECCIÓN ADICIONAL PARA USUARIOS DE UNIX / LINUX 147
From: louis@media.berkeley.edu
From: zqian@umich.edu
From: rjlowe@iupui.edu
Esto ordena a grep mostrar las líneas que comienzan con la cadena “From:” en
el archivo mbox-short.txt. Si experimentas un poco con el comando grep y lees su
documentación, encontrarás algunas sutiles diferencias entre el soporte de expre-
siones regulares en Python y en grep. Por ejemplo, grep no reconoce el carácter
de no espacio en blanco \S, por lo que deberás usar la notación de conjuntos [ˆ ],
que es un poco más compleja, y que significa que encontrará una coincidencia con
cualquier carácter que no sea un espacio en blanco.
11.7 Depuración
Python incluye una documentación simple y rudimentaria que puede ser de gran
ayuda si necesitas revisar para encontrar el nombre exacto de algún método. Esta
documentación puede revisarse en el intérprete de Python en modo interactivo.
Para mostrar el sistema de ayuda interactivo, se utiliza el comando help().
>>> help()
help> modules
Si sabes qué método quieres usar, puedes utilizar el comando dir() para encontrar
los métodos que contiene el módulo, de la siguiente manera:
>>> import re
>>> dir(re)
[.. 'compile', 'copy_reg', 'error', 'escape', 'findall',
'finditer' , 'match', 'purge', 'search', 'split', 'sre_compile',
'sre_parse' , 'sub', 'subn', 'sys', 'template']
La documentación incluida no es muy exhaustiva, pero puede ser útil si estás con
prisa o no tienes acceso a un navegador o motor de búsqueda.
11.8. GLOSARIO 149
11.8 Glosario
código frágil Código que funciona cuando los datos se encuentran en un formato
específico pero tiene a romperse si éste no se cumple. Lo llamamos “código
frágil” porque se rompe con facilidad.
coincidencia ambiciosa La idea de que los caracteres + y * en una expresión
regular se expanden hacia afuera para coincidir con la mayor cadena posible.
11.9 Ejercicios
Ejercicio uno: escribe un programa simple que simule la operación del
comando grep en Unix. Debe pedir al usuario que ingrese una expresión
regular y cuente el número de líneas que coincidan con ésta:
$ python grep.py
Ingresa una expresión regular: ^Author
mbox.txt tiene 1798 líneas que coinciden con ^Author
$ python grep.py
Ingresa una expresión regular: ^X-
mbox.txt tiene 14368 líneas que coinciden con ^X-
$ python grep.py
Ingresa una expresión regular: java$
mbox.txt tiene 4175 líneas que coinciden con java$
Programas en red
Aunque muchos de los ejemplos en este libro se han enfocado en leer archivos y
buscar datos en ellos, hay muchas fuentes de información diferentes si se tiene en
cuenta el Internet.
En este capítulo fingiremos ser un navegador web y recuperaremos páginas web
utilizando el Protocolo de Transporte de Hipertexto (HyperText Transfer Protocol
- HTTP). Luego revisaremos los datos de esas páginas web y los analizaremos.
151
152 CHAPTER 12. PROGRAMAS EN RED
import socket
while True:
datos = misock.recv(512)
if len(datos) < 1:
break
print(datos.decode(),end='')
misock.close()
# Code: http://www.py4e.com/code3/socket1.py
Tu
Programa
www.py4e.com
socket )
* Páginas Web
connect C
Port 80 .
send , .
-
recv T .
no quedan más datos por leer (es decir, la llamada a recv() devuelve una cadena
vacía).
El programa produce la salida siguiente:
HTTP/1.1 200 OK
Date: Wed, 11 Apr 2018 18:52:55 GMT
Server: Apache/2.4.7 (Ubuntu)
Last-Modified: Sat, 13 May 2017 11:22:22 GMT
ETag: "a7-54f6609245537"
Accept-Ranges: bytes
Content-Length: 167
Cache-Control: max-age=0, no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Connection: close
Content-Type: text/plain
La salida comienza con la cabecera que el servidor envía para describir el docu-
mento. Por ejemplo, la cabecera Content-Type indica que el documento es un
documento de texto sin formato (text/plain).
Después de que el servidor nos envía la cabecera, añade una línea en blanco para
indicar el final de la cabecera, y después envía los datos reales del archivo romeo.txt.
Este ejemplo muestra cómo hacer una conexión de red de bajo nivel con sockets.
Los sockets pueden ser usados para comunicarse con un servidor web, con un
servidor de correo, o con muchos otros tipos de servidores. Todo lo que se necesita
es encontrar el documento que describe el protocolo correspondiente y escribir el
código para enviar y recibir los datos de acuerdo a ese protocolo.
Sin embargo, como el protocolo que se usa con más frecuencia es el protocolo web
HTTP, Python tiene una librería especial diseñada especialmente para trabajar
con éste para recibir documentos y datos a través de la web.
154 CHAPTER 12. PROGRAMAS EN RED
import socket
import time
SERVIDOR = 'data.pr4e.org'
PUERTO = 80
misock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
misock.connect((SERVIDOR, PUERTO))
misock.sendall(b'GET http://data.pr4e.org/cover3.jpg HTTP/1.0\r\n\r\n')
contador = 0
imagen = b""
while True:
datos = misock.recv(5120)
if len(datos) < 1: break
#time.sleep(0.25)
contador = contador + len(datos)
print(len(datos), contador)
imagen = imagen + datos
misock.close()
# Code: http://www.py4e.com/code3/urljpeg.py
$ python urljpeg.py
5120 5120
5120 10240
4240 14480
5120 19600
...
5120 214000
3200 217200
5120 222320
5120 227440
3167 230607
Header length 394
HTTP/1.1 200 OK
Date: Fri, 21 Feb 2020 01:45:41 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Mon, 15 May 2017 12:27:40 GMT
ETag: "38342-54f8f2e5b6277"
Accept-Ranges: bytes
Content-Length: 230210
Vary: Accept-Encoding
Cache-Control: max-age=0, no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Connection: close
Content-Type: image/jpeg
Puedes observar que para esta url, la cabecera Content-Type indica que el cuerpo
del documento es una imagen (image/jpeg). Una vez que el programa termina,
puedes ver los datos de la imagen abriendo el archivo cosa.jpg en un visor de
imágenes.
Al ejecutar el programa, se puede ver que no se obtienen 5120 caracteres cada vez
que llamamos el método recv(). Se obtienen tantos caracteres como hayan sido
transferidos por el servidor web hacia nosotros a través de la red en el momento de
la llamada a recv(). En este emeplo, se obtienen al menos 3200 caracteres cada
vez que solicitamos hasta 5120 caracteres de datos.
Los resultados pueden variar dependiendo de tu velocidad de internet. Además,
observa que en la última llamada a recv() obtenemos 3167 bytes, lo cual es el
final de la cadena, y en la siguiente llamada a recv() obtenemos una cadena de
longitud cero que indica que el servidor ya ha llamado close() en su lado del
socket, y por lo tanto no quedan más datos pendientes por recibir.
Podemos retardar las llamadas sucesivas a recv() al descomentar la llamada a
time.sleep(). De esta forma, esperamos un cuarto de segundo después de cada
llamada de modo que el servidor puede “adelantarse” a nosotros y enviarnos más
156 CHAPTER 12. PROGRAMAS EN RED
datos antes de que llamemos de nuevo a recv(). Con el retraso, esta vez el
programa se ejecuta así:
$ python urljpeg.py
5120 5120
5120 10240
5120 15360
...
5120 225280
5120 230400
208 230608
Header length 394
HTTP/1.1 200 OK
Date: Fri, 21 Feb 2020 01:57:31 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Mon, 15 May 2017 12:27:40 GMT
ETag: "38342-54f8f2e5b6277"
Accept-Ranges: bytes
Content-Length: 230210
Vary: Accept-Encoding
Cache-Control: max-age=0, no-cache, no-store, must-revalidate
Pragma: no-cache
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Connection: close
Content-Type: image/jpeg
Ahora todas las llamadas a recv(), excepto la primera y la última, nos dan 5120
caracteres cada vez que solicitamos más datos.
Existe un buffer entre el servidor que hace las peticiones send() y nuestra apli-
cación que hace las peticiones recv(). Cuando ejecutamos el programa con el
retraso activado, en algún momento el servidor podría llenar el buffer del socket y
verse forzado a detenerse hasta que nuestro programa empiece a vaciar ese buffer.
La detención de la aplicación que envía los datos o de la que los recibe se llama
“control de flujo”.
import urllib.request
man_a = urllib.request.urlopen('http://data.pr4e.org/romeo.txt')
12.5. LEYENDO ARCHIVOS BINARIOS CON URLLIB 157
# Code: http://www.py4e.com/code3/urllib1.py
Una vez que la página web ha sido abierta con urllib.urlopen, se puede tratar
como un archivo y leer a través de ella utilizando un bucle for.
Cuando el programa se ejecuta, en su salida sólo vemos el contenido del archivo.
Las cabeceras siguen enviándose, pero el código de urllib se encarga de manejarlas
y solamente nos devuelve los datos.
Como ejemplo, podemos escribir un programa para obtener los datos de romeo.txt
y calcular la frecuencia de cada palabra en el archivo de la forma siguiente:
man_a = urllib.request.urlopen('http://data.pr4e.org/romeo.txt')
contadores = dict()
for linea in man_a:
palabras = linea.decode().split()
for palabra in palabras:
contadores[palabra] = contadores.get(palabra, 0) + 1
print(contadores)
# Code: http://www.py4e.com/code3/urlwords.py
De nuevo vemos que, una vez abierta la página web, se puede leer como si fuera
un archivo local.
A veces se desea obtener un archivo que no es de texto (o binario) tal como una
imagen o un archivo de video. Los datos en esos archivos generalmente no son
útiles para ser impresos en pantalla, pero se puede hacer fácilmente una copia de
una URL a un archivo local en el disco duro utilizando urllib.
El método consiste en abrir la dirección URL y utilizar read para descargar todo el
contenido del documento en una cadena (img) para después escribir esa información
a un archivo local, tal como se muestra a continuación:
158 CHAPTER 12. PROGRAMAS EN RED
img = urllib.request.urlopen('http://data.pr4e.org/cover3.jpg').read()
man_a = open('portada.jpg', 'wb')
man_a.write(img)
man_a.close()
# Code: http://www.py4e.com/code3/curl1.py
Este programa lee todos los datos que recibe de la red y los almacena en la vari-
able img en la memoria principal de la computadora. Después, abre el archivo
portada.jpg y escribe los datos en el disco. El argumento wb en la función open()
abre un archivo binario en modo de escritura solamente. Este programa funcionará
siempre y cuando el tamaño del archivo sea menor que el tamaño de la memoria
de la computadora.
Aún asi, si es un archivo grande de audio o video, este programa podría fallar o al
menos ejecutarse sumamente lento cuando la memoria de la computadora se haya
agotado. Para evitar que la memoria se termine, almacenamos los datos en bloques
(o buffers) y luego escribimos cada bloque en el disco antes de obtener el siguiente
bloque. De esta forma, el programa puede leer archivos de cualquier tamaño sin
utilizar toda la memoria disponible en la computadora.
img = urllib.request.urlopen('http://data.pr4e.org/cover3.jpg')
man_a = open('portada.jpg', 'wb')
tamano = 0
while True:
info = img.read(100000)
if len(info) < 1: break
tamano = tamano + len(info)
man_a.write(info)
# Code: http://www.py4e.com/code3/curl2.py
En este ejemplo, leemos solamente 100,000 caracteres a la vez, y después los es-
cribimos al archivo portada.jpg antes de obtener los siguientes 100,000 caracteres
de datos desde la web.
python curl2.py
230210 caracteres copiados.
12.6. ANÁLISIS THE HTML Y RASCADO DE LA WEB 159
Podemos construir una expresión regular bien formada para buscar y extraer los
valores de los enlaces del texto anterior, de esta forma:
href="http[s]?://.+?"
import re
import ssl
# Code: http://www.py4e.com/code3/urlregex.py
La librería ssl permite a nuestro programa acceder a los sitios web que estricta-
mente requieren HTTPS. El método read devuelve código fuente en HTML como
un objeto binario en vez de devolver un objeto HTTPResponse. El método de
expresiones regulares findall nos da una lista de todas las cadenas que coinci-
den con la expresión regular, devolviendo solamente el texto del enlace entre las
comillas dobles.
Cuando corremos el programa e ingresamos una URL, obtenemos lo siguiente:
Introduzca - https://docs.python.org
https://docs.python.org/3/index.html
https://www.python.org/
https://devguide.python.org/docquality/#helping-with-documentation
https://docs.python.org/3.9/
https://docs.python.org/3.8/
https://docs.python.org/3.7/
https://docs.python.org/3.6/
https://docs.python.org/3.5/
https://docs.python.org/2.7/
https://www.python.org/doc/versions/
https://www.python.org/dev/peps/
https://wiki.python.org/moin/BeginnersGuide
https://wiki.python.org/moin/PythonBooks
https://www.python.org/doc/av/
https://devguide.python.org/
https://www.python.org/
https://www.python.org/psf/donations/
https://docs.python.org/3/bugs.html
https://www.sphinx-doc.org/
Las expresiones regulares funcionan muy bien cuando el HTML está bien for-
mateado y es predecible. Pero dado que ahí afuera hay muchas páginas con HTML
“defectuoso”, una solución que solo utilice expresiones regulares podría perder al-
gunos enlaces válidos, o bien terminar obteniendo datos erróneos.
Esto se puede resolver utilizando una librería robusta de análisis de HTML.
12.8. ANÁLISIS DE HTML MEDIANTE BEAUTIFULSOUP 161
# O descarga el archivo
# http://www.py4e.com/code3/bs4.zip
# y descomprimelo en el mismo directorio que este archivo
# Code: http://www.py4e.com/code3/urllinks.py
1 El formato XML es descrito en el siguiente capítulo.
162 CHAPTER 12. PROGRAMAS EN RED
El programa solicita una dirección web, luego la abre, lee los datos y se los pasa al
analizador BeautifulSoup. Luego, recupera todas las etiquetas de anclaje e imprime
en pantalla el atributo href de cada una de ellas.
Introduzca - https://docs.python.org
genindex.html
py-modindex.html
https://www.python.org/
#
whatsnew/3.8.html
whatsnew/index.html
tutorial/index.html
library/index.html
reference/index.html
using/index.html
howto/index.html
installing/index.html
distributing/index.html
extending/index.html
c-api/index.html
faq/index.html
py-modindex.html
genindex.html
glossary.html
search.html
contents.html
bugs.html
https://devguide.python.org/docquality/#helping-with-documentation
about.html
license.html
copyright.html
download.html
https://docs.python.org/3.9/
https://docs.python.org/3.8/
https://docs.python.org/3.7/
https://docs.python.org/3.6/
https://docs.python.org/3.5/
https://docs.python.org/2.7/
https://www.python.org/doc/versions/
https://www.python.org/dev/peps/
https://wiki.python.org/moin/BeginnersGuide
https://wiki.python.org/moin/PythonBooks
https://www.python.org/doc/av/
https://devguide.python.org/
genindex.html
py-modindex.html
https://www.python.org/
#
copyright.html
https://www.python.org/psf/donations/
https://docs.python.org/3/bugs.html
https://www.sphinx-doc.org/
12.8. ANÁLISIS DE HTML MEDIANTE BEAUTIFULSOUP 163
Esta lista es mucho más larga porque algunas de las etiquetas de anclaje son rutas
relativas (e.g., tutorial/index.html) o referencias dentro de la página (p. ej., ‘#’)
que no incluyen “http://” o “https://”, lo cual era un requerimiento en nuestra
expresión regular.
También puedes utilizar BeautifulSoup para extraer varias partes de cada etiqueta
de este modo:
# O descarga el archivo
# http://www.py4e.com/code3/bs4.zip
# y descomprimelo en el mismo directorio que este archivo
# Code: http://www.py4e.com/code3/urllink2.py
python urllink2.py
Introduzca - http://www.dr-chuck.com/page1.htm
ETIQUETA: <a href="http://www.dr-chuck.com/page2.htm">
Second Page</a>
URL: http://www.dr-chuck.com/page2.htm
Contenido:
Second Page
Atributos: {'href': 'http://www.dr-chuck.com/page2.htm'}
$ curl -O http://www.py4e.com/cover.jpg
El comando curl es una abreviación de “copiar URL” y por esa razón los dos
ejemplos vistos anteriormente para obtener archivos binarios con urllib son as-
tutamente llamados curl1.py y curl2.py en www.py4e.com/code3 debido a que
ellos implementan una funcionalidad similar a la del comando curl. Existe tam-
bién un programa de ejemplo curl3.py que realiza la misma tarea de forma un
poco más eficiente, en caso de que quieras usar de verdad este diseño en algún
programa que estés escribiendo.
Un segundo comando que funciona de forma similar es wget:
$ wget http://www.py4e.com/cover.jpg
Ambos comandos hacen que obtener páginas web y archivos remotos se vuelva una
tarea fácil.
12.10 Glosario
BeautifulSoup Una librería de Python para analizar documentos HTML y ex-
traer datos de ellos, que compensa la mayoría de las imperfecciones que los
navegadores HTML normalmente ignoran. Puedes descargar el código de
BeautifulSoup desde www.crummy.com.
puerto Un número que generalmente indica qué aplicación estás contactando
cuando realizas una conexión con un socket en un servidor. Por ejemplo, el
tráfico web normalmente usa el puerto 80, mientras que el tráfico del correo
electrónico usa el puerto 25.
rascado Cuando un programa simula ser un navegador web y recupera una página
web, para luego realizar una búsqueda en su contenido. A menudo los pro-
gramas siguen los enlaces en una página para encontrar la siguiente, de modo
que pueden atravesar una red de páginas o una red social.
rastrear La acción de un motor de búsqueda web que consiste en recuperar una
página y luego todas las páginas enlazadas por ella, continuando así suce-
sivamente hasta que tienen casi todas las páginas de Internet, que usan a
continuación para construir su índice de búsqueda.
socket Una conexión de red entre dos aplicaciones, en la cual dichas aplicaciones
pueden enviar y recibir datos en ambas direcciones.
12.11. EJERCICIOS 165
12.11 Ejercicios
Ejercicio 1: Cambia el programa del socket socket1.py para que le pida al
usuario la URL, de modo que pueda leer cualquier página web. Puedes
usar split('/') para dividir la URL en las partes que la componen, de
modo que puedas extraer el nombre del host para la llamada a connect
del socket. Añade comprobación de errores utilizando try y except para
contemplar la posibilidad de que el usuario introduzca una URL mal
formada o inexistente.
Ejercicio 2: Cambia el programa del socket para que cuente el número
de caracteres que ha recibido y se detenga, con un texto en pantalla,
después de que se hayan mostrado 3000 caracteres. El programa debe
recuperar el documento completo y contar el número total de caracteres,
mostrando ese total al final del documento.
Ejercicio 3: Utiliza urllib para rehacer el ejercicio anterior de modo que
(1) reciba el documento de una URL, (2) muestre hasta 3000 caracteres,
y (3) cuente la cantidad total de caracteres en el documento. No te
preocupes de las cabeceras en este ejercicio, simplemente muesta los
primeros 3000 caracteres del contenido del documento.
Ejercicio 4: Cambia el programa urllinks.py para extraer y contar las
etiquetas de párrafo (p) del documento HTML recuperado y mostrar
el total de párrafos como salida del programa. No muestres el texto de
los párrafos, sólo cuéntalos. Prueba el programa en varias páginas web
pequeñas, y también en otras más grandes.
Ejercicio 5: (Avanzado) Cambia el programa del socket de modo que
solamente muestra datos después de que se haya recibido la cabecera y
la línea en blanco. Recuerda que recv recibe caracteres (saltos de línea
incluidos), no líneas.
166 CHAPTER 12. PROGRAMAS EN RED
Chapter 13
Una vez que recuperar documentos a través de HTTP y analizarlos usando pro-
gramas se convirtió en algo sencillo, no se tardó mucho en desarrollar un modelo
consistente en la producción de documentos específicamente diseñados para ser con-
sumidos por otros programas (es decir, no únicamente HTML para ser mostrado
en un navegador).
Existen dos formatos habituales que se usan para el intercambio de datos a través
de la web. El “eXtensible Markup Language” (lenguaje extensible de marcas), o
XML, ha sido utilizado durante mucho tiempo, y es el más adecuado para inter-
cambiar datos del tipo documento. Cuando los programas simplemente quieren
intercambiar diccionarios, listas u otra información interna, usan “JavaScript Ob-
ject Notation”, o JSON (Notación de Objetos Javascript; consulta www.json.org).
Nosotros examinaremos ambos formatos.
XML tiene un aspecto similar a HTML, pero más estructurado. Este es un ejemplo
de un documento XML:
<person>
<name>Chuck</name>
<phone type="intl">
+1 734 303 4456
</phone>
<email hide="yes" />
</person>
Cada par de etiquetas de apertura (p. ej., ‘’) y de cierre (p. ej., ‘’) representan
un elemento o nodo con el mismo nombre de la etiqueta (p. ej., ‘person’). Cada
elemento puede contener texto, atributos (p. ej., ‘hide’) y otros elementos anidados.
Si un elemento XML está vacío (es decir, no tiene contenido), puede representarse
con una etiqueta auto-cerrada (p. ej., ‘’).
167
168 CHAPTER 13. USO DE SERVICIOS WEB
persona
+1 734
Chuck
303 4456
import xml.etree.ElementTree as ET
data = '''
<person>
<name>Chuck</name>
<phone type="intl">
+1 734 303 4456
</phone>
<email hide="yes" />
</person>'''
tree = ET.fromstring(data)
print('Name:', tree.find('name').text)
print('Attr:', tree.find('email').get('hide'))
# Code: http://www.py4e.com/code3/xml1.py
Tanto la triple comilla simple (‘”‘’) como la triple comilla doble (’“”“’) permiten
la creación de cadenas que abarquen varias líneas.
La llamada a ‘fromstring’ convierte la representación de cadena del XML en un
“árbol” de nodos XML. Una vez tenemos el XML como un árbol, disponemos de
una serie de métodos que podemos llamar para extraer porciones de datos de ese
XML. La función ‘find’ busca a través del árbol XML y recupera el nodo que
coincide con la etiqueta especificada.
13.3. DESPLAZAMIENTO A TRAVÉS DE LOS NODOS 169
Name: Chuck
Attr: yes
import xml.etree.ElementTree as ET
input = '''
<stuff>
<users>
<user x="2">
<id>001</id>
<name>Chuck</name>
</user>
<user x="7">
<id>009</id>
<name>Brent</name>
</user>
</users>
</stuff>'''
stuff = ET.fromstring(input)
lst = stuff.findall('users/user')
print('User count:', len(lst))
# Code: http://www.py4e.com/code3/xml2.py
El método ‘findall’ devuelve una lista de subárboles que representan las estructuras
‘user’ del árbol XML. A continuación podemos escribir un bucle ‘for’ que busque
en cada uno de los nodos usuario, e imprima el texto de los elementos ‘name’ e
‘id’, además del atributo ‘x’ de cada nodo usuario.
User count: 2
Name Chuck
170 CHAPTER 13. USO DE SERVICIOS WEB
Id 001
Attribute 2
Name Brent
Id 009
Attribute 7
import xml.etree.ElementTree as ET
input = '''
<stuff>
<users>
<user x="2">
<id>001</id>
<name>Chuck</name>
</user>
<user x="7">
<id>009</id>
<name>Brent</name>
</user>
</users>
</stuff>'''
stuff = ET.fromstring(input)
lst = stuff.findall('users/user')
print('User count:', len(lst))
lst2 = stuff.findall('user')
print('User count:', len(lst2))
‘lst’ almacena todos los elementos ‘user’ anidados dentro de su base ‘users’. ‘lst2’
busca los elementos ‘user’ que no se encuentren anidados dentro del elemento de
nivel superior ‘stuff’ donde no hay ninguno.
User count: 2
User count: 0
He aquí una codificación JSON que es más o menos equivalente al XML del ejemplo
anterior:
{
"name" : "Chuck",
"phone" : {
"type" : "intl",
"number" : "+1 734 303 4456"
},
"email" : {
"hide" : "yes"
}
}
import json
data = '''
[
{ "id" : "001",
"x" : "2",
172 CHAPTER 13. USO DE SERVICIOS WEB
"name" : "Chuck"
} ,
{ "id" : "009",
"x" : "7",
"name" : "Brent"
}
]'''
info = json.loads(data)
print('User count:', len(info))
# Code: http://www.py4e.com/code3/json2.py
Si comparas el código que extrae los datos del JSON analizado y el del XML, verás
que lo que obtenemos de ‘json.loads()’ es una lista de Python que recorreremos con
un bucle for, y cada elemento dentro de esa lista es un diccionario de Python. Una
vez analizado el JSON, podemos usar el operador índice de Python para extraer
los distintos fragmentos de datos de cada usuario. No tenemos que usar la librería
JSON para rebuscar a través del JSON analizado, ya que los datos devueltos son
sencillamente estructuras nativas de Python.
La salida de este programa es exactamente la misma que la de la versión XML
anterior.
User count: 2
Name Chuck
Id 001
Attribute 2
Name Brent
Id 009
Attribute 7
de datos complejas para poder enviar y recibir los datos entre esas aplicaciones, a
través del eXtensibleMarkup Language (XML) o del JavaScript Object Notation
(JSON).
El paso siguiente es empezar a definir y documentar “contratos” entre aplicaciones
usando estas técnicas. El nombre habitual para estos contratos entre aplicaciones
es Interfaces de Programación de Aplicaciones (Application Program Interfaces), o
APIs. Cuando se utiliza una API, normalmente un programa crea un conjunto de
servicios disponibles para que los usen otras aplicaciones y publica las APIs (es de-
cir, las “reglas”) que deben ser seguidas para acceder a los servicios proporcionados
por el programa.
Cuando comenzamos a construir programas con funcionalidades que incluyen el
acceso a servicios proporcionados por otros programas, el enfoque se denomina
Service-Oriented Architecture (Arquitectura Orientada a Servicios), o SOA. Un
enfoque SOA es aquel en el cual nuestra aplicación principal usa los servicios de
otras aplicaciones. Un planteamiento no-SOA es aquel en el cual tenemos una
única aplicación independiente que contiene todo el código necesario para su im-
plementación.
Podemos encontrar multitud de ejemplos de SOAs cuando utilizamos servicios
de la web. Podemos ir a un único sitio web y reservar viajes en avión, hoteles
y automóviles, todo ello desde el mismo sitio. Los datos de los hoteles no están
almacenados en los equipos de la compañía aérea. En vez de eso, los computadores
de la aerolínea contactan con los servicios de los computadores de los hoteles,
recuperan los datos de éstos, y se los presentan al usuario. Cuando el usuario
acepta realizar una reserva de un hotel usando el sitio web de una aerolínea, ésta
utiliza otro servicio web en los sistemas de los hoteles para realizar la reserva
real. Y cuando llega el momento de cargar en tu tarjeta de crédito el importe de
la transacción completa, hay todavía otros equipos diferentes involucrados en el
proceso.
API
API API
Aplicación de
Viajes
Una Arquitectura Orientada a Servicios tiene muchas ventajas, que incluyen: (1)
siempre se mantiene una única copia de los datos (lo cual resulta particularmente
importante en ciertas áreas como las reservas hoteleras, donde no queremos du-
plicar excesivamente la información) y (2) los propietarios de los datos pueden
imponer reglas acerca del uso de esos datos. Con estas ventajas, un sistema SOA
debe ser diseñado cuidadosamente para tener buen rendimiento y satisfacer las
necesidades de los usuarios.
Cuando una aplicación ofrece un conjunto de servicios en su API disponibles a
través de la web, los llamamos servicios web.
13.8 Glossary
API Application Programming Interface (Interfaz de Programación de Aplica-
ciones) - Un contrato entre aplicaciones que define las pautas de interacción
entre los componentes de dos aplicaciones.
ElementTree Una librería interna de Python que se utiliza para analizar datos
XML.
JSON JavaScript Object Notation (Notación de Objetos JavaScript). Un formato
que permite el envío de estructuras de datos basadas en la sintaxis de los
Objetos JavaScript.
api_key = False
# If you have a Google Places API key, enter it here
# api_key = 'AIzaSy___IDByT70'
# https://developers.google.com/maps/documentation/geocoding/intro
if api_key is False:
api_key = 42
serviceurl = 'http://py4e-data.dr-chuck.net/json?'
else :
serviceurl = 'https://maps.googleapis.com/maps/api/geocode/json?'
ctx = ssl.create_default_context()
ctx.check_hostname = False
ctx.verify_mode = ssl.CERT_NONE
while True:
address = input('Enter location: ')
if len(address) < 1: break
parms = dict()
parms['address'] = address
if api_key is not False: parms['key'] = api_key
url = serviceurl + urllib.parse.urlencode(parms)
print('Retrieving', url)
uh = urllib.request.urlopen(url, context=ctx)
data = uh.read().decode()
print('Retrieved', len(data), 'characters')
try:
js = json.loads(data)
except:
js = None
print(json.dumps(js, indent=4))
lat = js['results'][0]['geometry']['location']['lat']
lng = js['results'][0]['geometry']['location']['lng']
print('lat', lat, 'lng', lng)
location = js['results'][0]['formatted_address']
print(location)
# Code: http://www.py4e.com/code3/geojson.py
$ python3 geojson.py
13.9. APLICACIÓN Nº 1: SERVICIO WEB DE GEOCODIFICACIÓN DE GOOGLE177
{
"results": [
{
"address_components": [
{
"long_name": "Ann Arbor",
"short_name": "Ann Arbor",
"types": [
"locality",
"political"
]
},
{
"long_name": "Washtenaw County",
"short_name": "Washtenaw County",
"types": [
"administrative_area_level_2",
"political"
]
},
{
"long_name": "Michigan",
"short_name": "MI",
"types": [
"administrative_area_level_1",
"political"
]
},
{
"long_name": "United States",
"short_name": "US",
"types": [
"country",
"political"
]
}
],
"formatted_address": "Ann Arbor, MI, USA",
"geometry": {
"bounds": {
"northeast": {
"lat": 42.3239728,
"lng": -83.6758069
},
"southwest": {
"lat": 42.222668,
"lng": -83.799572
178 CHAPTER 13. USO DE SERVICIOS WEB
}
},
"location": {
"lat": 42.2808256,
"lng": -83.7430378
},
"location_type": "APPROXIMATE",
"viewport": {
"northeast": {
"lat": 42.3239728,
"lng": -83.6758069
},
"southwest": {
"lat": 42.222668,
"lng": -83.799572
}
}
},
"place_id": "ChIJMx9D1A2wPIgR4rXIhkb5Cds",
"types": [
"locality",
"political"
]
}
],
"status": "OK"
}
lat 42.2808256 lng -83.7430378
Ann Arbor, MI, USA
Enter location:
Para usar estos programas debes tener una cuenta de Twitter, y autorizar a tu
código Python como aplicación permitida, estableciendo diversos parámetros (key,
secret, token y token secret). Luego deberás editar el archivo hidden.py y colocar
esas cuatro cadenas en las variables apropiadas dentro del archivo:
# https://apps.twitter.com/
# Create new App and get the four strings
def oauth():
return {"consumer_key": "h7Lu...Ng",
"consumer_secret" : "dNKenAC3New...mmn7Q",
"token_key" : "10185562-eibxCp9n2...P4GEQQOSGI",
"token_secret" : "H0ycCFemmC4wyf1...qoIpBo"}
# Code: http://www.py4e.com/code3/hidden.py
Se puede acceder al servicio web de Twitter mediante una URL como ésta:
https://api.twitter.com/1.1/statuses/user_timeline.json
Pero una vez añadida la información de seguridad, la URL se parecerá más a esto:
https://api.twitter.com/1.1/statuses/user_timeline.json?count=2 &oauth_version=1.0&oauth_token=101. . . SGI&screen_name=drch
Puedes leer la especificación OAuth si quieres saber más acerca del significado de
los distintos parámetros que hemos añadido para cumplir con los requerimientos
de seguridad de OAuth.
Para los programas que ejecutamos con Twitter, ocultamos toda la complejidad
dentro de los archivos oauth.py y twurl.py. Simplemente ajustamos los parámetros
secretos en hidden.py, enviamos la URL deseada a la función twurl.augment(), y el
código de la librería añade todos los parámetros necesarios a la URL.
Este programa recupera la línea de tiempo de un usuario de Twitter concreto y nos
la devuelve en formato JSON como una cadena. Vamos a imprimir simplemente
los primeros 250 caracteres de esa cadena:
# https://apps.twitter.com/
# Create App and get the four strings, put them in hidden.py
TWITTER_URL = 'https://api.twitter.com/1.1/statuses/user_timeline.json'
while True:
print('')
acct = input('Enter Twitter Account:')
if (len(acct) < 1): break
url = twurl.augment(TWITTER_URL,
{'screen_name': acct, 'count': '2'})
print('Retrieving', url)
connection = urllib.request.urlopen(url, context=ctx)
data = connection.read().decode()
print(data[:250])
headers = dict(connection.getheaders())
# print headers
print('Remaining', headers['x-rate-limit-remaining'])
# Code: http://www.py4e.com/code3/twitter1.py
Junto con los datos de la línea del tiempo, Twitter también devuelve metadatos
sobre la petición en las cabeceras de respuesta HTTP. Una cabecera en particular,
‘x-rate-limit-remaining’, nos informa sobre cuántas peticiones podremos hacer antes
de que seamos bloqueados por un corto periodo de tiempo. Puedes ver que cada vez
que realizamos una petición a la API nuestros intentos restantes van disminuyendo.
En el ejemplo siguiente, recuperamos los amigos de un usuario en Twitter, anal-
izamos el JSON devuelto y extraemos parte de la información sobre esos amigos.
Después de analizar el JSON e “imprimirlo bonito”, realizamos un volcado com-
pleto con un indentado de cuatro caracteres, que nos permite estudiar minuciosa-
mente los datos en caso de que queramos extraer más campos.
import json
import ssl
# https://apps.twitter.com/
# Create App and get the four strings, put them in hidden.py
TWITTER_URL = 'https://api.twitter.com/1.1/friends/list.json'
while True:
print('')
acct = input('Enter Twitter Account:')
if (len(acct) < 1): break
url = twurl.augment(TWITTER_URL,
{'screen_name': acct, 'count': '5'})
print('Retrieving', url)
connection = urllib.request.urlopen(url, context=ctx)
data = connection.read().decode()
js = json.loads(data)
print(json.dumps(js, indent=2))
headers = dict(connection.getheaders())
print('Remaining', headers['x-rate-limit-remaining'])
for u in js['users']:
print(u['screen_name'])
if 'status' not in u:
print(' * No status found')
continue
s = u['status']['text']
print(' ', s[:50])
# Code: http://www.py4e.com/code3/twitter2.py
La salida del programa se parece a la siguiente (parte de los datos se han acortado
para que quepan en la página):
{
"next_cursor": 1444171224491980205,
"users": [
{
"id": 662433,
"followers_count": 28725,
"status": {
"text": "@jazzychad I just bought one .__.",
"created_at": "Fri Sep 20 08:36:34 +0000 2013",
"retweeted": false,
},
"location": "San Francisco, California",
"screen_name": "leahculver",
"name": "Leah Culver",
},
{
"id": 40426722,
"followers_count": 2635,
"status": {
"text": "RT @WSJ: Big employers like Google ...",
"created_at": "Sat Sep 28 19:36:37 +0000 2013",
},
"location": "Victoria Canada",
"screen_name": "_valeriei",
"name": "Valerie Irvine",
}
],
"next_cursor_str": "1444171224491980205"
}
leahculver
@jazzychad I just bought one .__.
_valeriei
RT @WSJ: Big employers like Google, AT&T are h
ericbollens
RT @lukew: sneak peek: my LONG take on the good &a
halherzog
Learning Objects is 10. We had a cake with the LO,
scweeker
@DeviceLabDC love it! Now where so I get that "etc
El último trozo de la salida es donde podemos ver cómo el bucle for lee los cinco
“amigos” más nuevos de la cuenta de Twitter @drchuck e imprime el estado más
reciente de cada uno de ellos. Hay muchos más datos disponibles en el JSON
devuelto. Si miras la salida del programa, podrás ver que el “encuentra a los
amigos” de una cuenta particular tiene una limitación de usos distinta a la del
número de consultas de líneas de tiempo que está permitido realizar durante un
periodo de tiempo.
Estas claves de seguridad de la API permiten a Twitter tener la certeza de que
13.10. APLICACIÓN 2: TWITTER 183
sabe quién está usando su API de datos, y a qué nivel. El enfoque de límite de
usos nos permite hacer capturas de datos sencillas, pero no crear un producto que
extraiga datos de esa API millones de veces al día.
184 CHAPTER 13. USO DE SERVICIOS WEB
Chapter 14
Programación Orientada a
Objetos
• Código secuencial
• Código condicional (declaraciones if)
• Código repetitivo (bucles)
• Almacenar y reutilizar (funciones)
185
186 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
stuff = list()
stuff.append('python')
stuff.append('chuck')
stuff.sort()
print (stuff[0])
print (stuff.__getitem__(0))
print (list.__getitem__(stuff,0))
# Code: http://www.py4e.com/code3/party1.py
print (stuff.__getitem__(0))
print (list.__getitem__(stuff,0))
Las últimas tres líneas del programa son equivalentes, pero es más conveniente
simplemente usar corchetes para buscar un elemento en una posición específica
dentro de una lista.
Podemos ver las capacidades de un objeto mirando el resultado de la función dir():
El resto de este capítulo estará dedicado a definir estos términos, así que asegúrate
de volver a leer los párrafos anteriores una vez que termines de leerlo, para asegu-
rarte de haberlo comprendido correctamente.
# Code: http://www.py4e.com/code3/elev.py
Program
Input Output
# O descarga el archivo
# http://www.py4e.com/code3/bs4.zip
# y descomprimelo en el mismo directorio que este archivo
# Code: http://www.py4e.com/code3/urllinks.py
Convertimos la URL en una cadena y luego pasamos a ésta por urllib para
recuperar los datos de la web. La librería urllib utiliza la librería socket para
llevar a cabo la conexión que recupera los datos. Tomamos la cadena que retorna
urllib y se la entregamos a BeautifulSoup para su análisis. BeautifulSoup utiliza
el objeto html.parser1 y retorna un objeto. Luego, llamamos al método tags()
en el objeto retornado, lo que retorna un diccionario de etiquetas. Nos desplazamos
por las etiquetas y llamamos el método get() por cada etiqueta para imprimir el
atributo href.
Podemos hacer un diagrama de este programa y cómo los objetos funcionan en
conjunto.
1 https://docs.python.org/3/library/html.parser.html
14.5. SUBDIVIDIENDO UN PROBLEMA 189
Socket html.parser
Object Object
Una de las ventajas del enfoque orientado a objetos es que puede ocultar la com-
plejidad de un programa. Por ejemplo, aunque necesitamos saber cómo usar el
código de urllib y BeautifulSoup, no necesitamos saber cómo funcionan interna-
mente esas librerías. Esto nos permite enfocarnos en la parte del problema que
necesitamos resolver e ignorar las otras partes del programa.
Socket html.parser
Object Object
Socket html.parser
Object Object
class PartyAnimal:
x = 0
def party(self) :
self.x = self.x + 1
print("So far",self.x)
an = PartyAnimal()
an.party()
an.party()
an.party()
PartyAnimal.party(an)
# Code: http://www.py4e.com/code3/party2.py
Cada método parece una función: comienzan con la palabra clave def y consisten
en un bloque de código indentado. Este objeto tiene un atributo (x) y un método
(party). Los métodos tienen un primer parámetro especial al que, por convención,
llamamos self.
Tal como la palabra clave def no causa que el código de una función se ejecute, la
palabra clave class no crea un objeto. En vez, la palabra clave class define una
plantilla que indica qué datos y código contendrá cada objeto de tipo PartyAnimal.
14.6. NUESTRO PRIMER OBJETO DE PYTHON 191
La clase es como un molde para galletas y los objetos creados usándola son las
galletas2 . No se le echa el glaseado al molde de las galletas; se le echa glaseado a
las galletas mismas, con lo que se puede poner un glaseado distinto en cada galleta.
an = PartyAnimal()
Es aquí que le ordenamos a Python construir (es decir, crear) un objeto o instancia
de la clase PartyAnimal. Se ve como si fuera una llamada de función a la clase
misma. Python construye el objeto con los datos y métodos adecuados, asignándolo
luego a la variable an. En cierto modo, esto es muy similar a la siguiente línea,
que hemos estado usando todo este tiempo:
counts = dict()
an.party()
Al llamar al método party, el primer parámetro (al que por convención llamamos
self) apunta a la instancia específica del objeto PartyAnimal desde el que se llama
a party. Dentro del método party, vemos la siguiente línea:
2 Cookie image copyright CC-BY https://www.flickr.com/photos/dinnerseries/23570475099
192 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
self.x = self.x + 1
Esta sintaxis utiliza el operador de punto, con lo que significa ‘la x dentro de self’.
Cada vez que se llama a party(), el valor interno de x se incrementa en 1 y se
imprime su valor.
La siguiente línea muestra otra manera de llamar al método party dentro del
objeto an:
PartyAnimal.party(an)
So far 1
So far 2
So far 3
So far 4
class PartyAnimal:
x = 0
def party(self) :
self.x = self.x + 1
print("So far",self.x)
an = PartyAnimal()
print ("Type", type(an))
print ("Dir ", dir(an))
print ("Type", type(an.x))
print ("Type", type(an.party))
# Code: http://www.py4e.com/code3/party3.py
Puedes ver que, usando la palabra clave class, hemos creado un nuevo tipo. En
el resultado de usar dir, puedes ver que tanto el atributo de tipo entero x como el
método party están disponibles dentro del objeto.
class PartyAnimal:
x = 0
def __init__(self):
print('I am constructed')
def party(self) :
self.x = self.x + 1
print('So far',self.x)
def __del__(self):
print('I am destructed', self.x)
an = PartyAnimal()
an.party()
an.party()
an = 42
print('an contains',an)
# Code: http://www.py4e.com/code3/party4.py
I am constructed
So far 1
194 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
So far 2
I am destructed 2
an contains 42
Cuando Python construye el objeto, llama a nuestro método __init__ para darnos
la oportunidad de configurar algunos valores por defecto o iniciales para éste.
Cuando Python encuentra la línea:
an = 42
Hasta ahora hemos definido una clase, construido un solo objeto, usado ese objeto, y
luego descartado el objeto. Sin embargo, el auténtico potencial de la programación
orientada a objetos se manifiesta al construir múltiples instancias de nuestra clase.
Al construir múltiples instancias de nuestra clase, puede que queramos fijar dis-
tintos valores iniciales para cada objeto. Podemos pasar datos a los constructores
para dar a cada objeto un distinto valor inicial:
class PartyAnimal:
x = 0
name = ''
def __init__(self, nam):
self.name = nam
print(self.name,'constructed')
def party(self) :
self.x = self.x + 1
print(self.name,'party count',self.x)
s = PartyAnimal('Sally')
j = PartyAnimal('Jim')
s.party()
j.party()
s.party()
# Code: http://www.py4e.com/code3/party5.py
14.10. HERENCIA 195
El constructor tiene tanto un parámetro self, que apunta a la instancia del objeto,
como parámetros adicionales, que se pasan al constructor al momento de construir
el objeto:
s = PartyAnimal('Sally')
Dentro del constructor, la segunda línea copia el parámetro (nam), el que se pasa
al atributo nombre dentro del objeto.
self.name = nam
El resultado del programa muestra que cada objeto (s y j) contienen sus propias
copias independientes de x y nam:
Sally constructed
Sally party count 1
Jim constructed
Jim party count 1
Sally party count 2
14.10 Herencia
class CricketFan(PartyAnimal):
points = 0
def six(self):
self.points = self.points + 6
self.party()
print(self.name,"points",self.points)
s = PartyAnimal("Sally")
s.party()
j = CricketFan("Jim")
j.party()
j.six()
print(dir(j))
# Code: http://www.py4e.com/code3/party6.py
196 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
Sally constructed
Sally party count 1
Jim constructed
Jim party count 1
Jim party count 2
Jim points 6
['__class__', '__delattr__', ... '__weakref__',
'name', 'party', 'points', 'six', 'x']
14.11 Resumen
Esta es una introducción muy superficial a la programación orientada a objetos,
enfocada principalmente en la terminología y sintaxis necesarias para definir y usar
objetos. Vamos a reseñar rápidamente el código que vimos al comienzo del capítulo.
A estas alturas deberías entender completamente lo que está pasando.
stuff = list()
stuff.append('python')
stuff.append('chuck')
stuff.sort()
print (stuff[0])
print (stuff.__getitem__(0))
print (list.__getitem__(stuff,0))
# Code: http://www.py4e.com/code3/party1.py
La primera línea construye un objeto de clase list. Cuando Python crea el objeto
de clase list llama al método constructor (llamado __init__) para configurar los
atributos internos de datos que se utilizarán para almacenar los datos de la lista.
Aún no hemos pasado ningún parámetro al constructor. When el constructor
retorna, usamos la variable stuff para apuntar la instancia retornada de la clase
list.
La segunda y tercera líneas llaman al método append con un parámetro para
agregar un nuevo objeto al final de la lista actualizando los atributos al interior
14.12. GLOSARIO 197
de stuff. Luego, en la cuarta línea, llamamos al método sort sin darle ningún
parámetro para ordenar los datos dentro del objeto stuff.
Luego, imprimimos el primer objeto en la lista usando los corchetes, los que son
una abreviatura para llamar el método __getitem__ dentro de stuff. Esto es
equivalente a llamado al método __getitem__ dentro de la clase list y pasar el
objeto stuff como primer parámetro y la posición que necesitamos como segundo
parámetro.
Al final del programa, el objeto stuff es descartado, pero no antes de llamar al
método destructor (llamado __del__) de manera tal que el objeto pueda atar cabos
sueltos en caso de resultar necesario.
Estos son los aspectos básicos de la programación orientada a objetos. Hay mu-
chos detalles adicionales sobre cómo usar un enfoque de programación orientada
a objetos al desarrollar aplicaciones, así como librerías, las que van más allá del
ámbito de este capítulo.3
14.12 Glosario
atributo Una variable que es parte de una clase.
clase Una plantilla que puede usarse para construir un objeto. Define los atributos
y métodos que formarán a dicho objeto.
clase hija Una nueva clase creada cuando una clase padre es extendida. La clase
hija hereda todos los atributos y métodos de la clase padre.
constructor Un método opcional con un nombre especial (__init__) que es lla-
mado al momento en que se utiliza una clase para construir un objeto. Nor-
malmente se utiliza para determinar los valores iniciales del objeto.
destructor Un método opcional con un nombre especial (__del__) que es llamado
justo un momento antes de que un objeto sea destruido. Los destructores
rara vez son utilizados.
herencia Cuando creamos una nueva clase (hija) extendiendo una clase existente
(padre). La clase hija tiene todos los atributos y métodos de la clase padre,
más los atributos y métodos adicionales definidos por la clase hija.
método Una función contenida dentro de una clase y de los objetos construidos
desde esa clase. Algunos patrones de diseño orientados a objetos describen
este concepto como ‘mensaje’ en lugar de ‘método’.
objeto Una instancia construida de una clase. Un objeto contiene todos los atrib-
utos y métodos definidos por la clase. En algunos casos de documentación
orientada a objetos se utiliza el término ‘instancia’ de manera intercambiable
con ‘objeto’.
clase padre La clase que está siendo extendida para crear una nueva clase hija.
La clase padre aporta todos sus métodos y atributos a la nueva clase hija.
Una base de datos es un archivo que está organizado para almacenar datos. La
mayoría de las bases de datos están organizadas como diccionarios, en el sentido de
que realizan asociaciones entre claves y valores. La diferencia más importante es
que la base de datos se encuentra en el disco (u otro almacenamiento permanente),
de modo que su contenido se conserva después de que el programa finaliza. Gracias
a que la base de datos se guarda en almacenamiento permanente, puede almacenar
muchos más datos que un diccionario, que está limitado al tamaño de memoria
que tenga la computadora.
Como un diccionario, el software de una base de datos está diseñado para conseguir
que la inserción y acceso a los datos sean muy rápidos, incluso para grandes canti-
dades de datos. Este software mantiene su rendimiento mediante la construcción
de índices, como datos añadidos a la base de datos que permiten al equipo saltar
rápidamente hasta una entrada concreta.
Existen muchos sistemas de bases de datos diferentes, que se utilizan para una
amplia variedad de propósitos. Algunos de ellos son: Oracle, MySQL, Microsoft
SQL Server, PostgreSQL, y SQLite. En este libro nos enfocaremos en SQLite,
ya que se trata de una base de datos muy común y ya viene integrada dentro de
Python. SQLite está diseñada para ser incrustada dentro de otras aplicaciones, de
modo que proporcione soporte para bases de datos dentro de la aplicación. Por
ejemplo, el navegador Firefox es uno de los que utilizan la base de datos SQLite
internamente, al igual que muchos otros productos.
http://sqlite.org/
SQLite es muy adecuado para ciertos problemas de manipulación de datos que
nos encontramos en informática, como en la aplicación de rastreo de Twitter que
hemos descrito en el capítulo anterior.
Cuando se ve por primera vez una base de datos, se parece a una hoja de cálculo
con múltiples hojas. Las estructuras de datos primarias en una base de datos son:
tablas, files, and columnas.
column attribute
Table Relation
cada columna, pero en este capítulo nosotros vamos a mantener los tipos de datos estrictos, para
que los conceptos que aprendamos puedan ser igualmente aplicados a otras bases de datos como
MySQL.
200 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
import sqlite3
conn = sqlite3.connect('musica.sqlite')
cur = conn.cursor()
conn.close()
# Code: http://www.py4e.com/code3/db1.py
execute C
U
fetchone R Users Courses
fetchall S
O Members
close R
Your
Program
Una vez que tenemos el cursor, podemos comenzar a ejecutar comandos sobre el
contenido de la base de datos, usando el método execute().
Los comandos de las bases de datos se expresan en un lenguaje especial que ha
sido estandarizado entre varios proveedores de bases de datos diferentes para permi-
tirnos aprender un único lenguaje para todas ellas. Este lenguaje recibe el nombre
de Lenguaje de Consultas Estructurado (Structured Query Language), o SQL.
https://es.wikipedia.org/wiki/SQL
En nuestro ejemplo, estamos ejecutando dos comandos SQL sobre la base de datos.
Por convención, mostraremos las palabras claves de SQL en mayúscula y las partes
14.16. CREACIÓN DE UNA TABLA EN UNA BASE DE DATOS 201
de los comandos que añadamos nosotros (como los nombres de las tablas y las
columnas) irán en minúsculas.
El primer comando SQL elimina la tabla Canciones si ya existe. Este
planteamiento se utiliza simplemente para permitirnos ejecutar el mismo pro-
grama para crear la tabla Canciones una y otra vez sin provocar un error.
Observa que el comando DROP TABLE elimina la tabla y todo su contenido de la
base de datos (es decir, aquí no existe la opción “deshacer”).
El segundo comando crea una tabla llamada Canciones con una columna de texto
llamada titulo y una columna de enteros llamada reproducciones.
Ahora que ya hemos creado la tabla llamada Canciones, podemos guardar algunos
datos en ella usando la operación de SQL INSERT. Empezaremos realizando otra
vez una conexión con la base de datos y obteniendo el cursor. Luego podemos
ejecutar comandos SQL usando ese cursor.
El comando INSERT de SQL indica qué tabla se está utilizando y luego de-
fine una fila nueva, enumerando los campos que se desean incluir (titulo,
reproducciones) seguidos por los valores (VALUES) que queremos colocar en esa
fila. Nosotros vamos a especificar los valores como signos de interrogación (?, ?)
para indicarle que los valores reales serán pasados como una tupla ( 'My Way',
15 ) en el segundo parámetro de la llamada a execute().
import sqlite3
conn = sqlite3.connect('music.sqlite')
cur = conn.cursor()
print('Canciones:')
cur.execute('SELECT titulo, reproducciones FROM Canciones')
for fila in cur:
print(fila)
cur.close()
# Code: http://www.py4e.com/code3/db2.py
202 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
Tracks
title plays
Thunderstruck 20
My Way 15
Primero insertamos (INSERT) dos filas en la tabla y usamos commit() para forzar
a que los datos sean escritos en el archivo de la base de datos.
Después usamos el comando SELECT para recuperar las filas que acabamos de in-
sertar en la tabla. En el comando SELECT, indicamos qué columnas nos gustaría
obtener (titulo, reproducciones), y también desde qué tabla queremos recu-
perar los datos. Después de ejecutar la sentencia SELECT, el cursor se convierte
en algo con lo que podemos iterar mediante una sentencia for. Por eficiencia, el
cursor no lee todos los datos de la base de datos cuando se ejecuta la sentencia
SELECT. En lugar de ello, los datos van siendo leídos a medida que se van pidiendo
las filas desde el bucle creado con la sentencia for.
Canciones:
('Thunderstruck', 20)
('My Way', 15)
Nuestro bucle for encuentra dos filas, y cada fila es una tupla de Python cuyo
primer valor es el titulo y el segundo es el número de reproducciones.
Nota: Puede que veas cadenas comenzando con u' en otros libros o en Internet.
Esto es una indicación en Python 2 que dice que las cadenas son cadenas Unicode
que son capaces de almacenar caracteres no-latinos. En Python 3, todas las cadenas
son del tipo Unicode por defecto.
Al final del programa, ejecutamos un comando SQL para borrar (DELETE) las files
que acabamos de crear, de modo que podamos ejecutar el programa una y otra vez.
El comando DELETE nos muestra el uso de la cláusula WHERE, la cual nos permite
expresar un criterio de selección, de modo que podemos pedir a la base de datos
que aplique el comando solamente a las filas que cumplan ese criterio. En este
ejemplo, el criterio es cumplido por todas las filas, así que vaciamos la tabla para
que podamos ejecutar el programa de nuevo repetidamente. Después de que se ha
realizado el DELETE, llamamos de nuevo a commit() para forzar a los datos a ser
eliminados de la base de datos.
14.17. RESUMEN DE LENGUAJE DE CONSULTAS ESTRUCTURADO 203
Para insertar una fila en una tabla, usamos el comando de SQL INSERT:
El uso de * indica que se desea que la base de datos devuelva todas las columnas
para cada línea que cumpla la condición de la clausula WHERE.
Hay que notar que, a diferencia de lo que ocurre en Python, en SQL la clausula
WHERE utiliza un único signo igual para indicar una comprobación de igualdad, en
lugar de utilizar un signo doble igual. Otras operaciones lógicas que se permiten
en una clausula WHERE son <, >, <=, >=, !=, así como también AND y OR, y paréntesis
para construir expresiones lógicas.
Se puede solicitar que las columnas devueltas vengan ordenadas por uno de los
campos, de este modo:
Para eliminar una fila, es necesario usar una clausula WHERE en una sentencia
DELETE de SQL. La clausula WHERE determina qué filas serán eliminadas:
Es posible actualizar (UPDATE) una columna o varias de una o más filas en una
tabla usando la secuencia de SQL UPDATE, como se muestra a continuación:
una base de datos en nuestro PC, podremos detener y reanudar el programa tantas
veces como queramos.
Este programa es un poco complejo. Está basado en el código de un ejercicio
anterior del libro que usa la API de Twitter.
Aquí está el código fuente para nuestra aplicación araña de Twitter:
TWITTER_URL = 'https://api.twitter.com/1.1/friends/list.json'
conn = sqlite3.connect('arana.sqlite')
cur = conn.cursor()
cur.execute('''
CREATE TABLE IF NOT EXISTS Twitter
(nombre TEXT, recuperado INTEGER, amigos INTEGER)''')
while True:
cuenta = input('Ingresa una cuenta de Twitter, o salir: ')
if (cuenta == 'salir'): break
if (len(cuenta) < 1):
cur.execute('SELECT nombre FROM Twitter WHERE recuperado = 0 LIMIT 1'
try:
cuenta = cur.fetchone()[0]
except:
print('No se han encontrado cuentas de Twitter por recuperar')
continue
print('Restante', cabeceras['x-rate-limit-remaining'])
js = json.loads(datos)
# Depuración
# print json.dumps(js, indent=4)
contnuevas = 0
contantiguas = 0
for u in js['users']:
amigo = u['screen_name']
print(amigo)
cur.execute('SELECT amigos FROM Twitter WHERE nombre = ? LIMIT 1',
(amigo, ))
try:
contador = cur.fetchone()[0]
cur.execute('UPDATE Twitter SET amigos = ? WHERE nombre = ?',
(contador+1, amigo))
contantiguas = contantiguas + 1
except:
cur.execute('''INSERT INTO Twitter (nombre, recuperado, amigos)
VALUES (?, 0, 1)''', (amigo, ))
contnuevas = contnuevas + 1
print('Cuentas nuevas=', contnuevas, ' ya visitadas=', contantiguas)
conn.commit()
cur.close()
# Code: http://www.py4e.com/code3/twspider.py
contnuevas = 0
contantiguas = 0
for u in js['users']:
amigo = u['screen_name']
14.18. RASTREO EN TWITTER USANDO UNA BASE DE DATOS 207
print(amigo)
cur.execute('SELECT amigos FROM Twitter WHERE nombre = ? LIMIT 1',
(amigo, ))
try:
contador = cur.fetchone()[0]
cur.execute('UPDATE Twitter SET amigos = ? WHERE nombre = ?',
(contador+1, amigo))
contantiguas = contantiguas + 1
except:
cur.execute('''INSERT INTO Twitter (nombre, recuperado, amigos)
VALUES (?, 0, 1)''', (amigo, ))
contnuevas = contnuevas + 1
print('Cuentas nuevas=', contnuevas, ' ya visitadas=', contantiguas)
conn.commit()
Una vez que el cursor ejecuta la sentencia SELECT, tenemos que recuperar las filas.
Podríamos hacerlo con una sentencia for, pero dado que sólo estamos recuperando
una única fila (LIMIT 1), podemos también usar el método fetchone() para ex-
traer la primera (y única) fila que da como resultado la operación SELECT. Dado
que fetchone() devuelve la fila como una tupla (incluso si sólo contiene un campo),
tomamos el primer valor de la tupla mediante [0], para almacenar así dentro de la
variable contador el valor del contador de amigos actual.
Si esta operación tiene éxito, usamos la sentencia UPDATE de SQL con una clausula
WHERE para añadir 1 a la columa amigos de aquella fila que coincida con la cuenta
del amigo. Fíjate que hay dos marcadores de posición (es decir, signos de interro-
gación) en el SQL, y que el segundo parámetro de execute() es una tupla de dos
elementos que contiene los valores que serán sustituidos por esas interrogaciones
dentro de la sentencia SQL.
Si el código en el bloque try falla, se deberá probablemente a que ningún registro
coincide con lo especificado en la clausula WHERE nombre = ? de la setencia SE-
LECT. Así que en el bloque except, usamos la sentencia de SQL INSERT para
añadir el nombre a mostrar (screen_name) del amigo a la tabla, junto con una
indicación de que no lo hemos recuperado aún, y fijamos su contador de amigos a
cero.
La primera vez que el programa funciona e introducimos una cuenta de Twitter,
mostrará algo similar a esto:
Dado que es la primera vez que ejecutamos el programa, la base de datos está vacía,
así que creamos el archivo arana.sqlite y añadimos una tabla llamada Twitter
a la base de datos. A continuación recuperamos algunos amigos y los añadimos a
la base de datos, ya que ésta está vacía.
En este punto, tal vez sea conveniente escribir un programa de volcado de datos
sencillo, para echar un vistazo a lo que hay dentro del archivo spider.sqlite:
208 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
import sqlite3
conn = sqlite3.connect('arana.sqlite')
cur = conn.cursor()
cur.execute('SELECT * FROM Twitter')
contador = 0
for fila in cur:
print(fila)
contador = contador + 1
print(contador, 'filas.')
cur.close()
# Code: http://www.py4e.com/code3/twdump.py
Este programa simplemente abre la base de datos y selecciona todas las columnas
de todas las filas de la tabla Twitter, luego se mueve a través de las filas e imprime
en pantalla su contenido.
Si ejecutamos este programa después de la primera ejecución de nuestra araña de
Twitter, la salida que mostrará será similar a esta:
('opencontent', 0, 1)
('lhawthorn', 0, 1)
('steve_coppin', 0, 1)
('davidkocher', 0, 1)
('hrheingold', 0, 1)
...
20 filas.
Vemos una fila para cada nombre, que aún no hemos recuperado los datos de
ninguno de esos nombres, y que todo el mundo en la base de datos tiene un amigo.
En este momento la base de datos muestra la recuperación de los amigos de nuestra
primera cuenta de Twitter (drchuck). Podemos ejecutar de nuevo el programa y
pedirle que recupere los amigos de la siguiente cuenta “sin procesar”, simplemente
pulsando intro en vez de escribir el nombre de una cuenta:
Como hemos pulsado intro (es decir, no hemos especificado otra cuenta de Twitter),
se ha ejecutado el código siguiente:
if ( len(cuenta) < 1 ) :
cur.execute('SELECT nombre FROM Twitter WHERE recuperado = 0 LIMIT 1')
try:
14.18. RASTREO EN TWITTER USANDO UNA BASE DE DATOS 209
cuenta = cur.fetchone()[0]
except:
print('No se han encontrado cuentas de Twitter por recuperar')
continue
Usamos la sentencia de SQL SELECT para obtener el nombre del primer usuario
(LIMIT 1) que aún tiene su valor de “hemos recuperado ya este usuario” a cero.
También usamos el patrón fetchone()[0] en un bloque try/except para extraer
el “nombre a mostrar” (screen_name) de los datos recuperados, o bien mostrar un
mensaje de error y volver al principio.
Si hemos obtenido con éxito el nombre de una cuenta que aún no había sido
procesada, recuperamos sus datos de este modo:
Una vez recuperados correctamente los datos, usamos la sentencia UPDATE para
poner la columna recuperado a 1, lo que indica que hemos terminado la extracción
de amigos de esa cuenta. Esto impide que recuperemos los mismos datos una y
otra vez, y nos permite ir avanzando a través de la red de amigos de Twitter.
Si ejecutamos el programa de amigos y pulsamos intro dos veces para recuperar los
amigos del siguiente amigo no visitado, y luego ejecutamos de nuevo el programa
de volcado de datos, nos mostrará la salida siguiente:
('opencontent', 1, 1)
('lhawthorn', 1, 1)
('steve_coppin', 0, 1)
('davidkocher', 0, 1)
('hrheingold', 0, 1)
...
('cnxorg', 0, 2)
('knoop', 0, 1)
('kthanos', 0, 2)
('LectureTools', 0, 1)
...
55 filas.
Podemos ver que se han guardado correctamente las visitas que hemos realizado a
lhawthorn y opencontent. Además, las cuentas cnxorg y kthanos ya tienen dos
seguidores. Puesto que hemos recuperado los amigos de tres personas (drchuck,
opencontent, y lhawthorn), la tabla contiene 55 filas de amigos por recuperar.
Cada vez que ejecutamos el programa y pulsamos intro, se elegirá la siguiente
cuenta no visitada (es decir, ahora la siguiente cuenta sería steve_coppin), recu-
perará sus amigos, los marcará como recuperados y, para cada uno de los amigos
210 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
Cada vez que encontremos una persona de las que está siguiendo drchuck, in-
sertaremos una fila de esta forma:
Conforma vayamos procesando los 20 amigos de drchuck que nos envía Twitter,
insertaremos 20 registros con “drchuck” como primer parámetro, de modo que
terminaremos duplicando la cadena un montón de veces en la base de datos.
Esta duplicación de cadenas de datos viola una de las mejores prácticas para la
normalización de bases de datos, que básicamente consiste en que nunca se debe
guardar la misma cadena más de una vez en la base de datos. Si se necesitan los
datos varias veces, se debe crear una clave numérica para ellos y hacer referencia
a los datos reales a través de esa clave.
En términos prácticos, una cadena ocupa un montón de espacio más que un entero,
tanto en el disco como en la memoria del equipo, y además necesita más tiempo de
procesador para ser comparada y ordenada. Si sólo se tienen unos pocos cientos de
14.20. PROGRAMACIÓN CON MÚLTIPLES TABLAS 211
Ahora vamos a rehacer de nuevo el programa araña de Twitter usando dos tablas,
las claves primarias, y las claves de referencia, como hemos descrito antes. He aquí
el código de la nueva versión del programa:
212 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
People
Follows
id name retrieved
from_id to_id
1 drchuck 1
1 2
2 opencontent 1
1 3
3 lhawthorn 1
1 4
4 steve_coppin 0
...
...
TWITTER_URL = 'https://api.twitter.com/1.1/friends/list.json'
conn = sqlite3.connect('amigos.sqlite')
cur = conn.cursor()
while True:
cuenta = input('Ingresa una cuenta de Twitter, o salir: ')
if (cuenta == 'salir'): break
if (len(cuenta) < 1):
cur.execute('SELECT id, nombre FROM Personas WHERE recuperado=0 LIMIT 1')
try:
(id, cuenta) = cur.fetchone()
14.20. PROGRAMACIÓN CON MÚLTIPLES TABLAS 213
except:
print('No se han encontrado cuentas de Twitter sin recuperar')
continue
else:
cur.execute('SELECT id FROM Personas WHERE nombre = ? LIMIT 1',
(cuenta, ))
try:
id = cur.fetchone()[0]
except:
cur.execute('''INSERT OR IGNORE INTO Personas
(nombre, recuperado) VALUES (?, 0)''', (cuenta, ))
conn.commit()
if cur.rowcount != 1:
print('Error insertando cuenta:', cuenta)
continue
id = cur.lastrowid
datos = conexion.read().decode()
cabeceras = dict(conexion.getheaders())
print('Restantes', cabeceras['x-rate-limit-remaining'])
try:
js = json.loads(datos)
except:
print('Fallo al analizar json')
print(datos)
break
# Depuración
# print(json.dumps(js, indent=4))
contnuevas = 0
contantiguas = 0
for u in js['users']:
amigo = u['screen_name']
214 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
print(amigo)
cur.execute('SELECT id FROM Personas WHERE nombre = ? LIMIT 1',
(amigo, ))
try:
amigo_id = cur.fetchone()[0]
contantiguas = contantiguas + 1
except:
cur.execute('''INSERT OR IGNORE INTO Personas (nombre, recuperado)
VALUES (?, 0)''', (amigo, ))
conn.commit()
if cur.rowcount != 1:
print('Error inserting account:', amigo)
continue
amigo_id = cur.lastrowid
contnuevas = contnuevas + 1
cur.execute('''INSERT OR IGNORE INTO Seguimientos (desde_id, hacia_id)
VALUES (?, ?)''', (id, amigo_id))
print('Cuentas nuevas=', contnuevas, ' ya visitadas=', contantiguas)
print('Restantes', cabeceras['x-rate-limit-remaining'])
conn.commit()
cur.close()
# Code: http://www.py4e.com/code3/twfriends.py
Este programa empieza a resultar un poco complicado, pero ilustra los patrones de
diseño que debemos usar cuando utilizamos claves de enteros para enlazar tablas.
Esos patrones básicos son:
Estamos indicando que la columna nombre de la tabla Personas debe ser UNIQUE
(única). Además indicamos que la combinación de los dos números de cada fila
de la tabla Seguimientos debe ser también única. Estas restricciones evitan que
cometamos errores como añadir la misma relación entre las mismas personas más
de una vez.
Después, podemos aprovechar estas restricciones en el código siguiente:
amigo = u['screen_name']
cur.execute('SELECT id FROM Personas WHERE nombre = ? LIMIT 1',
(amigo, ) )
try:
amigo_id = cur.fetchone()[0]
contantiguas = contantiguas + 1
except:
cur.execute('''INSERT OR IGNORE INTO Personas (nombre, recuperado)
VALUES ( ?, 0)''', ( amigo, ) )
conn.commit()
if cur.rowcount != 1 :
print('Error al insertar cuenta:',amigo)
continue
amigo_id = cur.lastrowid
contnuevas = contnuevas + 1
Una vez que sabemos el valor de la clave tanto para el usuario de Twitter como para
el amigo que hemos extraído del JSON, resulta sencillo insertar ambos números en
la tabla de Seguimientos con el código siguiente:
Nota que dejamos que sea la base de datos quien se ocupe de evitar la “inserción
duplicada” de una relación, mediante la creación de una tabla con una restricción
de unicidad, de modo que luego en nuestra sentencia INSERT tan sólo añadimos o
ignoramos.
Aquí está un ejemplo de la ejecución de este programa:
Personas:
(1, 'drchuck', 1)
(2, 'opencontent', 1)
(3, 'lhawthorn', 1)
(4, 'steve_coppin', 0)
(5, 'davidkocher', 0)
55 filas.
Seguimientos:
(1, 2)
(1, 3)
(1, 4)
(1, 5)
(1, 6)
60 filas.
Puedes ver los campos id, nombre, visitado de la tabla Personas, y también
los números de ambos extremos de la relación en la tabla Seguimientos. En la
tabla Personas, vemos que las primeras tres personas ya han sido visitadas y
que sus datos han sido recuperados. Los datos de la tabla Seguidores indican
que drchuck (usuario 1) es amigo de todas las personas que se muestran en las
primeras cinco filas. Esto tiene sentido, ya que los primeros datos que recuperamos
y almacenamos fueron los amigos de Twitter de drchuck. Si imprimieras más filas
de la tabla Seguimientos verías también los amigos de los usuarios 2 y 3.
• Una clave lógica es una clave que se podría usar en el “mundo real” para
localizar una fila. En nuestro ejemplo de modelado de datos, el campo nombre
es una clave lógica. Es el nombre que se muestra en pantalla para el usuario y,
en efecto, usamos el campo nombre varias veces en el programa para localizar
la fila correspondiente a un usuario. Comprobarás que a menudo tiene sentido
añadir una restricción UNIQUE (única) a una clave lógica. Como las claves
218 CHAPTER 14. PROGRAMACIÓN ORIENTADA A OBJETOS
lógicas son las que usamos para buscar una fila desde el mundo exterior,
tendría poco sentido permitir que hubiera múltiples filas con el mismo valor
en la tabla.
• Una clave primaria es normalmente un número que es asignado automáti-
camente por la base de datos. En general no tiene ningún significado fuera
del programa y sólo se utiliza para enlazar entre sí filas de tablas diferentes.
Cuando queremos buscar una fila en una tabla, realizar la búsqueda usando
la clave primaria es, normalmente, el modo más rápido de localizarla. Como
las claves primarias son números enteros, necesitan muy poco espacio de al-
macenamiento y pueden ser comparadas y ordenadas muy rápido. En nuestro
modelo de datos, el campo id es un ejemplo de una clave primaria.
• Una clave foránea (foreign key) es normalmente un número que apunta a la
clave primaria de una fila asociada en una tabla diferente. Un ejemplo de
una clave foránea en nuestro modelo de datos es la columna desde_id.
Estamos usando como convención para los nombres el darle siempre al campo de
clave primaria el nombre id y añadir el sufijo _id a cualquier nombre de campo
que sea una clave foránea.
La clausula JOIN indica que los campos que estamos seleccionando mezclan las
tablas Seguimientos y Personas. La clausula ON indica cómo deben ser unidas
las dos tablas: Toma cada fila de Seguimientos y añade una fila de Personas en
la cual el campo desde_id en Seguimientos coincide con el valor id en la tabla
Personas.
El resultado del JOIN consiste en la creación de una “meta-fila” extra larga,
que contendrá tanto los campos de Personas como los campos de la fila de
Seguimientos que cumplan la condición. Cuando hay más de una coincidencia
entre el campo id de Personas y el desde_id de Seguimientos, JOIN creará una
meta-fila para cada una de las parejas de filas que coincidan, duplicando los datos
si es necesario.
El código siguiente muestra los datos que tendremos en la base de datos después de
que el programa multi-tabla araña de Twitter anterior haya sido ejecutado varias
veces.
14.22. USO DE JOIN PARA RECUPERAR DATOS 219
People
Follows
id name retrieved
from_id to_id
1 drchuck 1
1 2
2 opencontent 1
1 3
3 lhawthorn 1 1 4
4 steve_coppin 0 ...
...
import sqlite3
conn = sqlite3.connect('amigos.sqlite')
cur = conn.cursor()
cur.close()
# Code: http://www.py4e.com/code3/twjoin.py
python twjoin.py
Personas:
(1, 'drchuck', 1)
(2, 'opencontent', 1)
(3, 'lhawthorn', 1)
(4, 'steve_coppin', 0)
(5, 'davidkocher', 0)
55 filas.
Seguimientos:
(1, 2)
(1, 3)
(1, 4)
(1, 5)
(1, 6)
60 filas.
Conexiones para id=2:
(2, 1, 1, 'drchuck', 1)
(2, 28, 28, 'cnxorg', 0)
(2, 30, 30, 'kthanos', 0)
(2, 102, 102, 'SomethingGirl', 0)
(2, 103, 103, 'ja_Pac', 0)
20 filas.
Se pueden ver las columnas de las tablas Personas y Seguimientos, seguidos del
último conjunto de filas, que es el resultado del SELECT con la clausula JOIN.
En el último select, buscamos las cuentas que sean amigas de “opencontent” (es
decir, de Personas.id=2).
En cada una de las “meta-filas” del último select, las primeras dos columnas
pertenecen a la tabla Seguimientos, mientras que las columnas tres a cinco
pertenecen a la tabla Personas. Se puede observar también cómo la segunda
columna (Seguimientos.hacia_id) coincide con la tercera (Personas.id) en
cada una de las “meta-filas” unidas.
14.23. RESUMEN 221
14.23 Resumen
En este capítulo se han tratado un montón de temas para darte una visión de de lo
necesario para utilizar una base de datos en Python. Es más complicado escribir el
código para usar una base de datos que almacene los datos que utilizar diccionarios
de Python o archivos planos, de modo que existen pocas razones para usar una
base de datos, a menos que tu aplicación necesite de verdad las capacidades que
proporciona. Las situaciones en las cuales una base de datos pueden resultar bas-
tante útil son: (1) cuanto tu aplicación necesita realizar muchos cambios pequeños
de forma aleatoria en un conjunto de datos grandes, (2) cuando tienes tantos datos
que no caben en un diccionario y necesitas localizar información con frecuencia, o
(3) cuando tienes un proceso que va a funcionar durante mucho tiempo, y nece-
sitas poder detenerlo y volverlo a poner en marcha, conservando los datos entre
ejecuciones.
Una base de datos con una simple tabla puede resultar suficiente para cubrir las
necesidades de muchas aplicaciones, pero la mayoría de los problemas necesitarán
varias tablas y enlaces/relaciones entre filas de tablas diferentes. Cuando empieces
a crear enlaces entre tablas, es importante realizar un diseño meditado y seguir
las reglas de normalización de bases de datos, para conseguir el mejor uso de sus
capacidades. Como la motivación principal para usar una base de datos suele ser el
tener grandes cantidades de datos con las que tratar, resulta importante modelar
los datos de forma eficiente, para que tu programa funcione tan rápidamente como
sea posible.
14.24 Depuración
Debes tener cuidado, ya que SQLite se encarga de evitar que dos programas puedan
cambiar los mismos datos a la vez. Por ejemplo, si abres una base de datos en
el navegador y realizas un cambio en la base de datos, pero no has pulsado aún
el botón “guardar” del navegador, éste “bloqueará” el archivo de la base de datos
y evitará que cualquier otro programa acceda a dicho fichero. Concretamente, en
ese caso tu programa de Python no será capaz de acceder al archivo, ya que éste
se encontrará bloqueado.
De modo que la solución pasa por asegurarse de cerrar la ventana del navegador
de la base de datos, o bien usar el menú Archivo para cerrar la base de datos
abierta en el navegador antes de intentar acceder a ella desde Python, para evitar
encontrarse con el problema de que el código de Python falle debido a que la base
de datos está bloqueada.