SlideShare una empresa de Scribd logo
Aprenda a Pensar Como un
Programador
con Python
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Aprenda a Pensar Como un
Programador
con Python
Allen
Downey
Jeffrey Elkner
Chris Meyers
Traducido
por Miguel A´ngel
Vilella
A´ngel Arnal
Iv´an
Juanes Litza
Amurrio Efrain
Andia C´esar
Ballardini
Green Tea Press
Wellesley, Massachusetts
Copyright
Ⓧc
2002 Allen Downey, Jeffrey Elkner, y Chris Meyers.
Corregido por Shannon Turlington y Lisa Cutler.
Disen˜o de la cubierta por Rebecca Gimenez.
Green Tea
Press 1 Grove
St.
P.O. Box 812901
Wellesley, MA 02482
Se permite copiar, distribuir, y/o modificar este documento bajo los
t´erminos de la GNU Free Documentation License, Versio´n 1.1 o
cualquier versio´n posterior publicada por la Free Software
Foundation; siendo las Secciones Invariantes “Pr´ologo”,
“Prefacio”, y “Lista de Colaboradores”, sin texto de cubierta, y
sin texto de contracubierta. Se incluye una copia de la licencia en
el ap´endice titulado “GNU Free Documentation License”.
La GNU Free Documentation License esta´ disponible en
www.gnu.org o escri- biendo a la Free Software Foundation, Inc., 59
Temple Place, Suite 330, Boston, MA 02111-1307, USA.
La forma original de este libro es c´odigo fuente LATEX. La
compilacio´n de este fuente LATEX tiene el efecto de generar una
representaci´on independiente del dispositivo de un libro de texto,
que puede convertirse a otros formatos e im- primirse.
El fuente LATEX de este libro y m´as informaci´on sobre el
proyecto de Libro de Texto de C´odigo Abierto esta´n disponibles
en http://www.thinkpython.com
La composici´on de este libro se realizo´ utilizando LATEX y LyX.
Las ilustraciones se hicieron con xfig. Todos ellos son programas
gratuitos de c´odigo abierto.
Historia de la impresi´on:
Abril 2002: Primera
edicio´n. ISBN 0-
9716775-0-6
Pr´ologo
Por David Beazley
Como educador, investigador, y autor de libros, estoy encantado de
ver la fi- nalizaci´on de este libro. Python es un lenguaje de
programaci´on divertido y extremadamente f´acil de usar que en
los u´ltimos an˜os se ha hecho muy popu- lar. Desarrollado hace
diez an˜os por Guido van Rossum, su sintaxis simple y la sensacio´n
general se deriva en gran parte del ABC, un lenguaje desarrollado
en los 1980s para la ensen˜anza. Sin embargo, Python tambi´en se
cre´o para resolver problemas reales y presenta una variedad
amplia de caracter´ısticas de lenguajes de programaci´on como C+
+, Java, Modula-3 y Scheme. Debido a esto, una de las caracter
´ısticas notables de Python es su atractivo para los desarrolladores
profesionales de progamaci´on, cient´ıficos, investigadores, artistas,
y educadores. A pesar del atractivo de Python para muchas
comunidades diferentes, puede que au´n se pregunte “¿por qu´e
Python?” o “¿por qu´e ensen˜ar programaci´on con Python?”No es
tarea f´acil responder a estas preguntas, en especial cuando la
opini´on popular esta´ del lado de alternativas mas masoquistas
como C++ y Java. Sin embargo, pienso que la respuesta mas
directa es que la progrmaci´on en Python es simplemente m´as
divertida y mas productiva.
Cuando imparto cursos de inform´atica, quiero cubrir conceptos
importantes, hacer el material interesante y enganchar a los
estudiantes. Desgraciadamente, hay una tendencia en los cursos
de introducci´on a la programaci´on a prestar demasiada atenci
´on a la abstracci´on matem´atica que hace que los estudiantes se
frustren con problemas farragosos relacionados con detalles
nimios de la sin- taxis, compilacio´n, y la aplicaci´on de reglas
aparentemente arcanas. Aunque tal abstraci´on y formalismo son
importantes para ingenieros profesionales de la programaci´on y
estudiantes que planean continuar sus estudios de inform´atica,
decidirse por este enfoque en un curso introductorio s´olo tiene
´exito en hacer aburrida la inform´atica. Cuando imparto un curso,
no quiero tener un aula de estudiantes sin inspiraci´on. Quisiera
verlos intentando resolver problemas in- teresantes, explorando
ideas diferentes, probando enfoques no convencionales,
vi Pr´ologo
rompiendo las reglas, y aprendiendo de sus errores. Al hacerlo, no
quiero perder la mitad del semestre tratando de sortear problemas
con una sintaxis abstru- sa, mensajes de error del compilador
incomprensibles, o los varios cientos de maneras que un programa
puede generar un error de proteci´on general.
Una de las razones por las que me gusta Python es por que
proporciona un equi- librio muy bueno entre lo pr´actico y lo
conceptual. Puesto que Python es un lenguaje interpretado, los
principiantes pueden tomar el lenguaje y empezar a hacer cosas
interesantes casi inmediato, sin perderse el los problemas de
compila- cio´n y enlazado. Adem´as, Python viene con una gran
biblioteca de modulos que se pueden usar para hacer toda clase de
tareas que abarcan desde programaci´on para web a gr´aficos. Este
enfoque pr´actico es una buena manera de enganchar a estudiantes
y permite que completen proyectos significativos. Sin embargo,
Pyt- hon tambi´en puede servir como una base excelente para
intruducir conceptos importantes de inform´atica. Puesto que
Python soporta completamente proce- dimientos y clases, los
estudiantes pueden introducirse gradualmente en temas como
abstracci´an procedural, estructuras de datos, y programaci´on
orientada objetos, que son aplicables a cursos posteriores en Java o
C++. Python incluso toma prestada cierta cantidad de caracter
´ısticas de lenguajes de programaci´on funcionales y puede usarse
para intruducir conceptos que pudieran ser cubiertos en mas
detalle en cursos de Scheme o Lisp.
Leendo, el prefacio de Jeffrey, me sorprenden sus comentarios
sobre que Pyt- hon le permite ver un “m´as alto nivel de ´exito y
un bajo nivel de frustraci´on que puede “avanzar r´apido con
mejores resultados”. Aunque estos comentarios se refieren a sus
cursos introductorios, a veces uso Python por estas mismas
razones en cursos de inform´atica avanzada en la Universidad de
Chicago. En estos cursos me enfrento constantemente con la
desalentadora tarea de cubrir un mont´on de material dif´ıcil en un
agotador trimestre de nueve semanas. Aun- que es ciertamente
posible para m´ı infligir mucho dolor y sufrimiento usando un
lenguaje como C++, he visto a menudo que ese estilo es ineficaz,
especialmente cuando el curso se trata de un asunto sin relacio´n
apenas con la “programaci´on”. Encuentro que usar Python me
permite dedicarme mas al asunto en cuestio´n mientras permito a
los estudiantes completar proyectos u´tiles.
Aunque Python es todav´ıa un lenguaje joven y en desarollo, creo
que tiene un futuro brillante en la educacio´n. Este libro es un
paso importante en esa direcci´on.
David Beazley
Universidad de
Chicago
Autor de Python Essential Reference
Prefacio
Por Jeff Elkner
Este libro debe su existencia a la colaboraci´on hecha posible por
la Internet y al movimiento de software libre. Sus tres autores, un
profesor universitario, un profesor de instituto y un programador
profesional, todav´ıa tienen que conocerse cara a cara, pero hemos
sido capaces de colaborar estrechamente y hemos reci- bido la
ayuda de mucha gente maravillosa que han donado su tiempo y
esfuerzo para ayudar a mejorar este libro.
Creemos que este libro es un testamento a los beneficios y futuras
posibilidades de este tipo de colaboraci´on, cuyo marco han
establecido Richard Stallman y la Free Software Foundation.
Co´mo y por qu´e vine a usar Python
En 1999, el examen de Advanced Placement (AP) de Ciencias de la
Computa- cio´n del Claustro Escolar se realizo´ por primera vez en
C++. Como en muchos institutos en todo el pa´ıs, la decisi´on de
cambiar de lenguaje tuvo un impacto directo sobre el curriculum
de inform´atica en el Insituto de Yorktown en Ar- lington, Virgina,
donde doy clase. Hasta ese momento, el lenguaje de ensen˜anza
era Pascal tanto en nuestro curso de primer an˜o como en el AP. Al
seguir con la pr´actica anterior de dar a los estudiantes dos an˜os
de exposici´on al mismo lenguaje, tomamos la decisi´on de cambiar
a C++ en el aula de primer an˜o del curso 1997-98 de modo que
estar´ıamos en sinton´ıa con el cambio del Claustro Escolar para el
curso AP del an˜o siguiente.
Dos an˜os m´as tarde, me convenc´ı de que C++ era una mala
eleccio´n para iniciar a los estudiantes en la inform´atica. Aunque
es un lenguaje de programaci´on muy poderoso, tambi´en es
extremadamente dif´ıcil de aprender y ensen˜ar. Me encontr´e
luchando constantemente con la dif´ıcil sintaxis de C++ y sus mu
´ltiples formas de hacer las cosas, y como consecuencia perd´ıa
muchos estudiantes sin
Prefacio
vii
i
necesidad. Convencido de que deb´ıa de haber una eleccio´n mejor
para el lenguaje de nuestro curso de primer an˜o, me puse a buscar
una alternativa para C++. Necesitaba un lenguaje que funcionase
tanto en las maquinas de nuestro labo- ratorio de Linux como en
las plataformas Windows y Macintosh que la mayor´ıa de los
estudiantes ten´ıan en casa. Quer´ıa que fuera de c´odigo abierto,
para que los estudiantes pudieran usarlo en casa sin importar su
nivel econo´mico. Quer´ıa un lenguaje utilizado por programadores
profesionales, y que tuviera una co- munidad activa de
desarrolladores a su alrededor. Ten´ıa que soportar tanto la
programaci´on procedural como la orientada a objetos. Y lo mas
importante,
ten´ıa que ser f´acil de aprender y de ensen˜ar. Cuando investigu´e las
opciones con
estos obejetivos en mente, Python destac´o como el mejor candidato.
Ped´ı a uno de los estudiantes m´as talentosos de Yorktown, Matt
Ahrens, que probase Python. En dos meses, no s´olo hab´ıa
aprendido el lenguaje, sino que escribio´ una aplicaci´on llamada
pyTicket que permit´ıa a nuestro personal infor- mar de problemas
tecnol´ogicos via Web. Sab´ıa que Matt no pod´ıa terminar una
aplicaci´on de tal escala en tan poco tiempo con C++, y este logro,
combinado con la positiva valoraci´on de Python por parte de Matt,
suger´ıa que Python era la solucio´n que buscaba.
Encontrar un libro de texto
Una vez decidido a usar Python tanto en mis clases de inform´atica
basica como en el an˜o siguiente, el problema m´as acuciante era la
falta de un libro de texto disponible.
El contenido libre vino al rescate. Anteriormente en ese an˜o,
Richard Stallman me present´o a Allen Downey. Ambos hab´ıamos
escrito a Richard expresando nuestro inter´es en desarrollar
conenidos educativos libres. Allen ya hab´ıa escrito un libro de
texto de inform´atica de primer an˜o, How to Think Like a Com- puter
Scientist. Cuando le´ı ese libro, supe inmediatamente que quer´ıa
usarlo en mi clase. Era el libro de inform´atica mas claro y pr
´actico que hab´ıa visto. Pon´ıa el ´enfasis en los procesos de
pensamiento involucrados en la programaci´on m´as que en las
caracter´ısticas de un lenguaje en particular. Su lectura me hizo
inmediatamente un maestro mejor.
How to Think Like a Computer Scientist no era s´olo un libro excelente,
sino que se public´o bajo la licencia pu´blica GNU, lo que
significaba que pod´ıa usarse y modificarse libremente para
ajustarse a las necesidades de su usuario. Una vez que decid´ı usar
Python, se me ocurrio´ que podr´ıa traducir la versio´n original en
Java del libro de Allen al nuevo lenguaje. Aunque no hubiera sido
capaz de escribir un libro de texto por mi cuenta, tener el libro de
Allen para trabajar a
i
partir de ´el me hizo posible hacerlo, mostrando al mismo tiempo
que el modelo cooperativo de desarrollo que tan buenos resultados
hab´ıa dado en el software pod´ıa funcionar tambi´en para el
contenido educativo.
El trabajo en este libro durante los dos u´ltimos an˜os ha sido
gratificante para mis estudiantes y para m´ı, y mis estudiantes
desempen˜aron un importante papel en el proceso. Como pod´ıa
hacer cambios instantaneos cuando alguien encontraba un error
ortogr´afico o un pasaje dif´ıcil, los anim´e a buscar errores en el
libro d´andoles un punto extra cada vez que hac´ıan una
sugerencia que terminaba como un cambio en el texto. Esto tuvo el
doble beneficio de animarlos a leer el texto con mas atenci´on y
tener el texto revisado en profundidad por sus cr´ıticos m´as
importantes: los estudiantes que lo usan para aprender inform
´atica.
Para la segunda mitad del libro, acerca de la programaci´on
orientada a objetos, sab´ıa que necesitar´ıa a alguien con m´as
experiencia real en programaci´on de la que yo ten´ıa para
hacerlo bien. El libro se estanco´ en un estado inacabado durante
buena parte de un an˜o hasta que la comunidad de c´odigo
abierto de nuevo proporcion´o los medios necesarios para su
terminaci´on.
Recib´ı un correo electro´nico de Chris Meyers expresando su
inter´es en el li- bro. Chris es un programador profesional que
empez´o a impartir un curso de programaci´on con Python el
an˜o pasado en el Colegio de Lane Community, en Eugene,
Oregon. La perspectiva de impartir el curso llev´o a Chris
has- ta el libro, y empez´o a colaborar con ´el inmediatamente.
Hacia el final del an˜o escolar hab´ıa creado un proyecto
complementario en nuesto sitio web en http://www.ibiblio.org/obp
llamado Python for Fun y estaba trabajando con algunos de mis
estudiantes aventajados como profesor magistral, dirigi´endo- les m
´as all´a de donde yo pod´ıa llevarles.
Presentando la programaci´on con Python
El proceso de traducir y usar How to Think Like a Computer Scientist
duran- te los dos u´ltimos an˜os ha confirmado la idoneidad de
Python para ensen˜ar a estudiantes principiantes. Python
simplifica enormemente los ejemplos de pro- gramaci´on y facilita
la ensen˜anza de los conceptos importantes en programaci´on.
{ }
x Prefacio
El primer ejemplo del texto ilustra esta cuestio´n. Es el tradicional
programa “hola, mundo”, que en la versio´n C++ del libro es as´ı:
#include <iostream.h>
void main()
{
cout << "Hola, mundo" << endl;
}
en la versio´n Python se convierte en:
print "Hola, Mundo"
Aunque es un ejemplo trivial, destacan las ventajas de Python. El
curso de Informa´tica I en Yorktown no tiene prerrequisitos, as´ı
que muchos de los estu- diantes que ven este ejemplo esta´n
mirando su primer programa. Algunos de ellos esta´n sin duda un
poco nerviosos, tras haber o´ıdo que programar compu- tadores es
algo dif´ıcil de aprender. La versio´n C++ siempre me ha obligado
a elegir entre dos opciones insatisfactorias: explicar las sentencias
#include, void main(), , y y arriesgarme a confundir o intimidar a
algunos estudiantes desde el principio, o decirles “No te preocupes
de todo eso ahora, hablaremos de ello m´as tarde”, y arriesgarme a
lo mismo. Los objetivos educativos en este momento del curso son
exponer a los estudiantes a la idea de una sentencia de progra-
maci´on y llevarles a escribir su primer programa, presentandoles
de esta forma el entorno de programaci´on. La programaci´on con
Python tiene exactamente lo que necesito para hacer estas cosas, y
nada mas.
La comparacio´n del texto explicativo de este programa para cada
versio´n del libro ilustra mejor lo que esto significa para los
estudiantes principiantes. Hay trece p´arrafos de explicacio´n de
“¡Hola, mundo!” en la versio´n C++. En la versio´n Python s´olo
hay dos. Au´n m´as importante: los once parrafos que faltan no
tocan las “grandes ideas” de la programaci´on de computadores,
sino las minucias de la sintaxis de C++. Encontr´e que esto mismo
suced´ıa por todo el libro. P´arrafos enteros desapareciendo de la
versio´n Python del texto porque la sintaxis clara de Python los
hace innecesarios.
El uso de un lenguaje de muy alto nivel como Python permite que
el profesor deje para m´as tarde hablar sobre los detalles de bajo
nivel de la maquina hasta que los estudiantes tengan el fondo
necesario para entender los detalles. De este modo crea la
habilidad de poner pedago´gicamente “antes lo primero”. Uno de
los mejores ejemplos de ello es la manera en la cual Python maneja
las variables. En C++ una variable es un nombre para un lugar que
contiene una cosa. Las variables deben declararse segu´n su tipo
en parte porque el taman˜o del lugar al que apuntan tiene que
determinarse de antemano. As´ı, la idea de una variable esta´ ligada
al hardware de la m´aquina. El concepto poderoso y fundamental
de
x
lo que es una variable ya es suficientemente dif´ıcil para estudiantes
principiantes (tanto de inform´atica como de a´lgebra). Octetos y
direcciones no ayudan a la comprensio´n. En Python una variable
es un nombre que sen˜ala una cosa. Este es un concepto mucho m
´as intuitivo para estudiantes principiantes y esta´ mas cerca del
significado de “variable” que aprendieron en su clase de matem
´aticas. Este an˜o tuve muchas menos dificultades ensen˜ando lo
que son las variables que en el anterior, y pas´e menos tiempo
ayud´andoles con los problemas derivados de su uso.
Otro ejemplo de c´omo Python ayuda en la ensen˜anza y
aprendizaje de la pro- gramaci´on es en su sintaxis para las
funciones. Mis estudiantes siempre han tenido una gran dificultad
comprendiendo las funciones. El problema principal se centra
alrededor de la diferencia entre la definici´on de una funci´on y la
llama- da a una funci´on, y la distinci´on asociada entre un par
´ametro y un argumento. Python viene al rescate con una sintaxis a
la que no le falta belleza. La defini- cio´n de una funci´on empieza
con la palabra clave def, y simplemente digo a mis estudiantes:
“cuando definas una funci´on, empieza con def, seguido del nombre
de la funci´on que est´es definiendo; cuando llames a una funci´on,
simplemente di (escribe) su nombre”. Los par´ametros van con las
definiciones; los argumentos con las llamadas. No hay tipo de
retorno, tipos de par´ametros, o par´ametro por referencia y valor
de por medio, por lo que ahora soy capaz de ensen˜ar funciones en
la mitad de tiempo que antes, con mejor comprensio´n.
El uso de Python ha mejorado la eficacia de nuestro programa de
inform´atica para todos los estudiantes. Veo un mayor nivel
general de ´exito y un menor nivel de frustraci´on del que
experiment´e durante los dos an˜os que ensen˜´e C++. Avanzo m
´as r´apido con mejores resultados. Mas estudiantes terminan el
curso con la habilidad de crear programas u´tiles y con la actitud
positiva hacia la experiencia de programaci´on que esto engendra.
Formar una comunidad
He recibido correos electro´nicos de todos los rincones del
planeta de parte de gente que usa este libro para aprender o
enesen˜ar a programar. Ha em- pezando a surgir una comunidad
de usuarios, y muchas personas han contri- buido al proyecto
mandando materiales a trav´es del sitio web complementario
http://www.thinkpython.com.
Con la publicaci´on de este libro en forma impresa, espero que
continue y se acelere el crecimiento de la comunidad de usuarios.
La emergencia de esta co- munidad de usuarios y la posibilidad
que sugiere para colaboraciones similares entre educadores han
sido para m´ı las partes mas excitantes de trabajar en este
proyecto. Trabajando juntos, podemos incrementar la calidad de
los materiales
xii Prefacio
disponibles para nuestro uso y ahorrar un tiempo valioso. Les
invito a unirse a nuestra comunidad y espero con impaciencia
saber algo de ustedes. Por favor, escriban a los autores a
feedback@thinkpython.com.
Jeffrey Elkner
Escuela Secundaria Yortown
Arlington, Virginia
Lista de Colaboradores
Parafraseando la filosof´ıa de la Free Software Foundation, este libro
es libre como la libre expresio´n, pero no necesariamente gratis
como la pizza gratis. Se hizo realidad a causa de una colaboraci´on
que no habr´ıa sido posible sin la GNU Free Documentation
License. As´ı que queremos agradecer a la Free Software
Foundation por desarrollar esta licencia y, por supuesto, ponerla a
nuestra disposici´on.
Tambi´en nos gustar´ıa dar las gracias a los mas de cien lectores
de aguda vista que se han preocupado de enviarnos sugerencias y
correcciones en los dos u´lti- mos an˜os. Siguiendo el esp´ıritu del
software libre, decidimos expresar nuestra gratitud en la forma de
una lista de colaboradores. Desgraciadamente, esta listo no esta´
completa, pero hacemos lo que podemos para mantenerla
actualizada. Si se toma el tiempo de echar un vistazo a la lista, ver
´a que cada una de las personas que aparecen le ha ahorrado a
usted y a los lectores que le sucedan la confusio´n de un error t
´ecnico o una explicacio´n poco clara simplemente en- vi´andonos
una nota.
Pos imposible que parezca tras tantas correcciones, todav´ıa
puede haber errores en el libro. Si se encontrara con una,
esperamos que se tome un minuto para ponerse en contacto con
nosotros. La direcci´on de correo es feedback@thinkpython.com. Si
cambiamos algo a partir de su sugerencia, apa- recer´a en la
siguiente versio´n de la lista de colaboradores (a no ser que pida
quedar omitido). ¡Gracias!
Lloyd Hugh Allen envi´o una correccio´n de la Secci´on 8.4.
Yvon Boulianne envi´o una correccio´n de un error sema´ntico
en el Cap´ıtulo 5.
Fred Bremmer comunic´o una correccio´n de la Secci´on 2.1.
Jonah Cohen escribio´ los scripts en Perl para convertir la
fuente LATEX del libro en hermoso HTML.
Lista de
x
Michael Conlon envi´o una correccio´n gramatical del Cap
´ıtulo 2 y una mejora del estilo del Cap´ıtulo 1, e inici´o una
discusi´on sobre aspectos t´ecnicos de los int´erpretes.
Benoit Girard envi´o una correccio´n de un divertido error de
la Secci´on 5.6. Courtney Gleason y Katherine Smith
escribieron horsebet.py, que se us´o como un caso de estudio
en una versio´n temprana del libro. Su pro- grama puede
encontrarse en el sitio web.
Lee Harr comunic´o m´as correcciones de las que tenemos
sitio para enume- rar aqu´ı, y de verdad deber´ıa aparecer
como uno de los principales editores del texto.
James Kaylin es un estudiante que us´o el
texto. Envio´ciones.
numerosas correc-
David Kershaw arregl´o la funci´on catTwice que no funcionaba
en la Sec- cio´n 3.10.
Eddie Lam ha enviado numerosas correcciones de los Cap
´ıtulos 1, 2 y 3. Tambi´en arregl´o el Makefile de forma que
crea un ´ındice la primera vez que se ejecuta y nos ayud´o a
preparar un esquema de versiones.
Man-Yong Lee envi´o una correccio´n del c´odigo de ejemplo
de la Secci´on 2.4.
David Mayo sen˜al´o que la palabra “unconscientemente”en el
Cap´ıtulo 1 deb´ıa cambiarse por “subconscientemente”.
Chris McAloon envi´o varias correciones de las Secciones 3.9 y
3.10. Matthew J. Moelter ha sido un colaborador durante
mucho tiempo y ha enviado numerosas correcciones y
sugerencias.
Simon Dicon Montford inform´o de una definici´on de funci
´on faltante y varios errores tipogr´aficos en el Cap´ıtulo 3.
Tambi´en encontr´o errores en la funci´on incrementa del Cap
´ıtulo 13.
John Ouzts corrigio´ la definici´on de “valor de retorno”del
Cap´ıtulo 3. Kevin Parks envi´o valiosos comentarios y
sugerencias acerca de c´omo me- jorar la distribuci´on del
libro.
David Pool envi´o un error tipogr´afico en el glosario del
Cap´ıtulo 1, y tambi´en amables palabras de a´nimo.
xv
Michael Schmitt envi´o una correccio´n del Cap´ıtulo sobre
archivos y excep- ciones.
Robin Shaw sen˜al´o un error en la Secci´on 13.1, donde la
funci´on impri- meHora se usaba en un ejemplo sin haberla
definido.
Paul Sleigh encontr´o un error en el Cap´ıtulo 7 y un error en
el script Perl de Jonah Cohen que, a partir de LATEX genera, el
HTML.
Craig T. Snydal esta´ poniendo a prueba el texto en un curso
en la Uni- versidad de Drew. Ha contribuido con varias
sugerencias y correcciones de importancia.
Ian Thomas y sus estudiantes usan el texto en un curso de
programaci´on. Son los primeros en probar los Cap´ıtulos de la
segunda mitad del libro, y han hecho numerosas correcciones
y sugerencias.
Keith Verheyden envi´o una correccio´n del Cap´ıtulo 3.
Peter Winstanley nos hizo saber de un persistente error en
nuestro lat´ın del Cap´ıtulo 3.
Chris Wrobel hizo correcciones al c´odigo del Cap´ıtulo sobre
E/S de archi- vos y excepciones.
Moshe Zadka ha hecho contribuciones inestimables al
proyecto. Adem´as de escribir el primer borrador del Cap´ıtulo
sobre diccionarios, propor- ciono´ una gu´ıa continuada en las
primeras etapas del libro.
Christoph Zwerschke envi´o varias correcciones y sugerencias
pedago´gicas, y explico´ la diferencia entre gleich y selbe.
James Mayer envi´o un cargamento de errores tipogr´aficos y
ortogr´aficos, incluyendo dos en la lista de colaboradores.
Hayden McAfee pill´o una inconsistencia potencialmente
confusa entre dos ejemplos.
A´ngel Arnal es parte de un equipo internacional de
traductores que tra- bajan en la versio´n en espan˜ol del
texto. Tambi´en ha encontrado varios errores en la versio´n
inglesa.
Tauhidul Hoque y Lex Berezhny crearon las ilustraciones del
Cap´ıtulo 1 y mejoraron muchas de las otras ilustraciones.
Dr. Michele Alzetta pill´o un error en el Cap´ıtulo 8 y envio´ varios
comen-
tarios y sugerencias pedago´gicas interesantes sobre Fibonacci y La
Mona.
Lista de
x
Andy Mitchell pill´o un error tipogr´afico en el Cap´ıtulo 1 y
un ejemplo err´oneo en el Cap´ıtulo 2.
Kalin Harvey sugirio´ una clarificacio´n al Cap´ıtulo 7 y detect´o varios
errores tipogr´aficos.
Christopher P. Smith encontr´o varios errores tipogr´aficos y
nos esta´ ayu- dando a preparar la actualizaci´on del libro para
Python 2.2.
David Hutchins pill´o un error tipogr´afico en el Pr´ologo.
Gregor Lingl ensen˜a Python en un instituto de Viena, Austria. Est´a
tra-
bajando en una traducci´on del libro al alem
´an, y pillo´ graves en el Cap´ıtulo 5.
un par de errores
Julie Peters encontr´o un error tipogr´afico en el Prefacio.
´Indice general
Pr´ologo
Prefacio
Lista de Colaboradores
1. El Camino del Programa
v
vii
xiii
1
1.1. El lenguaje de programaci´on Python . . . . . . . . . . . . .
. .
1
1.2. ¿Qu´e es un programa? . . . . . . . . . . . . . . . . . . . . . .
.
3
1.3. ¿Qu´e es la depuraci´on (debugging)? . . . . . . . . . . . . .
. . .
4
1.4. Lenguajes formales y lenguajes naturales . . . . . . . . . .
. . .
6
1.5. El primer programa . . . . . . . . . . . . . . . . . . . . . . . .
.
8
1.6. Glosario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9
2. Variables, expresiones y sentencias 11
2.1. Valores y tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.2. Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
2.3. Nombres de variables y palabras reservadas . . . . . . . . .
. .
13
2.4. Sentencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
2.5. Evaluar expresiones . . . . . . . . . . . . . . . . . . . . . . . .
.
15
Lista de
x
2.6. Operadores y expresiones . . . . . . . . . . . . . . . . . . . .
.
16
´Indice
x
2.7. El orden de las operaciones..............................................17
2.8. Las operaciones sobre cadenas........................................17
2.9. Composici´on .....................................................................................18
2.10. Los comentarios................................................................19
2.11. Glosario.............................................................................19
3. Funciones 21
3.1. Llamadas a funciones.......................................................21
3.2. Conversio´n de tipos .......................................................................22
3.3. Coercio´n de tipos ............................................................................22
3.4. Funciones matem´aticas ................................................................23
3.5. Composici´on .....................................................................................24
3.6. An˜adir funciones nuevas ..............................................................24
3.7. Las definiciones y el uso...................................................26
3.8. Flujo de ejecucio´n ..........................................................................27
3.9. Par´ametros y argumentos ...........................................................28
3.10. Las variables y los par´ametros son locales ..........................29
3.11. Diagramas de pila.............................................................30
3.12. Funciones con resultado...................................................31
3.13. Glosario.............................................................................32
4. Condicionales y recursividad 35
4.1. El operador m´odulo .......................................................................35
4.2. Expresiones booleanas.....................................................36
4.3. Operadores l´ogicos ........................................................................36
4.4. Ejecuci´on condicional ...................................................................37
4.5. Ejecuci´on alternativa .....................................................................37
4.6. Condiciones encadenadas.................................................38
´Indice xi
4.7. Condiciones anidadas.......................................................39
4.8. La sentencia return................................................................40
4.9. Recursividad.....................................................................40
4.10. Diagramas de pila para funciones recursivas..................42
4.11. Recursividad infinita.........................................................43
4.12. Entrada por teclado..........................................................44
4.13. Glosario.............................................................................45
5. Funciones productivas 47
5.1. Valores de retorno.............................................................47
5.2. Desarrollo de programas..................................................48
5.3. Composici´on .....................................................................................51
5.4. Funciones booleanas.........................................................52
5.5. Mas recursividad..............................................................53
5.6. Acto de fe..........................................................................55
5.7. Un ejemplo m´as ...............................................................................56
5.8. Comprobaci´on de tipos .................................................................57
5.9. Glosario.............................................................................58
6. Iteracio´n 61
6.1. Asignaci´on mu´ltiple .....................................................................61
6.2. La sentencia while.................................................................62
6.3. Tablas................................................................................64
6.4. Tablas de dos dimensiones...............................................66
6.5. Encapsulado y generalizaci´on ....................................................67
6.6. Mas encapsulacio´n .........................................................................68
6.7. Variables locales...............................................................69
6.8. Mas generalizaci´on ........................................................................70
6.9. Funciones..........................................................................71
6.10. Glosario.............................................................................72
´Indice
x
7. Cadenas 75
7.1. Un tipo de datos compuesto . . . . . . . . . . . . . . . . . . . . 75
7.2. Longitud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76
7.3. Recorrido y el bucle for . . . . . . . . . . . . . . . . . . . . . . 76
7.4. Porciones de cadenas . . . . . . . . . . . . . . . . . . . . . . . . 78
7.5. Comparaci´on de cadenas . . . . . . . . . . . . . . . . . . .
. . .
78
7.6. Las cadenas son inmutables . . . . . . . . . . . . . . . . . . . . 79
7.7. Una funci´on “encuentra” . . . . . . . . . . . . . . . . . . .
. . .
80
7.8. Bucles y conteo . . . . . . . . . . . . . . . . . . . . . . . . . . . 80
7.9. El m´odulo “string” . . . . . . . . . . . . . . . . . . . . . .
. . .
81
7.10. Clasificaci´on de caracteres . . . . . . . . . . . . . . . . .
. . . .
82
7.11. Glosario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83
8. Listas 85
8.1. Valores de una lista . . . . . . . . . . . . . . . . . . . . . . . . . 85
8.2. Acceso a los elementos . . . . . . . . . . . . . . . . . . . . . . . 86
8.3. Longitud (taman˜o) de una lista . . . . . . . . . . . . . . .
. . .
87
8.4. Pertenencia a una lista . . . . . . . . . . . . . . . . . . . . . . . 88
8.5. Listas y bucles for . . . . . . . . . . . . . . . . . . . . . . . . . 88
8.6. Operaciones con listas . . . . . . . . . . . . . . . . . . . . . . . 89
8.7. Porciones (slices) . . . . . . . . . . . . . . . . . . . . . . . . . . 90
8.8. Las listas son mutables . . . . . . . . . . . . . . . . . . . . . . . 90
8.9. Borrado en una lista . . . . . . . . . . . . . . . . . . . . . . . . 91
8.10. Objetos y valores . . . . . . . . . . . . . . . . . . . . . . . . . . 91
8.11. Alias (poner sobrenombres) . . . . . . . . . . . . . . . . . . . . 92
8.12. Clonar listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93
8.13. Listas como par´ameteros . . . . . . . . . . . . . . . . . . . 94
´Indice x
. . .
8.14. Listas anidadas . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
´Indice
x
8.15. Matrices............................................................................95
8.16. Cadenas y listas................................................................96
8.17. Glosario.............................................................................97
9. Tuplas 99
9.1. Mutabilidad y tuplas.........................................................99
9.2. Asignaci´on de tuplas ...................................................................100
9.3. Tuplas como valor de retorno.........................................101
9.4. Nu´meros aleatorios .....................................................................101
9.5. Lista de nu´meros aleatorios .....................................................102
9.6. Conteo.............................................................................103
9.7. Muchos baldes................................................................104
9.8. Una solucio´n en una sola pasada ...........................................106
9.9. Glosario...........................................................................107
10. Diccionarios 109
10.1. Operaciones sobre diccionarios......................................110
10.2. M´etodos del diccionario ............................................................111
10.3. Asignaci´on de alias y copiado ..................................................112
10.4. Matrices dispersas..........................................................112
10.5. Pistas...............................................................................113
10.6. Enteros largos.................................................................115
10.7. Contar letras...................................................................116
10.8. Glosario...........................................................................116
11. Archivos y excepciones 119
11.1. Archivos de texto.............................................................121
11.2. Escribir variables............................................................123
´Indice x
11.3. Directorios......................................................................125
11.4. Encurtido........................................................................125
11.5. Excepciones....................................................................126
11.6. Glosario...........................................................................128
12. Clases y objetos 131
12.1. Tipos compuestos definidos por el usuario....................131
12.2. Atributos.........................................................................132
12.3. Instancias como par´ametro ......................................................133
12.4. Mismidad........................................................................134
12.5. Recta´ngulos ....................................................................................135
12.6. Instancias como valores de retorno................................136
12.7. Los objetos son mudables...............................................136
12.8. Copiado...........................................................................137
12.9. Glosario...........................................................................139
13. Clases y funciones 141
13.1. Hora................................................................................141
13.2. Funciones puras..............................................................142
13.3. Modificadores.................................................................143
13.4. ¿Qu´e es mejor? ..............................................................................144
13.5. Desarrollo de prototipos frente a planificaci´on ................145
13.6. Generalizacio´n ..............................................................................146
13.7. Algoritmos.......................................................................146
13.8. Glosario...........................................................................147
14. Clases y m´etodos 149
14.1. Caracter´ısticas de la orientaci´on a objetos ......................149
´Indice
x
14.2. imprimeHora...............................................................................150
14.3. Otro ejemplo...................................................................151
14.4. Un ejemplo m´as complicado ....................................................152
14.5. Argumentos opcionales...................................................153
14.6. El m´etodo de inicializaci´on .....................................................154
14.7. Revisio´n de los Puntos ...............................................................155
14.8. Sobrecarga de operadores..............................................156
14.9. Polimorfismo...................................................................158
14.10.................................................................................Glosario 160
15. Conjuntos de objetos 161
15.1. Composici´on ...................................................................................161
15.2. Objetos Carta............................................................................161
15.3. Atributos de clase y el m´etodo.......................................str 163
15.4. Comparaci´on de naipes .............................................................164
15.5. Mazos de naipes..............................................................165
15.6. Impresio´n del mazo de naipes ................................................166
15.7. Barajar el mazo...............................................................167
15.8. Eliminaci´on y reparto de los naipes ......................................168
15.9. Glosario...........................................................................169
16. Herencia 171
16.1. Herencia..........................................................................171
16.2. Una mano de cartas........................................................172
16.3. El reparto de los naipes..................................................173
16.4. Mostremos la mano........................................................174
16.5. La clase JuegoDeCartas...........................................................175
´Indice x
16.6. La clase ManoDeLaMona.........................................................176
16.7. La clase JuegoDeLaMona........................................................177
16.8. Glosario...........................................................................181
´Indice
x
17.Listas enlazadas 183
17.1. Referencias incrustadas . . . . . . . . . . . . . . . . . . . . . . . 18
3
17.2. La clase Nodo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
3
17.3. Listas como colecciones . . . . . . . . . . . . . . . . . . . . . . . 18
5
17.4. Listas y recursividad . . . . . . . . . . . . . . . . . . . . . . . . 18
6
17.5. Listas infinitas . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
7
17.6. Teorema fundamental de la ambigu¨edad . . . . . . . .
. . . . .
18
8
17.7. Modificar listas . . . . . . . . . . . . . . . . . . . . . . . . . . . 18
9
17.8. Envoltorios y ayudantes . . . . . . . . . . . . . . . . . . . . . . 19
0
17.9. La clase ListaEnlazada . . . . . . . . . . . . . . . . . . . . . . 19
0
17.10. Invariantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
1
17.11. Glosario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
2
18.Pilas 195
18.1. Tipos abstractos de datos . . . . . . . . . . . . . . . . . . . . . 19
5
18.2. El TAD Pila . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19
6
18.3. C´omo implementar pilas con listas de Python . . . . .
. . . . .
19
6
18.4. Uso de push y pop . . . . . . . . . . . . . . . . . . . . . . . . . 19
´Indice x
7
18.5. Usar una pila para evaluar postfijo . . . . . . . . . . . . . . . . 19
8
18.6. An´alisis sint´actico . . . . . . . . . . . . . . . . . . . . . .
. . . .
19
9
18.7. Evaluar un postfijo . . . . . . . . . . . . . . . . . . . . . . . . . 19
9
18.8. Clientes y proveedores . . . . . . . . . . . . . . . . . . . . . . . 20
0
18.9. Glosario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
1
19.Colas 203
19.1. El TAD Cola . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
3
19.2. Cola Enlazada . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4
19.3. Rendimiento t´ıpico . . . . . . . . . . . . . . . . . . . . . . . . . 20
5
´Indice
x
19.4. Cola Enlazada Mejorada.................................................205
19.5. Cola priorizada................................................................207
19.6. La clase Golfista..............................................................209
19.7. Glosario...........................................................................210
20. A´rboles 211
20.1. Crear a´rboles .................................................................................212
20.2. Recorrer a´rboles ..........................................................................213
20.3. A´rboles de expresio´n ................................................................213
20.4. Recorrido de un a´rbol ................................................................214
20.5. Construir un a´rbol de expresio´n ..........................................216
20.6. Manejar errores..............................................................220
20.7. El a´rbol de animales ...................................................................221
20.8. Glosario...........................................................................224
A. Depuraci´on 225
A.1. Errores de sintaxis..........................................................225
A.2. Errores en tiempo de ejecucio´n .............................................227
A.3. Errores sema´nticos ......................................................................231
B. Crear un nuevo tipo de datos 235
B.1. Multiplicaci´on de fracciones ....................................................236
B.2. Suma de fracciones.........................................................237
B.3. Algoritmo de Euclides.....................................................238
B.4. Comparar fracciones......................................................239
B.5. Forzando la m´aquina ..................................................................240
B.6. Glosario...........................................................................241
´Indice x
C. Listados Completos de Python 243
C.1. Clase Punto.....................................................................243
C.2. Clase Hora......................................................................244
C.3. Cartas, mazos y juegos...................................................245
C.4. Lists Enlazadas...............................................................249
C.5. Clase Pila........................................................................250
C.6. Colas y colas priorizadas................................................251
C.7. A´rboles .............................................................................................253
C.8. A´rboles de expresio´n ................................................................254
C.9. Adivina el animal............................................................255
C.10. Fraction class...........................................................256
D. Lecturas recomendadas 259
D.1. Libros y sitios web sobre Python....................................260
D.2. Libros recomendados sobre inform´atica en general ......261
E. GNU Free Documentation License 263
E.1. Applicability and Definitions...........................................264
E.2. Verbatim Copying...........................................................265
E.3. Copying in Quantity........................................................265
E.4. Modifications..................................................................266
E.5. Combining Documents....................................................268
E.6. Collections of Documents...............................................269
E.7. Aggregation with Independent Works............................269
E.8. Translation......................................................................269
E.9. Termination.....................................................................270
E.10. Future Revisions of This License....................................270
E.11. Addendum: How to Use This License for Your Documents270
´Indice
x
Cap´ıtulo 1
El Camino del Programa
El objetivo de este libro es ensen˜arle a pensar como lo hacen los
cient´ıficos inform´aticos. Esta manera de pensar combina las
mejores caracter´ısticas de la matem´atica, la ingenier´ıa, y las
ciencias naturales. Como los matem´aticos, los cient´ıficos inform
´aticos usan lenguajes formales para designar ideas (espec´ıfica-
mente, computaciones). Como los ingenieros, ellos disen˜an cosas,
ensamblando sistemas a partir de componentes y evaluando
ventajas y desventajas de cada una de las alternativas. Como los
cient´ıficos, ellos observan el comportamiento de sistemas
complejos, forman hip´otesis, y prueban sus predicciones.
La habilidad m´as importante del cient´ıfico inform´atico es la
soluci´on de pro- blemas. La solucio´n de problemas incluye poder
formular problemas, pensar en la solucio´n de manera creativa, y
expresar una solucio´n con claridad y precisi´on. Como se ver´a, el
proceso de aprender a programar es la oportunidad perfecta para
desarrollar la habilidad de resolver problemas. Por esa razo´n este
cap´ıtulo se llama “El Camino del programa”.
A cierto nivel, usted aprender´a a programar, lo cual es una
habilidad muy u´til por s´ı misma. A otro nivel, usted utilizar´a la
programaci´on para obtener algu´n resultado. Ese resultado se ver
´a m´as claramente durante el proceso.
1.1. El lenguaje de programaci´on Python
El lenguaje de programaci´on que aprender´a es Python. Python es
un ejemplar de un lenguaje de alto nivel; otros ejemplos de lenguajes
de alto nivel son C, C++, Perl y Java.
2 El Camino del
CODIGO FUENTE INTERPRETER SALIDA
CODIGO FUENTE
COMPILADOR SALIDA
EJECUTOR
CODIGO OBJETO
Como se puede deducir de la nomenclatura “lenguaje de alto
nivel”, tambi´en existen lenguajes de bajo nivel, a los que tambi
´en se califica como lengua- jes de m´aquina o lenguajes
ensambladores. A prop´osito, los computadores s´olo ejecutan
programas escritos en lenguajes de bajo nivel. Los programas de
alto nivel tienen que traducirse antes de ejecutarse. Esta traducci
´on lleva tiempo, lo cual es una pequen˜a desventaja de los
lenguajes de alto nivel.
Aun as´ı las ventajas son enormes. En primer lugar, la programaci
´on en lenguajes de alto nivel es mucho m´as f´acil; escribir
programas en un lenguaje de alto nivel toma menos tiempo, los
programas son m´as cortos y mas faciles de leer, y es mas probable
que estos programas sean correctos. En segundo lugar, los
lenguajes de alto nivel son portables, lo que significa que pueden
ejecutarse en tipos diferentes de computadores sin modificaci´on
alguna o con pocas modificaciones. Los programas escritos en
lenguajes de bajo nivel s´olo pueden ser ejecutarse en un tipo de
computador y deben reescribirse para ejecutarlos en otro.
Debido a estas ventajas, casi todos los programa se escriben en un
lenguaje de alto nivel. Los lenguajes de bajo nivel s´olo se usan
para unas pocas aplicaciones especiales.
Hay dos tipos de programas que traducen lenguajes de alto nivel a
lenguajes de bajo nivel: int´erpretes y compiladores. Un int´erprete
lee un programa de alto nivel y lo ejecuta, lo que significa que lleva
a cabo lo que indica el programa. Traduce el programa poco a
poco, leyendo y ejecutando cada comando.
Un compilador lee el programa y lo traduce todo al mismo tiempo,
antes de ejecutar cualquiera de las instrucciones. En este caso, al
programa de alto nivel se le llama el c´odigo fuente, y al
programa traducido el c´odigo de obje- to o el c´odigo ejecutable.
Una vez compilado el programa, puede ejecutarlo repetidamente
sin volver a traducirlo.
Python se considera como lenguaje interpretado porque los
programas de Pyt- hon se ejecutan por medio de un int´erprete.
Existen dos maneras de usar el
1.2 ¿Qu´e es un programa? 3
int´erprete: modo de comando y modo de guion. En modo de
comando se escri- ben sentencias en el lenguaje Python y el int
´erprete muestra el resultado.
$ python
Python 1.5.2 (#1, Feb 1 2000, 16:32:16)
Copyright 1991-1995 Stichting Mathematish Centrum, Amsterdam
>>> print 1 + 1
2
La primera l´ınea de este ejemplo es el comando que pone en
marcha el int´erprete Python. Las dos l´ıneas siguientes son
mensajes del int´erprete. La tercera l´ınea comienza con >>>, que
es la invitaci´on del int´erprete para indicar que esta´ listo.
Escribimos print 1 + 1 y el int´erprete contest´o 2.
Alternativamente, se puede escribir el programa en un archivo y
usar el int´erpre- te para ejecutar el contenido de dicho archivo. El
archivo se llama, en este ca- so, un gui´on. Por ejemplo, en un
editor de texto se puede crear un archivo latoya.py que contenga
esta l´ınea:
print 1 + 1
Por acuerdo un´anime, los archivos que contienen programas de
Python tienen nombres que terminan con .py.
Para ejecutar el programa, se le tiene que indicar el nombre del
guion al int´erpre- te.
$ python latoya.py
2
En otros entornos de desarrollo los detalles de la ejecucio´n de
programas pueden ser diferentes. Aem´as, la mayor´ıa de
programas son mas interesantes que el mencionado.
La mayor´ıa de ejemplos en este libro se ejecutan desde en la l´ınea
de comando. La l´ınea de comando es muy apropiada para el
desarrollo de programas y para pruebas r´apidas porque se pueden
teclear las instrucciones de Python y se pue- den ejecutar
inmediatamente. Una vez que un programa esta´ completo, puede
archivarse en un gui´on para ejecutarlo o modificarlo en el futuro.
1.2. ¿Qu´e es un programa?
Un programa es una secuencia de instrucciones que especifican c
´omo ejecutar una computacio´n. La computacio´n puede ser algo
matem´atico, como solucionar
4 El Camino del
un sistema de ecuaciones o determinar las ra´ıces de un polinomio,
pero tambi´en puede ser una computacio´n simb´olica, como
buscar y reemplazar el texto de un documento o (aunque parezca
raro) compilar un programa.
Las instrucciones (comandos, o´rdenes) tienen una apariencia
diferente en len- guajes de programaci´on diferentes, pero existen
algunas funciones basicas que se presentan en casi todo lenguaje:
entrada: Recibir datos del teclado, o un archivo u otro aparato.
salida: Mostrar datos en el monitor o enviar datos a un archivo u otro
aparato.
matem´aticas: Ejecutar operaciones b´asicas de matem´aticas
como la adici´on y la multiplicaci´on.
operaci´on condicional: Probar la veracidad de alguna condicio´n y
ejecutar una secuencia de instrucciones apropiada.
repetici´on: Ejecutar alguna acci´on repetidas veces, normalmente
con alguna variaci´on.
Lo crea o no, eso es todo. Todos los programas que existen, por
complicados que sean, esta´n formulados exclusivamente con tales
instrucciones. As´ı, una manera de describir la programaci´on es: El
proceso de romper una tarea en tareas cada vez m´as pequen˜as
hasta que estas tareas sean suficientemente simples para ser
ejecutadas con una de estas instrucciones simples.
Quiz´as esta descripcio´n sea un poco ambigua. No se preocupe. Lo
explicaremos con m´as detalle con el tema de los algoritmos.
1.3. ¿Qu´e es la depuraci´on (debugging)?
La programaci´on es un proceso complejo y, por ser realizado por
humanos, a menudo desemboca en errores. Por razones
caprichosas, esos errores se llaman bugs y el proceso de buscarlos
y corregirlos se llama depuraci´on (en ingl´es “debugging”).
Hay tres tipos de errores que pueden ocurrir en un programa, de
sintaxis, en tiempo de ejecucio´n y sema´nticos. Es muy u´til
distinguirlos para encontrarlos mas r´apido.
1.3 ¿Qu´e es la depuraci´on (debugging)? 5
1.3.1. Errores sint´acticos
Python s´olo puede ejecutar un programa si el programa es
correcto sintactica- mente. En caso contrario, es decir si el
programa no es correcto sintacticamente, el proceso falla y
devuelve un mensaje de error. El t´ermino sintaxis se refiere a la
estructura de cualquier programa y a las reglas de esa estructura.
Por ejem- plo, en espan˜ol la primera letra de toda oraci´on debe
ser mayu´scula, y todas las oraciones deben terminar con un punto.
esta oraci´on tiene un error sint´actico. Esta oraci´on tambi´en
Para la mayor´ıa de lectores, unos pocos errores sintacticos no son
significatvos, y por eso pueden leer la poes´ıa de e. e. cummings
sin anunciar errores de sin- taxis. Python no es tan permisivo. Si
hay aunque sea un solo error sintactico en el programa, Python
mostrar´a un mensaje de error y abortar´a la ejecucio´n del
programa. Durante las primeras semanas de su carrera como
programador pasar´a, seguramente, mucho tiempo buscando
errores sintacticos. Sin embargo, tal como adquiera experiencia
tendr´a menos errores y los encontrar´a mas r´apido.
1.3.2. Errores en tiempo de ejecucio´n
El segundo tipo de error es un error en tiempo de ejecucio´n. Este
error no aparece hasta que se ejecuta el programa. Estos errores
tambi´en se llaman excepciones porque indican que algo
excepcional (y malo) ha ocurrido.
Con los programas que vamos a escribir al principio, los errores en
tiempo de ejecucio´n ocurrira´n con poca frecuencia, as´ı que
puede pasar bastante tiempo hasta que vea uno.
1.3.3. Errores sem´anticos
El tercer tipo de error es el error sem´antico. Si hay un error de
logica en su programa, el programa se ejecutara´ sin ningu´n
mensaje de error, pero el resul- tado no ser´a el deseado. Ser´a
cualquier otra cosa. Concretamente, el programa har´a lo que usted
le dijo.
A veces ocurre que el programa escrito no es el programa que se
ten´ıa en mente. El sentido o significado del programa (su sema
´ntica) no es correcto. Es dif´ıcil ha- llar errores de l´ogica, porque
requiere trabajar al rev´es, observando el resultado del programa
para averiguar lo que hace.
6 El Camino del
1.3.4. Depuraci´on experimental
Una de las t´ecnicas m´as importantes que usted aprender´a es la
depuraci´on. Aun- que a veces es frustrante, la depuraci´on es una
de las partes mas intelectualmente ricas, interesantes y
estimulantes de la programaci´on.
La depuraci´on es una actividad parecida a la tarea de un
investigador: se tie- nen que estudiar las claves para inducir los
procesos y eventos llevaron a los resultados que tiene a la vista.
La depuraci´on tambi´en es una ciencia experimental. Una vez
que se tiene la idea de cua´l es el error, se modifica el programa y
se intenta nuevamente. Si su hip´otesis fue la correcta se pueden
predecir los resultados de la modificaci´on y estara´ m´as cerca de
un programa correcto. Si su hipotesis fue err´onea tendr´a que
idearse otra hip´otesis. Como dijo Sherlock Holmes, “Cuando se ha
descartado lo imposible, lo que queda, no importa cuan inveros´ımil,
debe ser la verdad.” (A. Conan Doyle, The Sign of Four)
Para algunas personas, la programaci´on y la depuraci´on son lo
mismo: la pro- gramaci´on es el proceso de depurar un programa
gradualmente hasta que haga lo que usted quiera. La idea es que
deber´ıa usted comenzar con un programa que haga algo y hacer
pequen˜as modificaciones, depur´andolas sobre la marcha, de
modo que siempre tenga un programa que funcione.
Por ejemplo, Linux es un sistema operativo que contiee miles de l
´ıneas de c´odigo, pero Linus Torvalds lo comenzo´ como un
programa para explorar el microproce- sador Intel 80836. Segu´n
Larry Greenfield, “Uno de los proyectos tempranos de Linus fue un
programa que alternaba la impresi´on de AAAA con BBBB. Este
programa evolucion´o en Linux” (de The Linux Users’Guide Versio´n
Beta 1). Otros cap´ıtulos tratar´an m´as acerca del tema de
depuraci´on y otras t´ecnicas de programaci´on.
1.4. Lenguajes formales y lenguajes naturales
Los lenguajes naturales son los lenguajes hablados por seres
humanos, como el espan˜ol, el ingl´es y el franc´es. No los han
disen˜ados personas (aunque se intente poner cierto orden en
ellos), sino que se han desarrollado naturalmente. Los lenguajes
formales son lenguajes disen˜ados por humanos y que tienen
aplicaciones espec´ıficas. La notaci´on matem´atica, por ejemplo,
es un lenguaje formal ya que se presta a la representaci´on de las
relaciones entre nu´meros y s´ımbolos. Los qu´ımicos utilizan un
lenguaje formal para representar la estructura qu´ımica de las mol
´eculas. Y lo m´as importante:
1.4 Lenguajes formales y lenguajes naturales 7
Los lenguajes de programaci´on son lenguajes formales
de- sarrollados para expresar computaciones.
Los lenguajes formales casi siempre tienen reglas sintacticas
estrictas. Por ejem- plo, 3 + 3 = 6 es una expresio´n matem´atica
correcta, pero 3 = +6$ no lo es. De la misma manera, H20 es una
nomenclatura qu´ımica correcta, pero 2Zz no lo es.
Existen dos clases de reglas sint´acticas, en cuanto a unidades y
estructura. Las unidades son los elementos b´asicos de un lenguaje,
como lo son las palabras, los nu´meros y los elementos qu´ımicos.
Por ejemplo, en 3=+6$, $ no es una unidad matem´atica aceptada (al
menos hasta donde nosotros sabemos. Similarmente, 2Xx no es
formal porque no hay ningu´n elemento con la abreviatura Zz.
La segunda clase de regla sint´actica esta´ relacionada con la
estructura de un elemento; o sea, el orden de las unidades. La
estructura de la sentencia 3=+6$ no se acepta porque no se puede
escribir el s´ımbolo de igualdad seguido de un s´ımbolo positivo.
Similarmente, las f´ormulas moleculares tienen que mostrar el nu
´mero de sub´ındice despu´es del elemento, no antes.
A manera de pra´ctica, trate de producir una oracio´n con
estructura aceptada pero que est´e compuesta de unidades
irreconocibles. Luego escriba otra oracio´n con unidades
aceptables pero con estructura no v´alida.
Al leer una oraci´on, sea en un lenguaje natural o una sentencia en
un lenguaje t´ecnico, se debe discernir la estructura de la oraci
´on. En un lenguaje natural este proceso, llamado an´alisis sint
´actico ocurre subconscientemente.
Por ejemplo cuando usted escucha la oraci´on “El otro zapato
cayo”, entiende que “el otro zapato” es el sujeto y “cay´o” es el
verbo. Cuando se ha analizado la oraci´on sint´acticamente, se
puede deducir el significado, o la sema´ntica, de la oraci´on.
Suponiendo que sepa lo ques es un zapato y lo que es caer,
entender´a el significado de la oraci´on.
Aunque existen muchas cosas en comu´n entre los lenguajes
naturales y los lenguajes formales—por ejemplo las unidades, la
estructura, la sintaxis y la sema´ntica—tambi´en existen muchas
diferencias:
ambigu¨edad: Los lenguajes naturales tienen much´ısimas
ambigu¨edades, que los hablantes sortean usando claves
contextuales y otra informaci´on. Los lenguajes formales se
disen˜an para estar completamente libres de am-
bigu¨edades o tanto como sea posible, lo que quiere decir
que cualquier sentencia tiene s´olo un significado, sin
importar el contexto.
8 El Camino del
redundancia: Para reducir la ambigu¨edad y los malentendidos, las
lenguas na- turales utilizan bastante redundancia. Como
resultado suelen ser prolijos. Los lenguajes formales son
menos redundantes y mas concisos.
literalidad: Los lenguajes naturales tienen muchas met´aforas y
frases hechas. El significado de un dicho, por ejemplo “Estirar
la pata”, es diferente al significado de sus sustantivos y
verbos. En este ejemplo, la oraci´on no tiene nada que ver con
un pie y significa ’morirse’. Los lenguajes formales no difieren
de su significado literal.
Los que aprenden a hablar un lenguaje natural—es decir, todo el
mundo— muchas veces tienen dificultad en adaptarse a los
lenguajes formales. A veces la diferencia entre los lenguajes
formales y los naturales es comparable a la diferencia entre la
prosa y la poes´ıa:
Poes´ıa: Se utiliza una palabra por su cualidad auditiva tanto como
por su signi- ficado. El poema, en su totalidad, produce un
efecto o reaccio´n emocional. La ambigu¨edad no es solo comu
´n sino utilizada a prop´osito.
Prosa: El significado literal de la palabra es mas importante y la
estructura da m´as significado au´n. La prosa se presta al
analisis mas que la poes´ıa, pero todav´ıa contiene
ambigu¨edad.
Programas: El significado de un programa es inequ´ıvoco y literal, y
es enten- dido en su totalidad analizando las unidades y la
estructura.
He aqu´ı unas sugerencias para la lectura de un programa (y de
otros lenguajes formales). Primero, recuerde que los lenguajes
formales son mucho mas densos que los lenguajes naturales, y por
consecuente lleva mas tiempo leerlos. Tam- bi´en, la estructura es
muy importante, as´ı que entonces no es una buena idea leerlo de
pies a cabeza, de izquierda a derecha. En vez de eso, aprenda a
sepa- rar las diferentes partes en su mente, identificar las unidades
e interpretar la estructura. Finalmente, ponga atenci´on a los
detalles. Los fallos de puntuaci´on y la ortograf´ıa, que puede obviar
en el lenguaje natural, pueden suponer una gran diferencia en un
lenguaje formal.
1.5. El primer programa
Tradicionalmente el primer programa en un lenguaje nuevo se
llama “Hola, mundo” (Hello world!) porque s´olo muestra las
palabras “Hola a todo el mundo”. En Python es as´ı:
print "Hola, mundo"
1.6 Glosario 9
Este es un ejemplo de una sentencia print, la cual no imprime nada
en papel, m´as bien muestra un valor. En este caso, el resultado es
las palabras
Hola, mundo
Las comillas sen˜alan el comienzo y el final del valor; no aparecen
en el resultado. Alguna gente evalu´a la calidad de un lenguaje de
programaci´on por la simplici- dad del programa “Hola, mundo”. Si
seguimos ese criterio, Python cumple con todas sus metas.
1.6. Glosario
soluci´on de problemas: El proceso de formular un problema, hallar
la solu- cio´n y expresar esa solucio´n.
lenguaje de alto nivel: Un lenguaje como Python disen˜ado para
ser facil de leer y escribir para la gente.
lenguaje de bajo nivel: Un lenguaje de programaci´on disen˜ado
para ser facil de ejecutar para un computador; tambi´en se lo
llama “lenguaje de maqui- na” o “lenguaje ensamblador”.
portabilidad: La cualidad de un programa que le permite ser
ejecutado en m´as de un tipo de computador.
interpretar: Ejecutar un programa escrito en un lenguaje de alto
nivel tradu- ci´endolo l´ınea por l´ınea
compilar: Traducir un programa escrito en un lenguaje de alto nivel
a un len- guaje de bajo nivel todo al mismo tiempo, en
preparaci´on para la ejecucio´n posterior.
c´odigo fuente: Un programa escrito en un lenguaje de alto nivel
antes de ser compilado.
c´odigo de objeto: La salida del compilador una vez que ha
traducido el pro- grama.
programa ejecutable: Otro nombre para el c´odigo de objeto que
esta´ listo para ejecutarse.
gui´on: Un programa archivado (que va a ser interpretado).
programa: Un conjunto de instrucciones que especifica una computacio´n.
algoritmo: Un proceso general para resolver una clase completa de
problemas.
1 El Camino del
error (bug): Un error en un programa.
depuraci´on: El proceso de hallazgo y eliminacio´n de los tres tipos
de errores de programaci´on.
sintaxis: La estructura de un programa.
error sint´actico: Un error en un programa que hace que el
programa sea im- posible de analizar sint´acticamente (e
imposible de interpretar).
error en tiempo de ejecuci´on: Un error que no ocurre hasta que el
progra- ma ha comenzado a ejecutarse e impide que el
programa continu´e.
excepci´on: Otro nombre para un error en tiempo de ejecucio´n.
error sem´antico: Un error en un programa que hace que ejecute
algo que no era lo deseado.
sem´antica: El significado de un programa.
language natural: Cualquier lenguaje hablado que evolucion´o de
forma natu- ral.
lenguaje formal: Cualquier lenguaje disen˜ado por humanos que
tiene un prop´osito espec´ıfico, como la representaci´on de
ideas matem´aticas o pro- gramas de computadores; todos los
lenguajes de programaci´on son lengua- jes formales.
unidad: Uno de los elementos b´asicos de la estructura sintactica
de un progra- ma, an´alogo a una palabra en un lenguaje
natural.
an´alisis sint´actico: La examinacio´n de un programa y el analisis
de su estruc- tura sint´actica.
sentencia print: Una instrucci´on que causa que el int´erprete
Python muestre un valor en el monitor.
Cap´ıtulo 2
Variables, expresiones y
sentencias
2.1. Valores y tipos
El valor es uno de los elementos fundamentales (como por ejemplo
una letra o un nu´mero) que manipula un programa. Los valores
que hemos visto hasta el momento son 2 (el resultado de sumar 1 +
1) y Hola, mundo.
Estos valores son de distintos tipos: 2 es un entero y Hola, mundo es
una cadena, llamada as´ı porque contiene una “cadena” de letras.
Usted (y el int´erprete) puede identificar las cadenas porque esta´n
encerradas entre comi- llas.
La sentencia print tambi´en funciona con enteros:
>>> print 4
4
Si no esta´ seguro del tipo que tiene un determinado valor, puede
preguntarselo al int´erprete de Python.
>>> type("Hola, mundo")
<type ’string’>
>>> type(17)
<type ’int’>
No es sorprendente que las cadenas sean de tipo string (cadena
en ingl´es) y los enteros sean de tipo int (por integer en ingl´es). De
forma menos obvia, los
1 Variables, expresiones y
nu´meros con decimales (separados por medio de un punto en ingl
´es) son de tipo float debido a la representaci´on de estos nu
´meros en el formato llamado de coma flotante (floating-point).
>>> type(3.2)
<type ’float’>
¿Qu´e ocurre con los valores como "17" y "3.2"? Parecen nu´meros,
pero esta´n entre comillas como las cadenas.
>>> type("17")
<type ’string’>
>>> type("3.2")
<type ’string’>
Son cadenas.
Cuando escriba un entero largo, podr´ıa estar tentado de usar
comas entre grupos de tres d´ıgitos, como en 1,000,000. E´ste no es
un entero legal en Python, pero es una expresio´n legal:
>>> print 1,000,000
1 0 0
En fin, no era eso lo que quer´ıamos. Python interpreta 1,000,000
como una lista de tres nu´meros que debe imprimir. As´ı que
recuerde no insertar comas en sus enteros. 1
2.2. Variables
Una de las caracter´ısticas m´as potentes de los lenguajes de
programaci´on es la capacidad de manipular variables. Una variable
es un nombre que hace referencia a un valor.
La sentencia de asignacio´n crea nuevas variables y les asigna un valor:
>>> mensaje = "Que onda?"
>>> n = 17
>>> pi = 3.14159
Este ejemplo muestra tres asignaciones. La primera de ellas asigna el
valor
"Que onda?" a una variable nueva, de nombre mensaje. La segunda le da el
1
El uso de la coma y el punto en nu´mero es en ingl´es el contrario al uso espan˜ol, como se
apunt´o en una nota anterior
mensaje
n pi
"Que onda?" 17
3.14159
2.3 Nombres de variables y palabras reservadas 13
valor entero 17 a n, y la tercera le da el valor de nu´mero en coma
flotante
3.14159 a pi.
Una forma habitual de representar variables sobre el papel es
escribir el nombre con una flecha sen˜alando al valor de la
variable. Este tipo de representaci´on se llama diagrama de
estado, ya que muestra en qu´e estado se halla cada una de las
variables (consid´erelo como el “estado de a´nimo” de la
variable”). El siguiente diagrama muestra el efecto de las tres
sentencias de asignaci´on anteriores:
La sentencia print tambi´en funciona con variables.
>>> print mensaje
"Que onda?"
>>> print n
17
>>> print pi
3.14159
En cada caso, el resultado es el valor de la variable. Las variables
tambi´en tienen tipo. De nuevo, podemos preguntar al int´erprete
lo que son.
>>> type(mensaje)
<type ’string’>
>>> type(n)
<type ’int’>
>>> type(pi)
<type ’float’>
El tipo de una variable es el tipo del valor al que se refiere.
2.3. Nombres de variables y palabras reservadas
Como norma general, los programadores eligen nombres
significativos para sus variables: esto permite documentar para qu
´e se usa la variable.
Los nombres de las variables pueden tener una longitud arbitraria.
Pueden estar formados por letras y nu´meros, pero deben
comenzar con una letra. Aunque es
1 Variables, expresiones y
aceptable usar mayu´sculas, por convencio´n no lo hacemos. Si lo
hace, recuerde que la distinci´on es importante: Bruno y bruno son
dos variables diferentes.
El gui´on bajo ( ) tambi´en es legal y se utiliza a menudo para
separar nombres con mu´ltiples palabras, como mi nombre o precio
del cafe colombiano.
Si intenta darle a una variable un nombre ilegal, obtendr´a un error de
sintaxis.
>>> 76trombones = "gran desfile"
SyntaxError: invalid syntax
>>> mas$ = 1000000
SyntaxError: invalid syntax
>>> class = "Curso de Programaci´on 101"
SyntaxError: invalid syntax
76trombones es ilegal porque no comienza por una letra. mas$ es
ilegal porque contiene un cara´cter ilegal, el signo del d´olar. Pero
¿qu´e tiene de malo class? Resulta que class es una de las palabras
reservadas de Python. El lenguaje usa las palabras reservadas para
definir sus reglas y estructura, y no pueden usarse como nombres
de variables.
Python tiene 28 palabras reservadas:
Tal vez quiera mantener esta lista a mano. Si el int´erprete se
queja de alguno de sus nombres de variable, y usted no sabe por qu
´e, compruebe si esta´ en esta lista.
2.4. Sentencias
Una sentencia es una instrucci´on que puede ejecutar el int
´erprete de Python. Hemos visto dos tipos de sentencias: print y la
asignaci´on.
Cuando usted escribe una sentencia en la l´ınea de comandos,
Python la ejecuta y muestra el resultado, si lo hay. El resultado de
una sentencia print es un valor. Las sentencias de asignaci´on no
entregan ningu´n resultado.
Normalmente un gui´on contiene una secuencia de sentencias. Si
hay mas de una sentencia, los resultados aparecen de uno en uno
tal como se van ejecutando las sentencias.
and continue else for import not raise
assert def except from in or return
break del exec global is pass try
class elif finally if lambda print while
2.5 Evaluar expresiones 15
Por ejemplo, el gui´on
print 1
x = 2
print x
prsenta la salida
1
2
De nuevo, la sentencia de asignaci´on no produce ninguna salida.
2.5. Evaluar expresiones
Una expresio´n es una combinaci´on de valroes, variables y
operadores. Si teclea una expresio´n en la l´ınea de comandos, el
int´erprete la evalu´a y muestra el resultado:
>>> 1 + 1
2
Un valor, y tambi´en una variable, se considera una expresio´n por s´ı
mismo.
>>> 17
17
>>> x
2
Para complicar las cosas, evaluar una expresio´n no es del todo lo
mismo que imprimir un valor.
>>> mensaje = "Que onda?"
>>> mensaje
"Que onda?"
>>> print mensaje
Que onda?
Cuando Python muestra el valor de una expresio´n, usa el mismo
formato que usted usar´ıa para introducir un valor. En el caso de las
cadenas, eso significa que incluye las comillas. Pero la sentencia
print imprime el valor de la expresio´n, lo que en este caso es el
contenido de la cadena.
En un gui´on, una expresio´n sin m´as es una sentencia valida, pero
no hace nada. El gui´on
1 Variables, expresiones y
17
3.2
"Hola, mundo"
1 + 1
no presenta ninguna salida. ¿Co´mo cambiar´ıa usted el guion para
mostrar los valores de estas cuatro expresiones?
2.6. Operadores y expresiones
Los operadores son s´ımbolos especiales que representan c´alculos
simples, como la suma y la multiplicaci´on. Los valores que usa el
operador se llaman operan- dos.
Las siguientes expresione son legales en Python y su significado es
mas o menos claro:
20+32 hora-1 hora*60+minuto minuto/60 5**2 (5+9)*(15-7)
Los s´ımbolos +, -, /, y el uso de los par´entesis para el
agrupamiento, se usan todos de la misma forma que en matem
´aticas. El asterisco (*) es el signo de multiplicaci´on y ** el s
´ımbolo para exponenciaci´on.
Cuando aparece el nombre de una variable en el lugar de un
operando, se sus- tituye con su valor antes de realizar la operacio
´n.
La suma, resta, multiplicaci´on y exponenciaci´on hacen lo
esperado, pero la divi- sio´n le puede sorprender. La operacio´n
que sigue tiene un resultado inesperado:
>>> minuto = 59
>>> minuto/60
0
El valor de la variable minuto es 59, y 59 dividido entre 60 es 0.98333 y
no
0. El motivo de la discrepancia reside en que Python esta´ llevando a cabo
una
divisi´on de enteros.
Cuando ambos operandos son enteros, el resultado ha de ser tambi
´en un entero; por convencio´n, la divisi´on de enterios simpre se
redondea a la baja, incluso en casos como estos en los que el
siguiente entero esta´ muy pr´oximo.
Una alternativa posible en este caso es el c´alculo de un
porcentaje y no el de una fracci´on:
>>> minuto*100/60
98
2.7 El orden de las operaciones 17
De nuevo se redondea el resultado a la baja, pero al menos ahora
la respuesta es aproximadamente correcta. Otra alternativa es la
divisi´on de coma flotante, que veremos en el Cap´ıtulo 3.
2.7. El orden de las operaciones
Cuando aparece m´as de un operador en una expresio´n, el orden
de evaluaci´on depende de las reglas de precedencia. Python sigue las
mismas reglas de precedencia que los propios matem´aticos para
sus operaciones matem´aticas. Los ingleses usan el acr´onimo
PEMDAS como regla parea recordar el orden de las operaciones:
Par´entesis: tienen la precedencia m´as alta y pueden usarse
para forzar que una expresio´n se evalu´e en el orden que
queramos nosotros. Puesto que las expresiones entre par
´entesis se evalu´an primero, 2 * (3-1) es igual a 4, y
(1+1)**(5-2) es igual a 8. Tambi´en puede usar par´entesis
para que una expresio´n sea m´as legible; por ejemplo (minuto
* 100) / 60, aunque el resultado no cambie de todas
formas.
Exponenciaci´on tiene la siguiente precedencia mas alta; as´ı pues
2**1+1
es igual a 3 y no a 4, y 3*1**3 es igual a 3 y no a 27.
La Multiplicaci´on y la Divisi´on tienen la misma precedencia,
que es mas alta que la de la Adici´on y la Sustracci´on, que
tienen tambi´en la misma precedencia. Por tanto 2*3-1
devuelve 5 y no 4, y 2/3-1 da -1, y no 1 (recuerde que en la divisi
´on de enteros 2/3 da 0).
Los operadores que tienen la misma precedencia se evalu´an
de izquierda a derecha. As´ı, en la expresio´n minuto*100/60,
tiene lugar primero la multiplicaci´on, devolviendo tt
5900/60, que a su vez da como resultado
98. Si las operaciones se hubiesen realizado de derecha a
izquierda, el resultado habr´ıa sido 59/1 que da 59, y que es
incorrecto.
2.8. Las operaciones sobre cadenas
En general no es posible realizar operaciones matem´aticas con
cadenas, incluso si las cadenas parecen nu´meros. Las siguientes
sentencias son ilegales (suponiendo que mensaje sea de tipo string)
mensaje-1 "Hola"/123 mensaje*"Hola" "15"+2
1 Variables, expresiones y
Es curioso que el operador + funcione con cadenas, aunque no
haga exactamente lo que usted esperar´ıa. Para cadenas, el
operador + representa la concatena- ci´on, lo que significa que se
unen los dos operandos uni´endolos extremo con extremo. Por
ejemplo:
fruta = "pl´atano"
bizcochoBueno = " pan de leche"
print fruta + bizcochoBueno
La salida del programa es pl´atano pan de leche. El espacio delante
de pan es parte de la cadena, y es necesario para introducir el
espacio que separa las cadenas concatenadas.
El operador * tambi´en funciona con cadenas; lleva a cabo la
repetici´on. Por ejemplo ’Chiste’*3 es ’ChisteChisteChiste’. Uno de
los operandos ha de ser una cadena, el otro ha de ser un
entero.
Por un lado, esta interpretaci´on de + y * cobra sentido por
analog´ıa con la suma y la multimplicaci´on. Igual que 4*3 es
equivalente a 4+4+4, esperamos que ’Chiste’*3 sea lo mismo que
’Chiste’+’Chiste’+’Chiste’, y as´ı es. Por otro lado, la concatenacio
´n y la repeticio´n son de alguna manera muy diferentes de la adici
´on y la multiplicaci´on de enteros. ¿Puede encontrar una
propiedad que tienen la suma y la multiplicaci´on de enteros y que
no tengan la concatenacio´n y la repeticio´n de cadenas?
2.9. Composicio´n
Hasta ahora hemos examinado los elementos de un programa
(variables, expre- siones y sentencias) por separado, sin hablar de c
´omo combinarlos.
Una de las caracter´ısticas m´as u´tiles de los lenguajes de
programaci´on es su capacidad de tomar pequen˜os bloques de
construccio´n y ensamblarlos. Por ejemplo, sabemos c´omo sumar
nu´meros y c´omo imprimirlos; resulta que pode- mos hacer las dos
cosas a un tiempo:
>>> print 17 + 3
20
En realidad, no debemos decir “al mismo tiempo”, puesto que
en realidad la suma tiene que realizarse antes de la impresi´on,
pero la cuestio´n es que cualquier expresio´n relacionada con nu
´meros, cadenas y variables puede usarse dentro de una sentencia
print. Ya hemos visto un ejemplo de ello:
print "N´umero de minutos desde la medianoche: ", hora*60+minuto
2.10 Los comentarios 19
Y tambi´en puede poner expresiones arbitrarias en la parte derecha
de una sen- tencia de asignaci´on:
porcentaje = (minuto * 100) / 60
Esta capacidad puede no resultar muy sorprendente, pero ya
veremos otros ejemplos donde la composici´on hace posible
expresar c´alculos complejos con limpieza y brevedad.
ATENCIO´ N: Hay l´ımites al lugar donde pueden usarse ciertas
expresiones. Por ejemplo, la parte izquierda de una sentencia de
asignaci´on tiene que ser un nombre de variable, no una exrpresio
´n. Por tanto es ilegal lo siguiente: minute+1
= hour.
2.10. Los comentarios
Conforme los programas van creciendo de taman˜o y complica
´ndose, se vuelven m´as complicados de leer. Los lenguajes
formales son densos y con frecuencia es dif´ıcil observar un trozo
de c´odigo y averiguar lo que hace, o por qu´e lo hace. Por ello es
una buena idea an˜adir notas a su programa que expliquen, en un
lenguaje natural, qu´e hace el programa. Estas notas se llaman
comentarios y
se marcan con el s´ımbolo #:
# calcula el porcentaje de la hora que ha pasado ya
porcentaje = (minuto * 100) / 60
En este caso, el comentario aparece en una l´ınea propia. Tambi´en
puede poner comentarios al final de otras l´ıneas:
porcentaje = (minuto * 100) / 60 # ojo: divisi´on de enteros
Todo lo que va del # al final de la l´ınea se ignora (no tiene efecto
sobre el programa). El mensaje esta´ destinado al programador, o
a futuros programa- dores que podr´ıan tener que usar el c´odigo.
En este caso avisa al lector sobre el sorprendente comportamiento
de la divisi´on de enteros.
2.11. Glosario
valor: un nu´mero o cadena (o cualquier otra cosa que se
especifique poste- riormente) que puede almacenarse en una
variable o calcularse en una expresio´n.
2 Variables, expresiones y
tipo: un conjunto de valores. El tipo de un valor determina c´omo
puede usarse en las expresiones. Hasta ahora, los tipos que
hemos visto son enteros (tipo int), nu´meros de coma
flotante (tipo float) y cadenas (tipo string).
coma flotante: un formato para representar nu´meros con decimales.
variable: nombre que hace referencia a un valor.
sentencia: es una porci´on de c´odigo que representa una orden o
acci´on. Hasta ahora, las sentencias que hemos vistos son
las asignaciones y las sentencias print.
asignacio´n: sentencia que asigna un valor a una variable.
diagrama de estado: representaci´on gr´afica de un conjunto de
variables y de los valores a los que se refiere.
palabra reservada: es una palabra clave que usa el compilador
para analizar sint´acticamente los programas. No pueden
usarse palabras reservadas, por ejemplo if, def y while como
nombres de variables.
operador: un s´ımbolo especial que representa un c´alculo sencillo,
como la su- ma, la multiplicaci´on o la concatenacio´n de
cadenas.
operando: uno de los valores sobre los que actu´a un operador.
expresi´on: una combinaci´on de variables, operadores y valores.
Dicha combi- naci´on representa un u´nico valor como
resultado.
evaluar: simplificar una expresio´n ejecutando las operaciones para
entregar un valor u´nico.
divisi´on de enteros: es una operacio´n que divide un entero entre
otro y de- vuelve un entero. La divisi´on de enteros devuelve
s´olo el nu´mero entero de veces que el numerador es
divisible por en denominador, y descarta el resto.
reglas de precedencia: la serie de reglas que especifican el orden en el
que las expresiones con mu´tiples operadores han de
evaluarse.
concatenar: unir dos operandos extremo con extremo.
composici´on: la capacidad de combinar expresiones sencillas y
sentencias has- ta crear sentencias y expresiones compuestas,
con el fin de representar c´alculos complejos de forma
concisa.
comentario: un segmento de informaci´on en un programa,
destinado a otros programadores (o cualquiera que lea el c
´odigo fuente) y que no tiene efecto sobre la ejecucio´n del
programa.
Cap´ıtulo 3
Funciones
3.1. Llamadas a funciones
Ya hemos visto un ejemplo de una llamada a una funcio´n:
>>> type("32")
<type ’string’>
El nombre de la funci´on es type, y muestra el tipo de un valor o de
una variable. El valor o variable, llamado el argumento de la funci
´on, ha de estar encerrado entre par´entesis. Es habitual decir
que una funci´on “toma” un argumento y “devuelve” un resultado.
El resultado se llama valor de retorno.
En lugar de imprimir el valor de retorno, podemos asign´arselo a una
variable.
>>> nereida = type("32")
>>> print nereida
<type ’string’>
Otro ejemplo m´as: la funci´on id toma como argumento un valor o
una variable y devuelve un entero que actu´a como identificador u
´nico de ese valor.
>>> id(3)
134882108
>>> yanira = 3
>>> id(yanira)
134882108
Cada valor tiene un id, que es un valor u´nico relacionado con
donde se almacena en la memoria del computador. El id de una
variable es el id del valor al que hace referencia.
2 Funcion
3.2. Conversio´n de tipos
Python proporciona una coleccio´n de funciones internas que
convierten valores de un tipo a otro. La funci´on int toma un valor y
lo convierte a un entero, si es posible, o da un error si no es
posible.
>>> int("32")
32
>>> int("Hola")
ValueError: invalid literal for int(): Hola
int tambi´en convierte valores de coma flotante a enteros, pero
recuerde que siempre redondea hacia abajo:
>>> int(3.99999)
3
La funci´on float que convierte enteros y cadenas en nu´meros en coma
flotante:
>>> float(32)
32.0
>>> float("3.14159")
3.14159
Finalmente, esta´ la funci´on str, que convierte a tipo string:
>>> str(32)
’32’
>>> str(3.14149)
’3.14149’
Pudiera parecer extran˜o que Python distinga entre el valor entero
1 y el valor de coma flotante 1.0. Tal vez representen el mismo nu
´mero, pero pertenecen a tipos distintos. El motivo es que se
representan de forma distinta dentro del computador.
3.3. Coercio´n de tipos
Ahora que ya sabemos convertir entre tipos, tenemos otra forma de
enfrentarnos a la divisi´on de enteros. Volviendo al ejemplo del cap
´ıtulo anterior, suponga que queremos calcular qu´e fracci´on de
una hora hab´ıa transcurrido. La expresio´n m´as obvia, minuto /
60, realiza una divisi´on de enteros, por lo que el resultado es
siempre 0, incluso 59 minutos despu´es de la hora.
Una alternativa es convetir minuto a tipo float (coma flotante) y
luego efectuar una divisi´on de coma flotante:
3.4 Funciones matem´aticas 23
>>> minuto = 59
>>> float(minuto) / 60.0
0.983333333333
O bien podemos sacar provecho de las reglas de la conversio´n
automatica de tipos, llamada coerci´on de tipos. Para los
operadores matem´aticos, si uno de los operandos matem´aticos es
tipo float, el otro se convierte automaticamente en float.
>>> minuto = 59
>>> minuto / 60.0
0.983333333333
Al usar un denomidador que es float, obligamos a Python a hacer
divisi´on de coma flotante.
3.4. Funciones matem´aticas
Es posible que ya haya visto usted en matem´aticas funciones como
sin (seno) y log, y que haya aprendido a evaluar expresiones
como sin(pi/2) y log(1/x). Primero evalu´a la expresio´n entre par
´entesis, (el argumento). Por ejemplo, pi/2 es aproximadamente
1.571, y 1/x es 0.1 (si x es igual a 10.0).
Luego evalu´a la funci´on en s´ı misma, bien mir´andola en una
tabla, bien llevando a cabo diversos c´alculos. El sin (seno) de
1.571 es 1, y el log de 0.1 es -1 (suponiendo que log indique el
logaritmo de base 10).
Este proceso puede aplicarse repetidamente para evaluar
expresiones mas com- plicadas como log(1/sin(pi/2)). Primero
evaluamos el argumento de la fun- cio´n m´as interna, luego se
evalu´a la funci´on, y as´ı sucesivamente.
Python dispone de un m´odulo matem´atico que proporciona la
mayor´ıa de las funciones matem´aticas habituales. Un m´odulo es
un archivo que contiene una coleccio´n de funciones agrupadas
juntas.
Antes de poder usar las funciones de un modulo, tenemos que importarlo:
>>>import math
Para llamar a una de las funciones, tenemos que especificar el
nombre del modulo y el nombre de la funci´on, separados por un
punto. A esto se le llama notaci´on de punto:
decibelio = math.log10 (17.0)
angulo = 1.5
altura = math.sin(angulo)
2 Funcion
La primera sentencia da a decibelio el valor del logaritmo de 17, en
base 10. Hay tambi´en una funci´on llamada log que toma
logaritmos en base e.
La tercera sentencia halla el seno del valor de la variable angulo. sin
y las otras funciones trigonom´etricas (cos, tan, etc.) toman sus
argumentos en radianes. Para convertir de grados a radianes,
puede dividir por 360 y multiplicar por 2*pi. Por ejemplo, para
hallar el seno de 45 grados, calcule primero el a´ngulo en radianes
y luego halle el seno:
grados = 45
angulo = grados * 2 * math.pi / 360.0
math.sin(angulo)
La constante pi tambi´en es parte del m´odulo math. Si se sabe la
geometr´ıa, puede verificar el resultado compara´ndolo con el de la
ra´ız cuadrada de 2, dividida entre 2.
>>> math.sqrt(2) / 2.0
0.707106781187
3.5. Composicio´n
Igual que con las funciones matem´aticas, las funciones de Python
se pueden componer; eso quiere decir que se usa una expresio´n
como parte de otra. Por ejemplo, puede usar cualquier expresio´n
como argumento de una funci´on:
x = math.cos(angulo + pi/2)
Esta sentencia toma el valor de pi, lo divide entre dos y le an˜ade el
resultado al valor de angulo. La suma se pasa luego como
argumento a la funci´on cos. Tambi´en puede tomar el resultado de
una funci´on y pas´arselo como argumento a otra:
x = math.exp(math.log(10.0))
Esta sentencia encuentra el logaritmo en base e de 10 y luego
eleva e a ese exponente. El resultado queda asignado a x.
3.6. An˜adir funciones nuevas
Hasta ahora s´olo hemos usado las funciones que vienen incluidas
con Python, pero tambi´en es posible an˜adir nuevas funciones. La
creacio´n de nuevas funciones para resolver sus problemas
partigulares es una de las cosas mas u´tiles de los lenguajes de
programaci´on de prop´osito general.
3.6 An˜adir funciones nuevas 25
En contextos de programaci´on, funcio´n es una secuencia de
instrucciones con nombre, que lleva a cabo la operacio´n deseada.
Esta operacio´n se especifica en una definici´on de funcio´n. Las
funciones que hemos usado hsta ahora las han definido por
nosotros, y esas definiciones esta´n ocultas. Eso es bueno, ya que
nos permite usar funciones sin preocuparnos sobre los detalles de
sus definiciones. La sintaxis de la definici´on de una funci´on es:
def NOMBRE( LISTA DE PARAMETROS ):
SENTENCIAS
Puede inventarse el nombre que desee para su funci´on, con la
excepcio´n de que no puede usar las palabras reservadas de
Python. La lista de par´ametros especifica qu´e informaci´on, en
caso de haberla, ha de proporcionar para usar la funci´on nueva.
Puede haber cualquier nu´mero de sentencias dentro de la funci
´on, pero tienen que estar indentadas desde el margen izquierdo.
En los ejemplos de este libro se usar´a una indentaci´on de dos
espacios.
El primer par de funciones que escribiremos no tienen par´ametros,
de manera que su sintaxis es:
def nueva_linea():
print
Esta funci´on se llama nueva linea. Los par´entesis vac´ıos indican
que no tiene par´ametros. Contiene una u´nica sentencia, que
muestra como salida un cara´cter de nueva l´ınea (es lo que
sucede cuando utiliza una orden print sin argumentos).
Llamamos entonces a la funci´on nueva usando la misma sintaxis
que usamos para las funciones internas:
print "Primera linea."
nueva_linea()
print "Segunda linea."
The output of this program is
Primera linea.
Segunda linea.
Observe el espacio an˜adido que hay entre las dos l´ıneas. Si
quisi´eramos mas espacios, entre las l´ıneas, ¿qu´e har´ıamos?
Podemos llamar varias veces a la misma funci´on:
print "Primera linea."
nueva_linea()
nueva_linea()
nueva_linea()
print "Segunda linea."
2 Funcion
O bien podemos escribir una nueva funci´on que llamaremos
tresLineas, y que imprima tres nuevas l´ıneas:
def tresLineas():
nueva_linea()
nueva_linea()
nueva_linea()
print "Primera Linea."
tresLineas()
print "Segunda Linea."
Esta funci´on contiene tres sentencias, las cuales esta´n todas
indentadas con dos espacios. Puesto que la siguiente sentencia no
esta´ indentada, Python sabe que no es parte de la funci´on.
Observe los siguientes puntos con respecto a este programa:
1. Se puede llamar al mismo procedimiento repetidamente. De
hecho es bas- tante u´til hacerlo, adem´as de habitual.
2. Se puede llamar a una funci´on desde dentro de otra funci´on: en este
caso
tresLineas llama a nueva linea.
Hasta ahora puede no haber quedar claro por qu´e vale la pena
crear todas estas funciones nuevas. En realidad hay much´ısimas
razones, pero este ejemplo demuestra dos:
Crear una nueva funci´on le da la oportunidad de dar un
nombre a un grupo de sentencias. Las funciones simplifican
su programa al ocultar c´alculos complejos detr´as de o
´rdenes sencillas, y usar palabras de su propia lengua en vez
de c´odigo arcano.
Crear una nueva funci´on hace que el programa sea mas
pequen˜o, al elimi- nar c´odigo repetitivo. Por ejemplo, una
manera de imprimir nueve l´ıneas consecutivas es llamar a
tresLineas tres veces.
Como actividad, escriba una funcion llamada nueveLineas que
use tresLineas para imprimir nueve l´ıneas en blanco. ¿Co´mo
impri- mir´ıa 27 l´ıneas nuevas?
3.7. Las definiciones y el uso
Juntando los fragmentos de c´odigo de la seccio´n anterior, el
programa completo queda de la siguiente manera:
3.8 Flujo de ejecuci´on 27
def nueva_linea():
print
def tresLineas():
nueva_linea()
nueva_linea()
nueva_linea()
print "Primera Linea."
tresLineas()
print "Segunda Linea."
El presente programa contiene dos definiciones de funciones:
nueva linea y tresLineas. Las definiciones de funciones se ejecutan
como el resto de senten- cias, pero el efecto es crear una nueva
funci´on. Las sentencias del interior de la funci´on no se ejecutan
hasta que se llama a la funci´on, y la definici´on de la funci´on no
genera salida.
Como era de esperar, tiene que crear una funci´on antes de poder
ejecutarla. En otras palabras, la definici´on de la funci´on tiene que
ejecutarse antes de la primera vez que se la invoque.
Como actividad, pruebe a ejecutar este programa moviendo las
tres u´ltimas sentencias al principio del programa. Registre qu´e
mensaje de error obtiene usted.
Como segunda actividad, pruebe a tomar la versi´on del programa
que funcionaba y a mover la defincio´n de nueva linea m´as abajo
que la definicio´n de tresLineas . ¿Qu´e ocurre cuando ejecuta el
programa?
3.8. Flujo de ejecucio´n
Para asegurarse de que una funci´on se define antes de su primer
uso, tiene que conocer el orden en el que se ejecutan las
sentencias; a esto se le llama flujo de ejecuci´on.
La ejecucio´n comienza siempre por la primera sentencia del
programa. Las sen- tencias se ejecutan a razo´n de una cada vez, en
orden, hasta que se alcanza una llamada a una funci´on.
Las definiciones de funciones no alteran el flujo de ejecucio´n del
programa, pero recuerde que las sentencias que hay dentro de la
funci´on no se ejecutan hasta que se hace la llamada a la funci´on.
Aunque no es habitual, puede definir una
2 Funcion
funci´on dentro de otra. En este caso, la definici´on de funci´on
interior no se ejecuta hasta que no se llama a la funci´on exterior.
Las llamadas a funciones son como un desv´ıo en el flujo de
ejecucio´n. En lugar de ir a la siguiente sentencia, el flujo salta
hasta la primera l´ınea de la funci´on a la que se llama, ejecuta
todas las sentencias que encuentre all´ı, y vuelve a retomar la
ejecucio´n en el punto donde lo dej´o.
Esto suena bastante sencillo... hasta que se acuerda de que una
funci´on puede llamar a otra. Mientras estamos en medio de una
funci´on, podr´ıamos vernos obligados a abandonarla e ir a
ejecutar sentencias en otra funci´on mas. Pero mientras estamos
en esta nueva funci´on, ¡podr´ıamos salirnos y ejecutar otra funci
´on m´as!
Afortunadamente, a Python se le da bien tomar nota de donde esta´,
de manera que cada vez que se completa una funci´on, el programa
retoma el punto en donde lo dej´o en la funci´on que hizo la
llamada. Cuando llega al final del programa, termina.
¿Cua´l es la moraleja de toda esta historia? Cuando est´e leyendo
un programa, no lo lea desde la parte superior a la inferior. En
lugar de eso, siga el flujo de ejecucio´n.
3.9. Par´ametros y argumentos
Algunas de las funciones internas que hemos usado precisan de
argumentos, los valores que controlan c´omo la funci´on lleva a
cabo su tarea. Por ejemplo, si desea encontrar el seno de un nu
´mero, tiene que indicar de qu´e nu´mero se trata. As´ı pues, sin
toma como argumento un valor num´erico.
Algunas funciones toman m´as de un argumento, como pow, que
toma dos argu- mentos: la base y el exponente. Dentro de la funci
´on, los valores que se le han pasado se asignan a variables
llamadas par´ametros.
He aqu´ı un ejemplo de una funci´on definida por el usuario, que
toma un par´ame- tro:
def imprimeDoble(paso):
print paso, paso
Esta funci´on toma un u´nico argumento y se lo asigna a un par
´ametro llamado paso. El valor del par´ametro (en este punto todav
´ıa no tenemos ni idea de cua´l ser´a) se imprime dos veces,
seguido por un cara´cter de nueva l´ınea. El nombre paso se eligio´
para sugerir que el nombre que le d´e a un par´ametro depende de
usted, pero en general es mejor que elija un nombre mas
ilustrativo que paso.
3.10 Las variables y los par´ametros son locales 29
La funci´on imprimeDoble sirve con cualquier tipo (de dato) que se
pueda im- primir:
>>> imprimeDoble(’Jam´on’) Jam
´on Jam´on
>>> imprimeDoble(5)
5 5
>>> imprimeDoble(3.14159)
3.14159 3.14159
En la primera llamada a la funci´on, el argumento es una cadena;
en la segunda es un entero, y en la tercera es un nu´mero de coma
flotante.
Las mismas reglas de composici´on que se aplican a las funciones
internas se aplican tambi´en a las funciones definidas por el
usuario, as´ı que puede usar cualquier tipo de expresio´n como
argumento de imprimeDoble.
>>> imprimeDoble(’Jam´on’*4) Jam´onJam
´onJam´onJam´on Jam´onJam´onJam´onJam´on
>>> imprimeDoble(math.cos(math.pi))
-1.0 -1.0
Como de costumbre, se evalu´a la expresio´n antes de ejecutar la
funci´on, de modo que imprimeDoble devuelve Jam´onJam´onJam´onJam
´on Jam´onJam´onJam´onJam´on en lugar de ’Jam´on’*4’Jam´on’*4.
Asimismo podemos usar una variable como argumento:
>>> latoya = ’Dafne, es mitad laurel mitad ninfa’
>>> imprimeDoble(latoya)
Dafne, es mitad laurel mitad ninfa. Dafne, es mitad laurel mitad ninfa.
Observe un aspecto realmente importante en este caso: el nombre
de la variable que pasamos como argumento (latoya) no tiene nada
que ver con el nombre del par´ametro (paso). No importa c´omo se
llamaba el valor en su lugar original (el lugar desde donde se invoc
´o); aqu´ı en imprimeDoble llamamos a todo el mundo paso.
3.10. Las variables y los par´ametros son locales
Cuando crea una variable dentro de una funci´on, s´olo existe
dentro de dicha funci´on, y no puede usarla fuera de ella. Por
ejemplo, la funci´on
>>> def catDoble(parte1, parte2):
... cat = parte1 + parte2
... imprimeDoble(cat)
...
>>>
3 Funcion
chant1
chant2
"Pie Jesu domine,"
"Dona eis requiem."
part1 part2
cat
"Pie Jesu domine," "Dona eis requiem."
"Pie Jesu domine, Dona eis requiem."
bruce"Pie Jesu domine, Dona eis requiem."
toma dos argumentos, los concatena y luego imprime el resultado
dos veces. Podemos llamar a la funci´on con dos cadenas:
>>> cantus1 = "Die Jesu domine, "
>>> cantus2 = "Dona eis requiem."
>>> catDoble(cantus1, cantus2)
Die Jesu domine, Dona eis requiem. Die Jesu domine, Dona eis requiem.
Cuando catDoble termina, la variable cat se destruye. Si trat´asemos
de impri- mirla, obtendr´ıamos un error:
>>> print cat
NameError: cat
Los par´ametros tambi´en son locales. Por ejemplo, una vez fuera
de la funci´on imprimeDoble, no existe nada llamado paso. Si trata de
usarla, Python se que- jar´a.
3.11. Diagramas de pila
Para mantener el rastro de qu´e variables pueden usarse y donde, a
veces es u´til dibujar un diagrama de pila. Como los diagramas de
estado, los diagramas de pila muestran el valor de cada variable,
pero tambi´en muestran la funci´on a la que cada variable
pertenece.
Cada funci´on se representa por una caja con el nombre de la funci´on
junto a
´el. Los par´ametros y variables que pertenecen a una funci´on van
dentro. Por ejemplo, el diagrama de stack para el programa
anterior tiene este aspecto:
main
catTwice
printTwice
El orden de la pila muestra el flujo de ejecucio´n. imprimeDoble
fue llamado por catDoble y a catDoble lo invoc´o main , que es
un nombre especial de la
3.12 Funciones con resultado 31
funci´on m´as alta. Cuando crea una variable fuera de cualquier
funci´on, pertenece a main .
En cada caso, el par´ametro se refiere al mismo valor que el
argumento corres- pondiente. As´ı que parte1 en catDoble tiene el
mismo valor que cantus1 en main .
Si sucede un error durante la llamada a una funci´on, Python
imprime el nombre de la funci´on y el nombre de la funci´on que la
llamo, y el nombre de la funci´on que llam´o a ´esa, y as´ı hasta
main .
Por ejemplo, si intentamos acceder a cat desde imprimeDoble,
provocaremos un NameError:
Traceback (innermost last):
File "test.py", line 13, in main
catDoble(cantus1, cantus2)
File "test.py", line 5, in catDoble
imprimeDoble(cat)
File "test.py", line 9, in imprimeDoble
print cat
NameError: cat
Esta lista de funciones de llama traceback (traza inversa). Le dice a
usted en qu´e archivo de programa sucedio´ el error, y en qu´e l
´ınea, y qu´e funciones se ejecutaban en ese momento. Tambi´en
muestra la l´ınea de c´odigo que causo´ el error.
F´ıjese en la similaridad entre la traza inversa y el diagrama de pila.
No es una coincidencia.
3.12. Funciones con resultado
Seguramente ha notado ya que algunas de las funciones que
estamos usando, igual que las funciones matem´aticas, devuelven
un resultado. Otras funciones, como nueva linea, llevan a cabo una
acci´on pero no devuelven un valor. Ello suscita varias preguntas:
1. ¿Qu´e sucede si llama usted a uana funci´on y no hace nada
con el resultado (es decir, no lo asigna a una variable ni lo usa
como parte de una expresio´n m´as amplia)?
2. ¿Qu´e sucede si usa una funci´on sin resultado como parte de
una expresio´n, por ejemplo nueva linea() + 7?
3 Funcion
3. ¿Se pueden escribir funciones que devuelvan resultados, o
debemos limi- tarnos a funcinoes simples como nueva linea e
imprimeDoble?
La respuesta a la tercera pregunta es “s´ı, puede escribir funciones
que devuelvan valores”, y lo haremos en el cap´ıtulo 5.
Como actividad final, consteste a las otras dos preguntas
intentando hacerlas en la pra´ctica. Cada vez que tenga una
duda sobre lo que es legal o ilegal en Python, perguntar al int
´erprete sera´ una buena manera de averiguarlo.
3.13. Glosario
llamada a funcio´n: Una sentencia que ejecuta una funci´on. Est´a
compuesta por el nombre de la funci´on m´as una lista de
argumentos encerrados entre par´entesis.
argumento: Valor que se le pasa a una funci´on cuando se la llama.
El valor se asigna al par´ametro correspondiente de la funci
´on.
valor de retorno: Es el resultado de una funci´on. Si se usa una
llamada a funci´on a modo de expresio´n, el valor de retorno es el
valor de la expresio´n. conversi´on de tipo: Una sentencia expl´ıcita
que toma un valor de un tipo y
calcula el valor correspondiente de otro tipo.
coerci´on: Conversio´n tipos que ocurre automaticamente de
acuerdo con las reglas de coercio´n de Python.
m´odulo: Fichero que contiene una coleccio´n de funciones y clases
relacionadas. notaci´on de punto: La sintaxis para llamar a una
funci´on de otro modulo, especificando el nombre del m´odulo,
seguido por un punto y el nombre de
la funci´on.
funcio´n: Secuencia de sentencias etiquetadas que llevan a cabo
determinada operacio´n de utilidad. Las funciones pueden
tomar par´ametros o no, y pueden producir un resultado o no.
definici´on de funcio´n: Sentencia que crea una nueva funci´on,
especificando su nombre, par´ametros y las sentencias que
ejecuta.
flujo de ejecuci´on: Orden en el que se ejecutan las sentencias
durante la eje- cucio´n de un programa.
3.13 Glosario 33
par´ametro: Nombre que se usa dentro de una funci´on para
referirse a el valor que se le pasa como argumento.
variable local: variable definida dentro de una funci´on. Las
variables locales s´olo pueden usarse dentro de su funci´on.
diagrama de pila: Representaci´on gr´afica de una pila de
funciones, sus varia- bles y los valores a los que se refieren.
traza inversa: (traceback en ingl´es) Una lista de las funciones en
curso de ejecucio´n, presentadas cuando sucede un error en
tiempo de ejecucio´n. notaci´on de punto traza inversa
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Cap´ıtulo 4
Condicionales y
recursividad
4.1. El operador mo´dulo
El operador m´odulo funciona con enteros (y expresiones enteras),
y devuelve el resto de dividir el primer operando entre el segundo.
En Python, el operador de m´odulo es el signo de tanto por ciento
( ). La sintaxis es la misma de los otros operadores:
>>> cociente = 7 / 3
>>> print cociente
2
>>> resto = 7 3
>>> print resto
1
As´ı, 7 dividido entre 3 da 2 con 1 de resto.
El operador de m´odulo resulta ser soprendentemente u´til. Por
ejemplo, puede comprobar si un nu´mero es divisible entre otro: si
x y es cero, entonces x es divisible entre y.
Tambi´en puede usar el operador m´odulo para extraer el d´ıgito
mas a la derecha de un nu´mero. Por ejemplo, x 10 devuelve el d
´ıgito mas a la derecha de x (en base 10). De forma similar, x 100
devuelve los dos u´ltimos d´ıgitos.
3 Condicionales y
4.2. Expresiones booleanas
Una expresi´on booleana es una expresio´n que es cierta o falsa. En
Python, una expresio´n que es cierta tiene el valor 1, y una expresio
´n que es falsa tiene el valor 0.
El operador == compara dos valores y entrega una expresio´n booleana:
>>> 5 == 5
1
>>> 5 == 6
0
En la primera sentencia, los dos operandos son iguales, as´ı que la
expresio´n se evalu´a como 1 (verdadero); en la segunda sentencia,
5 no es igual a 6, as´ı que obtenemos 0 (falso).
El operador == es uno de los operadores de comparacio´n; los otros son:
x != y # x no es igual a 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
Aunque probablemente estas operaciones le resulten familiares, los
s´ımbolos en Python son diferentes de los matem´aticos. Un error
habitual es utilizar un signo igual sencillo (=) en lugar del doble
(==). Recuerde que = es un operador de asignaci´on y == es un
operador de comparacio´n. Adem´as, no existen =< ni =>.
4.3. Operadores lo´gicos
Hay tres operadores l´ogicos: and, or, y not. La sema´ntica
(significado) de estos operadores es similar a sus significados en
ingl´es. Por ejemplo, x >0 and x <10 es verdadero s´olo si x es
mayor que 0 y menor que 10.
n 2 == 0 or n 3 == 0 es verdadero si cualquiera de las condiciones es
verda- dera, o sea, si el nu´mero es divisible por 2 o por 3.
Finalmente, el operador not niega una expresio´n booleana, de forma que
not(x
>y) es cierto si (x >y) es falso, o sea, si x es menor o igual que y.
Hablando estrictamente, los operandos de los operadores logicos
deber´ıan ser expresiones booleanas, pero Python no es muy
estricto. Cualqueir nu´mero que no sea cero se interpreta como
“verdadero”.
4.4 Ejecuci´on condicional 37
>>> x = 5
>>> x and 1
1
>>> y = 0
>>> y and 1
0
En general, este tipo de cosas no se considera buen estilo. Si
quiere comparar un valor con cero, deber´ıa hacerlo expl
´ıcitamente.
4.4. Ejecucio´n condicional
Para escribir programas u´tiles, casi siempre necesitamos la
capacidad de com- probar ciertas condiciones y cambiar el
comportamiento del programa en conso- nancia. Las sentencias
condicionales nos dan esta capacidad. La forma mas sencilla es la
sentencia if:
if x > 0:
print "x es positivo"
La expresio´n booleana tras el if se llama condicio´n. Si es
verdadera, entonces la sentencia indentada se ejecuta. Si la
condicio´n no es verdadera, no pasa nada. Como otras sentencias
compuestas, if consta de una cabecera y un bloque de
sentencias:
CABECERA:
PRIMERA SENTENCIA
...
ULITMA SENTENCIA
La cabecera comienza con una nueva l´ınea y termina con el signo
de dos puntos. Los elementos indentados que siguen se llaman
bloque de la sentencia. La primera sentencia no indentada marca el
fin del bloque. Un bloque de sentencias dentro de una sentencia
compuesta recibe el nombre de cuerpo de la sentencia. No hay l
´ımite a la cantidad de sentencias que pueden aparecer en el
cuerpo de una sentencia if, pero debe haber al menos una. A
veces, es u´til tener un cuerpo sin sentencias, (normalmente como
reserva de espacio para algo de c´odigo que todav´ıa no ha
escrito). En tales casos, puede usted utilizar la sentencia pass, que
no hace nada.
4.5. Ejecucio´n alternativa
Una segunda forma de la sentencia if es la ejecucio´n alternativa,
en la que hay dos posibilidades, y la condicio´n determina cua´l de
ellas se ejecuta. La sintaxis
3 Condicionales y
tiene este aspecto:
if x 2 == 0:
print x, "es par"
else:
print x, "es impar"
Si el resto cuando se divide x entre 2 es cero, entonces sabemos
que x es par, y este programa muestra un mensaje a tal efecto. Si
la condicio´n es falsa, se ejecuta el segundo lote de sentencias.
Puesto que la condicio´n debe ser verdadera o falsa, se ejecutara´
exactamente una de las alternativas. Llamamos ramas a las
posibilidades porque son ramas del flujo de ejecucio´n.
Como un aparte, si piensa que querr´a comprobar con frecuencia
la paridad de nu´meros, quiz´a desee “envolver” este c´odigo en
una funci´on:
def imprimeParidad(x):
if x 2 == 0:
print x, "es par"
else:
print x, "es impar"
Ahora tiene una funci´on llamada imprimeParidad que muestra el
mensaje apro- piado para cada nu´mero entero que usted le pase.
Llame a esta funci´on de la manera siguiente:
>>> imprimeParidad(17)
>>> imprimeParidad(y+1)
4.6. Condiciones encadenadas
A veces hay m´as de dos posibilidades y necesitamos mas de dos
ramas. Una forma de expresar tal computacio´n es un conditional
encadenado:
if x < y:
print x, "es menor que", y
elif x > y:
print x, "es mayor que", y
else:
print x, "y", y, "son iguales"
elif es una abreviatura de ”else if”. De nuevo, s´olo se ejecutara´
una rama. No hay l´ımite al nu´mero de sentencias elif, pero s
´olo se permite una sentencia else (que puede omitirse) y debe ser
la u´ltima rama de la sentencia:
if eleccion == ’A’:
funcionA()
elif eleccion == ’B’:
4.7 Condiciones anidadas 39
funcionB()
elif eleccion == ’C’:
funcionC()
else:
print "Eleccion no valida."
Las condiciones se comprueban en orden. Si la primera es falsa, se
comprueba la siguiente, y as´ı. Si una de ellas es cierta, se ejecuta
la rama correspondiente y termina la sentencia. Incluso si es cierta
mas de una condicio´n, s´olo se ejecuta la primera rama verdadera.
Como ejercicio, envuelva estos ejemplos en funciones llamadas
compara(x, y) y resuelve(eleccion).
4.7. Condiciones anidadas
Una condicio´n puede estar anidada dentro de otra. Pod´ıamos
haber escrito as´ı el ejemplo de tricotom´ıa:
~~if x == y:
~~~~print x, "y", y, "son iguales"
~~else:
~~~~if x < y:
~~~~~~print x, "es menor que", y
~~~~else:
~~~~~~print x, "es mayor que", y
La condicio´n externa que contiene dos ramas. La primera rama
contiene una sentencia simple de salida. La segunda rama contiene
otra sentencia if, que tiene dos ramas en s´ı misma. Estas dos
ramas son ambas sentencias de salida de datos, aunque podr´ıan
ser igualmente sentencias condicionales.
Aunque la indentaci´on de las sentencias hace la estructura
evidente, las condi- ciones anidadas en seguida se vuelven dif´ıciles
de leer. En general es una buena idea evitarlas cuando pueda.
Los operadores l´ogicos suelen facilitar un modo de simplificar
las sentencias condicionales anidadas. Por ejemplo, podemos
reescribir el c´odigo siguiente con un s´olo condicional:
if 0 < x:
if x < 10:
print "x es un n´umero positivo de un d´ıgito."
La sentencia print s´olo se ejecuta si conseguimos superar ambos
condicionales, as´ı que podemos usar el operador and:
4 Condicionales y
if 0 < x and x < 10:
print "x es un n´umero positivo de un d´ıgito."
Estos tipos de condiciones son habituales, por lo que Python nos
proporciona una sintaxis alternativa similar a la notaci´on matem
´atica:
if 0 < x < 10:
print "x es un n´umero positivo de un d´ıgito."
Esta condicio´n es sema´nticamente la misma que la expresio´n
booleana compues- ta y que el condicional anidado.
4.8. La sentencia return
La sentencia return le permite terminar la ejecucio´n de una funci
´on antes de alcanzar su final. Una razo´n para usarla es detectar
una condicio´n de error:
import math
def imprimeLogaritmo(x):
if x <= 0:
print "Solo numeros positivos, por favor."
return
result = math.log(x)
print "El log de x es", result
La funci´on imprimeLogaritmo toma un par´ametro llamado x. Lo
primero que hace es comprobar si x es menor o igual que cero,
en cuyo caso muestra un mensaje de error y luego usa return
para salir de la funci´on. El flujo de la ejecucio´n vuelve
inmediatamente al llamante y no se ejecutan las l´ıneas restantes
de la funci´on.
Recuerde que para usar una funci´on del modulo math tiene que importarlo.
4.9. Recursividad
Ya mencionamos que es legal que una funci´on llame a otra, y de
ello hemos visto ya varios ejemplos. Olvidamos mencionar que
tambi´en es legal que una funci´on se llame a s´ı misma. Puede no
resultar evidente por qu´e es bueno esto, pero viene a resultar
una de las cosas m´as interesantes y curiosas que puede hacer un
programa. Examine por ejemplo la siguiente funci´on:
4.9 Recursividad 41
def cuenta_atras(n):
if n == 0:
print "Despegando!"
else:
print n
cuenta_atras(n-1)
cuenta atras espera que su par´ametro, n, sea un entero positivo. Si
n el par´ame- tro es cero, muestra la palabra “Despegando!”. En
otro caso, muestra n y luego llama a la funci´on llamada cuenta
atras (ella misma) pas´andole como argu- mento n-1.
¿Qu´e sucede si llamamos a la funci´on de la siguiente manera?
>>> cuenta_atras(3)
La ejecucio´n de cuenta atras comienza con n=3, y puesto que n no
es cero, da como salida el valor 3, y luego se llama a s´ı misma ...
La ejecucio´n de cuenta atras comienza con n=2, y puesto
que n no es cero, muestra el valor 2 y luego se llama a s´ı
misma ...
La ejecucio´n de cuenta atras comienza con n=1, y
puesto que n no es cero, muestra el valor 1, y
luego se llama a s´ı misma...
La ejecucio´n de cuenta atras comienza
con n=0, y puesto que n es cero, muestra
la palabra “Des- pegando!” y luego
retorna.
La cuenta atras que dio n=1 retorna.
La cuenta atras que dio n=2 retorna.
La cuenta atras que dio n=3 retorna.
Y entonces ya esta´ de vuelta en main (menudo viaje). De
manera que la salida completa presenta el siguiente aspecto:
3
2
1
Despegando!
Como segundo ejemplo, consideremos de nuevo las funciones nuevaLinea
and
tresLineas.
def nuevaLinea():
print
4 Condicionales y
def tresLineas():
nuevaLinea()
nuevaLinea()
nuevaLinea()
Aunque todas funcionan, no ser´ıan de mucha ayuda si quisiera
mostrar 2 l´ıneas nuevas o 106. Una mejor alternativa ser´a:
def nLineas(n):
if n > 0:
print
nLineas(n-1)
Este programa es parecido a cuenta atras; mientras n sea mayor que
cero, muestra una nueva l´ınea, y luego se llama a s´ı misma para
mostrar >n-1 nuevas l´ıneas m´as. De esta manera, el nu´mero total
de nuevas l´ıneas es 1 + (n-1), que si rescata su a´lgebra ver´a que
es n.
El proceso por el que una funci´on se llama a s´ı misma se llama
recursividad, y dichas funciones se denominan recursivas.
4.10. Diagramas de pila para funciones recursi-
vas
El la Secci´on 3.11 utilizamos un diagrama de pila para representar
el estado de un programa durante la llamada de una funci´on. El
mismo tipo de diagrama puede hacer m´as f´acil interpretar una
funci´on recursiva.
Cada vez que se llama a una funci´on, Python crea un nuevo
marco para la funci´on, que contiene sus variables locales y par
´ametros. En el caso de una funci´on recursiva, puede haber m´as
de un marco en la pila al mismo tiempo.
La figura muestra un diagrama de pila para cuenta atras, invocada
con n = 3:
n3
n2
n1
n0
4.11 Recursividad infinita 43
main
countdown
countdown
countdown
countdown
Como es habitual, en lo alto de la pila esta´ el marco de main .
Est´a vac´ıa porque no hemos ninguna variable sobre main ni le
hemos pasado ningu´n par´ametro.
Los cuatro marcos de cuenta atras tienen valores diferentes para el par
´ametro
n. El fondo de la pila, donde n=0, se llama caso base. No hace una
llamada recursiva, de manera que no hay m´as marcos.
con el para´metro n=4. Como
actividad, dibuje un diagrama de pila para nLineas, invocada
4.11. Recursividad infinita
Si una recursio´n no alcanza nunca el caso base, seguira´ haciendo
llamadas re- cursivas para siempre y nunca terminar´a. Esta
circunstancia se conoce como recursividad infinita, y generalmente
no se la considera una buena idea. Este es un programa m´ınimo
con recursividad infinita:
def recurre():
recurre()
El la mayor´ıa de los entornos de programaci´on, un programa con
recursividad infinita no se ejecutara´ realmente para siempre.
Python informar´a de un mensaje de error cuando se alcance el
nivel m´aximo de recursividad:
File "<stdin>", line 2, in recurse
(98 repetitions omitted)
File "<stdin>", line 2, in recurse
RuntimeError: Maximum recursion depth exceeded
4 Condicionales y
Esta traza inversa es un poco mayor que la que vimos en el cap´ıtulo
anterior.
¡Cuando sucede el error, hay 100 marcos recurre en la pila!
Como actividad, escriba una funci´on con recursividad infinita y
ejecu´tela en el int´erprete de Python.
4.12. Entrada por teclado
Los programas que hemos escrito hasta ahora son un poco
maleducados en el sentido de que no aceptan entradas de datos del
usuario. Simplemente hacen lo mismo siempre.
Python proporciona funciones internas que obtienen entradas
desde el teclado. La m´as sencilla se llama raw input. Cuando
llamamos a esta funci´on, el pro- grama se detiene y espera a
que el usuario escriba algo. Cuando el usuario pulsa la tecla
Return o Enter, el programa se reanuda y raw input devuelve lo
que el usuario escribio´ como tipo string:
>>> entrada = raw_input () A
qu´e est´as esperando?
>>> print entrada
A qu´e est´as esperando?
Antes de llamar a raw input es conveniente mostrar un mensaje que
le pida al usuario el dato solicitado. Este mensaje se llama indicador
(prompt en ingl´es). Puede proporcionarle un indicador a raw input
como argumento:
>>> nombre = raw_input ("C´omo te llamas? ") C
´omo te llamas? H´ector, h´eroe de los
Troyanos!
>>> print nombre
H´ector, h´eroe de los Troyanos!
Si espera que la entrada sea un entero, utilice la funci´on input. Por
ejemplo:
>>> indicador = 
... "Cu´al es la velocidad de una golondrina sin carga?n"
>>> velocidad = input (indicador)
Si el usuario teclea una cadena de nu´meros, se convertira´ en
un entero y se asignar´a a velocidad. Por desgracia, si el usuario
escribe algo que no sea un d´ıgito, el programa dar´a un error:
>>> velocidad = input (indicador)
Cu´al es la velocidad de una golondrina sin carga?
Se refiere usted a la golondrina europea o a la africana?
SyntaxError: invalid syntax
Para evitar este tipo de error, generalmente es buena idea usar raw
input para obtener una cadena y usar entonces las funciones de
conversio´n para convertir a otros tipos.
4.13 Glosario 45
4.13. Glosario
operador m´odulo: Operador, sen˜alado con un signo de tanto por
ciento ( ), que trabaja sobre enteros y devuelve el resto
cuando un nu´mero se divide entre otro.
expresi´on booleana: Una exprersio´n que es cierta o falsa.
operador de comparacio´n: Uno de los operadores que comparan dos
valores:
==, !=, >, <, >= y <=.
operador l´ogico: Uno de los operadores que combinan expresiones
booleanas:
and, or y not.
sentencia condicional: Sentencia que controla el flujo de ejecucio´n
de un pro- grama dependiendo de cierta condicio´n.
condicio´n: La expresio´n booleana de una sentencia condicional
que determina qu´e rama se ejecutara´.
sentencia compuesta: Estructura de Python que esta´ formado por
una cabe- cera y un cuerpo. La cabecera termina en dos
puntos (:). El cuerpo tiene una sangr´ıa con respecto a la
cabecera.
bloque: Grupo sentencias consecutivas con el mismo sangrado.
cuerpo: En una sentencia compuesta, el bloque de sentencias que
sigue a la cabecera de la sentencia.
anidamiento: Una estructura de programa dentro de otra; por
ejemplo, una sentencia condidional dentro de una o ambas
ramas de otra sentencia condicional.
recursividad: El proceso de volver a llamar a la funci´on que se esta
´ ejecutando en ese momento.
caso base: En una funci´on recursiva, la rama de una sentencia
condicional que no ocasiona una llamada recursiva.
recursividad infinita: Funci´on que se llama a s´ı misma
recursivamente sin alcanzar nunca el caso base. A la larga una
recursio´n infinita provocara´ un error en tiempo de ejecucio
´n.
indicador: indicador visual que invita al usuario a introducir datos.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Cap´ıtulo 5
Funciones productivas
5.1. Valores de retorno
Algunas de las funciones internas que hemos usado, como las
funciones math o funciones matem´aticas, han producido
resultados. Llamar a la funci´on genera un nuevo valor, que
normalmente asignamos a una variable pasa usar como parte de
una expresio´n.
import math
e = math.exp(1.0)
altura = radio * math.sin(angulo)
Pero hasta ahora, ninguna de las funciones que hemos escrito ha
devuelto un valor.
En este cap´ıtulo escribiremos funciones que devuelvan valores, que
llamaremos funciones productivas, a falta de un nombre mejor. El
primer ejemplo es area, que devuelve el a´rea de un c´ırculo con un
radio dado:
import math
def area(radio):
temporal = math.pi * radio**2
return temporal
Ya hemos visto antes la sentencia return, pero en una funci´on
productiva la sentencia return incluye un valor de retorno. Esta
sentencia quiere decir “re- torna inmediatamente de la funci´on y
usa la siguiente expresio´n como valor de retorno”. La expresio´n
dada puede ser arbitrariamente complicada; as´ı pues, podr´ıamos
haber escrito esta funci´on m´as concisamente:
4 Funciones
def area(radio):
return math.pi * radio**2
Por otra parte, las variables temporales como temporal suelen hacer
mas f´acil el depurado.
A veces es u´til disponer de varias sentencias de retorno, una en
cada rama de una condicio´n:
def valorAbsoluto(x):
if x < 0:
return -x
else:
return x
Puesto que estas sentencias return esta´n en una condicio´n
alternativa, s´olo se ejecutara´ una de ellas. En cuanto se ejecuta
una de ellas, la funci´on termina sin ejecutar ninguna de las
sentencias siguientes.
El c´odigo que aparece despu´es de una sentencia return o en
cualquier otro lugar donde el flujo de ejecucio´n no pueda llegar,
recibe el nombre de c´odigo muerto. En una funci´on productiva es
una buena idea asegurarse de que cualquier posible recorrido del
programa alcanza una sentencia return. Por ejemplo:
def valorAbsoluto(x):
if x < 0:
return -x
elif x > 0:
return x
Este programa no es correcto porque si resulta que x vale 0,
entonces no se cum- ple ninguna de ambas condiciones y la funci
´on termina sin alcanzar la setencia return. En este caso, el valor de
retorno es un valor especial llamado None:
>>> print valorAbsoluto(0)
None
Como actividad, escriba una funci´on comparar que devuelva 1 si x
>y , 0 si x == y , y -1 si x <y .
5.2. Desarrollo de programas
Llegados a este punto, tendr´ıa que poder mirar a funciones Python
completas y adivinar qu´e hacen. Tambi´en, si ha hecho los
ejercicios, habr´a escrito algu- nas funcioncillas. Tal como vaya
escribiendo funciones mayores puede empezar
√
5.2 Desarrollo de programas 49
a experimentar m´as dificultades, especialmente con los errores
en tiempo de ejecucio´n y los sema´nticos.
Para lidiar con programas de complejidad creciente, vamos a
sugerirle una t´ecni- ca que llamaremos desarrollo incremental. El
objetivo del desarrollo incre- mental es sustituir largas sesiones de
depuraci´on por la adici´on y prueba de pequen˜as porciones de c
´odigo en cada vez.
Por ejemplo, supongamos que desea encontrar la distancia entre
dos puntos, da- dos por las coordenadas (x1, y1) y (x2, y2). Por el
teorema de Pitagoras, podemos escribir la distancia es:
distancia = (x2 − x1)2
+ (y2 − y1)2
(5.1) El primer paso es considerar qu´e aspecto tendr´ıa una
funci´on distancia en Python. En otras palabras, ¿cua´les son las
entradas (par´ametros) y cua´l es la salida (valor de retorno)?
En este caso, los dos puntos son los par´ametros, que podemos
representar usando cuatro par´ametros. El valor de retorno es la
distancia, que es un valor en coma flotante.
Ya podemos escribir un bosquejo de la funci´on:
def distancia(x1, y1, x2, y2):
return 0.0
Obviamente, la funci´on no calcula distancias; siempre devuelve
cero. Pero es sint´acticamente correcta y se ejecutara´, lo que
significa que podemos probarla antes de complicarla m´as.
Para comprobar la nueva funci´on, tenemos que llamarla con valores de
ejemplo:
>>> def distancia(x1, y1, x2, y2):
... return 0.0
...
>>> distancia(1, 2, 4, 6)
0.0
>>>
Elegimos estos valores de tal forma que la distancia horizontal sea
igual a 3 y la distancia vertical sea igual a 4; de esa manera el
resultado es 5 (la hipotenusa del tri´angulo 3-4-5). Cuando se
comprueba una funci´on, es u´til saber la respuesta correcta.
Hasta el momento, hemos comprobado que la funci´on es
sintacticamente correc- ta, as´ı que podemos empezar a an˜adir l
´ıneas de c´odigo. Despu´es de cada cambio
5 Funciones
− −
incremental, comprobamos de nuevo la funci´on. Si en un momento
dado apare- ce un error, sabremos d´onde esta´ exactamente: en la
u´ltima l´ınea que hayamos an˜adido.
El siguiente paso en el c´alculo es encontrar las diferencias entre x2
x1 y y2 y1. Almacenaremos dichos valores en variables temporales
llamadas dx y dy y las imprimiremos.
def distancia(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
print "dx es", dx
print "dy es", dy
return 0.0
Si la funci´on funciona, valga la redundancia, las salidas deber´ıan
ser 3 y 4. Si es as´ı, sabemos que la funci´on recibe correctamente
los par´ametros y realiza correctamente el primer c´alculo. Si no, s
´olo hay unas pocas l´ıneas que revisar. Ahora calculamos la suma
de los cuadarados de dx y dy:
def distancia(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dalcuadrado = dx**2 + dy**2
print "dalcuadrado es: ", dalcuadrado
return 0.0
F´ıjese en que hemos eliminado las sentencias print que escribimos
en el pa- so anterior. Este c´odigo se llama andamiaje porque es u
´til para construir el programa pero no es parte del producto final.
De nuevo querremos ejecutar el programa en este estado y
comprobar la salida (que deber´ıa dar 25).
Por u´ltimo, si hemos importado el m´odulo math, podemos usar la funci´on
sqrt
para calcular y devolver el resultado:
def distancia(x1, y1, x2, y2):
dx = x2 - x1
dy = y2 - y1
dalcuadrado = dx**2 + dy**2
resultado = math.sqrt(dalcuadrado)
return resultado
Si esto funciona correctamente, ha terminado. Si no, podr´ıa ser
que quisiera usted imprimir el valor de resultado antes de la
sentencia return.
5.3 Composici´on 51
Al principio, deber´ıa an˜adir solamente una o dos l´ıneas de c
´odigo cada vez. Conforme vaya ganando experiencia, puede que
se encuentre escribiendo y de- purando trozos mayores. Sin
embargo, el proceso de desarrollo incremental puede ahorrarle
mucho tiempo de depurado.
Los aspectos fundamentales del proceso son:
1. Comience con un programa que funcione y hagale pequen˜os
cambios in- crementales. Si hay un error, sabra´ exactamente
donde esta´.
2. Use variables temporales para mantener valores intermedios,
de tal manera que pueda mostrarlos por pantalla y
comprobarlos.
3. Una vez que el programa est´e funcionando, tal vez prefiera
eliminar parte del andamiaje o aglutinar mu´ltiples sentencias
en expresiones compuestas, pero s´olo si eso no hace que el
programa sea dif´ıcil de leer.
Como actividad, utilice el desarrollo incremental para escribir una
funci´on de nombre hipotenusa que devuelva la longitud de la
hipo- tenusa de un tria´ngulo rect´angulo, dando como para
´metros los dos catetos. Registre cada estado del desarrollo
incremental segu´n vaya avanzando.
5.3. Composicio´n
Como seguramente a estas alturas ya supondr´a, se puede llamar a
una funci´on desde dentro de otra. Esta habilidad se llama composici
´on .
Como ejemplo, escribiremos una funci´on que tome dos puntos,
el centro del c´ırculo y un punto del per´ımetro, y calcule el a´rea
del c´ırculo.
Supongamos que el punto central esta´ almacenado en las variables
xc e yc, y que el punto del per´ımetro lo esta´ en xp e yp. El primer
paso es hallar el radio del c´ırculo, que es la distancia entre los dos
puntos. Afortunadamente hay una funci´on, distancia, que realiza
esta tarea:
radio = distancia(xc, yc, xp, yp)
El segundo paso es encontrar el a´rea de un c´ırculo con ese radio y
devolverla:
resultado = area(radio)
return resultado
Envolviendo todo esto en una funci´on, obtenemos:
def area2(xc, yc, xp, yp):
radio = distancia(xc, yc, xp, yp)
resultado = area(radio)
return resultado
5 Funciones
Hemos llamado a esta funci´on area2 para distinguirla de la funci
´on area definida anteriormente. S´olo puede haber una u´nica
funci´on con un determinado nombre dentro de un m´odulo.
Las variables temporales radio y area son u´tiles para el desarrollo y
el depu- rado, pero una vez que el programa esta´ funcionando,
podemos hacerlo mas conciso integrando las llamadas a las
funciones en una sola l´ınea:
def area2(xc, yc, xp, yp):
return area(distancia(xc, yc, xp, yp))
Como actividad, escriba una funci´on pendiente(x1, y1, x2, y2) que
devuelva la pendiente de la l´ınea que atraviesa los puntos (x1,
y1) y (x2, y2). Luego use esta funcion en una funci´on que se llame
intercepta(x1, y1, x2, y2) que devuelva la [[y-intercepta]] de la l
´ınea a trav´es de los puntos (x1, y1) y (x2, y2).
5.4. Funciones booleanas
Las funciones pueden devolver valores booleanos, lo que a menudo
es conveniente para ocultar complicadas comprobaciones dentro
de funciones. Por ejemplo:
def esDivisible(x, y):
if x y == 0:
return 1 # it’s true
else:
return 0 # it’s false
La funci´on lleva por nombre esDivisible. Es habitual dar a las
funciones boo- leanas nombres que suenan como preguntas s´ı/no.
Devuelve 1 o´ 0 para indicar si la x es o no divisibelo por y.
Podemos reducir el taman˜o de la funci´on aprovechandonos del
hecho de que la sentencia condicional que hay despu´es del if es
en s´ı misma una expresio´n booleana. Podemos devolverla
directamente, evitando a la vez la sentencia if:
def esDivisible(x, y):
return x y == 0
La siguiente sesio´n muestra a la nueva funci´on en acci´on:
>>> esDivisible(6, 4)
0
>>> esDivisible(6, 3)
1
El uso m´as comu´n para las funciones booleanas es dentro de
sentencias condi- cionales:
5.5 M´as recursividad 53
if esDivisible(x, y):
print "x es divisible entre y"
else:
print "x no es divisible entre y"
Puede parecer tentador escribir algo como:
if esDivisible(x, y) == 1:
Pero la comparacio´n extra es innecesaria.
Como actividad, escriba una funcion estaEntre(x, y, z) que de-
vuelva 1 en caso de que y <= x <= z y que devuelva 0 en cualquier
otro caso.
5.5. M´as recursividad
Hasta ahora, usted ha aprendido solamente un pequen˜o
subconjunto de Python, pero puede que le interese saber que ese
subconjunto es ya un lenguaje de programaci´on completo; con esto
queremos decir que cualquier cosa que pueda computarse se puede
expresar en este lenguaje. Cualquier programa que se haya escrito
alguna vez puede reescribirse utilizando u´nicamente las caracter
´ısticas del lenguaje que ha aprendido hasta el momento (de hecho,
necesitar´ıa algunas o´rdenes para controlar dispositivos como el
teclado, el rato´n, los discos, etc, pero eso es todo).
Probar tal afirmaci´on es un ejercicio nada trivial, completado por
primera vez por Alan Turing, uno de los primeros cient´ıficos
inform´aticos (algunos argumen- tar´an que era un matem´atico,
pero muchos de los cient´ıficos inform´aticos pione- ros
comenzaron como matem´aticos). En correspondencia, se la conoce
como la tesis de Turing. Si estudia un curso de Teor´ıa de la
Computaci´on, tendr´a opor- tunidad de ver la prueba.
Para darle una idea de lo que puede hacer con las herramientas
que ha aprendido hasta ahora, evaluaremos una serie de
funciones matem´aticas que se definen recursivamente. Una
definici´on recursiva es semejante a una definici´on circular, en el
sentido de que la definici´on contiene una referencia a lo que se
define. Una definici´on verdaderamente circular no es muy u´til:
frangoso: adjetivo que describe algo que es frangoso
Si usted viera esa definici´on en el diccionario, se quedar´ıa
confuso. Por otra parte, si ha buscado la definici´on de la funci´on
matem´atica factorial, habr´a visto algo sejemante a lo siguiente:
5 Funciones
0! = 1
n! = n · (n − 1)!
Esta definici´on establece que el factoral de 0 es 1, y que el
factorial de cualquier otro valor, n, es n multiplicado por el factorial
de n − 1.
As´ı pues, 3! es 3 veces 2!, que es 2 veces 1!, que es una vez 0!.
Juntandolos todos,
, 3! es igual a 3 veces 2 veces 1 vez 1, que es 6.
Si puede escribir una definici´on recursiva de algo, normalmente
podr´a escribir un programa de Python para evaluarlo. El primer
paso es decidir cua´les son los par´ametros para esta funci´on. Con
poco esfuerzo llegar´a a la conclusio´n de que factorial toma un u
´nico par´ametro:
def factorial(n):
Si resultase que el argumento fuese 0, todo lo que hemos de hacer es
devolver 1:
def factorial(n):
if n == 0:
return 1
En otro caso, y he aqu´ı la parte interesante, tenemos que hacer
una llamada recursiva para hallar el factorial de n − 1 y luego
multiplicarlo por n:
def factorial(n):
if n == 0:
return 1
else:
recursivo = factorial(n-1)
resultado = n * recursivo
return resultado
El flujo de ejecucio´n de este programa es similar al de cuenta
atras de la Secci´on 4.9. Si llamamos a factorial con el valor 3:
Puesto que 3 no es 0, tomamos la segunda rama y calculamos el factorial de
n-1...
Puesto que 2 no es 0, tomamos la segunda rama y
calculamos el factorial de n-1...
Puesto que 1 no es 0, tomamos la segunda rama
y calcu- lamos el factorial de n-1...
Puesto que 0 es 0, tomamos la primera
rama y devolvemos el valor 1 sin hacer
mas llamadas re- cursivas.
n0
n1recurse1return1
n2recurse1return2
n3recurse2return6
5.6 Acto de fe 55
El valor de retorno (1) se multiplica por n, que es 1, y
se devuelve el resultado.
El valor de retorno (1) se multiplica por n, que es 2, y se
devuelve el resultado.
El valor de retorno (2) se multiplica por n, que es 3, y el resultado
6, se convierte en el valor de retorno de la llamada a la funci´on
que comenzo´ todo el proceso. He aqu´ı el aspecto que tiene el
diagrama de pila para esta secuencia de llamadas a funci´on:
main
6
factorial
2
factorial
1
factorial
1
factorial
Los valores de retorno se muestran segu´n se pasan hacia la parte
superior de la pila. En cada marco, el valor de retorno es el valor
de resultado, que es el producto de n por recursivo.
N´otese que en el u´ltimo marco las variables locales recursivo y
resultado no existen porque la rama que las crea no se ejecuta.
5.6. Acto de fe
Seguir el flujo de ejecucio´n es una de las maneras de leer
programas; pero puede volverse r´apidamente una tarea laber
´ınitca. La alternativa es lo que llamamos el “acto de fe”. Cuando
llegamos a una funci´on, en lugar de seguir el flujo de ejecucio´n,
damos por sentado que la funci´on trabaja correctamente y devuelve
el valor apropiado.
De hecho, usted ya practica dicho salto de fe cuando usa funciones
internas. Cuando llama a math.cos o a math.exp, no examina la
implementaci´on de
5 Funciones
−
dichas funciones. Simplemente da por sentado que funcionan
porque los que escribieron las bibliotecas internas de Python son
buenos programadores.
Lo mismo se aplica cuando llama a una de las funciones
programadas por usted. Por ejemplo en la Secci´on 5.4,
escribimos una funci´on llamada esDivisible que determina si un
nu´mero es divisible por otro. Una vez que nos hayamos
convencido de que dicha funci´on es correcta, comprobando y
examinando el c´odigo, podremos usar la funci´on sin tener
siquiera que volver a mirar el c´odigo otra vez.
Lo mismo vale para los programas recursivos. Cuando llegue a la
llamada recur- siva, en lugar de seguir el flujo de ejecucio´n, tendr
´ıa que dar por supuesto que la llamada recursiva funciona (es
decir, devuelve el resultado correcto) y luego preguntarse:
“suponiendo que puedo hallar el factorial de n 1, ¿puedo hallar el
factorial de n?” En este caso, esta´ claro que s´ı puede, multiplic
´andolo por n. Por supuesto, es un tanto extran˜o dar por
supuesto que la funci´on esta´ bien cuando ni siquiera ha acabado
de escribirla, pero precisamente por eso se llama acto de fe.
5.7. Un ejemplo m´as
En el ejemplo anterior, usamos variables temporales para ir
apuntando los re- sultados y para hacer que el c´odigo fuese mas
facil de depurar, pero podr´ıamos habernos ahorrado unas cuantas
l´ıneas:
def factorial(n):
if n == 0:
return 1
else:
return n * factorial(n-1)
De ahora en adelante, tenderemos a usar la versio´n mas concisa,
pero le reco- mendamos que utilice la versio´n m´as expl´ıcita
mientras se halle desarrollando c´odigo. Cuando lo tenga
funcionando, lo podr´a acortar, si se siente inspirado. Despu´es de
factorial, el ejemplo m´as comu´n de una funci´on matem´atica
re- cursivamente definida es fibonacci, que presenta la siguiente
definici´on:
fibonacci(0) = 1
fibonacci(1) = 1
fibonacci(n) = fibonacci(n − 1) + fibonacci(n − 2);
Traducido a Python, es como sigue:
5.8 Comprobaci´on de tipos 57
def fibonacci (n):
if n == 0 or n == 1:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
Si intenta seguir el flujo de ejecucio´n aqu´ı, incluso para valores
relativamente pequen˜os de n, le puede dar un dolor de cabeza.
Pero si confiamos en el acto de fe, si da por supuesto que las dos
llamadas recursivas funcionan correctamente, entonces estara´
claro que obtiene el resultado correcto al sumarlas juntas.
5.8. Comprobaci´on de tipos
¿Qu´e sucede si llamamos a factorial y le damos 1.5 como argumento?
>>> factorial (1.5)
RuntimeError: Maximum recursion depth exceeded
Tiene todo el aspecto de una recursio´n infinita. Pero, ¿c´omo ha
podido ocurrir? Hay una condicio´n de salida o caso base: cuando n
== 0. El problema es que el valor de n yerra el caso base.
En la primera llamada recursiva, el valor de n es 0.5. En la
siguiente vez su valor es -0.5. A partir de ah´ı, se vuelve m´as y mas
pequen˜o, pero nunca ser´a 0.
Tenemos dos opciones. Podemos intentar generalizar la funci´on
factorial para que trabaje con nu´meros de coma flotante, o
podemos hacer que factorial compruebe el tipo de su par´ametro.
La primera opcio´n se llama funci´on gamma, y esta´ m´as all´a del
objetivo de este libro. As´ı pues, tomemos la segunda.
Podemos usar la funci´on type para comparar el tipo del par´ametro
con el tipo de un valor entero conocido (por ejemplo 1). Ya que
estamos en ello, podemos asegurarnos de que el par´ametro sea
positivo:
def factorial (n):
if type(n) != type(1):
print "El factorial est´a definido s´olo para enteros."
return -1
elif n < 0:
print "El factorial est´a definido s´olo para enteros
positivos."
return -1
elif n == 0:
return 1
else:
return n * factorial(n-1)
5 Funciones
Ahora tenemos tres condiciones de salida o casos base. El primero
filtra los nu´meros no enteros. El segundo evita los enteros
negativos. En ambos casos, se muestra un mensaje de error y se
devuelve un valor especial, -1, para indicar a quien hizo la llamada
a la funci´on que algo fue mal:
>>> factorial (1.5)
El factorial esta definido solo para enteros.
-1
>>> factorial (-2)
El factorial esta definido solo para enteros positivos.
-1
>>> factorial ("paco")
El factorial esta definido solo para enteros.
-1
Si pasamos ambas comprobaciones, entonces sabemos que n es un
entero positivo y podemos probar que la recursio´n termina.
Este programa muestra un patr´on que se llama a veces guardi´an.
Las primeras dos condicionales actu´an como guardianes,
protegiendo al c´odigo que sigue de los valores que pudieran
causar errores. Los guardianes hacen posible demostrar la
correccio´n del c´odigo.
5.9. Glosario
funcio´n productiva: Funci´on que devuelve un valor de retorno.
valor de retorno: El valor obtenido como resultado de una llamada a
una funci´on.
variable temporal: Variable utilizada para almacenar un valor
intermedio en un c´alculo complejo.
c´odigo muerto: Parte de un programa que no podr´a ejecutarse
nunca, a me- nudo debido a que aparece tras una sentencia de
return.
None: Valor especial de Python que devuelven funciones que o bien no
tienen sentencia de return o bien tienen una sentencia de return sin
argumento. desarrollo incremental: Un m´etodo de desarrollo de
programas que busca evitar el depurado an˜adiendo y probando una
pequen˜a cantidad de c´odigo
en cada paso.
andamiaje: El c´odigo que se usa durante el desarrollo del
programa pero que no es parte de la versio´n final.
5.9 Glosario 59
guardi´an: Una condicio´n que comprueba y maneja circunstancias
que pudieran provocar un error.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
bruce
5
7
Cap´ıtulo 6
Iteracio´n
6.1. Asignaci´on mu´ltiple
Es posible que haya descubierto que es posible hacer mas de una
asignaci´on a una misma variable. El efecto de la nueva asignaci´on
es redirigir la variable de manera que deje de remitir al valor
antiguo y empieze a remitir al valor nuevo.
bruno = 5
print bruno,
bruno = 7
print bruno
La salida del programa es 5 7, ya que la primera vez que
imprimimos bruno su valor es 5, y la segunda vez su valor es 7. La
coma al final de la primera sentencia print impide que se imprima
una nueva l´ınea en ese punto, por eso ambos valores aparecen en
la misma l´ınea.
He aqu´ı el aspecto de una asignaci´on mu´ltiple en un diagrama de estado:
Cuando hay asignaciones mu´ltiples a una variable, es
especialmente importante distinguir entre una sentencia de
asignaci´on y una sentencia de igualdad. Puesto que Python usa el
s´ımbolo = para la asignaci´on, es tentador interpretar una
sentencia como a = b como sentencia de igualdad. Pero no lo es.
6 Itera
Para empezar, la igualdad es commutativa, y la asignaci´on no lo
es. Por ejemplo en matem´aticas si a = 7 entonces 7 = a. Pero en
Python la sentencia a = 7 es legal, y 7 = a no lo es.
Y lo que es m´as, en matem´aticas, una sentencia de igualdad es
verdadera todo el tiempo. Si a = b ahora, entonces a siempre ser´a
igual a b. En Python, una sentencia de asignaci´on puede hacer
que dos variables sean iguales, pero no tienen por qu´e quedarse
as´ı.
a = 5
b = a # a y b son ahora iguales
a = 3 # a y b ya no son iguales
La tercera l´ınea cambia el valor de a pero no cambia el valor de b, y
por lo tanto ya dejan de ser iguales. En algunos lenguajes de
programaci´on, se usa para la asignaci´on un s´ımbolo distinto,
como <- o como :=, para evitar la confusio´n. Aunque la asignaci´on
mu´ltiple es u´til a menudo, debe usarla con cuidado. Si los valores
de las variables van cambiando constantemente en distintas partes
del programa, podr´ıa suceder que el c´odigo sea dif´ıcil de leer y
mantener.
6.2. La sentencia while
Una de las tareas para las que los computadores se usan con
frecuencia es la automatizaci´on de tareas repetitivas. Repetir
tareas similares o id´enticas es algo que los computadores hacen
bien y las personas no hacen tan bien.
Hemos visto dos programas, nLineas y cuenta atras, que usan la
recursividad para llevar a cabo la repeticio´n, que tambi´en se
llama iteracio´n. Por ser la iteraci´on tan habitual, Python
proporciona como lenguaje varias caracter´ısticas que la hacen m
´as f´acil. La primera caracter´ıstica que vamos a considerar es la
sentencia while.
E´ste es el aspecto de cuenta atras con una sentencia while:
def cuenta_atras(n):
while n > 0:
print n
n = n-1
print "Despegando!"
Como eliminamos la llamada recursiva, esta funci´on no es recursiva.
Casi podr´ıa leer una sentencia while como si fuera ingl´es
(castellano “mien- tras”). Quiere decir que “Mientras n sea mayor
que cero, continu´a mostrando el valor de n y despu´es resta´ndole
1 al valor de n. Cuando llegues a cero, muestra la palabra
“Despegando!”.
6.2 La sentencia while 63
M´as formalmente, el flujo de ejecucio´n de una sentencia while es el
siguiente:
1. Evaluar la condicio´n, devolviendo 0 o 1.
2. Si la condicio´n es falsa (0), salir de la sentencia while y
continuar la ejecucio´n en la siguiente sentencia.
3. Si la condicio´n es verdadera (1), ejecutar cada una de las
sentencias en el cuerpo del bucle while, y luego volver al paso
1.
El cuerpo esta´ formado por todas las sentencias bajo el
encabezado que tienen el mismo sangrado.
Este tipo de flujo de llama bucle porque el tercer paso vuelve de
nuevo arriba. N´otese que si la condicio´n es falsa la primera vez
que se atraviesa el bucle, las sentencias del interior del bucle no se
ejecutan nunca.
El cuerpo del bucle debe cambiar el valor de una o mas variables
de manera que, llegado el momento, la condicio´n sea falsa y el
bucle termine. En caso contrario, el bucle se repetira´ para
siempre, que es lo que se llama bucle infinito. Una infinita fuente
de diversio´n para los cient´ıficos inform´aticos es la observaci´on
de que las instrucciones del champu´ “lavar, aclarar, repetir”, son
un bucle infinito. En el caso de cuenta atras, podemos probar que
el bucle terminar´a porque sabemos que el valor de n es finito, y
podemos ver que el valor de n disminuye cada vez que se atraviesa
el bucle (cada iteraci´on), de manera que ea la larga tenemos que
llegar a cero. En otros casos no es tan facil decirlo:
def secuencia(n):
while n != 1:
print n,
if n 2 == 0: # n es par
n = n/2
else: # n es impar
n = n*3+1
La condicio´n de este bucle es n != 1, de manera que el bucle
continuar´a hasta que n sea 1, que har´a que la condicio´n sea
falsa.
En cada iteraci´on, el programa muestra como salida el valor de n y
luego com- prueba si es par o impar. Si es par, el valor de n se
divide entre dos. Si es impar, el valor se sustituye por 3n+1. Por
ejemplo, si el valor de comienzo (el argumento pasado a la
secuencia) es 3, la secuencia resultante es 3, 10, 5, 16, 8, 4, 2, 1.
Puesto que n a veces aumenta y a veces disminuye, no hay una
prueba obvia de que n alcance alguna vez el valor 1, o de que el
programa vaya a terminar. Para algunos valores particulares de n,
podemos probar la terminaci´on. Por ejemplo,
6 Itera
si el valor de inicio es una potencia de dos, entonces el valor de n
ser´a par cada vez que se pasa a trav´es del bucle, hasta que
lleguemos a 1. El ejemplo anterior acaba con dicha secuencia,
empezando por 16.
Dejando aparte valores particulares, la pregunta interesante es si
podemos pro- bar que este programa terminar´a para todos los
valores de n. Hasta la fecha, nadie ha sido capaz de probarlo o
negarlo.
Como actividad, reescriba la funci´on nLines de la secci´on 4.9 utili-
zando iteracio´n en lugar de recursividad.
6.3. Tablas
Una de las cosas para las que resultan buenos los bucles es para
generar datos ta- bulares. Antes de que los computadores
estuvieran disponibles de forma masiva, la gente ten´ıa que calcular
a mano logaritmos, senos, cosenos y otras funciones matem´aticas.
Para facilitarlo, los libros de matem´aticas conten´ına largas tablas
donde aparec´ıan los valores de estas funciones. Confeccionar estas
tablas era una tarea lenta y pesada, y el resultado estaba lleno de
erratas.
Cuando los computadores aparecieron en escena, una de las
primeras reacciones fue “¡Qu´e bueno! Podemos usar los
computadores para generar las tablas, as´ı no habr´a errores”.
Resulto´ cierto (casi), pero no se vio mas alla. Poco despu´es los
computadores y las calculadoras cient´ıficas se hicieron tan ubicuas
que las tablas resultaron obsoletas.
Bueno, casi. Resulta que para ciertas operaciones, los
computadores usan tablas para obtener un valor aproximado, y
luego ejecutan c´alculos para mejorar la aproximaci´on. En
algunos casos, ha habido errores en las tablas subyacentes; el m´as
famoso estaba en la tabla que el Pentium de Intel usaba para llevar
a cabo la divisi´on de coma flotante.
Aunque una tabla logar´ıtmica ya no es tan u´til como lo fuera
antan˜o, todav´ıa constituye un buen ejemplo de iteraci´on. El
siguiente programa muestra una secuencia de valores en la
columna izquierda y sus logaritmos en la columna derecha:
x = 1.0
while x < 10.0:
print x, ’t’, math.log(x)
x = x + 1.0
El t representa un cara´cter de tabulacio´n.
6.3 Tablas 65
Tal como se van mostrando en la pantalla caracteres y cadenas, un
sen˜alador invisible llamado cursor lleva la cuenta de donde ir´a el
pr´oximo cara´cter. Tras una sentencia print, el cursor va
normalmente al principio de la l´ınea siguiente. El cara´cter de
tabulaci´on hace que el cursor se desplace a la derecha hasta que
alcance uno de los marcadores de tabulaci´on. Los tabuladores son
u´tiles para alinear columnas de texto, como en la salida del
programa anterior:
1.0 0.0
2.0 0.69314718056
3.0 1.09861228867
4.0 1.38629436112
5.0 1.60943791243
6.0 1.79175946923
7.0 1.94591014906
8.0 2.07944154168
9.0 2.19722457734
Si estos valores le parecen raros, recuerde que la funci´on log usa
como base e. Debido a que las potencias de dos son muy
importantes en las ciencias de la computacio´n, generalmente
querremos hallar los logaritmos en relacio´n con la base dos. Para
llevarlo a cabo, podemos usar la siguiente formula:
lo
g2
Cambiar la sentencia de salida
a:
x =
logex
loge2
(6.1)
print x, ’t’, math.log(x)/math.log(2.0)
devuelve
Podemos ver que 1, 2, 4 y 8 son potencias de dos, porque sus
logaritomos de base 2 son nu´meros enteros. Si quisi´eramos
encontrar los logaritmos de otras potencias de dos, podr´ıamos
modificar el programa de la siguiente manera:
x = 1.0
while x < 100.0:
1.0 0.0
2.0 1.0
3.0 1.58496250072
4.0 2.0
5.0 2.32192809489
6.0 2.58496250072
7.0 2.80735492206
8.0 3.0
9.0 3.16992500144
6 Itera
print x, ’t’, math.log(x)/math.log(2.0)
x = x * 2.0
Ahora, en lugar de an˜adir algo a x cada vez que atravesamos el
bucle, que devuelve una secuencia aritm´etica, multiplicamos x por
algo, devolviendo una secuencia geom´etrica. El resultado es:
1.0 0.0
2.0 1.0
4.0 2.0
8.0 3.0
16.0 4.0
32.0 5.0
64.0 6.0
Debido a que usamos caracteres de tabulaci´on entre las columnas,
la posici´on de la segunda columna no depende del nu´mero de d
´ıgitos de la primera columna. Las tablas logar´ıtimicas quiz´as ya
no sean u´tiles, pero conocer las potencias de dos no ha dejado de
serlo para los cient´ıficos inform´aticos.
cias de dos hasta 65536 (es decir, 216
). Impr´ımala y memor´ıcela.
Como actividad, modifique el programa para que muestre las
poten-
El cara´cter de barra invertida en ’t’ indica el principio de una
secuencia de escape. Las secuencias de escape se usan para
representar caracteres invisibles como tabulaciones y retornos
de carro. La secuencia n representa un retorno de carro.
Una sentencia de escape puede aparecer en cualquier lugar de una
cadena; en el ejemplo, la secuencia de escape del tabulador es la u
´nica de la cadena.
¿Co´mo cree que puede representar una barra invertida en una cadena?
Como ejercicio, escriba un u´nica cadena que
presente
esta
salida.
6.4. Tablas de dos dimensiones
Una tabla de dos dimensiones es una tabla en la que Usted elige
una fila y una columna y lee el valor de la intersecci´on. Un buen
ejemplo es una tabla de
6.5 Encapsulado y generalizaci´on 67
multiplicar. Supongamos que desea imprimir una tabla de
multiplicar para los valores del 1 al 6.
Una buena manera de comenzar es escribir un bucle sencillo que
imprima los mu´ltiplos de 2, todos en una l´ınea.
i = 1
while i <= 6:
print 2*i, ’t’,
i = i + 1
print
La primera l´ınea inicializa una variable lllamada i, que actuar´a
como contador, o variable de bucle. Conforme se ejecuta el bucle,
el valor de i se incrementa de 1 a 6. Cuando i vale 7, el bucle
termina. Cada vez que se atraviesa el bucle, imprimimos el
valor 2*i seguido por tres espacios.
De nuevo, la coma de la sentencia print suprime el salto de l´ınea.
Despu´es de completar el bucle, la segunda sentencia print crea
una l´ınea nueva.
La salida de este programa es:
2 4 6 8 10 12
Hasta ahora, bien. El siguiente paso es encapsular y generalizar.
6.5. Encapsulado y generalizaci´on
Por “encapsulado” generalmente se entiende tomar una pieza de c
´odigo y envol- verla en una funci´on, permiti´endole obtener las
ventajas de todo aquello para lo que valen las funciones. Hemos
visto dos ejemplos de encapsulado, cuando escribimos
imprimeParidad en la Secci´on 4.5 y esDivisible en la Secci´on 5.4.
Por “generalizaci´on” entendemos tomar algo espec´ıfico, como
imprimir los mu´lti- plos de 2, y hacerlo m´as general, como
imprimir los mu´ltiplos de cualquier entero. He aqu´ı una funci´on
que encapsula el bucle de la seccio´n anterior y la generaliza para
imprimir mu´ltiplos de n.
def imprimeMultiplos(n):
i = 1
while i <= 6:
print n*i, ’t’,
i = i + 1
print
6 Itera
Para encapsular, todo lo que hubimos de hacer fue an˜adir la
primera l´ınea, que declara el nombre de la funci´on y la lista de
par´ametros. Para generalizar, todo lo que tuvimos que hacer fue
sustituir el valor 2 por el par´ametro n.
Si llamamos a esta funci´on con el argumento 2, obtenemos la
misma salida que antes. Con el argumento 3, la salida es:
3 6 9 12 15 18
y con argumento 4, la salida es
4 8 12 16 20 24
A estas alturas es probable que haya adivinado c´omo vamos a
imprimir una tabla de multiplicaci´on: llamaremos a
imprimeMultiplos repetidamente con diferentes argumentos. De
hecho, podemos a usar otro bucle:
i = 1
while i <= 6:
imprimeMultiplos(i)
i = i + 1
Observe hasta qu´e punto este bucle es similar al que hay en
el interior de imprimeMultiplos. Todo lo que hicimos fue sustituir
la sentencia print por una llamada a una funci´on.
La salida de este programa es una tabla de multiplicaci´on:
6.6. M´as encapsulacio´n
Para dar m´as ejemplos de encapsulacio´n, tomaremos el c´odigo
del final de la Secci´on 6.5 y lo envolveremos en una funci´on:
def imprimeTablaMult():
i = 1
while i <= 6:
imprimeMultiplos(i)
i = i + 1
El proceso que mostramos aqu´ı es un plan de desarrollo habitual. Se
desa- rrolla gradualmente el c´odigo an˜adiendo l´ıneas fuera de
cualquier funci´on o en
1 2 3 4 5 6
2 4 6 8 10 12
3 6 9 12 15 18
4 8 12 16 20 24
5 10 15 20 25 30
6 12 18 24 30 36
1
i2
3
n 3 i
1
2
6.7 Variables locales 69
el int´erprete. Cuando conseguimos que funcionen, se extraen y se
envuelven en una funci´on.
Este plan de desarrollo es especialmente si, al comenzar a escribir,
no sabe c´omo dividir el programa en funciones. Este enfoque le
permite disen˜arlo sobre la marcha.
6.7. Variables locales
Quiz´a se est´e preguntando c´omo podemos usar la misma
variable tanto en imprimeMultiplos como en imprimeTablaMult. ¿No
habr´a problemas cuan- do una de las funciones cambie los
valores de la variable?
La respuesta es no, ya que la variable i en imprimeMultiplos y la
variable i
in imprimeTablaMult no son la misma variable.
Las variables creadas dentro de una funci´on son locales. No puede
acceder a una variable local fuera de su funci´on “hu´esped”. Eso
significa que es posible tener mu´ltiples variables con el mismo
nombre, siempre que no est´en en la misma funci´on.
El diagrama de pila de esta funci´on muestra claramente que las
dos variables llamadas i no son la misma variable. Pueden
referirse a diferentes valores, y cambiar uno no afecta al otro.
printMultTable
printMultiples
El valor de i en imprimeTablaMult va desde 1 hasta 6. En el
diagrama, re- sulta ser 3. El pr´oximo recorrido del bucle ser´a 4.
En cada recorrido del bucle, imprimeTablaMult llama a
imprimeMultiplos con el valor actual de i como argumento. Ese
valor se asigna al par´ametro n.
Dentro de imprimeMultiplos, el valor de i va desde 1 hasta 6.
En el diagrama, resulta ser 2. Los cambios en esta variable no
tienen ningu´n efecto sobre el valor de i en imprimeTablaMult.
Es habitual y perfectamente legal tener diferentes variables
locales con el mismo nombre. En especial, los nombres i, j y k
se suelen usar como variables de
7 Itera
bucle. Si evita usarlas en una funci´on porque las utiliz´o en algu´n
otro lugar, probablemente consiga que el programa sea mas dif´ıcil
de leer.
6.8. M´as generalizaci´on
Como otro ejemplo de generalizaci´on, imagine que desea un
programa que im- prima una tabla de multiplicaci´on de cualquier
taman˜o, y no s´olo la tabla de 6x6. Podr´ıa an˜adir un par´ametro
a imprimeTablaMult:
def imprimeTablaMult(mayor):
i = 1
while i <= mayor:
imprimeMultiplos(i)
i = i + 1
Hemos sustituido el valor 6 con el par´ametro mayor. Si ahora se
llama a
imprimeTablaMult con el argumento 7, obtenemos:
lo que es correcto, excepto por el hecho de que seguramente
queremos que la tabla est´e cuadrada, con el mismo nu´mero de
filas que de columnas. Para hacer- lo, an˜adimos otro par´ametro a
imprimeMultiplos, a fin de especificar cua´ntas columnas tendr´ıa
que tener la tabla.
S´olo para fastidiar, llamaremos tambi´en a este par´ametro mayor,
para demostrar que diferentes funciones pueden tener par´ametros
con el mismo nombre (al igual que las variables locales). Aqu´ı
tenemos el programa completo:
def imprimeMultiplos(n, mayor):
int i = 1
while i <= mayor:
print n*i, ’t’,
i = i + 1
print
def imprimeTablaMult(mayor):
int i = 1
while i <= mayor:
1 2 3 4 5 6
2 4 6 8 10 12
3 6 9 12 15 18
4 8 12 16 20 24
5 10 15 20 25 30
6 12 18 24 30 36
7 14 21 28 35 42
6.9 Funciones 71
imprimeMultiplos(i, mayor)
i = i + 1
N´otese que al an˜adir un nuevo par´ametro, tuvimos que cambiar
la primera l´ınea de la funci´on (el encabezado de la funci´on), y
tuvimos tambi´en que cambiar el lugar donde se llama a la funci´on
en imprimeTablaMult.
Segu´n lo esperado, este programa genera una tabla cuadrada de 7x7:
Cuando generaliza correctamente una funci´on, a menudo se
encuentra con que el programa resultante tiene capacidades que
Usted no pensaba. Por ejemplo, quiz´a observe que la tabla de
multiplicaci´on es sim´etrica, porque ab = ba, de manera que todas
las entradas de la tabla aparecen dos veces. Puede ahorrar tinta
imprimiendo s´olo la mitad de la tabla. Para hacerlo, s´olo tiene que
cambiar una l´ınea de imprimeTablaMult. Cambie
imprimeMultiplos(i, mayor)
po
r
imprimeMultiplos(i, i)
y obtendr´a
1
2 4
3 6 9
4 8 12 16
5 10 15 20 25
6 12 18 24 30 36
7 14 21 28 35 42 49
Como actividad, siga o trace la ejecuci´on de esta nueva versi´on de
imprimeTablaMult para hacerse una idea de c´omo funciona.
6.9. Funciones
Hasta el momento hemos mencionado en alguna ocasio´n “todas
las cosas para las que sirven las funciones”. Puede que ya se est´e
preguntando qu´e cosas son exactamente. He aqu´ı algunas de las
razones por las que las funciones son u´tiles:
1 2 3 4 5 6 7
2 4 6 8 10 12 14
3 6 9 12 15 18 21
4 8 12 16 20 24 28
5 10 15 20 25 30 35
6 12 18 24 30 36 42
7 14 21 28 35 42 49
7 Itera

Al dar un nombre a una secuencia de sentencias, hace que su
programa sea m´as f´acil de leer y depurar.
Dividir un programa largo en funciones le permite separar
partes del pro- grama, depurarlas aisladamente, y luego
recomponerlas en un todo.
Las funciones facilitan tanto la recursividad como la iteraci´on.
Las funciones bien disen˜adas son generalmente u´tiles para
mas de un pro- grama. Una vez escritas y depuradas, puden
reutilizarse.
6.10. Glosario
asignacio´n mu´ltiple: Hacer m´as de una asignaci´on a la misma
variable du- rante la ejecucio´n de un programa.
iteracio´n: La ejecucio´n repetida de un conjunto de sentencias
por medio de una llamada recursiva a una funci´on o un bucle.
bucle: Sentencia o grupo de sentencias que se ejecutan
repetidamente hasta que se cumple una condicio´n de
terminaci´on.
bucle infinito: Bucle cuya condicio´n de terminaci´on nunca se cumple.
cuerpo: Las sentencias que hay dentro de un bucle.
variable de bucle: Variable que se usa para determinar la condicio
´n de ter- minaci´on de un bucle.
tabulador: Car´acter especial que hace que el cursor se mueva
hasta la siguiente marca de tabulaci´on en la l´ınea actual.
nueva l´ınea: Un cara´cter especial que hace que le cursor se
mueva al inicio de la siguiente l´ınea.
cursor: Un marcador invisible que sigue el rastro de donde se
imprimir´a el siguiente cara´cter.
secuencia de escape: Car´acter de escape ( ) seguido por uno o mas
caracteres imprimibles, que se usan para designar un cara´cter no
imprimible. encapsular: Dividir un programa largo y complejo en
componentes (como las funciones) y aislar los componentes unos
de otros (por ejemplo usando
variables locales).
6.10 Glosario 73
generalizar: Sustituir algo innecesariamente espec´ıfico (como es
un valor cons- tante) con algo convenientemente general
(como es una variable o par´ame- tro). La generalizaci´on hace
el c´odigo mas vers´atil, mas apto para reutili- zarse y algunas
veces incluso m´as f´acil de escribir.
plan de desarrollo: Proceso para desarrollar un programa. En este
cap´ıtulo, hemos mostrado un estilo de desarrollo basado en
desarrollar c´odigo para hacer cosas simples y espec´ıficas, y
luego encapsularlas y generalizarlas.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Cap´ıtulo 7
Cadenas
7.1. Un tipo de datos compuesto
Hasta el momento hemos visto tres tipos: int, float, y string. Las
cadenas son cuantitativamente diferentes de los otros dos porque
esta´n hechas de piezas menores: caracteres.
Los tipos que comprenden piezas menores se llaman tipos de datos
com- puestos. Dependiendo de qu´e estemos haciendo, podemos
querer tratar un tipo compuesto como una u´nica cosa o acceder a
sus partes. Esta ambigu¨edad es u´til. El operador corchete
selecciona un cara´cter suelto de una cadena.
>>> fruta = "banana"
>>> letra = fruta[1]
>>> print letra
La expresio´n fruta[1] selecciona el cara´cter nu´mero 1 de fruta.
La variable letra apunta al resultado. Cuando mostramos letra,
nos encontramos con una sorpresa:
a
La primera letra de "banana" no es a. A no ser que usted sea un
programador. Por perversas razones, los cient´ıficos de la
computacio´n siempre empiezan a contar desde cero. La 0-sima
letra (“cer´osima”) de "banana" es b. La 1-´esima (“un´esima”) es a,
y la 2-´esima (“dos´esima”) letra es n.
Si quiera la cer´osima letra de una cadena, simplemente pone 0,
o cualquier expresio´n de valor 0, entre los corchetes:
7 Caden
>>> letra = fruta[0]
>>> print letra
b
A la expresio´n entre corchetes se le llama ´ındice. Un ´ındice
identifica a un miembro de un conjunto ordenado, en este caso el
conjunto de caracteres de la cadena. El ´ındice indica cua´l quiere
usted, de ah´ı el nombre. Puede ser cualquier expresio´n entera.
7.2. Longitud
La funci´on len devuelve el nu´mero de caracteres de una cadena:
>>> fruta = "banana"
>>> len(fruta)
6
Para obtener la u´ltima letra de una cadena puede sentirse tentado
a probar algo como esto:
longitud = len(fruta)
ultima = fruta[longitud] # ERROR!
Eso no funcionar´a. Provoca un error en tiempo de ejecucio´n
IndexError: string index out of range. La razo´n es que no hay
una sexta letra en "banana". Como empezamos a contar por cero,
las seis letras esta´n numeradas del 0 al 5. Para obtener el u´ltimo
cara´cter tenemos que restar 1 de longitud:
longitud = len(fruta)
ultima = fruta[longitud-1]
De forma alternativa, podemos usar ´ındices negativos, que
cuentan hacia atr´as desde el final de la cadena. La expresio´n
fruta[-1] nos da la u´ltima letra. fruta[-2] nos da la penu´ltima, y
as´ı.
7.3. Recorrido y el bucle for
Muchos c´alculos incluyen el proceso de una cadena cara´cter a
cara´cter. A me- nudo empiezan por el principio, seleccionan cada
cara´cter por turno, hacen algo con ´el y siguen hasta el final. Este
patr´on de proceso se llama recorrido. Una forma de codificar un
recorrido es una sentencia while:
indice = 0
while indice < len(fruta):
letra = fruta[indice]
7.3 Recorrido y el bucle for 77
print letra
indice = indice + 1
Este bucle recorre la cadena y muestra cada letra en una l
´ınea distinta. La condicio´n del bucle es indice < len(fruta), de
modo que cuando indice es igual a la longitud de la cadena, la
condicio´n es falsa y no se ejecuta el cuerpo del bucle. El u´ltimo
cara´cter al que se accede es el que tiene el ´ındice len(fruta)-1,
que es el u´ltimo cara´cter de la cadena.
Como ejercicio, escriba una funci´on que tome una cadena como ar-
gumento y entregue las letras en orden inverso, una por l´ınea.
Es tan habitual usar un ´ındice para recorrer un conjunto de
valores que Python facilita una sintaxis alternativa m´as simple: el
bucle for:
for car in fruta:
print car
Cada vez que recorremos el bucle, se asigna a la variable car el
siguiente cara´cter de la cadena. El bucle continu´a hasta que no
quedan caracteres.
El ejemplo siguiente muestra c´omo usar la concatenacio´n junto a
un bucle for para generar una serie abeceda´rica. “Abeceda´rica”
es la serie o lista en la que cada uno de los elementos aparece en
orden alfab´etico. Por ejemplo, en el libro de Robert McCloskey
Make Way for Ducklings (Dejad paso a los patitos), los nombres de los
patitos son Jack, Kack, Lack, Mack, Nack, Ouack, Pack, y
Quack. Este bucle saca esos nombres en orden:
prefijos = "JKLMNOPQ"
sufijo = "ack"
for letra in prefijos:
print letra + sufijo
La salida del programa es:
Jack
Kack
Lack
Mack
Nack
Oack
Pack
Qack
Por supuesto, esto no es del todo correcto, porque “Ouack” y
“Quack” no esta´n correctamente escritos.
7 Caden
Como ejercicio, modifique el programa para corregir este error.
7.4. Porciones de cadenas
Llamamos porci´on a un segmento de una cadena. La seleccio´n de
una porci´on es similar a la seleccio´n de un cara´cter:
>>> s = "Pedro, Pablo, y Mar´ıa"
>>> print s[0:5]
Pedro
>>> print s[7:12]
Pablo
>>> print s[15:20] Mar
´ıa
El operador [n:m] devuelve la parte de la cadena desde el en
´esimo cara´cter hasta el “em´esimo”, incluyendo el primero pero
excluyendo el u´ltimo. Este com- portamiento contradice a nuestra
intuici´on; tiene mas sentido si imagina los
´ındices sen˜alando entre los caracteres, como en el siguiente diagrama:
indice 0 1 2 3 4 5 6
Si omite el primer ´ındice (antes de los dos puntos), la porci´on
comienza al principio de la cadena. Si omite el segundo ´ındice, la
porci´on llega al final de la cadena. As´ı:
>>> fruta = "banana"
>>> fruta[:3]
’ban’
>>> fruta[3:]
’ana’
¿Qu´e cree usted que significa s[:]?
7.5. Comparaci´on de cadenas
Los operadores de comparacio´n trabajan sobre cadenas. Para ver
si dos cadenas son iguales:
fruta " b a n a n a "
7.6 Las cadenas son inmutables 79
if palabra == "banana":
print "S´ı, no tenemos bananas!"
Otras operaciones de comparacio´n son u´tiles para poner
palabras en orden al- fab´etico:
if palabra < "banana":
print "Tu palabra," + palabra + ", va antes de banana."
elif palabra > "banana":
print "Tu palabra," + palabra + ", va despu´es de banana."
else:
print "Sı, no tenemos bananas!"
Sin embargo, deber´ıa usted ser consciente de que Python no
maneja las mayu´scu- las y minu´sculas como lo hace la gente.
Todas las mayu´suculas van antes de la minu´sculas. Como
resultado de ello:
Tu palabra, Zapato, va antes de banana.
Una forma comu´n de abordar este problema es convertir las
cadenas a un forma- to esta´ndar, como pueden ser las minu
´sculas, antes de realizar la comparacio´n. Un problema mayor es
hacer que el programa se d´e cuenta de que los zapatos no son
frutas.
7.6. Las cadenas son inmutables
Es tentador usar el operador [] en el lado izquierdo de una
asignaci´on, con la intenci´on de cambiar un cara´cter en una
cadena. Por ejemplo:
saludo = "Hola, mundo"
saludo[0] = ’M’ # ERROR!
print saludo
En lugar de presentar la salida Mola, mundo, este c´odigo presenta el
siguien- te error en tiempo de ejecucio´n TypeError: object doesn’t
support item assignment.
Las cadenas son inmutables, lo que significa que no puede cambiar
una cade- na existente. Lo m´as que puede hacer es crear una
nueva cadena que sea una variaci´on de la original:
saludo = "Hola, mundo"
nuevoSaludo = ’M’ + saludo[1:]
print nuevoSaludo
8 Caden
Aqu´ı la solucio´n es concatenar una nueva primera letra a una
porci´on de saludo. Esta operacio´n no tiene efectos sobre la
cadena original.
7.7. Una funcio´n “encuentra”
¿Qu´e hace la siguiente funci´on?
def encuentra(cad, c):
indice = 0
while indice < len(cad):
if cad[indice] == c:
return indice
indice = indice + 1
return -1
En cierto sentido, encuentra es lo contrario del operador []. En
lugar de tomar un ´ındice y extraer el cara´cter correspondiente,
toma un cara´cter y encuentra el ´ındice donde aparece el cara
´cter. Si el cara´cter no se encuentra, la funci´on devuelve -1.
Este es el primer ejemplo que hemos visto de una sentencia return
dentro de un bucle. Si cad[indice] == c, la funci´on vuelve
inmediatamente, escapando del bucle prematuramente.
Si el cara´cter no aparece en la cadena, el programa sale del bucle
normalmente y devuelve -1.
Este patr´on de computacio´n se llama a veces un recorrido
“eureka” porque en cuanto encontramos lo que buscamos,
podemos gritar “¡Eureka!” y dejar de buscar.
A modo de ejercicio, modifique la funci´on encuentra para que
acepte un tercer para´metro, el ´ındice de la cadena donde deber
´ıa empezar a buscar.
7.8. Bucles y conteo
El programa que sigue cuenta el nu´mero de veces que la letra a
aparece en una cadena:
7.9 El m´odulo “string” 81
fruta = "banana"
cuenta = 0
for car in fruta:
if car == ’a’:
cuenta = cuenta + 1
print cuenta
Este programa muestra otro patr´on de computacio´n llamado
contador. La variable cuenta se incializa a 0 y luego se incrementa
cada vez que se encuentra una a. (Incrementar es aumentar en uno;
es lo contario de decrementar, y sin relacio´n alguna con
“excremento”, que es un nombre.) Al salir del bucle, cuenta
contiene el resultado – el nu´mero total de aes.
Como ejercicio, encapsule este c´odigo en una funci´on
llamada cuentaLetras, y general´ıcela de forma que acepte la
cadena y la letra como para´metros.
Como un segundo ejercicio, reescriba esta funci´on para que en
lu- gar de recorrer la cadena, use la versi´on de tres para
´metros de encuentra del anterior.
7.9. El mo´dulo “string”
El m´odulo string contiene funciones u´tiles para manipular
cadenas. Como es habitual, tenemos que importar el m´odulo antes
de poder usarlo:
>>> import string
El m´odulo string incluye una funci´on llamada find que hace lo
mismo que la funci´on encuentra que escribimos. Para llamarla
debemos especificar el nombre del m´odulo y el nombre de la funci
´on por medio de la notaci´on de punto.
>>> fruta = "banana"
>>> indice = string.find(fruta, "a")
>>> print indice
1
Este ejemplo demuestra uno de los beneficios de los modulos:
ayudan a evitar las colisiones entre los nombres de las funciones
predefinidas y las definidas por el usuario. Al usar la notaci´on de
punto podr´ıamos especificar qu´e versio´n de find queremos en
caso de haberle daddo un nombre en ingl´es a nuestra funci´on. En
realidad, string.find es m´as general que nuestra versio´n. Para
empezar, puede encontrar subcadenas, no s´olo caracteres:
8 Caden
>>> string.find("banana", "na")
2
Adem´as, acepta un argumento adicional que especifica el ´ındice
en el que deber´ıa comenzar:
>>> string.find("banana", "na", 3)
4
O puede tomar dos argumentos adicionales que especifican un
intervalo de ´ındi- ces:
>>> string.find("sus", "s", 1, 2)
-1
En este ejemplo, la bu´squeda falla porque la letra s no aparece en
el intervalo de ´ındices desde 1 hasta 2 (sin incluir 2).
7.10. Clasificaci´on de caracteres
A menudo viene bien examinar un cara´cter y comprobar si es una
letra mayu´scu- la o minu´scula, o si es un cara´cter o un d´ıgito.
El modulo string proporciona varias constantes que son u´tiles
para estos menesteres.
La cadena string.lowercase contiene todas las letras que el sistema
conside- ra como minu´sculas. De forma similar, string.uppercase
contiene todas las mayu´sculas. Pruebe lo que sigue y vea qu´e
obtiene:
>>> print string.lowercase
>>> print string.uppercase
>>> print string.digits
Podemos usar estas constantes y find para clasificar caracteres.
Por ejemplo, si find(lowercase, c) devuelve un valor que no sea -1,
entonces c es una minu´scula:
def esMinuscula(c):
return find(string.lowercase, c) != -1
Alternativamente, podemos aprovecharnos del operador in, que
determina si un cara´cter aparece en una cadena:
def esMinuscula(c):
return c in string.lowercase
7.11 Glosario 83
Como una alternativa m´as, podemos usar el operador de
comparacio´n, aunque esta solucio´n s´olo sea pr´actica para el
alfabeto ingl´es:
def esMinuscula(c):
return ’a’ <= c <= ’z’
Si c esta´ entre a y z, tiene que ser una minu´scula.
Como ejercicio, explique qu´e versi´on de esMinuscula cree que es
m´as r´apida. ¿Puede pensar en otras razones aparte de la
velocidad para preferir una sobre la otra?
Otra constante definida en el m´odulo string puede sorprenderle
cuando la imprima:
>>> print string.whitespace
Los caracteres de whitespace mueven el cursor sin imprimir
nada. Crean los espacios en blanco entre los caracteres visibles
(al menos sobre papel blanco). La constante string.whitespace
contiene todos los caracteres de espacio en blanco, inclu´ıdos
espacio, tabulador (t), y salto de l´ınea (n).
Hay otras funciones u´tiles en el m´odulo string, pero este libro no
pretende ser un manual de referencia. Por otra parte, la Referencia
de la Biblioteca de Python s´ı lo es. Junto con un mont´on m´as de
documentaci´on, esta´ disponible en el sitio web de Python,
www.python.org.
7.11. Glosario
tipo de datos compuesto: Un tipo de datos en el que los valores
esta´n hechos de componentes o elementos que son a su vez
valores.
recorrer: Realizar de forma iterativa una operacio´n similar sobre
cada uno de los elementos de un conjunto.
´ındice: Una variable o valor usado para seleccionar un miembro de
un conjunto ordenado, como puede ser un cara´cter de una
cadena.
porci´on: Una parte de una cadena especificada por un intervalo de
´ındices.
mutable: Un tipo de datos compuesto a cuyos elementos se les
puede asignar nuevos valores.
contador: Una variable usada para contar algo, normalmente
inicializado a cero e incrementado posteriormente.
8 Caden
incrementar: Aumentar el valor de una variable en una unidad.
decrementar: Disminuir el valor de una variable en una unidad.
espacio en blanco: Cualquiera de los caracteres que mueven el cursor
sin im- primir caracteres visibles. La constante
string.whitespace contiene to- dos los caracterse de espacio en
blanco.
Cap´ıtulo 8
Listas
Una lista es un conjunto ordenado de valores, en el cual cada valor
va identifica- do por un ´ındice. Los valores que constituyen una
lista son sus elementos. Las listas son similares a las cadenas de
texto (strings), que son conjuntos ordenados de caracteres, excepto
en que los elementos de una lista pueden ser de cualquier tipo. Las
listas y las cadenas, y otras cosas que se comportan como
conjuntos ordenados, se llaman secuencias.
8.1. Valores de una lista
Hay varias maneras de crear una nueva lista; la mas sencilla es
encerrar sus elementos entre corchetes:
[10, 20, 30, 40]
["spam", "el´astico", "golondrina"]
El primer ejemplo es una lista de cuatro enteros. El segundo es
una lista de tres cadenas de texto. Los elementos de una lista no
tienen por qu´e ser del mismo tipo. La siguiente lista contiene una
cadena, un nu´mero con decimales y un entero, y, maravilla de las
maravillas, otra lista:
["hola", 2.0, 5, [10, 20]]
Se dice que una lista dentro de otra lista esta´ anidada.
Las listas que contienen nu´meros enteros consecutivos son
comunes, de manera que Python proporciona una manera sencilla
de crearlas:
>>> range(1,5)
[1, 2, 3, 4]
8 List
La funci´on range toma dos argumentos y devuelve una lista que
contiene todos los enteros entre el primero y el segundo,
¡incluyendo el primero pero no el segundo!
Hay dos formas alternativas para range. Con un solo argumento,
crea una lista que empieza desde 0:
>>> range(10)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
Si hay un tercer argumento, especificara´ el espacio entre dos
valores sucesivos; a esto se le llama paso (step). Este ejemplo
cuenta de 1 a 10 de dos en dos (con pasos de 2).
>>> range(1, 10, 2)
[1, 3, 5, 7, 9]
Para terminar, hay una lista especial que no contiene
elementos. Se la llama lista vac´ıa y se representa [].
Con todas estas maneras para crear listas, ser´ıa decepcionante
que no pudi´era- mos asignar valores de listas a variables o pasar
listas como par´ametros a fun- ciones. Por supuesto que podemos.
vocabulario = ["mejorar", "castigar", "defenestrar"]
numeros = [17, 123]
vacio = []
print vocabulario, numeros, vacio
[’mejorar’, ’castigar’, ’defenestrar’] [17, 123] []
8.2. Acceso a los elementos
La sintaxis para acceder a los elementos de una lista es la misma
que para acceder a los caracteres de una cadena: el operador
corchetes []. La expresio´n dentro de los corchetes especifica el
´ındice. Recuerde que los ´ındices siempre comienzan en cero:
print numeros[0]
numeros[1] = 5
El operador [] puede aparecer en cualquier parte de una expresio
´n. Cuando aparece a la izquierda de una asignaci´on, cambia uno
de los elementos de la lista, de manera que el “un´esimo” elemento
de numeros, que era 123, ahora es 5.
Se puede usar como ´ındice cualquier expresio´n entera.
8.3 Longitud (taman˜o) de una lista 87
>>> numeros[3-2]
5
>>> numeros[1.0]
TypeError: sequence index must be integer
Si intenta acceder (leer o modificar) un elemento que no existe,
obtendr´a un error en tiempo de ejecucio´n:
>>> numeros[2] = 5
IndexError: list assignment index out of range
Si se da un ´ındice negativo, se cuenta hacia atr´as desde el final de la lista.
>>> numeros[-1]
5
>>> numeros[-2]
17
>>> numeros[-3]
IndexError: list index out of range
numeros[-1] es el u´ltimo elemento de la lista, numeros[-2] es el penu´ltimo, y
numeros[-3] no existe.
Es muy habitual usar una varible de bucle como ´ındice para una lista:
jinetes = ["guerra", "hambre", "peste", "muerte"]
i = 0
while i < 4:
print jinetes[i]
i = i + 1
Este bucle while cuenta desde 0 hasta 4. Cuando la variable de
bucle vale 4, la condicio´n falla y acaba el bucle. Por tanto, el
cuerpo del bucle s´olo se ejecuta cuando i es 0, 1, 2 y 3.
Cada vez que recorremos el bucle, la variable i se usa como
´ındice de la lis- ta, imprimiendo el elemento i-´esimo. Esta
plantilla de computacio´n se llama recorrido de lista.
8.3. Longitud (taman˜o) de una lista
La funci´on len toma una lista y devuelve su taman˜o. Es una
buena idea usar este valor como l´ımite superior de un bucle, en
lugar de una constante. De esta manera, si el taman˜o de la lista
cambia, no habr´a que estar haciendo cambios en todos los bucles;
funcionar´an correctamente con cualquier taman˜o de lista.
jinetes = ["guerra", "hambre", "peste", "muerte"]
i = 0
8 List
while i < len(jinetes):
print jinetes[i]
i = i + 1
La u´ltima vez que se ejecuta el cuerpo del bucle, i es len(jinetes)
- 1, que es el ´ındice del u´ltimo elemento. Cuando i se iguala a
len(jinetes), la condicio´n falla y no se ejecuta el cuerpo, lo
que es una cosa buena, ya que len(jinetes) no es un ´ındice
legal.
Aunque una lista puede contener otra lista como elemento, la lista
anidada cuenta como un elemento sencillo. El taman˜o de esta lista
es 4:
[’spam!’, 1, [’Brie’, ’Roquefort’, ’Pol le Veq’], [1, 2, 3]]
Como ejercicio, escriba un bucle que recorra la lista anterior e im-
prima la longitud de cada elemento. ¿qu´e ocurre si env´ıa un
entero a len?
8.4. Pertenencia a una lista
in es un operador booleano que comprueba la pertenencia a una
secuencia. Lo usamos en la Secci´on 7.10 con las cadenas, pero
tambi´en funciona con las listas y otras secuencias:
>>> jinetes = [’guerra’, ’hambre’, ’peste’, ’muerte’]
>>> ’peste’ in jinetes
1
>>> ’libertinaje’ in jinetes
0
Como “peste” es un miembro de la lista jinetes, el operador in
devuelve verdadero. Como “libertinaje” no esta´ en la lista, in
devuelve falso.
Podemos usar not en combinaci´on con in para comprobar si un
elemento no es miembro de una lista:
>>> ’libertinaje’ not in jinetes
1
8.5. Listas y bucles for
El bucle for que vimos en la Secci´on 7.3 tambi´en funciona con las
listas. La sintaxis generalizada de un bucle for es:
8.6 Operaciones con listas 89
for VARIABLE in LISTA:
CUERPO
Esta sentencia es equivalente a:
i = 0
while i < len(LISTA):
VARIABLE = LISTA[i]
CUERPO
i = i + 1
El bucle for es m´as conciso porque podemos eliminar la variable
de bucle, i. Aqu´ı tenemos el bucle anterior con un bucle for:
for jinete in jinetes:
print jinete
M´as au´n, casi se lee igual que en espan˜ol, “Para (cada) jinete en
(la lista de) jinetes, imprime (el nombre del) jinete”.
Se puede usar cualquier expresio´n de lista en un
bucle for: for numero in range(20):
if numero 2 == 0:
print numero
for fruta in ["pl´atano", "manzana",
"membrillo"]: print "Me gusta comer " + fruta +
"s!"
El primer ejemplo imprime todos los nu´meros pares entre el 0 y el
19. El segundo ejemplo expresa su entusiasmo por diferentes
frutas.
8.6. Operaciones con listas
El operador + concatena listas:
>>> a = [1, 2, 3]
>>> b = [4, 5, 6]
>>> c = a + b
>>> print c
[1, 2, 3, 4, 5, 6]
De forma similar, el operador * repite una lista un nu´mero dado de veces:
>>> [0] * 4
[0, 0, 0, 0]
>>> [1, 2, 3] * 3
[1, 2, 3, 1, 2, 3, 1, 2, 3]
En el primer ejemplo la lista [0] contiene un solo elemento que
es repetido cuatro veces. En el segundo ejemplo, la lista [1, 2, 3]
se repite tres veces.
9 List
8.7. Porciones (slices)
Las operaciones de porciones que vimos en la Secci´on 7.4 tambi
´en funcionan en sobre las listas:
>>> lista = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’]
>>> lista[1:3]
[’b’, ’c’]
>>> lista[:4]
[’a’, ’b’, ’c’, ’d’]
>>> lista[3:]
[’d’, ’e’, ’f’]
>>> lista[:]
[’a’, ’b’, ’c’, ’d’, ’e’, ’f’]
8.8. Las listas son mutables
A diferencia de las cadenas, las listas son mutables, lo que significa
que pode- mos cambiar sus elementos. Podemos modificar uno de
sus elementos usando el operador corchetes en el lado izquierdo de
una asignaci´on:
>>> fruta = ["pl´atano", "manzana", "membrillo"]
>>> fruta[0] = "pera"
>>> fruta[-1] = "naranja"
>>> print fruta
[’pera’, ’manzana’, ’naranja’]
Con el operador de porci´on podemos reemplazar varios elementos a la vez:
>>> lista = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’]
>>> lista[1:3] = [’x’, ’y’]
>>> print lista
[’a’, ’x’, ’y’, ’d’, ’e’, ’f’]
Adem´as, puede eliminar elementos de una lista asign´andoles la lista vac
´ıa:
>>> lista = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’]
>>> lista[1:3] = []
>>> lista
[’a’, ’d’, ’e’, ’f’]
Y puede an˜adir elementos a la lista embuti´endolos en una porci
´on vac´ıa en la posici´on deseada:
>>> lista = [’a’, ’d’, ’f’]
>>> lista[1:1] = [’b’, ’c’]
>>> print lista
8.9 Borrado en una lista 91
[’a’, ’b’, ’c’, ’d’, ’f’]
>>> lista[4:4] = [’e’]
>>> print lista
[’a’, ’b’, ’c’, ’d’, ’e’, ’f’]
8.9. Borrado en una lista
El uso de porciones para borrar elementos de una lista puede ser
extran˜o, y por ello propicio a los errores. Python nos da una
alternativa que resulta mas legible.
del elimina un elemento de una lista:
>>> a = [’uno’, ’dos’, ’tres’]
>>> del a[1]
>>> a
[’uno’, ’tres’]
Como podr´ıa esperar, del maneja ´ındices negativos y provoca un
error en tiempo de ejecucio´n sin el ´ındice esta´ fuera de l´ımites.
Puede usar una porci´on como ´ındice para del:
>>> lista = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’]
>>> del lista[1:5]
>>> print lista
[’a’, ’f’]
Como es habitual, las porciones seleccionan todos los elementos
hasta, pero no inclu´ıdo, el segundo ´ındice.
8.10. Objetos y valores
Si ejecutamos estas sentencias de asignaci´on:
a = "banana"
b = "banana"
Est´a claro que a y b apuntan ambos a cadenas con las letras
"banana". Pero no podemos saber si esta´n apuntando a la misma
cadena.
Hay dos posibles estados:
9 List
a
b
"banana"
"banana"
a
b "banana"
a
b
[ 1, 2, 3 ]
[ 1, 2, 3 ]
En un caso, a y b se refieren a dos cosas diferentes que tienen el
mismo valor. En el segundo caso, se refieren a la misma cosa.
Estas “cosas” tienen nombres; se les denomina objetos. Un objeto
es una cosa a la que se puede referir una variable.
Cada objeto tiene un identificador u´nico, que podemos obtener por
medio de la funci´on id. Imprimiendo los identificadores de a y b
podemos saber si apuntan al mismo objeto.
>>> id(a)
135044008
>>> id(b)
135044008
En este caso, las dos veces obtenemos el mismo identificador, lo
que significa que Python s´olo cre´o una cadena y ambas variables,
a y b, apuntan a ella.
Como las cadenas de texto son inmutables, no hay diferencia pr
´actica entre los dos posibles estados. Para tipos mutables como las
listas, s´ı que importa.
Curiosamente, las listas se comportan de otra manera. Cuando
crea dos listas, obtiene dos objetos:
>>> a = [1, 2, 3]
>>> b = [1, 2, 3]
>>> id(a)
135045528
>>> id(b)
135041704
De manera que el diagrama de estado ser´ıa tal como ´este:
a y b tienen el mismo valor, pero no se refieren al mismo objeto.
8.11. Alias (poner sobrenombres)
Como las variables apuntan a objetos, si asigna una variable a otra,
ambas variables se refieren al mismo objeto:
a
b [ 1, 2, 3 ]
8.12 Clonar listas 93
>>> a = [1, 2, 3]
>>> b = a
En este caso, el diagrama de estados ser´ıa como ´este:
Como la misma lista tiene dos nombres diferentes, a y b, podemos
decir que se le ha puesto un alias. Los cambios hechos a un alias
afectan al otro:
>>> b[0] = 5
>>> print a
[5, 2, 3]
Aunque este comportamiento puede ser u´til, a veces es inesperado
o indeseable. En general, es m´as seguro evitar los alias cuando
trabajemos con objetos muta- bles. Por supuesto, no hay problema
con los objetos inmutables. Por ello Python se toma la libertad de
poner alias a las cadenas cuando ve una oportunidad de
economizar.
8.12. Clonar listas
Si queremos modificar una lista y mantener una copia del original,
necesitaremos ser capaces de hacer una copia de la lista en s´ı, no
s´olo de su referencia. Este proceso a veces se denomina clonado,
para evitar la ambigu¨edad de la palabra “copia”.
La forma mas f´acil de clonar una lista es por medio del operador de porci
´on:
>>> a = [1, 2, 3]
>>> b = []
>>> b[:] = a[:]
>>> print b
[1, 2, 3]
La extraccio´n de una porci´on de a crea una nueva lista. En este
caso, la porci´on consta de la lista completa.
Ahora tenemos libertad de hacer cambios en b sin preocuparnos de a:
>>> b[0] = 5
>>> print a
[1, 2, 3]
Como ejercicio, dibuje un diagrama de estado de a y b antes y des-
pues del cambio.
9 List
numbers
list
8.13. Listas como par´ameteros
Cuando se pasa una lista como argumento, en realidad se pasa una
referencia a ella, no una copia de la lista. Por ejemplo, la funci´on
cabeza toma una lista como par´ametro y devuelve el primer
elemento.
def cabeza(lista):
return lista[0]
As´ı es como se usa.
>>> numeros = [1,2,3]
>>> cabeza(numeros)
1
El par´ametro lista y la variable numeros son alias de un mismo
objeto. El diagrama de estado es as´ı:
main
head
[ 1, 2, 3 ]
Como el objeto lista esta´ compartido por dos marcos, lo dibujamos
entre ambos. Si la funci´on modifica una lista pasada como par
´ametro, el que hizo la llamada ver´a el cambio. borra cabeza
elimina el primer elemento de una lista.
def borra_cabeza(lista):
del lista[0]
Aqu´ı vemos el uso de borra cabeza:
>>> numeros = [1,2,3]
>>> borra_cabeza(numeros)
>>> print numeros
[2, 3]
Si una funci´on devuelve una lista, devuelve una referencia a la
lista. Por ejemplo, cola devuelve una lista que contiene todos los
elementos de una lista dada, excepto el primero.
def cola(lista):
return lista[1:]
Aqu´ı vemos c´omo se usa cola:
>>> numeros = [1,2,3]
>>> resto = cola(numeros)
>>> print resto
>>> [2, 3]
8.14 Listas anidadas 95
Como el valor de retorno se cre´o con una porci´on, es una lista. La creacio
´n de
rest, as´ı como cualquier cambio posterior en rest, no afectar´a a numbers.
8.14. Listas anidadas
Una lista anidada es una lista que aparece como elemento dentro
de otra lista. En esta lista, el tri-´esimo elemento es una lista
anidada:
>>> lista = ["hola", 2.0, 5, [10, 20]]
Si imprimimos lista[3], obtendremos [10, 20]. Para extraer los
elementos de la lista anidada, podemos proceder en dos pasos:
>>> elt = lista[3]
>>> elt[0]
10
O podemos combinarlos:
>>> lista[3][1]
20
Los operadores corchete se evalu´an de izquierda a derecha, as´ı
que esta expresio´n saca el tri-´esimo elemento de lista y luego
extrae el un´esimo elemento de ella.
8.15. Matrices
Es comu´n usar listas anidadas para representar matrices. Por ejemplo, la
matriz:
1 2 3
4 5 6
7 8 9
puede ser representada como:
>>> matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
matriz es una lista con tres elementos, siendo cada elemento una
fila de la matriz. Podemos elegir una fila entera de la matriz de la
forma normal:
>>> matriz[1]
[4, 5, 6]
O tomar s´olo un elemento de la matriz usando la forma de doble ´ındice:
>>> matriz[1][1]
5
9 List
El primer ´ındice escoge la fila y el segundo la columna. Aunque
esta manera de representar matrices es comu´n, no es la u´nica
posibilidad. Una pequen˜a variaci´on consiste en usar una lista de
columnas en lugar de flas. Mas adelante veremos una alternativa m
´as radical usando un diccionario.
8.16. Cadenas y listas
Dos de las funciones m´as u´tiles del m´odulo string tienen que ver
con listas de cadenas. La funci´on split divide una cadena en una
lista de palabras. Por defecto, cualquier nu´mero de caracteres de
espacio en blanco se considera un l´ımite de palabra:
>>> import string
>>> cancion = "La lluvia en Sevilla..."
>>> string.split(cancion)
[’La’, ’lluvia’, ’en’, ’Sevilla...’]
Se puede usar un argumento opcional llamado delimitador para
especificar qu´e caracteres se usar´an como l´ımites de palabra.
El siguiente ejemplo usa la cadena ll como delimitador:
>>> string.split(cancion, ’ll’)
[’La ’, ’uvia en Sevi’, ’a...’]
Observe que el delimitador no aparece en la lista.
La funci´on join es la inversa de split. Toma una lista de cadenas y
concatena los elementos con un espacio entre cada par:
>>> lista = [’La’, ’lluvia’, ’en’, ’Sevilla...’]
>>> string.join(lista)
’La lluvia en Sevilla...’
Como split, join acepta un delimitador opcional que se inserta
entre los ele- mentos. El delimitador por defecto es el espacio.
>>> string.join(lista, ’_’)
’La_lluvia_en_Sevilla...’
A modo de ejercicio, describa la relaci´on que hay
entre string.join(string.split(cancion)) y cancion. ¿Es la
misma para todas las cadenas? ¿Cua´ndo ser´ıa
diferente?
8.17 Glosario 97
8.17. Glosario
lista: Una coleccio´n de objetos con nombre, en la que cada objeto
es identificado por un ´ındice.
´ındice: Una variable o valor enteros que se usan para indicar un
elemento de una lista.
elemento: Uno de los valores de una lista (u otra secuencia). El
operador cor- chete selecciona elementos de una lista.
secuencia: Cualquier tipo de datos que consita en un conjunto
ordenado de elementos, con cada elemento identificado por
un ´ındice.
lista anidada: Una lista que es elemento de otra lista.
recorrido de lista: Acceso secuencial a cada elemento de una lista.
objeto: Una cosa a la que se puede referir una variable.
alias: Mu´ltiples variables que contienen referencias al mismo objeto.
clonar: Crear un objeto nuevo que tiene el mismo valor que un
objeto ya exis- tente. Copiar una referencia a un objeto crea
un alias, pero no clona el objeto.
delimitador: Un cara´cter o cadena utilizado para indicar donde
debe cortarse una cadena.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Cap´ıtulo 9
Tuplas
9.1. Mutabilidad y tuplas
Hasta ahora, ha visto dos tipos compuestos: cadenas, que esta´n
hechas de ca- racteres, y listas, que esta´n hechas de elementos de
cualquier tipo. Una de las diferencias que sen˜alamos es que los
elementos de una lista se pueden modifi- car, pero los caracteres
de una cadena no. En otras palabras, las cadenas son inmutables y
las listas son mutables.
En Python hay otro tipo llamado tupla que es similar a una lista
salvo en que es inmutable. Sint´acticamente, una tupla es una lista
de valores separados por comas:
>>> tupla = ’a’, ’b’, ’c’, ’d’, ’e’
Aunque no es necesario, la convencio´n dice que hay que encerrar
las tuplas entre par´entesis:
>>> tupla = (’a’, ’b’, ’c’, ’d’, ’e’)
Para crear una tupla con un solo elemento, debemos incluir una coma final:
>>> t1 = (’a’,)
>>> type(t1)
<type ’tuple’>
Sin la coma, Python trata (´a’) como una cadena entre par´entesis:
>>> t2 = (’a’)
>>> type(t2)
<type ’string’>
1 Tupl
Dejando a un lado las cuestiones de sintaxis, las operaciones sobre
las tuplas son las mismas que sobre las listas. El operador ´ındice
selecciona un elemento de la tupla.
>>> tupla = (’a’, ’b’, ’c’, ’d’, ’e’)
>>> tupla[0]
’a’
Y el operador de porci´on selecciona un intervalo de elementos.
>>> tupla[1:3]
(’b’, ’c’)
Pero si intentamos modificar uno de los elementos de la tupla
provocaremos un error:
>>> tupla[0] = ’A’
TypeError: object doesn’t support item assignment
Por supuesto, incluso aunque no podamos modificar los elementos
de una tupla, podemos sustituir una tupla por otra diferente:
>>> tupla = (’A’,) + tupla[1:]
>>> tupla
(’A’, ’b’, ’c’, ’d’, ’e’)
9.2. Asignaci´on de tuplas
De vez en cuando, es u´til intercambiar los valores de dos variables.
Para ha- cerlo con sentencias de asignaci´on convencionales
debemos usar una variable temporal. Por ejemplo, para
intercambiar a y b:
>>> temp = a
>>> a = b
>>> b = temp
Si tenemos que hacer esto a menudo, esta aproximaci´on resulta
aparatosa. Pyt- hon proporciona una forma de asignacio´n de
tuplas que soluciona este pro- blema elegantemente:
>>> a, b = b, a
El lado izquierdo es una tupla de variables, el lado derecho es una
tupla de valores. Cada valor se asigna a su respectiva variable.
Todas las expresiones del lado derecho se evalu´an antes de las
asignaciones. Esta caracter´ıstica hace de la asignaci´on de tuplas
algo muy vers´atil.
9.3 Tuplas como valor de retorno 101
Naturalmente, el nu´mero de variables a la izquierda y el nu´mero
de valores a la derecha deben ser iguales:
>>> a, b, c, d = 1, 2, 3
ValueError: unpack tuple of wrong size
9.3. Tuplas como valor de retorno
Las funciones pueden devolver tuplas como valor de retorno. Por
ejemplo, podr´ıamos escribir una funci´on que intercambie dos par
´ametros:
def intercambio(x, y):
return y, x
Luego podemos asignar el valor de retorno a una tupla con dos variables:
a, b = intercambio(a, b)
En este caso, no hay ninguna ventaja en convertir intercambio en
una funci´on. De hecho, existe un peligro al intentar encapsular
intercambio, y es el tentador error que sigue:
def intercambio(x, y): # versi´on
incorrecta x, y = y, x
Si llamamos a esta funci´on as´ı:
intercambio(a, b)
a y x son alias del mismo valor. Cambiar x dentro de intercambio hace
que x se refiera a un valor diferente, pero no tiene efecto alguno
sobre a en main . De forma similar, cambiar y no tiene efecto sobre
b.
Esta funci´on se ejecuta sin generar un mensaje de error, pero no
hace lo que intentamos. Este es un ejemplo de error sema´ntico.
A modo de ejercicio, dibuje un diagrama de estados para esta
funci´on de manera que pueda ver por qu´e no trabaja como usted
quiere.
9.4. Nu´meros aleatorios
La mayor parte de los programas hacen lo mismo cada vez que los
ejecutamos, por lo que se dice que son deterministas. Normalmente
el determinismo es una cosa buena, ya que esperamos que un c
´alculo nos d´e siempre el mismo
1 Tupl
resultado. Para algunas aplicaciones, sin embargo, queremos que
el computador sea impredecible. El ejemplo obvio son los juegos,
pero hay mas.
Hacer que un programa sea realmente no determinista resulta no
ser tan sencillo, pero hay formas de que al menos parezca no
determinista. Una de ellas es generar nu´meros aleatorios y usarlos
para determinar el resultado del programa. Python proporciona una
funci´on interna que genera nu´meros pseudoaleatorios, que no son
verdaderamente aleatorios en un sentido matem´atico, pero servira
´n para nuestros prop´ositos.
El m´odulo random contiene una funci´on llamada random que
devuelve un nu´mero en coma flotante entre 0,0 y 1,0. Cada vez
que usted llama a random obtiene el siguiente nu´mero de una larga
serie. Para ver un ejemplo, ejecute este bucle:
import random
for i in range(10):
x = random.random()
print x
Para generar un nu´mero aleatorio entre 0,0 y un l´ımite superior
como maximo, multiplique x por maximo.
Como ejercicio adicional, genere un nu´mero aleatorio entero
entre Como ejercicio, genere un nu´mero aleatorio entre minimo y
maximo.
minimo y maximo, incluyendo ambos extremos.
9.5. Lista de nu´meros aleatorios
El primer paso es generar una lista de valores aleatorios.
listaAleatorios acepta un par´ametro entero y devuelve una lista de
nu´meros aleatorios de la longitud dada. Comienza con una lista
de n ceros. Cada vez que ejecuta el bucle, sustituye uno de los
elementos con un nu´mero aleatorio. El valor de retorno es una
referencia a la lista completa:
def listaAleatorios(n):
s = [0] * n
for i in range(n):
s[i] =
random.random() return
s
Vamos a probar esta funci´on con una lista de ocho elementos. A
la hora de depurar es una buena idea empezar con algo pequen˜o.
9.6 Conteo 103
>>> listaAleatorios(8)
0.15156642489
0.498048560109
0.810894847068
0.360371157682
0.275119183077
0.328578797631
0.759199803101
0.800367163582
Se supone que los nu´meros generados por random esta´n
distribuidos uniforme- mente, lo que significa que cada valor es
igualmente probable.
Si dividimos el intervalo de valores posibles en “baldes” de igual
taman˜o y contamos el nu´mero de veces que un valor cae en cada
balde, deber´ıamos tener m´as o menos el mismo nu´mero en
todos.
Podemos contrastar esta teor´ıa escribiendo un programa que
divida el intervalo en baldes y contando el nu´mero de valores en
cada uno.
9.6. Conteo
Un buen enfoque sobre problemas como ´este es dividir el
problema en subpro- blemas que encajen en un esquema
computacional que hayamos visto antes. En este caso, queremos
recorrer una lista de nu´meros y contar el nu´mero de veces que
un valor cae en un intervalo dado. Eso nos suena. En la Secci´on
7.8 escribimos un programa que recorr´ıa una cadena de texto y
contaba el nu´mero de veces que aparec´ıa una letra determinada.
As´ı, podemos hacerlo copiando el programa viejo y adaptandolo al
problema actual. El programa original era:
cuenta = 0
for car in fruta:
if car == ’a’:
cuenta = cuenta + 1
print cuenta
El primer paso es sustituir fruta con lista y car con num. Esto
no cambia el programa, s´olo lo hace m´as legible.
El segundo paso es cambiar la comprobacio´n. No estamos
interesados en encon- trar letras. Queremos ver si num esta´ entre
los valores de minimo y maximo.
1 Tupl
cuenta = 0
for num in lista
if minimo < num < maximo:
cuenta = cuenta + 1
print cuenta
El u´ltimo paso es encapsular este c´odigo en una funci´on
llamada enElBalde. Los par´ametros son la lista y los valores minimo
y maximo.
def enElBalde(lista, minimo, maximo):
cuenta = 0
for num in lista:
if minimo < num < maximo:
cuenta = cuenta + 1
return cuenta
Copiar y modificar un programa existente nos facilita escribir esta
funci´on r´api- damente y nos ahorra un mont´on de tiempo de
depuraci´on. Este plan de desa- rrollo se llama coincidencia de
esquemas. Si se encuentra trabajando en un problema que ya
soluciono´, reutilice la solucio´n.
9.7. Muchos baldes
Tal como aumenta el nu´mero de baldes, enElBalde se hace un
tanto dif´ıcil de manejar. Con dos baldes, no esta´ mal:
bajo = enElBalde(a, 0.0, 0.5)
alto = enElBalde(a, 0.5, 1)
Pero con cuatro baldes ya es aparatoso.
balde1 = enElBalde(a, 0.0, 0.25)
balde2 = enElBalde(a, 0.25, 0.5)
balde3 = enElBalde(a, 0.5, 0.75)
balde4 = enElBalde(a, 0.75, 1.0)
Hay dos problemas. Uno es que tenemos que inventar nuevos
nombres de va- riables para cada resultado. El otro es que tenemos
que calcular el intervalo de cada balde.
Empezaremos por solucionar el segundo problema. Si el nu´mero de
baldes es
numBaldes, la anchura de cada balde es 1.0 / numBaldes.
Usaremos un bucle para calcular el intervalo de cada balde. La
variable del bucle, i, cuenta de 1 a numBaldes-1:
9.7 Muchos baldes 105
anchuraBalde = 1.0 /
numBaldes for i in
range(numBaldes):
minimo = i * anchuraBalde
maximo = minimo + anchuraBalde
print minimo, "hasta", maximo
Para calcular el l´ımite inferior de cada balde, multiplicamos la
variable de bucle por la anchura de balde. El l´ımite superior esta´
a tan s´olo una anchuraBalde. Con numBaldes = 8, la salida es:
0.0 hasta 0.125
0.125 hasta 0.25
0.25 hasta 0.375
0.375 hasta 0.5
0.5 hasta 0.625
0.625 hasta 0.75
0.75 hasta 0.875
0.875 hasta 1.0
Puede confirmar que todos los bucles tienen la misma anchura, que
no se solapan y que cubren todo el intervalo entre 0,0 y 1,0.
Volvamos ahora al primer problema. Necesitamos un modo de
almacenar ocho enteros, usando la variable de bucle para
sen˜alarlos uno por uno. En estos mo- mentos deber´ıa usted estar
pensando “¡Lista!”.
Debemos crear la lista de baldes fuera del bucle, porque s´olo
queremos hacer- lo una vez. Dentro del bucle, podemos llamar
repetidamente a enElBalde y actualizar el i-´esimo elemento de la
lista:
numBaldes = 8
baldes = [0] * numBaldes
anchuraBalde = 1.0 /
numBaldes for i in
range(numBaldes):
minimo = i * anchuraBalde
maximo = minimo + anchuraBalde
baldes[i] = enElBalde(lista, minimo, maximo)
print baldes
Con una lista de 1000 valores, este c´odigo genera esta lista de baldes:
[138, 124, 128, 118, 130, 117, 114, 131]
Estos nu´meros son razonablemente pr´oximos a 125, que es lo que
esper´abamos Por lo menos, esta´n lo bastante cerca como para
que podamos pensar que el generador de nu´meros aleatorios
funciona.
1 Tupl
Como ejercicio, compruebe esta funci´on con listas m´as largas, y
vea si el nu´mero de valores en cada balde tiende a equilibrarse.
9.8. Una solucio´n en una sola pasada
Aunque este programa funciona, no es tan eficiente como podr´ıa
ser. Cada vez que llama a enElBalde recorre la lista entera. Con el
aumento del nu´mero de baldes, llega a ser un mont´on de
recorridos.
Ser´ıa mejor hacer una sola pasada por la lista y calcular para cada
valor el ´ındice del balde en el que cae. Luego podemos
incrementar el contador apropiado. En la seccio´n anterior
tomamos un ´ındice, i, y lo multiplicamos por la anchuraBalde
para hallar el l´ımite inferior de un balde dado. Ahora quere- mos
tomar un valor del intervalo 0,0 a 1,0 y hallar el ´ındice del balde en
el que cae.
Como el problema es el inverso del anterior, podemos suponer que
deber´ıamos dividir por anchuraBalde en lugar de multiplicar. La
suposici´on es correcta. Como anchuraBalde = 1.0 / numBaldes,
dividir por anchuraBalde es lo mis- mo que multiplicar por numBaldes.
Si multiplicamos un nu´mero del intervalo que va de 0,0 a 1,0 por
numBaldes, obtenemos un nu´mero del intervalo entre 0,0 y
numBaldes. Si redondeamos ese nu´mero al entero inferior
obtendremos exactamente lo que estamos buscando, un ´ındice de
balde:
numBaldes = 8
baldes = [0] * numBaldes
for i in lista:
indice = int(i * numBaldes)
baldes[indice] = baldes[indice] + 1
Usamos la funci´on int para convertir un nu´mero en coma flotante en un
entero.
¿Es posible que este c´alculo genere un ´ındice que est´e fuera del
intervalo (tanto negativo como mayor que len(baldes)-1)?
Una lista como baldes que contiene conteos del nu´mero de valores
en cada intervalo se llama histograma.
Como ejercicio, escriba una funcion llamada histograma que tome
como para´metros una lista y un nu´mero de baldes y devuelva un
histograma con el nu´mero dado de baldes.
9.9 Glosario 107
9.9. Glosario
tipo inmutable: Un tipo en el cual los elementos no se puede
modificar. Las asignaciones de elementos o porciones de tipos
inmutables provocan un error.
tipo mutable: Un tipo de datos en el cual los elementos pueden ser
modifi- cados. Todos los tipos mutables son compuestos. Las
listas y diccionarios son tipos de datos mutables, las cadenas y
las tuplas no.
tupla: Un tipo de secuencia que es similar a una lista excepto en
que es in- mutable. Las tuplas se pueden usar donde quiera
que se necesite un tipo inmutable, como puede ser la clave de
un diccionario.
asignacio´n de tuplas: Una asignaci´on de todos los elementos de
una tupla usando una u´nica sentencia de asignaci´on. La
asignaci´on de tuplas sucede m´as bien en paralelo que
secuencialmente, haci´endola u´til para intercam- biar valores.
determinista: Un programa que hace lo mismo todas las veces que se ejecuta.
pseudoaleatorio: Una secuencia de nu´meros que parece ser
aleatoria pero que en realidad es el resultado de un c´alculo
determinista.
histograma: Una lista de enteros en la que cada elemento cuenta el
nu´mero de veces que ocurre algo.
coincidencia de esquemas: Un plan de desarrollo de programas que
implica la identificaci´on de un esquema computacional
conocido y el copiado de la solucio´n para un problema
similar.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Cap´ıtulo 10
Diccionarios
Los tipos compuestos que ha visto hasta ahora (cadenas, listas y
tuplas) usan en- teros como ´ındices. Si intenta usar cualquier otro
tipo como ´ındice provocara´ un error.
Los diccionarios son similares a otros tipos compuestos excepto en
que pueden usar como ´ındice cualquier tipo inmutable. A modo de
ejemplo, crearemos un diccionario que traduzca palabras inglesas
al espan˜ol. En este diccionario, los
´ındices son strings (cadenas).
Una forma de crear un diccionario es empezar con el diccionario
vac´ıo y an˜adir elementos. El diccionario vac´ıo se expresa como {}:
>>> ing_a_esp = {}
>>> ing_a_esp[’one’] = ’uno’
>>> ing_a_esp[’two’] = ’dos’
La primera asignaci´on crea un diccionario llamado ing a esp; las
otras asig- naciones an˜aden nuevos elementos al diccionario.
Podemos presentar el valor actual del diccionario del modo
habitual:
>>> print ing_a_esp
{’one’: ’uno’, ’two’: ’dos’}
Los elementos de un diccionario aparecen en una lista separada
por comas. Cada entrada contiene un ´ındice y un valor separado
por dos puntos (:). En un diccionario, los ´ındices se llaman claves,
por eso los elementos se llaman pares clave-valor.
Otra forma de crear un diccionario es dando una lista de pares
clave-valor con la misma sintaxis que la salida del ejemplo anterior:
1 Diccionari
>>> ing_a_esp = {’one’: ’uno’, ’two’: ’dos’, ’three’: ’tres’}
Si volvemos a imprimir el valor de ing a esp, nos llevamos una sorpresa:
>>> print ing_a_esp
{’one’: ’uno’, ’three’: ’tres’, ’two’: ’dos’}
¡Los pares clave-valor no esta´n en orden! Afortunadamente, no
necesitamos preo- cuparnos por el orden, ya que los elementos de
un diccionario nunca se indexan con ´ındices enteros. En lugar de
eso, usamos las claves para buscar los valores correspondientes:
>>> print ing_a_esp[’two’]
’dos’
La clave ’two’ nos da el valor ’dos’ aunque aparezca en el
tercer par clave- valor.
10.1. Operaciones sobre diccionarios
La sentencia del elimina un par clave-valor de un diccionario. Por
ejemplo, el diccionario siguiente contiene los nombres de varias
frutas y el nu´mero de esas frutas en el almac´en:
>>> inventario = {’manzanas’: 430, ’bananas’: 312,
... ’naranjas’: 525, ’peras’: 217}
>>> print inventario
{’naranjas’: 525, ’manzanas’: 430, ’peras’: 217, ’bananas’: 312}
Si alguien compra todas las peras, podemos eliminar la entrada del
diccionario:
>>> del inventario[’peras’]
>>> print inventario
{’naranjas’: 525, ’manzanas’: 430, ’bananas’: 312}
O si esperamos recibir m´as peras pronto, podemos simplemente
cambiar el in- ventario asociado con las peras:
>>> inventario[’peras’] = 0
>>> print inventario
{’naranajas’: 525, ’manzanas’: 430, ’peras’: 0, ’bananas’: 312}
La funci´on len tambi´en funciona con diccionarios; devuelve el nu
´mero de pares clave-valor:
>>> len(inventario)
4
10.2 M´etodos del diccionario 111
10.2. M´etodos del diccionario
Un m´etodo es similar a una funci´on, acepta par´ametros y
devuelve un valor, pero la sintaxis es diferente. Por ejemplo, el m
´etodo keys acepta un diccionario y devuelve una lista con las
claves que aparecen, pero en lugar de la sintaxis de la funci´on
keys(ing a esp), usamos la sintaxis del m´etodo ing a esp.keys().
>>> ing_a_esp.keys()
[’one’, ’three’, ’two’]
Esta forma de notaci´on de punto especifica el nombre de la funci
´on, keys, y el nombre del objeto al que se va a aplicar la funci´on,
ing a esp. Los par´entesis indican que este m´etodo no admite par
´ametros.
La llamda a un m´etodo se denomina invocaci´on; en este caso, dir
´ıamos que estamos invocando keys sobre el objeto ing a esp.
El m´etodo values es similar; devuelve una lista de los valores del
diccionario:
>>> ing_a_esp.values()
[’uno’, ’tres’, ’dos’]
El m´etodo items devuelve ambos, una lista de tuplas con los pares
clave-valor del diccionario:
>>> ing_a_esp.items()
[(’one’,’uno’), (’three’, ’tres’), (’two’, ’dos’)]
La sintaxis nos proporciona informaci´on muy u´til acerca del tipo
de datos. Los corchetes indican que es una lista. Los par´entesis
indican que los elementos de la lista son tuplas.
Si un m´etodo acepta un argumento, usa la misma sintaxis que una
llamada a una funci´on. Por ejemplo, el m´etodo has key acepta una
clave y devuelve verdadero (1) si la clave aparece en el diccionario:
>>> ing_a_esp.has_key(’one’)
1
>>> ing_a_esp.has_key(’deux’)
0
Si usted invoca un m´etodo sin especificar un objeto, provoca un
error. En este caso, el mensaje de error no es de mucha ayuda:
>>> has_key(’one’)
NameError: has_key
1 Diccionari
10.3. Asignaci´on de alias y copiado
Debe usted estar atento a los alias a causa de la mutabilidad de los
diccionarios. Si dos variables se refieren al mismo objeto los
cambios en una afectan a la otra. Si quiere modificar un
diccionario y mantener una copia del original, use el m´etodo copy.
Por ejemplo, opuestos es un diccionario que contiene pares de
opuestos:
>>> opuestos = {’arriba’: ’abajo’, ’derecho’: ’torcido’,
... ’verdadero’: ’falso’}
>>> alias = opuestos
>>> copia = opuestos.copy()
alias y opuestos se refieren al mismo objeto; copia hace referencia
a una copia nueva del mismo diccionario. Si modificamos alias,
opuestos tambi´en resulta cambiado:
>>> alias[’derecho’] = ’sentado’
>>> opuestos[’derecho’]
’sentado’
Si modificamos copia, opuestos no var´ıa:
>>> copia[’derecho’] = ’privilegio’
>>> opuestos[’derecho’]
’sentado’
10.4. Matrices dispersas
En la Secci´on 8.14 usamos una lista de listas para representar
una matriz. Es una buena opcio´n para una matriz en la que la
mayor´ıa de los valores es diferente de cero, pero piense en una
matriz como ´esta:
La representaci´on de la lista contiene un monton de ceros:
matriz = [ [0,0,0,1,0],
[0,0,0,0,0],
0 0 0 1 0
0 0 0 0 0
0 2 0 0 0
0 0 0 0 0
0 0 0 3 0
10.5 Pistas 113
[0,2,0,0,0],
[0,0,0,0,0],
[0,0,0,3,0] ]
Una posible alternativa es usar un diccionario. Como claves,
podemos usar tuplas que contengan los nu´meros de fila y
columna. E´sta es la representaci´on de la misma matriz por
medio de un diccionario:
matriz = {(0,3): 1, (2, 1): 2, (4, 3): 3}
S´olo hay tres pares clave-valor, una para cada elemento de la
matriz diferente de cero. Cada clave es una tupla, y cada valor es
un entero.
Para acceder a un elemento de la matriz, podemos usar el operador []:
matriz[0,3]
1
F´ıjese en que la sintaxis para la representaci´on por medio del
diccionario no es la misma de la representaci´on por medio de la
lista anidada. En lugar de dos
´ındices enteros, usamos un ´ındice que es una tupla de enteros.
Hay un porblema. Si apuntamos a un elemento que es cero, se
produce un error porque en el diccionario no hay una entrada con
esa clave:
>>> matriz[1,3]
KeyError: (1, 3)
El m´etodo get soluciona este problema:
>>> matriz.get((0,3), 0)
1
El primer argumento es la clave; el segundo argumento es el valor
que debe devolver get en caso de que la clave no est´e en el
diccionario:
>>> matriz.get((1,3), 0)
0
get mejora sensiblemente la sema´ntica del acceso a una matriz
dispersa. L´astima de sintaxis.
10.5. Pistas
Si estuvo jugando con la funci´on fibonacci de la Secci´on 5.7, es
posible que haya notado que cuanto m´as grande es el argumento
que le da, mas tiempo le
1 Diccionari
fibonacci n0
fibonacci n1
fibonacci n0
fibonacci n1
fibonacci n1
fibonacci n2
fibonacci n2
fibonacci n3
fibonacci n4
cuesta ejecutarse. M´as au´n, el tiempo de ejecucio´n aumenta
muy r´apidamente. En nuestra m´aquina, fibonacci(20) acaba
instantaneamente, fibonacci(30) tarda m´as o menos un segundo, y
fibonacci(40) tarda una eternidad.
Para entender por qu´e, observe este gr´afico de llamadas de fibonacci con
n=4:
Un gr´afico de llamadas muestra un conjunto de cajas de funci´on
con l´ıneas que conectan cada caja con las cajas de las funciones a
las que llama. En lo alto del gr´afico, fibonacci con n=4 llama a
fibonacci con n=3 y n=2. A su vez, fibonacci con n=3 llama a fibonacci
con n=2 y n=1. Y as´ı sucesivamente. Cuente cua´ntas veces se llama
a fibonacci(0) y fibonacci(1). Es una solucio´n ineficaz al
problema, y empeora mucho tal como crece el argumento.
Una buena solucio´n es llevar un registro de los valores que ya se
han calculado almacen´andolos en un diccionario. A un valor que ya
ha sido calculado y alma- cenado para un uso posterior se le llama
pista. Aqu´ı hay una implementaci´on de fibonacci con pistas:
anteriores = {0:1, 1:1}
def fibonacci(n):
if anteriores.has_key(n):
return anteriores[n]
else:
nuevoValor = fibonacci(n-1) + fibonacci(n-2)
anteriores[n] = nuevoValor
return nuevoValor
El diccionario llamado anteriores mantiene un registro de los valores
de Fibo- nacci que ya conocemos. El programa comienza con s´olo
dos pares: 0 corresponde a 1 y 1 corresponde a 1.
10.6 Enteros largos 115
Siempre que se llama a fibonacci comprueba si el diccionario
contiene el resul- tado ya calculado. Si esta´ ah´ı, la funci´on puede
devolver el valor inmediatamente sin hacer m´as llamadas
recursivas. Si no, tiene que calcular el nuevo valor. El nuevo valor
se an˜ade al diccionario antes de que la funci´on vuelva.
Con esta versio´n de fibonacci, nuestra maquina puede calcular
fibonacci(40) en un abrir y cerrar de ojos. Pero cuando intentamos
calcular fibonacci(50), nos encontramos con otro problema:
>>> fibonacci(50)
OverflowError: integer addition
La respuesta, como ver´a en un momento, es 20.365.011.074. El
problema es que este nu´mero es demasiado grande para caber en
un entero de Python. Se desborda. Afortunadamente, hay una
solucio´n facil para este problema.
10.6. Enteros largos
Python proporciona un tipo llamado long int que puede manejar
enteros de cualquier taman˜o. Hay dos formas de crear un valor
long int. Una es escribir un entero con una L mayu´scula al final:
>>> type(1L)
<type ’long int’>
La otra es usar la funci´on long para convertir un valor en long int.
long acepta cualquier tipo num´erico e incluso cadenas de d´ıgitos:
>>> long(1)
1L
>>> long(3.9)
3L
>>> long(’57’)
57L
Todas las operaciones matem´aticas funcionan sobre los long ints,
as´ı que no tenemos que hacer mucho para adaptar fibonacci:
>>> previous = {0:1L, 1:1L}
>>> fibonacci(50)
20365011074L
Simplemente cambiando el contenido inicial de anteriores
cambiamos el com- portamiento de fibonacci. Los primeros dos nu
´meros de la secuencia son long ints, as´ı que todos los nu´meros
subsiguientes lo ser´an tambi´en.
1 Diccionari
Como ejercicio, modifique factorial de forma que produzca un
long int como resultado.
10.7. Contar letras
En el cap´ıtulo 7 escribimos una funci´on que contaba el nu´mero
de apariciones de una letra en una cadena. Una versio´n mas gen
´erica de este problema es crear un histograma de las letras de la
cadena, o sea, cua´ntas veces aparece cada letra. Ese histograma
podr´ıa ser u´til para comprimir un archivo de texto. Como las
diferentes letras aparecen con frecuencias distintas, podemos
comprimir un ar- chivo usando c´odigos cortos para las letras mas
habituales y c´odigos mas largos para las que aparecen con menor
frecuencia.
Los diccionarios facilitan una forma elegante de generar un histograma:
>>> cuentaLetras = {}
>>> for letra in "Mississippi":
... cuentaLetras[letra] = cuentaLetras.get (letra, 0) + 1
...
>>> cuentaLetras
{’M’: 1, ’s’: 4, ’p’: 2, ’i’: 4}
>>>
Inicialmente, tenemos un diccionario vac´ıo. Para cada letra de la
cadena, bus- camos el recuento actual (posiblemente cero) y lo
incrementamos. Al final, el diccionario contiene pares de letras y
sus frecuencias.
Puede ser mas atractivo mostrar el histograma en orden alfab
´etico. Podemos hacerlo con los m´etodos items y sort:
>>> itemsLetras = cuentaLetras.items()
>>> itemsLetras.sort()
>>> print itemsLetras
[(’M’, 1), (’i’, 4), (’p’, 2), (’s’, 4)]
Ya hab´ıa visto usted el m´etodo items, pero sort es el primer m
´etodo aplicable a listas que hemos visto. Hay varios m´as, como
append, extend, y reverse. Consulte la documentaci´on de Python
para ver los detalles.
10.8. Glosario
diccionario: Una coleccio´n de pares clave-valor que establece una
correspon- dencia entre claves y valores. Las claves pueden
ser de cualquier tipo in- mutable, los valores pueden ser de
cualquier tipo.
10.8 Glosario 117
clave: Un valor que se usa para buscar una entrada en un diccionario.
par clave-valor: Uno de los elementos de un diccionario, tambi´en
llamado “asociacio´n”.
m´etodo: Un tipo de funci´on al que se llama con una sintaxis
diferente y al que se invoca “sobre” un objeto.
invocar: Llamar a un m´etodo.
pista: Almacenamiento temporal de un valor precalculado para
evitar c´alculos redundantes.
desbordamiento: Un resultado num´erico que es demasiado grande para
re- presentarse en formato num´erico.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Cap´ıtulo 11
Archivos y excepciones
Cuando un programa se esta´ ejecutando, sus datos esta´n en la
memoria. Cuando un programa termina, o se apaga el computador,
los datos de la memoria desa- parecen. Para almacenar los datos
de forma permanente debe usted ponerlos en un archivo.
Normalmente los archivos se guardan en un disco duro, disquete o
CD-ROM.
Cuando hay un gran nu´mero de archivos, suelen estar organizados
en directo- rios (tambi´en llamados “carpetas”). Cada archivo se
identifica con un nombre u´nico, o una combinaci´on de nombre de
archivo y nombre de directorio.
Leyendo y escribiendo archivos, los programas pueden
intercambiar informaci´on entre ellos y generar formatos
imprimibles como PDF.
Trabajar con archivos se parece mucho a trabajar con libros. Para
usar un libro, tiene que abrirlo. Cuando ha terminado, tiene que
cerrarlo. Mientras el libro esta´ abierto, puede escribir en ´el o
leer de ´el. En cualquier caso, sabe en qu´e lugar del libro se
encuentra. Casi siempre lee el libro segu´n su orden natural, pero
tambi´en puede ir saltando de una p´agina a otra.
Todo esto sirve tambi´en para los archivos. Para abrir un archivo,
especifique su nombre e indique si quiere leer o escribir.
La apertura de un archivo crea un objeto archivo. En este ejemplo, la
variable
f apunta al nuevo objeto archivo.
>>> f = open("test.dat","w")
>>> print f
<open file ’test.dat’, mode ’w’ at fe820>
1 Archivos y
La funci´on open toma dos argumentos. El primero es el nombre
del archivo y el segundo es el modo. El modo ’w’ (write) significa
que lo estamos abriendo para escribir.
Si no hay un archivo llamado test.dat se creara´. Si ya hay uno, el
archivo que estamos escribiendo lo reemplazara´.
Al imprimir el objeto archivo, vemos el nombre del archivo, el modo
y la loca- lizaci´on del objeto.
Para meter datos en el archivo invocamos al m´etodo write sobre el
objeto archivo:
>>> f.write("Ya es hora")
>>> f.write("de cerrar el archivo")
El cierre del archivo le dice al sistema que hemos terminado de
escribir y deja el archivo listo para leer:
>>> f.close()
Ya podemos abrir el archivo de nuevo, esta vez para lectura, y
poner su contenido en una cadena. Esta vez el argumento de
modo es ’r’ (read) para lectura:
>>> f = open("test.dat","r")
Si intentamos abrir un archivo que no existe, recibimos un mensaje de
error:
>>> f = open("test.cat","r")
IOError: [Errno 2] No such file or directory: ’test.cat’
Como era de esperar, el m´etodo read lee datos del archivo. Sin
argumentos, lee el archivo completo:
>>> text = f.read()
>>> print text
Ya es horade cerrar el archivo
No hay un espacio entre “hora” y “de” porque no escribimos un
espacio entre las cadenas.
read tambi´en puede aceptar un argumento que le indica cua´ntos
caracteres leer:
>>> f = open("test.dat","r")
>>> print f.read(7)
Ya es h
Si no quedan suficientes caracteres en el archivo, read devuelve los
que haya. Cuando llegamos al final del archivo, read devuelve una
cadena vac´ıa:
11.1 Archivos de texto 121
>>> print f.read(1000006)
orade cerrar el archivo
>>> print f.read()
>>>
La siguiente funci´on copia un archivo, leyendo y escribiendo los
caracteres de cincuenta en cincuenta. El primer argumento es el
nombre del archivo original; el segundo es el nombre del archivo
nuevo:
def copiaArchivo(archViejo,
archNuevo): f1 = open(archViejo, "r")
f2 = open(archNuevo, "w")
while 1:
texto = f1.read(50)
if texto == "":
break
f2.write(texto)
f1.close()
f2.close()
return
La sentencia break es nueva. Su ejecucio´n interrumpe el bucle;
el flujo de la ejecucio´n pasa a la primera sentencia tras el bucle.
En este ejmplo, el bucle while es infinito porque el valor 1 siempre
es verdadero. La u´nica forma de salir del bucle es ejecutar break, lo
que sucede cuando texto es una cadena vac´ıa, lo que sucede
cuando llegamos al final del archivo.
11.1. Archivos de texto
Un archivo de texto es un archivo que contiene caracteres
imprimibles y es- pacios organizados en l´ıneas separadas por
caracteres de salto de l´ınea. Como Python esta´ disen˜ado espec
´ıficamente para procesar archivos de texto, propor- ciona m
´etodos que facilitan la tarea.
Para hacer una demostraci´on, crearemos un archivo de texto con
tres l´ıneas de texto separadas por saltos de l´ınea:
>>> f = open("test.dat","w")
>>> f.write("l´ınea unonl´ınea dosnl´ınea tresn")
>>> f.close()
El m´etodo readline lee todos los caracteres hasta e inclusive el
siguiente salto de l´ınea:
1 Archivos y
>>> f = open("test.dat","r")
>>> print f.readline() l
´ınea uno
>>>
readlines devuelve todas las l´ıneas que queden como una lista de cadenas:
>>> print f.readlines()
[’l´ınea dos012’, ’l´ınea tres012’]
En este caso, la salida esta´ en forma de lista, lo que significa que
las cadenas aparecen con comillas y el cara´cter de salto de l´ınea
aparece como la secuencia de escape
012.
Al final del archivo, readline devuelve una cadena vac´ıa y readlines
devuelve una lista vac´ıa:
>>> print f.readline()
>>> print f.readlines()
[]
Lo que sigue es un ejemplo de un programa de proceso de l´ıneas.
filtraArchivo
hace una copia de archViejo, omitiendo las l´ıneas que comienzan por #:
def filtraArchivo(archViejo, archNuevo):
f1 = open(archViejo, "r")
f2 = open(archNuevo,
"w") while 1:
texto =
f1.readline() if
texto == "":
break
if texto[0] == ’#’:
continue
f2.write(texto)
f1.close()
f2.close()
return
La sentencia continue termina la iteraci´on actual del bucle, pero
sigue haciendo bucles. El flujo de ejecucio´n pasa al principio del
bucle, comprueba la condicio´n y continu´a en consecuencia.
11.2 Escribir variables 123
As´ı, si texto es una cadena vac´ıa, el bucle termina. Si el primer
cara´cter de texto es una almohadilla, el flujo de ejecucio´n va al
principio del bucle. Solo si ambas condiciones fallan copiamos texto
en el archivo nuevo.
11.2. Escribir variables
El argumento de write debe ser una cadena, as´ı que si
queremos poner otros valores en un archivo, tenemos que
convertirlos antes en cadenas. La forma mas f´acil de hacerlo es
con la funci´on str:
>>> x = 52
>>> f.write (str(x))
Una alternativa es usar el operador de formato . Cuando aplica a
enteros, es el operador de m´odulo. Pero cuando el primer
operando es una cadena, es el operador de formato.
El primer operando es la cadena de formato, y el segundo operando
es una tupla de expresiones. El resultado es una cadena que
contiene los valores de las expresiones, formateados de acuerdo a
la cadena de formato.
A modo de ejemplo simple, la secuencia de formato ’ d’ significa
que la primera expresio´n de la tupla deber´ıa formatearse como
un entero. Aqu´ı la letra d quiere decir “decimal”:
>>> motos = 52
>>> " d" motos
’52’
El resultado es la cadena ’52’, que no debe confundirse con el
valor entero 52. Una secuencia de formato puede aparecer en
cualquier lugar de la cadena de formato, de modo que podemos
incrustar un valor en una frase:
>>> motos = 52
>>> "En julio vendimos d motos." motos
’En julio vendimos 52 motos.’
La secuencia de formato ’ f’ formatea el siguiente elemento
de la tupla como un nu´mero en coma flotante, y ’ s’ formatea el
siguiente elemento como una cadena:
>>> "En d d´ıas ingresamos f millones de
s." (34,6.1,’d´olares’)
’En 34 d´ıas ingresamose 6.100000 miliones de d´olares.’
1 Archivos y
Por defecto, el formato de coma flotante imprime seis decimales.
El nu´mero de expresiones en la tupla tiene que coincidir con el nu
´mero de secuencias de formato de la cadena. Igualmente, los tipos
de las expresiones deben coincidir con las secuencias de formato:
>>> " d d d" (1,2)
TypeError: not enough arguments for format string
>>> " d" ’d´olares’
TypeError: illegal argument type for built-in operation
En el primer ejemplo, no hay suficientes expresiones; en el
segundo, la expresio´n es de un tipo incorrecto.
Para tener m´as control sobre el formato de los nu´meros,
podemos detallar el nu´mero de d´ıgitos como parte de la
secuencia de formato:
>>> " 6d" 62
’ 62’
>>> " 12f"
6.1 ’ 6.100000’
El nu´mero tras el signo de porcentaje es el nu´mero m´ınimo de
espacios que ocupara´ el nu´mero. Si el valor necesita menos d
´ıgitos, se an˜aden espacios en blanco delante del nu´mero. Si el
nu´mero de espacios es negativo, se an˜aden los espacios tras el nu
´mero:
>>> " -6d"
62 ’62 ’
Tambi´en podemos especificar el nu´mero de decimales para los nu
´meros en coma flotante:
>>> " 12.2f" 6.1
’ 6.10’
En este ejemplo, el resultado ocupa doce espacios e incluye dos d
´ıgitos tras la coma. Este formato es u´til para imprimir cantidades
de dinero con las comas alineadas.
Imagine, por ejemplo, un diccionario que contiene los nombres de
los estudiantes como clave y las tarifas horarias como valores. He
aqu´ı una funci´on que imprime el contenido del diccionario como
un informe formateado:
def informe (tarifas) :
estudiantes = tarifas.keys()
estudiantes.sort()
11.3 Directorios 125
for estudiante in estudiantes :
print " -20s 12.02f" (estudiante, tarifas[estudiante])
Para probar la funci´on, crearemos un pequen˜o diccionario e
imprimiremos el contenido:
>>> tarifas
>>> informe
= {’mar
´ıa’:
(tarifas)
6.23, ’jos´e’: 5.45, ’jes´us’:
4.25}
jos´e 5.45
jes´us 4.25
mar´ıa 6.23
Controlando la anchura de cada valor nos aseguramos de que las
columnas van a quedar alineadas, siempre que los nombres tengan
menos de veintiu´n caracteres y las tarifas sean menos de mil
millones la hora.
11.3. Directorios
Cuando usted crea un archivo nuevo abri´endolo y escribiendo, el
nuevo archi- vo va al directorio en uso (aqu´el en el que etuviese al
ejecutar el programa). Del mismo modo, cuando abre un archivo
para leerlo, Python lo busca en el directorio en uso.
Si quiere abrir un archivo de cualquier otro sitio, tiene que especificar la
ruta
del archivo, que es el nombre del directorio (o carpeta) donde se encuentra
´este:
>>> f = open("/usr/share/dict/words","r")
>>> print f.readline()
Aarhus
Este ejemplo abre un archivo llamado words que esta´ en un
directorio llamado dict, que esta´ en share, que esta´ en usr, que
esta´ en el directorio de nivel superior del sistema, llamado /.
No puede usar / como parte del nombre de un archivo; esta´
reservado como delimitador entre nombres de archivo y
directorios.
El archivo /usr/share/dict/words contiene una lista de palabras en
orden alfab´etico, la primera de las cuales es el nombre de una
universidad danesa.
11.4. Encurtido
Para poner valores en un archivo, debe convertirlos en cadenas.
Ya ha visto c´omo hacerlo con str:
1 Archivos y
>>> f.write (str(12.3))
>>> f.write (str([1,2,3]))
El problema es que cuando vuelve usted a leer el valor, obtiene una
cadena. Ha perdido la informaci´on del tipo de dato original. En
realidad, no puede distinguir d´onde termina un valor y comienza el
siguiente:
>>> f.readline()
’12.3[1, 2, 3]’
La solucio´n es el encurtido, llamado as´ı porque “conserva”
estructuras de datos. El m´odulo pickle contiene las o´rdenes
necesarias. Para usarlo, importe pickle y luego abra el archivo de la
forma habitual:
>>> import pickle
>>> f = open("test.pck","w")
Para almacenar una estructura de datos, use el m´etodo dump y
luego cierre el archivo de la forma habitual:
>>> pickle.dump(12.3, f)
>>> pickle.dump([1,2,3], f)
>>> f.close()
Ahora podemos abrir el archivo para leer y cargar las estructuras
de datos que volcamos ah´ı:
>>> f = open("test.pck","r")
>>> x = pickle.load(f)
>>> x
12.3
>>> type(x)
<type ’float’>
>>> y = pickle.load(f)
>>> y
[1, 2, 3]
>>> type(y)
<type ’list’>
Cada vez que invocamos load obtenemos un valor del archivo,
completo con su tipo original.
11.5. Excepciones
Siempre que ocurre un error en tiempo de ejecucio´n, se crea una
excepci´on. Normalmente el programa se para y Pythton presenta
un mensaje de error.
11.5 Excepciones 127
Por ejemplo, la divisi´on por cero crea una excepcio´n:
>>> print 55/0
ZeroDivisionError: integer division or modulo
Un elemento no existente en una lista hace lo mismo:
>>> a = []
>>> print a[5]
IndexError: list index out of range
O el acceso a una clave que no esta´ en el diccionario:
>>> b = {}
>>> print b[’qu
´e’] KeyError: qu
´e
En cada caso, el mensaje de error tiene dos partes: el tipo de error
antes de los dos puntos y detalles sobre el error depu´es de los dos
puntos. Normalmente Python tambi´en imprime una traza de d
´onde se encontraba el programa, pero la hemos omitido en los
ejemplos.
A veces queremos realizar una operacio´n que podr´ıa provocar
una excepcio´n, pero no queremos que se pare el programa.
Podemos manejar la excepcio´n usando las sentencias try y except.
Por ejemplo, podemos preguntar al usuario por el nombre de un
archivo y luego intentar abrirlo. Si el archivo no existe, no
queremos que el programa se pare; queremos manejar la excepcio
´n.
nombreArch = raw_input(’Introduce un nombre de archivo: ’)
try:
f = open (nombreArch, "r")
except:
print ’No hay ning´un archivo que se llame’, nombreArch
La sentencia try ejecuta las sentencias del primer bloque. Si no se
produce nin- guna excepcio´n, pasa por alto la sentencia except. Si
ocurre cualquier excepcio´n, ejecuta las sentencias de la rama
except y despu´es continu´a.
Podemos encapsular esta capacidad en una funci´on: existe acepta
un nombre de archivo y devuelve verdadero si el archivo existe y
falso si no:
def existe(nombreArch):
try:
f = open(nombreArch)
f.close()
1 Archivos y
return 1
except:
return 0
Puede usar mu´ltiples bloques except para manejar diferentes tipos
de excep- ciones. El Manual de Referencia de Python contiene los
detalles.
Si su programa detecta una condicio´n de error, puede hacer que
lance (raise en ingl´es) una excepcio´n. Aqu´ı tiene usted un
ejemplo que acepta una entrada del usuario y comprueba si es 17.
Suponiendo que 17 no es una entrada valida por cualquier razo´n,
lanzamos una excepcio´n.
def tomaNumero () : # Recuerde, los acentos est
´an x = input (’Elige un n´umero: ’) # prohibidos en los
nombres if x == 17 : # de funciones y variables!
raise ’ErrorN´umeroMalo’, ’17 es un mal n
´umero’ return x
La sentencia raise acepta dos argumentos: el tipo de excepcio´n e
informaci´on espec´ıfica acerca del error. ErrorN´umeroMalo es un
nuevo tipo de excepcio´n que hemos inventado para esta aplicaci
´on.
Si la funci´on llamada tomaNumero maneja el error, el programa
puede continuar; en caso contrario, Python imprime el mensaje de
error y sale:
>>> tomaNumero ()
Elige un n´umero: 17
ErrorN´umeroMalo: 17 es un mal n´umero
El mensaje de error incluye el tipo de excepcio´n y la informaci´on
adicional que usted proporcion´o.
Como ejercicio, escriba una funci´on que use tomaNumero para
leer un nu´mero del teclado y que maneje la excepci´on ErrorN
´umeroMalo.
11.6. Glosario
archivo: Una entidad con nombre, normalmente almacenada en un
disco duro, disquete o CD-ROM, que contiene una secuencia de
caracteres.
directorio: Una coleccio´n, con nombre, de archivos, tambi´en llamado
carpeta.
ruta: Una secuencia de nombres de directorio que especifica la
localizacio´n exacta de un archivo.
11.6 Glosario 129
archivo de texto: Un archivo que contiene caracteres imprimibles
organizados en l´ıneas separadas por caracteres de salto de l
´ınea.
sentencia break: Una sentencia que provoca que el flujo de ejecucio
´n salga de un bucle.
sentencia continue: Una sentencia que provoca que termine la
iteraci´on ac- tual de un bucle. El flujo de la ejecucio´n va al
principio del bucle, evalu´a la condicio´n, y procede en
consecuencia.
operador de formato: El operador toma una cadena de formato y una
tupla de expresiones y entrega una cadena que incluye las
expresiones, forma- teadas de acuerdo con la cadena de
formato.
cadena de formato: Una cadena que contiene caracteres imprimibles
y se- cuencias de formato que indican c´omo formatear
valores.
secuencia de formato: Una secuencia de caracteres que comienza con
e in- dica c´omo formatear un valor.
encurtir: Escribir el valor de un dato en un archivo junto con la
informaci´on sobre su tipo de forma que pueda ser
reconstituido mas tarde.
excepci´on: Un error que ocurre en tiempo de ejecucio´n.
manejar: Impedir que una excepcio´n detenga un programa
utilizando las sen- tencias try y except.
lanzar: Sen˜alar una excepcio´n usando la sentencia raise.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Cap´ıtulo 12
Clases y objetos
12.1. Tipos compuestos definidos por el usuario
Una vez utilizados algunos de los tipos internos de Python, estamos
listos para crear un tipo definido por el usuario: el Punto.
Piense en el concepto de un punto matem´atico. En dos
dimensiones, un punto es dos nu´meros (coordenadas) que se
tratan colectivamente como un solo objeto. En notaci´on matem
´atica, los puntos suelen escribirse entre par´entesis con una coma
separando las coordenadas. Por ejemplo, (0, 0) representa el
origen, y (x, y) representa el punto x unidades a la derecha e y
unidades hacia arriba desde el origen.
Una forma natural de representar un punto en Python es con dos
valores en coma flotante. La cuestio´n es, entonces, c´omo agrupar
esos dos valores en un objeto compuesto. La solucio´n r´apida y
burda es utilizar una lista o tupla, y para algunas aplicaciones esa
podr´ıa ser la mejor opcio´n.
Una alternativa es que el usuario defina un nuevo tipo
compuesto, tambi´en llamado una clase. Esta aproximaci´on exige
un poco mas de esfuerzo, pero tiene sus ventajas que pronto se
har´an evidentes.
Una definici´on de clase se parece a esto:
class Punto:
pass
Las definiciones de clase pueden aparecer en cualquier lugar de
un programa, pero normalmente esta´n al principio (tras las
sentencias import). Las reglas
1 Clases y
3.0
4.0
x
y
sint´acticas de la definici´on de clases son las mismas que para
cualesquiera otras sentencias compuestas. (ver la Secci´on 4.4).
Esta definici´on crea una nueva clase llamada Punto. La sentencia
pass no tiene efectos; s´olo es necesaria porque una sentencia
compuesta debe tener algo en su cuerpo.
Al crear la clase Punto hemos creado un nuevo tipo, que tambi´en se
llama Punto. Los miembros de este tipo se llaman instancias del tipo
u objetos. La creacio´n de una nueva instancia se llama instanciacio
´n. Para instanciar un objeto Punto ejecutamos una funci´on que se
llama (lo ha adivinado) Punto:
blanco = Punto()
A la variable blanco se le asigna una referencia a un nuevo objeto
Punto. A una funci´on como Punto que crea un objeto nuevo se le
llama constructor.
12.2. Atributos
Podemos an˜adir nuevos datos a una instancia utilizando la notaci´on de
punto:
>>> blanco.x = 3.0
>>> blanco.y = 4.0
Esta sintaxis es similar a la sintaxis para seleccionar una variable
de un modu- lo, como math.pi o string.uppercase. En este caso, sin
embargo, estamos seleccionando un dato de una instancia. Estos
´ıtemes con nombre se llaman atributos.
El diagrama de estados que sigue muestra el resultado de esas
asignaciones:
blanco
La variable blanco apunta a un objeto Punto, que contiene dos
atributos. Cada atributo apunta a un nu´mero en coma flotante.
Podemos leer el valor de un atributo utilizando la misma sintaxis:
>>> print blanco.y
4.0
>>> x = blanco.x
>>> print x
3.0
12.3 Instancias como par´ametro 133
La expresio´n blanco.x significa, “ve al objeto al que apunta blanco
y toma el valor de x”. En este caso, asignamos ese valor a una
variable llamada x. No hay conflicto entre la variable x y el atributo
x. El prop´osito de la notaci´on de punto es identificar de forma
inequ´ıvoca a qu´e variable se refiere.
Puede usted usar la notaci´on de punto como parte de cualquier
expresio´n. As´ı, las sentencias que siguen son correctas:
print ’(’ + str(blanco.x) + ’, ’ + str(blanco.y) + ’)’
distanciaAlCuadrado = blanco.x * blanco.x + blanco.y *
blanco.y
La primera l´ınea presenta (3.0, 4.0); la segunda l´ınea
calcula el valor 25.0. Puede tentarle imprimir el propio
valor de blanco:
>>> print blanco
< main .Punto instance at 80f8e70>
El resultado indica que blanco es una instancia de la clase Punto que
se de- fini´o en main . 80f8e70 es el identificador u´nico de este
objeto, escrito en hexadecimal. Probablemente no es esta la
manera mas clara de mostrar un objeto Punto. En breve ver´a c
´omo cambiarlo.
Como ejercicio, cree e imprima un objeto Punto y luego use id
pa- ra imprimir el identificador u´nico del objeto. Traduzca el nu
´mero hexadecimal a decimal y asegu´rese de que coinciden.
12.3. Instancias como par´ametro
Puede usted pasar una instancia como par´ametro de la forma
habitual. Por ejemplo:
def imprimePunto(p):
print ’(’ + str(p.x) + ’, ’ + str(p.y) + ’)’
imprimePunto acepta un punto como argumento y lo muestra en
formato esta´ndar. Si llama a imprimePunto(blanco), el resultado es
(3.0, 4.0).
Como ejercicio, reescriba la funcion distancia de la Secci´on 5.2
de forma que acepte dos Puntos como para´metros en lugar de
cuatro nu´meros.
1 Clases y
12.4. Mismidad
El significado de la palabra “mismo” parece totalmente claro hasta
que uno se para un poco a pensarlo, y entonces se da cuenta de
que hay algo mas de lo que supon´ıa.
Por ejemplo, si dice “Pepe y yo tenemos la misma moto”, lo que
quiere decir es que su moto y la de usted son de la misma marca y
modelo, pero que son dos motos distintas. Si dice “Pepe y yo
tenemos la misma madre”, quiere decir que su madre y la de usted
son la misma persona1
. As´ı que la idea de “identidad” es diferente
segu´n el contexto.
Cuando habla de objetos, hay una ambigu¨edad parecida. Por
ejemplo, si dos Puntos son el mismo, ¿significa que contienen los
mismos datos (coordenadas) o que son de verdad el mismo objeto?
Para averiguar si dos referencias se refieren al mismo objeto, utilice el
operador
==. Por ejemplo:
>>> p1 = Punto()
>>> p1.x = 3
>>> p1.y = 4
>>> p2 = Punto()
>>> p2.x = 3
>>> p2.y = 4
>>> p1 == p2
0
Aunque p1 y p2 contienen las mismas coordenadas, no son el
mismo objeto. Si asignamos p1 a p2, las dos variables son alias del
mismo objeto:
>>> p2 = p1
>>> p1 == p2
1
Este tipo de igualdad se llama igualdad superficial porque s´olo
compara las referencias, pero no el contenido de los objetos.
Para comparar los contenidos de los objetos (igualdad profunda)
podemos escribir una funci´on llamada mismoPunto:
def mismoPunto(p1, p2) :
return (p1.x == p2.x) and (p1.y == p2.y)
1
No todas las lenguas tienen el mismo problema. Por ejemplo, el alem´an tiene
palabras diferentes para los diferentes tipos de identidad. “Misma moto” en este contexto
ser´ıa “gleiche Motorrad” y “misma madre” ser´ıa “selbe Mutter”.
12.5 Rect´angulos 135
Si ahora creamos dos objetos diferentes que contienen los mismos
datos podre- mos usar mismoPunto para averiguar si representan el
mismo punto:
>>> p1 = Punto()
>>> p1.x = 3
>>> p1.y = 4
>>> p2 = Punto()
>>> p2.x = 3
>>> p2.y = 4
>>> mismoPunto(p1, p2)
1
Por supuesto, si las dos variables apuntan al mismo objeto
mismoPunto devuelve verdadero.
12.5. Rect´angulos
Digamos que queremos una clase que represente un recta´ngulo. La
pregunta es,
¿qu´e informaci´on tenemos que proporcionar para definir un recta
´ngulo? Para simplificar las cosas, supongamos que el recta´ngulo
esta´ orientado vertical u horizontalmente, nunca en diagonal.
Tenemos varias posibilidades: podemos sen˜alar el centro del recta
´ngulo (dos coordenadas) y su taman˜o (anchura y altura); o
podemos sen˜alar una de las esquinas y el taman˜o; o podemos
sen˜alar dos esquinas opuestas. Un modo con- vencional es
sen˜alar la esquina superior izquierda del recta´ngulo y el taman˜o.
De nuevo, definiremos una nueva clase:
class Rectangulo: # Prohibidos los acentos fuera de las cadenas!
pass
Y la instanciaremos:
caja = Rectangulo()
caja.anchura = 100.0
caja.altura = 200.0
Este c´odigo crea un nuevo objeto Rectangulo con dos atributos en
coma flo- tante. ¡Para sen˜alar la esquina superior izquierda
podemos incrustar un objeto dentro de otro!
caja.esquina = Punto()
caja.esquina.x = 0.0;
caja.esquina.y = 0.0;
1 Clases y
anchura
altura esquina
100.0
200.0
0.0
0.0
x
y
El operador punto compone. La expresio´n caja.esquina.x significa
“ve al ob- jeto al que se refiere caja y selecciona el atributo llamado
esquina; entonces ve a ese objeto y selecciona el atributo llamado
x”.
La figura muestra el estado de este objeto:
caja
12.6. Instancias como valores de retorno
Las funciones pueden devolver instancias. Por ejemplo,
encuentraCentro acep- ta un Rectangulo como argumento y devuelve
un Punto que contiene las coor- denadas del centro del Rectangulo:
def encuentraCentro(caja):
p = Punto()
p.x = caja.esquina.x + caja.anchura/2.0
p.y = caja.esquina.y + caja.altura/2.0
return p
Para llamar a esta funci´on, pase caja como argumento y asigne el
resultado a una variable:
>>> centro = encuentraCentro(caja)
>>> imprimePunto(centro)
(50.0, 100.0)
12.7. Los objetos son mudables
Podemos cambiar el estado de un objeto efectuando una asignaci
´on sobre uno de sus atributos. Por ejemplo, para cambiar el
taman˜o de un recta´ngulo sin cambiar su posici´on, podemos
cambiar los valores de anchura y altura:
caja.anchura = caja.anchura + 50
caja.altura = caja.altura + 100
Podemos encapsular este c´odigo en un m´etodo y generalizarlo
para agrandar el recta´ngulo en cualquier cantidad:
12.8 Copiado 137
def agrandaRect(caja, danchura, daltura) :
caja.anchura = caja.anchura + danchura
caja.altura = caja.altura + daltura
Las variables danchura y daltura indican cua´nto debe agrandarse el
recta´ngulo en cada direcci´on. Invocar este m´etodo tiene el
efecto de modificar el Rectangulo que se pasa como argumento.
Por ejemplo, podemos crear un nuevo Rectangulo llamado bob y pas´arselo
a
agrandaRect:
>>> bob = Rectangulo()
>>> bob.anchura = 100.0
>>> bob.altura = 200.0
>>> bob.esquina = Punto()
>>> bob.esquina.x = 0.0;
>>> bob.esquina.y = 0.0;
>>> agrandaRect(bob, 50, 100)
Mientras agrandaRect se esta´ ejecutando, el par´ametro caja es un
alias de bob. Cualquier cambio que haga a caja afectar´a tambi´en a
bob.
A modo de ejercicio, escriba una funci´on llamada mueveRect
que tome un Rectangulo y dos para´metros llamados dx y dy.
Tiene que cambiar la posicio´n del rect´angulo an˜adiendo dx a la
coordenada x de esquina y an˜adiendo dy a la coordenada y de
esquina.
12.8. Copiado
El uso de alias puede hacer que un programa sea dif´ıcil de leer,
porque los cambios hechos en un lugar pueden tener efectos
inesperados en otro lugar. Es dif´ıcil estar al tanto de todas las
variables a las que puede apuntar un objeto dado.
Copiar un objeto es, muchas veces, una alternativa a la creacio´n
de un alias. El m´odulo copy contiene una funci´on llamada copy
que puede duplicar cualquier objeto:
>>> import copy
>>> p1 = Punto()
>>> p1.x = 3
>>> p1.y = 4
>>> p2 = copy.copy(p1)
>>> p1 == p2
1 Clases y
anchura
altura esquina
100.0
200.0
100.0
200.0
anchura altura
esquina
0.0
0.0
x
y
0
>>> mismoPunto(p1, p2)
1
Una vez que hemos importado el m´odulo copy, podemos usar el m
´etodo copy para hacer un nuevo Punto. p1 y p2 no son el mismo
punto, pero contienen los mismos datos.
Para copiar un objeto simple como un Punto, que no contiene
objetos incrusta- dos, copy es suficiente. Esto se llama copiado
superficial.
Para algo como un Rectangulo, que contiene una referencia a un
Punto, copy no lo hace del todo bien. Copia la referencia al objeto
Punto, de modo que tanto el Rectangulo viejo como el nuevo apuntan
a un u´nico Punto.
Si creamos una caja, b1, de la forma habitual y entonces hacemos una
copia,
b2, usando copy, el diagrama de estados resultante se ve as´ı:
b1 b2
Es casi seguro que esto no es lo que queremos. En este caso, la
invocacio´n de agrandaRect sobre uno de los Rectangulos no afectar´ıa
al otro, ¡pero la invo- cacio´n de mueveRect sobre cualquiera
afectaria a ambos! Este comportamiento es confuso y propicia los
errores.
Afortunadamente, el m´odulo copy contiene un m´etodo llamado
deepcopy que copia no s´olo el objeto sino tambi´en cualesquiera
objetos incrustados. No le sorprendera´ saber que esta operacio´n
se llama copia profunda (deep copy).
>>> b2 = copy.deepcopy(b1)
Ahora b1 y b2 son objetos totalmente independientes.
Podemos usar deepcopy para reescribir agrandaRect de modo que en
lugar de modificar un Rectangulo existente, cree un nuevo
Rectangulo que tiene la misma localizacio´n que el viejo pero nuevas
dimensiones:
def agrandaRect(caja, danchura, daltura) :
import copy
nuevaCaja = copy.deepcopy(caja)
nuevaCaja.anchura = nuevaCaja.anchura + danchura
nuevaCaja.altura = nuevaCaja.altura + daltura
return nuevaCaja
12.9 Glosario 139
Como ejercicio, resscriba mueveRect de modo que cree y
devuelva un nuevo Rectangulo en lugar de modificar el viejo.
12.9. Glosario
clase: Un tipo compuesto definido por el usuario. Tambi´en se
puede pensar en una clase como una plantilla para los objetos
que son instancias de la misma.
instanciar: Crear una instancia de una clase.
instancia: Un objeto que pertenece a una clase.
objeto: Un tipo de dato compuesto que suele usarse para
representar una cosa o concepto del mundo real.
constructor: Un m´etodo usado para crear nuevos objetos.
atributo: Uno de los elementos de datos con nombre que
constituyen una ins- tancia.
igualdad superficial: Igualdad de referencias, o dos referencias que
apuntan al mismo objeto.
igualdad profunda: Igualdad de valores, o dos referencias que
apuntan a ob- jetos que tienen el mismo valor.
copia superficial: Copiar el contenido de un objeto, incluyendo
cualquier refe- rencia a objetos incrustados; implementada por
la funci´on copy del modulo copy.
copia profunda: Copiar el contenido de un objeto as´ı como
cualesquiera ob- jetos incrustados, y los incrustados en estos,
y as´ı sucesivamente; imple- mentada por la funci´on deepcopy
del modulo copy.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
11
59
30
hour minute
second
Cap´ıtulo 13
Clases y funciones
13.1. Hora
Como otro ejemplo de un tipo definido por el usuario, definiremos
una clase llamada Hora que registra la hora del d´ıa. La definici´on
de la clase es como sigue:
class Hora:
pass
Podemos crear un nuevo objeto Hora y asignar atributos para
contener las horas, minutos y segundos:
hora = Hora()
hora.horas = 11
hora.minutos = 59
hora.segundos = 30
El diagrama de estado del objeto Hora es as´ı:
time
A modo de ejercicio, escriba una funci´on imprimeHora que acep-
te un objeto Hora como argumento y lo imprima en el formato
horas:minutos:segundos.
1 Clases y
Como un segundo ejercicio, escriba una funci´on booleana
despues que tome dos objetos Hora, t1 y t2, como argumentos y
devuelva verdadero (1) si t1 sigue cronolo´gicamente a t2 y falso
(0) en caso contrario.
13.2. Funciones puras
En las pr´oximas secciones, escribiremos dos versiones de una
funci´on llamada sumaHora que calcule la suma de dos Horas. Mostrar
´an dos tipos de funciones: funciones puras y modificadores.
E´ste es un esbozo de sumaHora:
def sumaHora(t1, t2):
suma = Hora()
suma.horas = t1.horas + t2.horas
suma.minutos = t1.minutos + t2.minutos
suma.segundos = t1.segundos + t2.segundos
return suma
La funci´on crea un nuevo objeto Hora, inicializa sus atributos y
devuelve una referencia al nuevo objeto. A esto se le llama funcio´n
pura porque no modifica ninguno de los objetos que se le pasan y
no tiene efectos laterales, como mostrar un valor o tomar una
entrada del usuario.
Aqu´ı tiene un ejemplo de c´omo usar esta funci´on. Crearemos dos
objetos Hora: horaActual, que contiene la hora actual, y horaPan, que
contiene la cantidad de tiempo que necesita un panadero para
hacer pan. Luego usaremos sumaHora para averiguar cua´ndo
estara´ hecho el pan. Si au´n no ha terminado de escribir
imprimeHora, eche un vistazo a la Secci´on 14.2 antes de probar
esto:
>>> horaActual = Hora()
>>> horaActual.horas = 9
>>> horaActual.minutos = 14
>>> horaActual.segundos = 30
>>> horaPan = Hora()
>>> horaPan.horas = 3
>>> horaPan.minutos = 35
>>> horaPan.segundos = 0
>>> horaHecho = sumaHora(horaActual, horaPan)
>>> imprimeHora(horaHecho)
13.3 Modificadores 143
La salida de este programa es 12:49:30, lo que es correcto. Por otra
parte, hay casos en los que el resultado no es correcto. ¿Puede
imaginar uno?
El problema es que esta funci´on no trata los casos en los que el
nu´mero de segundos o minutos suma m´as que sesenta. Cuando
ocurre eso, debemos “llevar” los segundos sobrantes a la columna
de los minutos o los minutos extras a la columna de las horas.
He aqu´ı una versio´n corregida de la funci´on:
def sumaHora(t1, t2):
suma = Hora()
suma.horas = t1.horas + t2.horas
suma.minutos = t1.minutos + t2.minutos
suma.segundos = t1.segundos + t2.segundos
if suma.segundos >= 60:
suma.segundos = suma.segundos -
60 suma.minutos = suma.minutos + 1
if suma.minutos >= 60:
suma.minutos = suma.minutos -
60 suma.horas = suma.horas + 1
return suma
Aunque esta funci´on es correcta, empieza a ser grande. Mas
adelante sugeriremos una aproximaci´on alternativa que nos dar´a
un c´odigo mas corto.
13.3. Modificadores
Hay veces en las que es u´til que una funci´on modifique uno o mas
de los objetos que recibe como par´ametros. Normalmente, el
llamante conserva una referencia a los objetos que pasa, as´ı que
cualquier cambio que la funci´on haga ser´a visible para el
llamante. Las funciones que trabajan as´ı se llaman modificadores.
incremento, que an˜ade un nu´mero dado de segundos a un
objeto Hora, se escribir´ıa de forma natural como un modificador.
Un esbozo r´apido de la funci´on podr´ıa ser ´este:
1 Clases y
def incremento(hora, segundos):
hora.segundos = hora.segundos + segundos
if hora.segundos >= 60:
hora.segundos = hora.segundos - 60
hora.minutos = hora.minutos + 1
if hora.minutos >= 60:
hora.minutos = hora.minutos - 60
hora.horas = hroa.horas + 1
La primera l´ınea realiza la operacio´n b´asica, las restantes tratan
con los casos especiales que vimos antes.
¿Es correcta esta funci´on? ¿Qu´e ocurre si el par´ametro
segundos es mucho mayor que sesenta? En tal caso, no es
suficiente con acarrear una vez; debemos seguir haci´endolo hasta
que segundos sea menor que sesenta. Una solucio´n es sustituir las
sentencias if por sentencias while:
def incremento(hora, segundos):
hora.segundos = hora.segundos + segundos
while hora.segundos >= 60:
hora.segundos = hora.segundos - 60
hora.minutos = hora.minutos + 1
while hora.minutos >= 60:
hora.minutos = hora.minutos - 60
hora.horas = hroa.horas + 1
Ahora esta funci´on es correcta, pero no es la solucio´n mas eficiente.
Como ejercicio, reescriba esta funcion de modo que no contenga tan-
tos bucles.
Como un segundo ejercicio, reescriba incremento como una funci
´on pura, y escriba una funcion que llame a ambas versiones.
13.4. ¿Qu´e es mejor?
Todo lo que se pueda hacer con modificadores puede hacerse tambi
´en con fun- ciones puras. En realidad, algunos lenguajes de
programaci´on s´olo permiten funciones puras. Hay ciertas
evidencias de que los programas que usan funcio- nes puras son m
´as r´apidos de desarrollar y menos propensos a los errores que
13.5 Desarrollo de prototipos frente a planificaci´on 145
los programas que usan modificadores. Sin embargo, a veces los
modificadores son u´tiles, y en algunos casos los programas
funcionales son menos eficientes. En general, recomendamos que
escriba funciones puras siempre que sea razona- ble hacerlo as´ı y
recurra a los modificadores s´olo si hay una ventaja convincente.
Este enfoque podr´ıa llamarse estilo funcional de programaci´on.
13.5. Desarrollo de prototipos frente a planifi-
caci´on
En este cap´ıtulo mostramos una aproximaci´on al desarrollo de
programas a la que llamamos desarrollo de prototipos. En cada caso,
escribimos un esbozo basto (o prototipo) que realizaba el c´alculo
basico y luego lo probamos sobre unos cuantos casos, corrigiendo
los fallos tal como los encontr´abamos.
Aunque este enfoque puede ser efecitvo, puede conducirnos a c
´odigo que es innecesariamente complicado, ya que trata con
muchos casos especiales, y poco fiable, porque es dif´ıcil saber si
encontr´o todos los errores.
Una alternativa es el desarrollo planificado, en el que una
comprensio´n del problema en profundidad puede hacer la
programaci´on mucho mas facil. En este caso, el enfoque es que un
objeto Hora es en realidad ¡un nu´mero de tres d´ıgitos en base 60!
El componente segundo es la “columna de unidades”, el
componente minuto es la “columna de las sesententas” y el
componente hora es la “columna de las tresmilseiscentenas”.
Cuando escribimos sumaHora e incremento, en realidad esta´bamos
haciendo una suma en base 60, que es por lo que deb´ıamos
acarrear de una columna a la siguiente.
Esta observaci´on sugiere otro enfoque para el problema. Podemos
convertir un objeto Hora en un simple nu´mero y sacar provecho del
hecho de que la maquina sabe la aritm´etica necesaria. La
siguiente funci´on convierte un objeto Hora en un entero:
def convierteASegundos(t):
minutos = t.horas * 60 + t.minutos
segundos = minutos * 60 + t.segundos
return segundos
Ahora, s´olo necesitamos una forma de convertir un entero en un objeto
Hora:
def haceHora(segundos):
hora = Hora()
hora.horas = segundos/3600
1 Clases y
segundos = segundos - hora.horas * 3600
hora.minutos = segundos/60
segundos = segundos - hora.minutos * 60
hora.segundos = segundos
return hora
Puede que tenga usted que pensar un poco para convencerse de
que esta t´ecni- ca para convertir de una base a otra es correcta.
Suponiendo que esta´ usted convencido, puede usar estas
funciones para reescribir sumaHora:
def sumaHora(t1, t2):
segundos = convierteASegundos(t1) + convierteASegundos(t2)
return haceHora(segundos)
Esta versio´n es mucho m´as corta que la original, y es mucho mas
facil de de- mostrar que es correcta (suponiendo, como es habitual,
que las funciones a las que llama son correctas).
Como ejercicio, reescriba incremento de la misma forma.
13.6. Generalizacio´n
De algu´n modo, convertir de base 60 a base 10 y de vuelta es
mas dif´ıcil que simplemente manejarse con las horas. La conversio
´n de base es mas abstracta; nuestra intuici´on para tratar con las
horas es mejor.
Pero si tenemos la comprensio´n para tratar las horas como nu
´meros en base 60, y hacer la inversio´n de escribir las funciones
de conversio´n (convierteASegundos y haceHora), obtenemos un
programa que es mas corto, mas facil de leer y depurar y m´as
fiable.
Tambi´en es m´as f´acil an˜adir funcionalidades mas tarde. Por
ejemplo, imagine restar dos Horas para hallar el intervalo entre
ellas. La aproximaci´on ingenua ser´ıa implementar la resta con
acarreo. Con el uso de las funciones de conversio´n ser´a m´as f
´acil y con mayor probabilidad, correcto.
Ir´onicamente, a veces hacer un poblema mas complejo (o mas
general) lo hace m´as f´acil (porque hay menos casos especiales y
menos oportunidades de error).
13.7. Algoritmos
Cuando escribe una solucio´n general para una clase de problemas,
en contraste con una solucio´n espec´ıfica a un problema concreto,
ha escrito un algoritmo.
−
−
13.8 Glosario 147
Mencionamos esta palabra antes pero no la definimos con precisi
´on. No es facil de definir, as´ı que probaremos un par de enfoques.
Primero, piense en algo que no es un algoritmo. Cuando usted
aprendi´o a mul- tiplicar nu´meros de una cifra, probablemente
memoriz´o la tabla de multiplicar. En efecto, memoriz´o 100
soluciones espec´ıficas. Ese tipo de conocimiento no es algor´ıtmico.
Pero si usted era “harag´an” probablemente hizo trampa
aprendiendo algunos trucos. Por ejemplo, para encontrar el
producto de n por 9, puede escribir n
1 como el primer d´ıgito y 10 n como el segundo d´ıgito. Este truco
es una solucio´n general para multiplicar cualquier nu´mero de una
cifra por 9. ¡Eso es un algoritmo!
De forma similar, las t´ecnicas que aprendi´o para la suma y la
resta con aca- rreo y la divisi´on larga son todas algoritmos. Una
de las caracter´ısticas de los algoritmos es que no requieren
inteligencia para llevarse a cabo. Son procesos mec´anicos en los
que cada paso sigue al anterior de acuerdo a un conjunto simple de
reglas.
En nuestra opini´on, es un poco vergonzoso que los humanos pasen
tanto tiempo en la escuela aprendiendo a ejecutar algoritmos que,
de forma bastante similar, no exigen inteligencia.
Por otra parte, el proceso de disen˜ar algoritmos es interesante, un
desaf´ıo inte- lectual y una parte primordial de lo que llamamos
programar.
Algunas de las cosas que la gente hace naturalmente, sin dificultad
ni pensa- miento consciente, son las m´as dif´ıciles de expresar
algor´ıtmicamente. Entender el lenguaje natural es un buen
ejemplo. Todos lo hacemos, pero hasta el momen- to nadie ha sido
capaz de explicar c´omo lo hacemos, al menos no en la forma de un
algoritmo.
13.8. Glosario
funcio´n pura: Una funci´on que no modifica ninguno de los objetos
que recibe como par´ametros. La mayor´ıa de las funciones puras
son rentables. modificador: Una funci´on que modifica uno o mas
de los objetos que recibe como par´ametros. La mayor´ıa de los
modificadores no entregan resultado. estilo funcional de
programaci´on: Un estilo de programaci´on en el que la
mayor´ıa de las funciones son puras.
desarrollo de prototipos: Una forma de desarrollar programas
empezando con un prototipo y prob´andolo y mejor´andolo
gradualmente.
1 Clases y
desarrollo planificado: Una forma de desarrollar programas que
implica una comprensio´n de alto nivel del problema y mas
planificaci´on que desarrollo incremental o desarrollo de
prototipos.
algoritmo: Un conjunto de instrucciones para solucionar una clase
de proble- mas por medio de un proceso mec´anico sin
intervencio´n de inteligencia.
Cap´ıtulo 14
Clases y m´etodos
14.1. Caracter´ısticas de la orientaci´on a objetos
Python es un lenguaje de programaci´on orientado a objetos, lo que
signi- fica que porporciona caracter´ısticas que apoyan la
programaci´on orientada a objetos.
No es f´acil definir la programaci´on orientada a objetos, pero ya
hemos visto algunas de sus caracter´ısticas:
Los programas se hacen a base de definiciones de objetos y
definiciones de funciones, y la mayor parte de la computacio´n
se expresa en t´erminos de operaciones sobre objetos.
Cada definici´on de un objeto se corresponde con un objeto o
concepto del mundo real, y las funciones que operan en ese
objeto se corresponden con las formas en que interactu´an los
objetos del mundo real.
Por ejemplo, la clase Hora definida en el Cap´ıtulo 13 se
corresponde con la forma en la que la gente registra la hora del d
´ıa, y las funciones que definimos se corres- ponden con el tipo de
cosas que la gente hace con las horas. De forma similar, las clases
Punto y Rectangulo se corresponden con los conceptos matem´aticos
de un punto y un recta´ngulo.
Hasta ahora, no nos hemos aprovechado de las caracter´ısticas que
Python nos ofrece para dar soporte a la programaci´on orientada a
objetos. Hablando estric- tamente, estas caracter´ısticas no son
necesarias. En su mayor´ıa, proporcionan una sintaxis alternativa
para cosas que ya hemos hecho, pero en muchos casos,
1 Clases y m
la alternativa es m´as concisa y expresa con mas precisi´on a la
estructura del programa.
Por ejemplo, en el programa Hora no hay una conexio´n obvia entre
la definici´on de la clase y las definiciones de las funciones que
siguen. Observando bien, se hace patente que todas esas funciones
toman al menos un objeto Hora como par´ametro.
Esta observaci´on es la que motiva los m´etodos. Ya hemos visto
varios m´etodos, como keys y values, que se invocan sobre
diccionarios. Cada m´etodo esta´ aso- ciado con una clase y esta´
pensado para invocarse sobre instancias de esa clase. Los m´etodos
son como las funciones, con dos diferencias:
Los m´etodos se definen dentro de una definici´on de clase
para explicitar la relacio´n entre la clase y el m´etodo.
La sintaxis para invocar un m´etodo es diferente de la de una
llamada a una funci´on.
En las pr´oximas secciones tomaremos las funciones de los cap
´ıtulos anteriores y las transformaremos en m´etodos. Esta
transformaci´on es puramente mec´anica; puede hacerla
simplemente siguiendo una secuencia de pasos. Si se acostumbra a
convertir de una forma a la otra ser´a capaz de elegir la mejor
forma de hacer lo que quiere.
14.2. imprimeHora
En el Cap´ıtulo 13, definimos una clase llamada Hora y escribimos
una fuci´on llamada imprimeHora, que deber´ıa ser parecida a esto:
class Hora:
pass
def imprimeHora(hora):
print str(hora.horas) + ":" +
str(hora.minutos) + ":" +
str(hora.segundos)
Para llamar a esta funci´on, pas´abamos un objeto Hora como par´ametro:
>>> horaActual = Hora()
>>> horaActual.horas = 9
>>> horaActual.minutos = 14
>>> horaActual.segundos = 30
>>> impriemHora(horaActual)
14.3 Otro ejemplo 151
Para convertir imprimeHora en un m´etodo, todo lo que necesitamos
hacer es mover la definici´on de la funci´on al interior de la
definici´on de la clase. F´ıjese en c´omo cambia el sangrado.
class Hora:
def imprimeHora(hora):
print str(hora.horas) + ":" +
str(hora.minutos) + ":" +
str(hora.segundos)
Ahora podemos invocar imprimeHora usando la notaci´on de punto.
>>> horaActual.imprimeHora()
Como es habitual, el objeto sobre el que se invoca el m´etodo
aparece delante del punto y el nombre del m´etodo aparece tras el
punto.
El objeto sobre el que se invoca el m´etodo se asigna al primer par
´ametro, as´ı que en este caso horaActual se asigna al par´ametro
hora.
Por convenio, el primer par´ametro de un m´etodo se llama self.
La razo´n de esto es un tanto rebuscada, pero se basa en una met
´afora u´til.
La sintaxis para la llamada a una funci´on, imprimeHora(horaActual),
sugiere que la funci´on es el agente activo. Dice algo como “¡Oye
imprimeHora! Aqu´ı hay un objeto para que lo imprimas”.
En programaci´on orientada a objetos, los objetos son los agentes
activos. Una in- vocacio´n como horaActual.imprimeHora() dice “¡Oye
horaActual! ¡Impr´ıme- te!”
Este cambio de perspectiva puede ser m´as elegante, pero no es
obvio que sea u´til. En los ejemplos que hemos visto hasta ahora,
puede no serlo. Pero a veces transferir la responsabilidad de las
funciones a los objetos hace posible escribir funciones mas vers
´atiles, y hace m´as f´acil mantener y reutilizar c´odigo.
14.3. Otro ejemplo
Vamos a convertir incremento (de la Secci´on 13.3) en un m´etodo.
Para aho- rrar espacio, dejaremos a un lado los m´etodos ya
definidos, pero usted deber´ıa mantenerlos en su versio´n:
class Hora:
#aqu´ı van las definiciones anteriores de m´etodos...
1 Clases y m
def incremento(self, segundos):
self.segundos = segundos + self.segundos
while self.segundos >= 60:
self.segundos = self.segundos - 60
self.minutos = self.minutos + 1
while self.minutos >= 60:
self.minutos = self.minutos - 60
self.horas = self.horas + 1
La transformaci´on es puramente mec´anica; hemos llevado la
definici´on del m´eto- do al interior de la definici´on de la clase y
hemos cambiado el nombre del primer par´ametro.
Ahora podemos invocar incremento como un m´etodo.
horaActual.incremento(500)
De nuevo, el objeto sobre el que invocamos el m´etodo se asigna al
primer par´ame- tro, self. El segundo par´ametro, segundos toma el
valor de 500.
Como ejercicio, convierta convertirASegundos (de la Secci´on
13.5) en un m´etodo de la clase Hora.
14.4. Un ejemplo m´as complicado
La funci´on despues es ligeramente m´as complicada porque
opera sobre dos objetos Hora, no s´olo sobre uno. S´olo podemos
convertir uno de los par´ametros en self; el otro se queda como
esta´:
class Hora:
#aqu´ı van las definiciones anteriores de m´etodos...
def despues(self, hora2):
if self.horas > hora2.horas:
return 1
if self.horas < hora2.horas:
return 0
if self.minutos > hora2.minutos:
return 1
14.5 Argumentos opcionales 153
if self.minutos < hora2.minutos:
return 0
if self.segundos > hora2.segundos:
return 1
return 0
Invocamos este m´etodo sobre un objeto y pasamos el otro como
argumento:
if horaHecho.despues(horaActual):
print "El pan estar´a hecho despu´es de empezar."
Casi puede leer la invocacio´n como una mezcla de ingl´es y
espan˜ol: “Si la hora- hecho es depu´es de la hora-actual,
entonces...”
14.5. Argumentos opcionales
Hemos visto funciones internas que toman un nu´mero variable de
argumentos. Por ejemplo, string.find puede tomar dos, tres o cuatro
argumentos.
Es posible escribir funciones definidas por el usuario con listas de
argumentos op- cionales. Por ejemplo, podemos modernizar
nuestra propia versio´n de encuentra para que haga lo mismo que
string.find.
Esta es la versio´n original de la Secci´on 7.7:
def encuentra(cad, c):
indice = 0
while indice < len(cad):
if str[indice] == c:
return indice
indice = indice + 1
return -1
Esta es la versio´n aumentada y mejorada:
def encuentra(cad, c, comienzo=0):
indice = comienzo
while indice < len(cad):
if str[indice] == c:
return indice
indice = indice + 1
return -1
1 Clases y m
El tercer par´ametro, comienzo, es opcional porque se proporciona
un valor por omisi´on, 0. Si invocamos encuentra s´olo con dos
argumentos, utilizamos el valor por omisi´on y comenzamos por el
principio de la cadena:
>>> encuentra("arriba", "r")
1
Si le damos un tercer par´ametro, anula el predefinido:
>>> encuentra("arriba", "r", 2)
2
>>> encuentra("arriba", "r", 3)
-1
Como ejercicio, an˜ada un cuarto para´metro, fin, que
especifique d´onde dejar de buscar.
Cuidado: Este ejercicio tiene truco. El valor por omisio´n de fin de-
ber´ıa ser len(cad), pero eso no funciona. Los valores por omisio
´n se evalu´an al definir la funci´on, no al llamarla. Cuando se
define encuentra, cad au´n no existe, as´ı que no puede averiguar
su longi- tud.
14.6. El m´etodo de inicializaci´on
El m´etodo de inicializacio´n es un m´etodo especial que se invoca
al crear un objeto. El nombre de este m´etodo es init (dos
guiones bajos, seguidos de init y dos guiones bajos m´as). Un m
´etodo de inicializaci´on para la clase Hora es as´ı:
class Hora:
def init (self, horas=0, minutos=0, segundos=0):
self.horas = horas
self.minutos = minutos
self.segundos = segundos
No hay conflicto entre el atributo self.horas y el par´ametro horas.
la notaci´on de punto especifica a qu´e variable nos referimos.
Cuando invocamos el constructor Hora, los argumentos que damos se pasan
a
init:
>>> horaActual = Hora(9, 14, 30)
>>> horaActual.imprimeHora()
>>> 9:14:30
14.7 Revisi´on de los Puntos 155
Como los par´ametros son opcionales, podemos omitirlos:
>>> horaActual = Hora()
>>> horaActual.imprimeHora()
>>> 0:0:0
O dar s´olo el primer par´ametro:
>>> horaActual = Hora (9)
>>> horaActual.imprimeHora()
>>> 9:0:0
O los dos primeros par´ametros:
>>> horaActual = Hora (9, 14)
>>> horaActual.imprimeHora()
>>> 9:14:0
Finalmente, podemos dar un subconjunto de los par´ametros nombr
´andolos ex- plicitamente:
>>> horaActual = Hora(segundos = 30, horas = 9)
>>> horaActual.imprimeHora()
>>> 9:0:30
14.7. Revisio´n de los Puntos
Vamos a reescribir la clase Punto de la Secci´on 12.1 con un estilo
mas orientado a objetos:
class Punto:
def init (self, x=0, y=0):
self.x = x
self.y = y
def str (self):
return ’(’ + str(self.x) + ’, ’ + str(self.y) + ’)’
El m´etodo de inicializaci´on toma los valores de x e y como par
´ametros opcio- nales; el valor por omisi´on de cada par´ametro es
0.
El siguiente m´etodo, str , devuelve una representaci´on en
forma de cadena de un objeto Punto. Si una clase ofrece un m
´etodo llamado str , se impone al comportamiento por defecto de
la funci´on interna str de Python.
1 Clases y m
>>> p = Punto(3, 4)
>>> str(p)
’(3, 4)’
Imprimir un objeto Punto invoca impl´ıcitamente a str sobre el
objeto, as´ı que definir str tambi´en cambia el comportamiento
de print:
>>> p = Punto(3, 4)
>>> print p
(3, 4)
Cuando escribimos una nueva clase, casi siempre empezamos
escribiendo init , que facilita el instanciar objetos, y str , que
casi siempre es u´til para la depuraci´on.
14.8. Sobrecarga de operadores
Algunos lenguajes hacen posible cambiar la definici´on de los
operadores internos cuando se aplican a tipos definidos por el
usuario. Esta caracter´ıstica se llama sobrecarga de operadores. Es
especialmente u´til cuando definimos nuevos tipos matem´aticos.
Por ejemplo, para suplantar al operador de suma + necesitamos
proporcionar un m´etodo llamado add :
class Punto:
# aqu´ı van los m´etodos que ya hab´ıamos definido...
def add (self, otro):
return Punto(self.x + otro.x, self.y + otro.y)
Como es habitual, el primer par´ametro es el objeto sobre el que
se invoca el m´etodo. El segundo par´ametro se llama
convenientemente otro para distinguirlo del mismo (self). Para
sumar dos Puntos, creamos y devolvemos un nuevo Punto que
contiene la suma de las coordenadas x y la suma de las
coordenadas y.
Ahora, cuando apliquemos el operador + a objetos Punto, Python invocara´
a
add :
>>> p1 = Punto(3, 4)
>>> p2 = Punto(5, 7)
>>> p3 = p1 + p2
>>> print p3
(8, 11)
14.8 Sobrecarga de operadores 157
La expresio´n p1 + p2 equivale a p1. add (p2), pero es obviamente
mas ele- gante.
Como ejercicio, an˜ada un m´etodo sub (self, otro) que
sobre- cargue el operador resta y pru´ebelo.
Hay varias formas de sobrecargar el comportamiento del operador
multiplica- cio´n: definiendo un m´etodo llamado mul , o rmul , o
ambos.
Si el operando a la izquierda de * es un Punto, Python invoca a
mul , lo que presupone que el otro operando es tambi´en un Punto.
Calcula el producto interno de dos puntos, definido segu´n las
reglas del a´lgebra lineal:
def mul (self, otro):
return self.x * otro.x + self.y * otro.y
Si el operando a la izquierda de * es un tipo primitivo y el
operando de la derecha es un Punto, Python invca a rmul , lo que
realiza una multiplicaci´on escalar:
def rmul (self, otro):
return Punto(otro * self.x, otro * self.y)
El resultado es un nuevo Punto cuyas coordenadas son mu´ltiplos de
las coorde- nadas originales. Si otro es un tipo que no se puede
multiplicar por un nu´mero en coma flotante, entonces rmul
causara´ un error.
Este ejemplo muestra ambos tipos de multiplicaci´on:
>>> p1 = Punto(3, 4)
>>> p2 = Punto(5, 7)
>>> print p1 * p2
43
>>> print 2 * p2
(10, 14)
¿Qu´e ocurre si intentamos evaluar p2 * 2? Como el primer par
´ametro es un Punto, Python invoca a mul con 2 como el segundo
par´ametro. Dentro de mul , el programa intenta acceder a la
coordenada x de otro, pero no lo consigue porque un entero no
tiene atributos:
>>> print p2 * 2
AttributeError: ’int’ object has no attribute ’x’
Desgraciadamente, el mensaje de error es un poco opaco. Este
ejemplo muestra algunas de las difucultades de la programaci´on
orientada a objetos. A veces es dif´ıcil averiguar simplemente qu´e
c´odigo se esta´ ejecutando.
1 Clases y m
Para ver un ejemplo m´as completo de sobrecarga de operadores,
vaya al Ap´endi- ce B.
14.9. Polimorfismo
La mayor´ıa de los m´etodos que hemos escrito funcionan s´olo
para un tipo es- pec´ıfico. Cuando usted crea un nuevo objeto,
escribe m´etodos que operan sobre ese tipo.
Pero hay ciertas operaciones que querr´a aplicar a muchos tipos,
como las opera- ciones aritm´eticas de las secciones anteriores. Si
muchos tipos admiten el mismo conjunto de operaciones, puede
escribir funciones que trabajen sobre cualquiera de esos tipos.
Por ejemplo, la operacio´n multisuma (comu´n en a´lgebra lineal) toma
tres
par´ametros; multiplica los dos primeros y luego suma el tercero.
Podemos escri- birla en Python as´ı:
def multisuma (x, y, z):
return x * y + z
Este m´etodo trabajar´a con cualquier valor de x e y que se pueda
multiplicar y con cualquier valor de z que se pueda sumar al
producto.
Podemos invocarlo con valores num´ericos:
>>> multisuma (3, 2, 1)
7
O con Puntos:
>>> p1 = Punto(3, 4)
>>> p2 = Punto(5, 7)
>>> print multisuma (2, p1, p2)
(11, 15)
>>> print multisuma (p1, p2, 1)
44
En el primer caso, el Punto se multiplica por un escalar y luego se
suma a otro Punto. En el segundo caso, el producto interior produce
un valor num´erico, as´ı que el tercer par´ametro tambi´en debe
ser un valor num´erico.
Una funci´on como ´esta que puede tomar par´ametros con
diferentes tipos se llama polim´orfica.
Como un ejemplo m´as, observe el m´etodo delDerechoYDelReves,
que imprime dos veces una lista, hacia adelante y hacia atr´as:
14.9 Polimorfismo 159
def delDerechoYDelReves(derecho):
import copy
reves = copy.copy(derecho)
reves.reverse()
print str(derecho) + str(reves)
Como el m´etodo reverse es un modificador, hacemos una copia
de la lista antes de darle la vuelta. As´ı, este m´etodo no modifica
la lista que recibe como par´ametro.
He aqu´ı un ejemplo que aplica delDerechoYDelReves a una lista:
>>> miLista = [1, 2, 3, 4]
>>> delDerechoYDelReves(miLista)
[1, 2, 3, 4][4, 3, 2, 1]
Por supuesto, pretend´ıamos aplicar esta funci´on a listas, as´ı que
no es sorpren- dente que funcione. Lo sorprendente es que pudi
´eramos usarla con un Punto. Para determinar si una funci´on se
puede aplicar a un nuevo tipo, aplicamos la
regla fundamental del polimorfismo:
Si todas las operaciones realizadas dentro de la funcio´n se
pueden aplicar al tipo, la funcio´n se puede aplicar al tipo.
Las operaciones del m´etodo incluyen copy, reverse y print.
copy trabaja sobre cualquier objeto, y ya hemos escrito un m´etodo
str para los Puntos, as´ı que todo lo que necesitamos es un m
´etodo reverse en la clase Punto:
def reverse(self):
self.x , self.y = self.y, self.x
Ahora podemos pasar Puntos a delDerechoYDelReves:
>>> p = Punto(3, 4)
>>> delDerechoYDelReves(p)
(3, 4)(4, 3)
El mejor tipo de polimorfismo es el que no se busca, cuando usted
descubre que una funci´on que hab´ıa escrito se puede aplicar a
un tipo para el que nunca la hab´ıa planeado.
1 Clases y m
14.10. Glosario
lenguaje orientado a objetos: Un lenguaje que ofrece caracter´ısticas,
como clases definidas por el usuario y herencia, que facilitan la
programaci´on orientada a objetos.
programaci´on orientada a objetos: Un estilo de programaci´on en
el que los datos y las operaciones que los manipulan esta´n
organizadas en clases y m´etodos.
m´etodo: Una funci´on definida dentro de una definici´on de clase y
que se invoca sobre instancias de esa clase.
imponer: Reemplazar una opcio´n por omisi´on. Los ejemplos
incluyen el reem- plazo de un par´ametro por omisi´on con un
argumento particular y el reem- plazo de un m´etodo por omisi
´on proporcionando un nuevo m´etodo con el mismo nombre.
m´etodo de inicializacio´n: Un m´etodo especial que se invoca
automaticamen- te al crear un nuevo objeto y que inicializa los
atributos del objeto. sobrecarga de operadores: Ampliar los operadores
internos (+, -, *, >, <, etc.) de modo que trabajen con tipos
definidos por el usuario.
producto interno: Una operacio´n definida en a´lgebra lineal que
multiplica dos Puntos y entrega un valor num´erico.
multiplicaci´on escalar: Una operacio´n definida en a´lgebra lineal
que multi- plica cada una de las coordenadas de un Punto por un valor
num´erico. polim´orfica: Una funci´on que puede operar sobra mas de
un tipo. Si todas las operaciones realizadas dentro de una funci´on se
pueden aplicar a un tipo,
la funci´on se puede aplicar a ese tipo.
Cap´ıtulo 15
Conjuntos de objetos
15.1. Composicio´n
Hasta ahora, ya ha visto varios ejemplos de composici´on. Uno de
los primeros ejemplos fue el uso de la llamada a un m´etodo como
parte de una expresio´n. Otro ejemplo es la estructura anidada
de las sentencias; se puede escribir una sentencia if dentro de
un bucle while, dentro de otra sentencia if, y as´ı suce- sivamente.
Una vez visto este patr´on, y sabiendo acerca de listas y objetos,
no le deber´ıa sorprender que pueda crear listas de objetos. Tambi
´en puede crear objetos que contengan listas (en forma de
atributos); puede crear listas que contengan listas; objetos que
contengan objetos, y as´ı indefinidamente.
En este cap´ıtulo y el siguiente, exploraremos algunos ejemplos de
estas combi- naciones, y usaremos objetos Carta como ejemplo.
15.2. Objetos Carta
Si no esta´ usted familiarizado con los naipes de juego comunes,
puede ser un buen momento para que consiga un mazo, si no este
cap´ıtulo puede que no tenga mucho sentido. Hay cincuenta y dos
naipes en una baraja inglesa, cada uno de los cuales pertenece a
un palo y tiene un valor; hay cuatro palos diferentes y trece valores.
Los palos son Picas, Corazones, Diamantes, y Tr´eboles (en el orden
descendente segu´n el bridge). Los valores son As, 2, 3, 4, 5, 6, 7,
8, 9, 10, Sota, Reina, y Rey. Dependiendo del tipo de juego que se
juegue, el valor del As puede ser mayor al Rey o inferior al 2.
1 Conjuntos de
Si queremos definir un nuevo objeto para representar un naipe, es
obvio qu´e atri- butos deber´ıa tener: valor y palo. Lo que no es tan
obvio es el tipo que se debe dar a los atributos. Una posibilidad es
usar cadenas de caracteres que contengan palabras como "Picas"
para los palos y "Reina" para los valores. Un problema de esta
implementaci´on es que no ser´a f´acil comparar naipes para ver
cua´l tiene mayor valor o palo.
Una alternativa es usar nu´meros enteros para codificar los valores
y palos. Con el t´ermino “codificar” no queremos significar lo que
algunas personas pueden pensar, acerca de cifrar o traducir a un c
´odigo secreto. Lo que un programador entiende por “codificar” es
“definir una correspondencia entre una secuencia de nu´meros y
los elementos que se desea representar”. Por ejemplo:
Picas ›→ 3
Corazones ›→ 2
Diamantes ›→ 1
Tr´eboles ›→ 0
Esta correspondencia tiene una caracter´ıstica obvia: los palos
corresponden a nu´meros enteros en orden, o sea que podemos
comparar los palos al comparar los nu´meros. La asociacio´n de los
valores es bastante obvia; cada uno de los valores num´ericos se
asocia con el entero correspondiente, y para las figuras:
Sota ›→ 11
Reina ›→ 12
Rey ›→ 13
Estamos usando una notaci´on matem´atica para estas
asociaciones por una razo´n: no son parte del programa Python.
Son parte del disen˜o del progra- ma, pero nunca aparecen expl
´ıcitamente en el c´odigo fuente. La definici´on de clase para el
tipo Carta se parecer´a a:
class Carta:
def init (self, palo=0, valor=0):
self.palo = palo
self.valor = valor
Como acostumbramos, proporcionaremos un m´etodo de inicializaci
´on que toma un par´ametro opcional para cada atributo.
Para crear un objeto que representa el 3 de Tr´eboles, usaremos la instrucci
´on:
tresDeTreboles = Carta(0, 3)
El primer argumento, 0, representa el palo de Tr´eboles.
15.3 Atributos de clase y el m´etodostr 163
15.3. Atributos de clase y el m´etodo str
Para poder imprimir los objetos Carta de una manera facil de leer
para las personas, vamos a establecer una correspondencia entre
los c´odigos enteros y las palabras. Una manera natural de hacer
esto es con listas de cadenas de caracteres. Asignaremos estas
listas dentro de atributos de clase al principio de la definici´on de
clase:
class Carta:
listaDePalos = ["Tr´eboles", "Diamantes",
"Corazones", "Picas"]
listaDeValores = ["nada", "As", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "Sota", "Reina", "Rey"]
# se omite el m´etodo init
def str (self):
return (self.listaDeValores[self.valor] + " de " +
self.listaDePalos[self.palo])
Un atributo de clase se define fuera de cualquier m´etodo, y
puede accederse desde cualquiera de los m´etodos de la clase.
Dentro de str , podemos usar listaDePalos y listaDeValores
para aso- ciar los valores num´ericos de palo y valor con
cadenas de caracteres. Por ejemplo, la expresio´n
self.listaDePalos[self.palo] significa “usa el atribu- to palo del
objeto self como un ´ındice dentro del atributo de clase
denominado listaDePalos, y selecciona la cadena apropiada”.
El motivo del ‘‘nada" en el primer elemento de listaDeValores es
para relleno del elemento de posici´on cero en la lista, que nunca
se usar´a. Los u´nicos valores l´ıcitos para el valor van de 1 a 13.
No es obligatorio que desperdiciemos este primer elemento.
Podr´ıamos haber comenzado en 0 como es usual, pero es menos
confuso si el 2 se codifica como 2, el 3 como 3, y as´ı
sucesivamente.
Con los m´etodos que tenemos hasta ahora, podemos crear e imprimir
naipes:
>>> carta1 = Carta(1, 11)
>>> print carta1
Sota de Diamantes
Los atributos de clase como listaDePalos son compartidos por todos
los ob- jetos de tipo Carta. La ventaja de esto es que podemos usar
cualquier objeto Carta para acceder a los atributos de clase:
1 Conjuntos de
>>> carta2 = Carta(1, 3)
>>> print carta2
3 de Diamantes
>>> print carta2.listaDePalos[1]
Diamantes
La desventaja es que si modificamos un atributo de clase,
afectaremos a cada instancia de la clase. Por ejemplo, si decidimos
que “Sota de Diamantes” en realidad deber´ıa llamarse “Sota de
Ballenas Bailarinas”, podr´ıamos hacer lo siguiente:
>>> carta1.listaDePalos[1] = "Ballenas Bailarinas"
>>> print carta1
Sota de Ballenas Bailarinas
El problema es que todos los Diamantes se transformar´an en Ballenas
Bailarinas:
>>> print carta2
3 de Ballenas Bailarinas
En general no es una buena idea modificar los atributos de clase.
15.4. Comparaci´on de naipes
Para los tipos primitivos, existen operadores condicionales (< , >,
==, etc.) que comparan valores y determinan cuando uno es mayor,
menor, o igual a otro. Para los tipos definidos por el usuario,
podemos sustituir el comportamiento de los operadores internos si
proporcionamos un m´etodo llamado cmp . Por convencio´n, cmp
toma dos par´ametros, self y otro, y retorna 1 si el primer objeto es
el mayor, -1 si el segundo objeto es el mayor, y 0 si ambos son
iguales.
Algunos tipos esta´n completamente ordenados, lo que significa
que se pueden comparar dos elementos cualesquiera y decir cua´l
es el mayor. Por ejemplo, los nu´meros enteros y los nu´meros en
coma flotante tienen un orden completo. Algu- nos conjuntos no
tienen orden, o sea, que no existe ninguna manera significativa de
decir que un elemento es mayor a otro. Por ejemplo, las frutas no
tienen orden, lo que explica por qu´e no se pueden comparar peras
con manzanas.
El conjunto de los naipes tiene un orden parcial, lo que significa
que algunas veces se pueden comparar los naipes, y otras veces
no. Por ejemplo, usted sabe que el 3 de Tr´eboles es mayor que el
2 de Tr´eboles y el 3 de Diamantes es mayor que el 3 de Tr
´eboles. Pero, ¿cua´l es mejor?, ¿el 3 de Tr´eboles o el 2 de
Diamantes?. Uno tiene mayor valor, pero el otro tiene mayor palo.
15.5 Mazos de naipes 165
A los fines de hacer que los naipes sean comparables, se debe
decidir qu´e es mas importante: valor o palo. Para no mentir, la
seleccio´n es arbitraria. Como algo hay que elegir, diremos que el
palo es m´as importante, porque un mazo nuevo viene ordenado
con todos los Tr´eboles primero, luego con todos los Diamantes, y
as´ı sucesivamente.
Con esa decisi´on tomada, podemos escribir cmp :
def cmp (self, otro):
# controlar el palo
if self.palo > otro.palo: return 1
if self.palo < otro.palo: return -1
# si son del mismo palo, controlar el valor
if self.valor > otro.valor: return 1
if self.valor < otro.valor: return -1
# los valores son iguales, es un empate
return 0
En este ordenamiento, los Ases son menores que los doses.
Como ejercicio, modifique cmp de tal manera que los Ases tengan
mayor valor que los Reyes.
15.5. Mazos de naipes
Ahora que ya tenemos los objetos para representar las Cartas, el pr
´oximo paso l´ogico es definir una clase para representar un Mazo.
Por supuesto, un mazo esta´ compuesto de naipes, as´ı que cada
objeto Mazo contendr´a una lista de naipes como atributo.
A continuaci´on se muestra una definici´on para la clase Mazo. El
m´etodo de inicializaci´on crea el atributo cartas y genera el
conjunto esta´ndar de cincuenta y dos naipes.
class Mazo:
def init (self):
self.cartas = []
for palo in range(4):
for valor in range(1, 14):
self.cartas.append(Carta(palo, valor))
La forma mas f´acil de poblar el mazo es mediante un bucle
anidado. El bucle exterior enumera los palos desde 0 hasta 3. El
bucle interior enumera los va- lores desde 1 hasta 13. Como el
bucle exterior itera cuatro veces, y el interior
1 Conjuntos de
itera trece veces, la cantidad total de veces que se ejecuta el
cuerpo interior es cincuenta y dos (trece por cuatro). Cada iteraci
´on crea una nueva instancia de Carta con el palo y valor actual, y
agrega dicho naipe a la lista de cartas.
El m´etodo append funciona sobre listas pero no sobre tuplas, por supuesto.
15.6. Impresio´n del mazo de naipes
Como es usual, cuando definimos un nuevo tipo de objeto queremos
un m´etodo que imprima el contenido del objeto. Para imprimir un
Mazo, recorremos la lista e imprimimos cada Carta:
class Mazo:
...
def muestraMazo(self):
for carta in self.cartas:
print carta
Desde ahora en adelante, los puntos suspensivos (...) indicar´an
que hemos omitido los otros m´etodos en la clase.
En lugar de escribir un m´etodo muestraMazo, podr´ıamos escribir
un m´etodo str para la clase Mazo. La ventaja de str esta´ en
que es mas flexible. En lugar de imprimir directamente el
contenido del objeto, str genera una re- presentaci´on en forma de
cadena de caracteres que las otras partes del programa pueden
manipular antes de imprimir o almacenar para un uso posterior.
Se presenta ahora una versio´n de str que retorna una
representaci´on como cadena de caracteres de un Mazo. Para darle
un toque especial, acomoda los naipes en una cascada, de tal
manera que cada naipe esta´ sangrado un espacio m´as que el
precedente.
class Mazo:
...
def str (self):
s = ""
for i in range(len(self.cartas)):
s = s + " "*i + str(self.cartas[i]) + "n"
return s
Este ejemplo demuestra varias caracter´ısticas. Primero, en
lugar de recorrer self.cartas y asignar cada naipe a una variable,
usamos i como variable de bucle e ´ındice de la lista de naipes.
Segundo, utilizamos el operador de multiplicaci´on de cadenas de
caracteres para sangrar cada naipe un espacio m´as que el
anterior. La expresio´n *i prooprciona una cantidad de espacios
igual al valor actual de i.
15.7 Barajar el mazo 167
Tercero, en lugar de usar la instrucci´on print para imprimir los
naipes, utiliza- mos la funci´on str. El pasar un objeto como
argumento a str es equivalente a invocar el m´etodo str sobre
dicho objeto.
Finalmente, usamos la variable s como acumulador. Inicialmente, s
es una cadena de caracteres vac´ıa. En cada pasada a trav´es del
bucle, se genera una nueva cadena de caracteres que se concatena
con el viejo valor de s para obtener el nuevo valor. Cuando el bucle
termina, s contiene la representaci´on completa en formato de
cadena de caracteres del Mazo, la cual se ve como a continuaci´on
se presenta:
>>> mazo = Mazo()
>>> print mazo
As de Tr
´eboles
2 de Tr´eboles
3 de Tr´eboles
4 de Treboles
5 de Tr´eboles
6 de Tr´eboles
7 de Tr´eboles
8 de Tr´eboles
9 de Tr´eboles
10 de Tr´eboles
Sota de Tr
´eboles
Reina de Tr
´eboles Rey de
Tr´eboles As of
Diamantes
Y as´ı sucesivamente. Au´n cuando los resultados aparecen en 52
renglones, se trata de s´olo una u´nica larga cadena de caracteres
que contiene los saltos de l´ınea.
15.7. Barajar el mazo
Si un mazo esta´ perfectamente barajado, cualquier naipe tiene la
misma proba- bilidad de aparecer en cualquier posici´on del mazo,
y cualquier lugar en el mazo tiene la misma probabilidad de
contener cualquier naipe.
Para mezclar el mazo, utilizaremos la funci´on randrange del modulo
random. Es- ta funci´on toma dos enteros como argumentos a y b, y
elige un nu´mero entero en
1 Conjuntos de
forma aleatoria en el rango a <= x <b. Como el l´ımite superior es
estrictamen- te menor a b, podemos usar la longitud de la lista
como el segundo argumento y de esa manera tendremos
garantizado un ´ındice legal dentro de la lista. Por ejemplo, esta
expresio´n selecciona el ´ındice de un naipe al azar dentro del
mazo:
random.randrange(0, len(self.cartas))
Una manera sencilla de mezclar el mazo es recorrer los naipes e
intercambiar cada una con otra elegida al azar. Es posible que el
naipe se intercambie consigo mismo, pero no es un problema. De
hecho, si eliminamos esa posibilidad, el orden de los naipes no ser
´a completamente al azar:
class Mazo:
...
def mezclar(self):
import random
nCartas = len(self.cartas)
for i in range(nCartas):
j = random.randrange(i, nCartas)
self.cartas[i], self.cartas[j] =
self.cartas[j], self.cartas[i]
En lugar de presuponer que hay cincuenta y dos naipes en el mazo,
obtenemos la longitud real de la lista y la almacenamos en nCartas.
Para cada naipe del mazo, seleccionamos un naipe al azar entre
aquellos que no han sido intercambiados au´n. Luego
intercambiamos el naipe actual (i) con el naipe seleccionado (j).
Para intercambiar los naipes usaremos la asignaci´on de tuplas,
como se describe en la Secci´on 9.2:
self.cartas[i], self.cartas[j] = self.cartas[j], self.cartas[i]
Como ejercicio, reescriba esta l´ınea de c´odigo sin usar una
asigna- ci´on de secuencias.
15.8. Eliminacio´n y reparto de los naipes
Otro m´etodo que podr´ıa ser u´til para la clase Mazo es
eliminaCarta, que toma un naipe como par´ametro, lo elimina, y
retorna verdadero (1) si el naipe estaba en el mazo, y falso (0) si no
estaba:
15.9 Glosario 169
class Mazo:
...
def eliminaCarta(self, carta):
if carta in self.cartas:
self.cartas.remove(carta)
return 1
else:
return 0
El operador in retorna verdadero si el primer operando esta´ en el
segundo, el cual debe ser una lista o tupla. Si el primer
operando es un objeto, Python usa el m´etodo cmp del objeto
para determinar la igualdad entre los elementos de la lista. Como
el cmp en la clase Carta verifica la igualdad en profundidad, el m
´etodo eliminaCarta tambi´en verifica igualdad en profundidad.
Para repartir los naipes, queremos eliminar y devolver el naipe que
ocupa la posici´on superior en el mazo. El m´etodo pop de las listas
proporciona una manera conveniente de realizar esto:
class Mazo:
...
def darCarta(self):
return self.cartas.pop()
En realidad, pop elimina el u´ltimo naipe en la lista, as´ı que en
efecto estamos repartiendo desde el extremo inferior del mazo.
Otra operacio´n m´as que es muy probable necesitemos es la funci´on
booleana
estaVacio, la cual devuelve verdadero si el mazo no contiene ningu´n naipe:
class Deck:
...
def estaVacio(self):
return (len(self.cartas) == 0)
15.9. Glosario
codificar: Representar un conjunto de valores uilizando otro
conjunto de valo- res, entre los cuales se construye una
correspondencia.
atributo de clase: Una variable que se define dentro de la definici
´on de una clase pero fuera de cualquiera de sus m´etodos.
Los atributos de clase son accesibles desde cualquier m´etodo
de la clase y esta´n compartidos por todas las instancias de la
misma.
1 Conjuntos de
acumulador: Una variable que se usa en un bucle para acumular una
serie de valores, por ejemplo concatena´ndolos dentro de una
cadena de caracteres o adicion´andolos a una suma.
Cap´ıtulo 16
Herencia
16.1. Herencia
La caracter´ıstica de un lenguaje que m´as se asocia con la
programaci´on orientada a objetos es la herencia. La herencia es la
capacidad de definir una nueva clase que es una versio´n
modificada de otra ya existente.
La principal ventaja de esta caracter´ıstica es que se pueden
agregar nuevos m´eto- dos a una clase sin modificar la clase
existente. Se denomina “herencia” porque la nueva clase hereda
todos los m´etodos de la clase existente. Si extendemos esta mat
´efora, a la clase existente a veces se la denomina clase padre. La
nueva clase puede denominarse clase hija, o tambi´en “subclase”.
La herencia es una caracter´ıstica poderosa. Ciertos programas que
ser´ıan compli- cados sin herencia pueden escribirse de manera
simple y concisa gracias a ella. Adem´as, la herencia puede
facilitar la reutilizacio´n del c´odigo, pues se puede adaptar el
comportamiento de la clase padre sin tener que modificarla. En
algu- nos casos, la estructura de la herencia refleja la propia
estructura del problema, lo que hace que el programa sea m´as f
´acil de comprender.
Por otro lado, la herencia pude hacer que los porgramas sean dif
´ıciles de leer. Cuando se llama a un m´etodo, a veces no esta´ claro
donde debe uno encontrar su definici´on. El c´odigo relevante
puede estar diseminado por varios modulos. Adem´as, muchas de
las cosas que se hacen mediante el uso de la herencia, se pue- den
lograr de forma igualmente (incluso mas) elegante sin ella. Si la
estructura general del problema no nos gu´ıa hacia la herencia,
dicho estilo de programaci´on puede hacer m´as mal que bien.
1 Herenc
En este cap´ıtulo demostraremos el uso de la herencia como parte
de un programa que juega a las cartas a la “Mona”. Una de
nuestras metas ser´a que el c´odigo que escribamos se pueda
reutilizar para implementar otros juegos de naipes.
16.2. Una mano de cartas
Para casi cualquier juego de naipes, necesitamos representar una
mano de cartas. Una mano es similar a un mazo, por supuesto.
Ambos esta´n compuestos de un conjunto de naipes, y ambos
requieren de operaciones tales como agregar y eliminar una carta.
Adem´as, necesitaremos la capacidad de mezclar tanto un mazo
como una mano de cartas.
Una mano es diferente de un mazo en ciertos aspectos. Segu´n el
juego al que se est´e jugando, podemos querer realizar ciertas
operaciones sobre una mano que no tienen sentido sobre un mazo.
Por ejemplo, en el poker queremos clasificar una mano (straight
(consecutiva), flush (de un solo palo), etc.) y compararla con otra.
En bridge necesitaremos calcular el puntaje para la mano para as´ı
poder hacer la subasta.
Esta situacio´n sugiere el uso de la herencia. Si Mano es una
subclase de Mazo, entonces tendr´a todos los m´etodos de Mazo y le
podremos agregar otros m´etodos nuevos.
En la definici´on de clase, el nombre de la clase padre aparece entre par
´entesis:
class Mano(Mazo):
pass
Esta sentencia indica que la nueva clase Mano hereda de la clase
existente Mazo. El constructor de Mano inicializa los atributos para
la mano, que son nombre y cartas. La cadena de caracteres nombre
identifica a esta mano, probablemente mediante el nombre del
jugador que la sostiene. El nombre es un par´ametro opcional con
un valor por omisi´on de cadena vac´ıa. cartas es la lista de cartas
de la mano, inicializada como lista vac´ıa.
class Mano(Mazo):
def init (self, nombre=""):
self.cartas = []
self.nombre = nombre
Casi para cualquier juego de naipes, es necesario agregar y quitar
cartas del ma- zo. La eliminacio´n de cartas ya ha sido resuelta,
pues Mano hereda eliminaCarta de Mazo. Pero deberemos escribir
agregaCarta:
16.3 El reparto de los naipes 173
class Mano(Mazo):
...
def agregaCarta(self,carta) :
self.cartas.append(carta)
De nuevo, los puntos suspensivos indican que hemos omitido los
otrs m´etodos. El m´etodo de lista append agrega la nueva carta al
final de la lista de cartas.
16.3. El reparto de los naipes
Ahora que ya tenemos la clase Mano, queremos repartir las cartas
del Mazo en manos. No es claramente obvio si este m´etodo debe ir
en la clase Mano o en la clase Mazo, pero como opera sobre un mazo
u´nico y (posiblemente) sobre varias manos, es m´as natural
ponerlo en el Mazo.
repartir debe ser bastante general, pues los diferentes juegos
tienen distintos requerimentos. Puede que necesitemos
repartir todo el mazo de una vez, o que agreguemos una carta
a cada mano.
repartir toma dos par´ametros, una lista (o tupla) de manos y la
cantidad total de naipes a repartir. Si no hay suficientes cartas en
el mazo, el m´etodo reparte todas las cartas y se detiene:
class Mazo :
...
def repartir(self, manos, nCartas=999):
nManos = len(manos)
for i in range(nCartas):
if self.estaVacio(): break # fin si se acaban las cartas
carta = self.darCarta() # da la carta superior
mano = manos[i nManos] # a qui´en le toca?
mano.agregaCarta(carta) # agrega la carta a la mano
El segundo par´ametro, nCartas es opcional; el valor por omisi´on
es un nu´mero muy grande, lo cual es lo mismo que decir que se
repartira´n todos los naipes del mazo.
La variable de bucle i va desde 0 hasta nCartas-1. A cada paso a
trav´es del bucle, se elimina una carta del mazo mediante el m
´etodo de lista pop, que quita y devuelve el u´ltimo elemento de la
lista.
El operador m´odulo ( ) permite que podamos repartir las cartas
de una en una (una carta cada vez para cada mano). Cuando i
es igual a la cantidad de manos en la lista, la expresio´n i
nManos salta hacia el comienzo de la lista (el ´ındice es 0).
1 Herenc
16.4. Mostremos la mano
Para mostrar el contenido de una mano, podemos sacar partido de
la existencia de los m´etodos muestraMazo y str que se heredan
de Mazo. Por ejemplo:
>>> mazo = Mazo()
>>> mazo.mezclar()
>>> mano = Mano("hugo")
>>> mazo.repartir([mano], 5)
>>> print mano
La mano de hugo contiene
2 de Picas
3 de Picas
4 de Picas
As de Corazones
9 de Tr´eboles
No es una gran mano, pero tiene lo necesario como para disponer
de una escalera de color.
Aunque es conveniente usar la herencia de los m´etodos existentes,
existe infor- maci´on adicional en una Mano que desear´ıamos
mostrar al imprimirla. Para ello, podemos proporcionar a la clase
Mano un m´etodo str que reemplace al de la clase Mazo:
class Mano(Mazo)
...
def str (self):
s = "La mano de " + self.nombre
if self.estaVacio():
s = s + " est´a vac´ıan"
else:
s = s + " contienen"
return s + Mazo. str (self)
Al principio s es una cadena de caracteres que identifica a la mano.
Si la mano esta´ vac´ıa, el programa agrega las palabras est´a vac
´ıa y devuelve s.
En caso contrario, el programa agrega la palabra contiene y la
representaci´on como cadena de caracteres del Mazo, que se obtiene
llamando al m´etodo str de la clase Mazo sobre la instancia self.
Puede parecer extran˜o que enviemos a self, que se refiere a la
Mano actual, como argumento de un m´etodo de la clase Mazo, hasta
que nos damos cuenta de que una Mano es un tipo de Mazo. Los
objetos Mano pueden hacer cualquier
16.5 La clase JuegoDeCartas 175
cosa que pueda hacer un objeto Mazo, y por ello es legal que pasemos una
Mano
a un m´etodo de Mazo.
En general, siempre es legal usar una instancia de una subclase en
el lugar de una instancia de una clase padre.
16.5. La clase JuegoDeCartas
La clase JuegoDeCartas asume la responsabilidad sobre algunas
obligaciones b´asicas comunes a todos los juegos, tales como la
creacio´n del mazo y la mezcla de los naipes:
class JuegoDeCartas:
def init (self):
self.mazo = Mazo()
self.mazo.mezclar()
Esta es la primera vez que vemos que un m´etodo de inicializaci´on realiza
una
actividad computacional significativa, m´as alla´ de la inicializaci´on de
atributos.
Para implementar juegos espec´ıficos, debemos heredar de
JuegoDeCartas y agregar las caracter´ısticas del nuevo juego. Como
ejemplo, escribiremos una simulaci´on para La Mona.
La meta de La Mona es desembarazarse de las cartas que uno
tiene en la mano. Uno se saca las cartas de encima empareja
´ndolas por valor y color. Por ejemplo, el 4 de Tr´eboles se
empareja con el 4 de Picas porque ambos palos son negros. La Sota
de Corazones se empareja con la Sota de Diamantes porque ambos
son rojos.
Para iniciar el juego, se elimina la Reina de Tr´eboles del mazo, de
manera que la Reina de Picas no tiene con qui´en emparejarse. Las
cincuenta y una cartas restantes se reparten entre los jugadores,
de una en una. Luego del reparto, todos los jugadores emparejan y
descartan tantas cartas como sea posible.
Cuando no se pueden realizar m´as concordancias, el juego
comienza. Por turnos, cada jugador toma una carta (sin mirarla)
del vecino mas cercano de la izquierda que au´n tiene cartas. Si la
carta elegida concuerda con una de la mano del jugador, se
elimina dicho par. Si no, la carta se agrega a la mano del jugador.
Llega el momento en el que se realizan todas las concordancias
posibles, con lo que queda s´olo la Reina de Picas en la mano del
perdedor.
En nuestra simulaci´on inform´atica del juego, la computadora
juega todas las manos. Desafortunadamente, se pierden algunos
de los matices del juego real. En una partida real, el jugador que
tiene la Mona realiza ciertos esfuerzos para
1 Herenc
que su vecino la tome, por ejemplo mostr´andola prominentemente
o al contrario, errando al intentar mostrarla abiertamente, o
incluso puede fallar al tratar de errar en su intento de mostrarla
prominentemente. La computadora simplemente toma una carta al
azar de su vecino.
16.6. La clase ManoDeLaMona
Una mano para jugar a La Mona requiere ciertas capacidades que
esta´n mas all´a de las que posee una Mano. Definiremos una
nueva clase ManoDeLaMona, que hereda de Mano y nos proporciona
un m´etodo adicional denominado eliminaCoincidencias:
class ManoDeLaMona(Mano):
def eliminaCoincidencias(self):
cant = 0
cartasOriginales = self.cartas[:]
for carta in cartasOriginales:
empareja = Carta(3 - carta.palo, carta.valor)
if empareja in self.cartas:
self.cartas.remove(carta)
self.cartas.remove(empareja)
print "Mano s: s con s" (self.nombre,carta,empareja)
cant = cant + 1
return cant
Comenzamos por hacer una copia de la lista de las cartas, de tal
manera que podamos recorrer la copia mientras vamos quitando
cartas de la lista original. Como self.cartas se modifica en el bucle,
no vamos a querer usarla para controlar el recorrido. ¡Python
puede quedar realmente confundido si se recorre una lista que esta
´ cambiando!
Para cada carta de la mano, averiguamos cua´l es la carta que
concordara´ con ella y la buscamos. La carta que concuerda
tiene el mismo valor y el otro palo del mismo color. La expresio
´n 3 - carta.palo transforma un Tr´ebol (palo 0) en una Pica (palo
3) y un Diamante (palo 1) en un Coraz´on (palo 2). Verifique por su
cuenta que las operaciones opuestas tambi´en funcionan. Si la
carta que concuerda esta´ en la mano, ambas se eliminan.
El siguiente ejemplo demuestra el uso de eliminaCoincidencias:
>>> juego = JuegoDeCartas()
>>> mano = ManoDeLaMona("hugo")
>>> juego.mazo.repartir([mano], 13)
16.7 La clase 1
>>> print mano
La mano de hugo contiene
As de Picas
2 de Diamantes
7 de Picas
8 de Treboles
6 de Corazones
8 de Picas
7 de Tr´eboles
Raina de Tr
´eboles
7 de Diamantes
5 de Tr´eboles
Sota de Diamantes
10 de Diamantes
10 de Corazones
>>> mano.eliminaCoincidencias()
Mano hugo: 7 de Picas con 7 de Tr
´eboles Mano hugo: 8 de Picas con 8 de
Tr´eboles
Mano hugo: 10 de Diamantes con 10 de Corazones
Debe usted notar que no existe un m´etodo init para la clase
ManoDeLaMona. Lo heredamos de Mano.
16.7. La clase JuegoDeLaMona
Ahora podemos poner nuestra atenci´on en el juego en s´ı mismo.
JuegoDeLaMona es una subclase de JuegoDeCartas con un m´etodo
nuevo denominado jugar que toma una lista de jugadores como par
´ametro.
Como el m´etodo init se hereda de JuegoDeCartas, el nuevo objeto
JuegoDeLaMona contiene un mazo recientemtente mezclado:
class JuegoDeLaMona(JuegoDeCartas):
def jugar(self, nombres):
# quitamos la Reina de Tr´eboles
self.mazo.eliminaCarta(Carta(0,12))
# construimos una mano para cada jugador
self.manos = []
for nombre in nombres :
self.manos.append(ManoDeLaMona(nombre))
1 Herenc
# repartimos los naipes
self.mazo.repartir(self.manos)
print "----- Se han repartido las cartas."
self.muestraManos()
# eliminamos las coincidencias iniciales
emparejadas = self.eliminaTodasLasCoincidencias()
print "----- Coincidencias eliminadas, el juego comienza."
self.muestraManos()
# se juega hasta que se han descartado las 50 cartas
turno = 0
cantManos = len(self.manos)
while emparejadas < 25:
emparejadas = emparejadas + self.jugarUnTurno(turno)
turno = (turno + 1) cantManos
print "----- El juego termin´o."
self.muestraManos()
Algunos de los pasos que componen el juego se han colocado en
m´etodos se- parados. eliminaTodasLasCoincidencias recorre la
lista de manos y llama a eliminaCoincidencias para cada una de
ellas:
class JuegoDeLaMona(JuegoDeCartas):
...
def eliminaTodasLasCoincidencias(self):
cant = 0
for mano in self.manos:
cant = cant + mano.eliminaCoincidencias()
return cant
Como ejercicio, escriba muestraManos, el cual recorre self.manos
y muestra cada mano.
cant es un acumulador que va sumando la cantidad de
concordancias en cada mano y devuelve el total.
Cuando la cantidad total de coincidencias alcanza a las veinticinco
significa que se han eliminado cincuenta cartas de las manos, lo
que es lo mismo que decir que s´olo queda una carta y el juego ha
terminado.
La variable turno recuerda el turno de cua´l jugador se esta´
jugando. Comienza en cero y se incrementa en uno cada vez;
cuando alcanza el valor cantManos, el operador de modulo lo hace
volver a cero.
16.7 La clase 1
El m´etodo jugarUnTurno toma un par´ametro que indica de qui´en
es el turno. El valor de retorno es la cantidad de concordancias que
se han realizado durante ese turno:
class JuegoDeLaMona(JuegoDeCartas):
...
def jugarUnTurno(self, i):
if self.manos[i].estaVacio():
return 0
vecino = self.encuentraVecino(i)
cartaElegida = self.manos[vecino].darCarta()
self.manos[i].agregaCarta(cartaElegida)
print "Mano", self.manos[i].nombre, "eligi´o",
cartaElegida cant = self.manos[i].eliminaCoincidencias()
self.manos[i].mezclar()
return cant
Si la mano de un jugador esta´ vac´ıa, el jugador salio´ del juego, as
´ı que no hace nada y devuelve 0.
Si no, un turno consiste en encontrar el primer jugador a la
izquierda que au´n tiene cartas, tomar una carta de las que posee,
y controlar si hay concordancias. Antes de volver se mezclan las
cartas de la mano, de tal manera que la seleccio´n del siguiente
jugador sea al azar.
El m´etodo encuentraVecino comienza con el jugador que esta´
inmediatamante a la izquierda y continu´a alrededor del c´ırculo
hasta que encuentra un jugador que au´n tiene cartas.
class JuegoDeLaMona(JuegoDeCartas):
...
def encuentraVecino(self, i):
cantManos = len(self.manos)
for proximo in range(1,cantManos):
vecino = (i + proximo) cantManos
if not self.manos[vecino].estaVacio():
return vecino
Si por cualquier motivo encuentraVecino llegara a dar la vuelta
completa al c´ırculo sin encontrar cartas, devolver´ıa None y eso
causar´ıa un error en alguna otra parte del programa.
Afortunadamante podemos probar que eso no va a suceder nunca
(siempre y cuando se detecte correctamente el final del juego).
Hemos omitido el m´etodo muestraManos. E´se puede escribirlo
usted mismo. La siguiente salida proviene de una forma reducida
del juego, en la cual solamen- te se reparten las quince cartas m´as
altas (desde los dieces hacia arriba) a tres
1 Herenc
jugadores. Con este mazo m´as pequen˜o, el juego termina tras
siete coincidencias, en lugar de veinticinco.
>>> import cartas
>>> juego = cartas.JuegoDeLaMona()
>>> juego.jugar(["Allen","Jeff","Chris"])
----- Se han repartido las cartas.
Mano Allen contiene
Rey de Corazones
Sota de Tr
´eboles Reina de
Picas
Rey de Picas
10 de Diamantes
Mano Jeff contiene
Reina de
Corazones Sota de
Picas
Sota de Corazones
Rey de Diamantes
Reina de Diamantes
Mano Chris contiene
Sota de Diamantes
Rey de Tr´eboles
10 de Picas
10 de Corazones
10 de Tr´eboles
Mano Jeff: Reina de Corazones con Reina de Diamantes
Mano Chris: 10 de Picas con 10 de Tr´eboles
----- Se eliminaron las coincidencias, el juego comienza.
Mano Allen contiene
Rey de Corazones
Sota de Tr
´eboles Reina de
Picas
Rey de Picas
10 de Diamantes
Mano Jeff contiene
Sota de Picas
Sota de Corazones
Rey de Diamantes
16.8 Glosario 181
Mano Chris contiene
Sota de Diamantes
Rey de Tr´eboles
10 de Corazones
Mano Allen: eligi´o Rey de Diamantes
Mano Allen: Rey de Corazones con Rey de Diamantes
Mano Jeff: eligi´o 10 de Corazones
Mano Chris: eligi´o Sota de Tr
´eboles Mano Allen: eligi´o Sota de
Corazones Mano Jeff: eligi´o Sota de
Diamantes Mano Chris: eligi´o Reina
de Picas Mano Allen: eligi´o Sota de
Diamantes
Mano Allen: Sota de Corazones con Sota de Diamantes
Mano Jeff: eligi´o Rey de Tr´eboles
Mano Chris: eligi´o Rey de Picas
Mano Allen: eligi´o 10 de
Corazones
Mano Allen: 10 de Diamantes con 10 de Corazones
Mano Jeff: eligi´o Reina de Picas
Mano Chris: eligi´o Sota de Picas
Mano Chris: Sota de Tr´eboles con Sota de Picas
Mano Jeff: eligi´o Rey de Picas
Mano Jeff: Rey de Tr´eboles con Rey de Picas
----- El juego termin´o.
La mano de Allen est´a vac´ıa.
La mano de Jeff contiene
Reina de Picas
La mano de Chris est´a vac´ıa.
As´ı que Jeff es quien perdio´.
16.8. Glosario
herencia: La capacidad de definir una nueva clase que es una versio
´n modifi- cada de una clase previamente definida.
clase padre: Aquella clase de la cual la clase hija hereda.
1 Herenc
clase hija: Una nueva clase creada heredando de una clase
existente; tambi´en se la llama “subclase”.
Cap´ıtulo 17
Listas enlazadas
17.1. Referencias incrustadas
Hemos visto ejemplos de atributos que hacen referencia a otros
objetos, a los que llamamos referencias incrustadas (v´ease la Secci
´on 12.8). Una estrutura de datos comu´n, la lista enlazada, saca
partido de esta caracter´ıstica.
Las listas enlazadas se componen de nodos, donde cada nodo
contiene una referencia al pr´oximo nodo de la lista. Adem´as, cada
nodo contiene una unidad de datos llamada carga
Podemos considerar una lista enlazada como una estructura de
datos recur- siva porque tiene una definici´on recursiva.
Una lista enlazada puede ser:
la lista vac´ıa, representada por None, o bien
un nodo que contiene un objeto de carga y una
referencia a una lista enlazada.
Las estructuras recursivas de datos nos llevan a m´etodos recursivos.
17.2. La clase Nodo
Como es habitual cuando se escribe una clase, comenzaremos con
los m´etodos de inicializaci´on y str , para poder comprobar el
mecanismo basico de crear y mostrar el nuevo tipo:
1 Listas
cargo
next
1
None
cargo
next
2
None
cargo
next
3
None
class Nodo:
def init (self, carga=None, siguiente=None):
self.carga = carga
self.siguiente = siguiente
def str (self):
return str(self.carga)
Como es habitual, los par´ametros para el m´etodo de inicializaci
´on son opciona- les. Por defecto, la carga y el enlace, siguiente, se
ponen a None.
La representaci´on alfanum´erica de un nodo es u´nicamente la de
la carga. Como se puede pasar cualquier valor a la funci´on str,
podemos guardar cualquier valor en una lista.
Para comprobar la implementaci´on en este punto, podemos crear
un Nodo e imprimirlo:
>>> nodo = Nodo("prueba")
>>> print nodo
prueba
Para hacerlo m´as interesante, necesitaremos una lista que
contenga mas de un nodo:
>>> nodo1 = Nodo(1)
>>> nodo2 = Nodo(2)
>>> nodo3 = Nodo(3)
Este c´odigo crea tres nodos, pero au´n no tenemos una lista
porque los nodos todav´ıa no esta´n enlazados. El diagrama de
estados tiene el siguiente aspecto:
node1
node2 node3
Para enlazar los nodos, debemos hacer que el primer nodo haga
referencia al segundo, y que ´este haga referencia al tercero:
>>> nodo1.siguiente = nodo2
>>> nodo2.siguiente = nodo3
La referencia del tercer nodo ser´a None, que indica que es el final
de la lista. Ahora el diagrama de estados tendr´a el siguiente
aspecto:
17.3 Listas como colecciones 185
node1
node2 node3
cargo
next
1 cargo
next
2 cargo 3
next None
Ahora ya sabe c´omo crear nodos y enlazarlos en listas. Lo que
podr´ıa estar menos claro es por qu´e.
17.3. Listas como colecciones
Las listas son utiles porque aportan un modo de ensamblar mu
´ltiples objetos dentro de una u´nica entidad, a veces llamada
coleccio´n. En el ejemplo, el primer nodo de la lista sirve como
referencia a la lista completa.
Para pasar la lista como par´ametro, s´olo tenemos que hacer
referencia al primer nodo. Por ejemplo, la funci´on imprimeLista
toma como argumento un nodo simple. Empezando con la cabeza
de la lista, imprime cada nodo hasta que llega al final.
def imprimeLista(nodo):
while nodo:
print nodo,
nodo = nodo.siguiente
print
Para llamar a este m´etodo, pasamos una referencia al primer nodo:
>>> imprimeLista(nodo1)
1 2 3
Dentro de imprimeLista tenemos una referencia al primer nodo
de la lista, pero no hay una variable que haga referencia a los
otros nodos. Tendremos que usar el valor de siguiente de cada
nodo para acceder al siguiente nodo.
Para recorrer una lista enlazada es habitual usar la variable de un bucle
como
nodo para hacer referencia sucesivamente a cada uno de los nodos.
Este diagrama muestra el valor de lista y los valores que va tomando
nodo:
1 Listas
cargo
y
1 cargo
y
2 3
None
cargo
y
node1 node2 node3
node
Por convenio, las listas suelen imprimirse entre
corchetes con co- mas entre los elementos, como [1, 2,
3]. Como ejercicio, modifique imprimeLista para que
genere una salida con este formato.
17.4. Listas y recursividad
Es natural expresar muchas operaciones con listas por medio de
m´etodos re- cursivos. El siguiente ejemplo es un algoritmo
recursivo para imprimir una lista hacia atr´as:
1. Separar la lista en dos partes: el primer nodo (llamado
cabeza) y el resto (llamado cola).
2. Imprimir la cola hacia atr´as.
3. Imprimir la cabeza.
Por supuesto, el paso 2, la llamada recursiva, supone que tenemos
una manera de imprimir una lista del rev´es. Pero si suponemos
que la llamada recursiva funciona —el acto de fe— entonces
podemos estar convencidos de que este algoritmo funciona.
Todo lo que necesitamos es un caso b´asico y una forma de
demostrar que para cualquier lista podemos obtener el caso b
´asico. Dada la definici´on recursiva de una lista, un caso b´asico
natural es la lista vac´ıa, representada por None:
def imprimeAlReves(lista):
if lista == None: return
cabeza = lista
cola = lista.siguiente
imprimeAlReves(cola)
print cabeza,
cargo
next
1 2
cargo
next
17.5 Listas infinitas 187
La primera l´ınea maneja el caso inicial no haciendo nada. Las dos
siguientes l´ıneas dividen la lista en cabeza y cola. Las dos u
´ltimas l´ıneas imprimen la lista. La coma que hay al final de la u
´ltima l´ınea evita que Python salte de l´ınea despu´es de imprimir
un nodo.
Llamamos este m´etodo tal como antes invocamos a imprimeLista:
>>> imprimeAlReves(nodo1)
3 2 1
El resultado es una lista invertida.
Es posible que se pregunte por qu´e imprimeLista e imprimeAlReves
son fun- ciones y no m´etodos de la clase Nodo. La razo´n es que
queremos usar None para representar la lista vac´ıa y no es legal
llamar un m´etodo sobre None. Esta limi- taci´on resulta algo inc
´omoda a la hora de escribir c´odigo para manipular listas con un
estilo orientado a objetos puro.
¿Podemos demostrar que imprimeAlReves siempre acabar´a? En otras
palabras,
¿siempre alcanzaremos el caso b´asico? De hecho, la respuesta es
no. Algunas listas har´an que el m´etodo no funcione.
17.5. Listas infinitas
No hay nada que impida a un nodo hacer referencia a un nodo
anterior de la lista, incluido ´el mismo. Por ejemplo, esta figura
muestra una lista con dos nodos, uno de los cuales apunta a s´ı
mismo:
list
Si llamamos a imprimeLista sobre esta lista, entrar´a en un bucle
infinito. Si lla- mamos a imprimeAlReves, lo har´a de forma
infinitamente recursiva. Eeste tipo de comportamiento da lugar a
que sea muy dif´ıcil trabajar con listas infinitas. Sin embargo, en
ocasiones resultan u´tiles. Por ejemplo, podr´ıamos representar un
nu´mero como una lista de d´ıgitos y usar una lista infinita para
representar una fracci´on repetida.
1 Listas
A pesar de todo, es un problema que no podamos probar que
imprimeLista e imprimeAlReves puedan terminar correctamente. Lo
mejor que podemos hacer es afirmar que “si la lista no contiene
bucles, estos m´etodos podr´an terminar”. Este tipo de
afirmaciones se conocen como condiciones previas. Imponen una
restriccio´n sobre uno de los par´ametros y describen el
comportamiento del m´etodo si la restriccio´n se satisface.
Veremos mas ejemplos mas adelante.
17.6. Teorema fundamental de la ambigu¨edad
Una parte de imprimeAlReves podr´ıa habernos sorprendido:
cabeza = lista
cola = lista.siguiente
Despu´es de la primera asignaci´on, la cabeza y la cola tienen el
mismo tipo y el mismo valor. Asi que, ¿para qu´e hemos creado un
nueva variable?
La razo´n es que las dos variables desempen˜an papeles diferentes.
Pensamos en la cabeza como una referencia al primer nodo de la
lista. Estos “papeles” no forman parte del programa, sino que esta
´n en la mente del programador.
En general no podemos decir con s´olo mirar un programa qu´e
papel desem- pen˜ar´a un variable. Esta ambigu¨edad puede ser u
´til, pero tambi´en puede difi- cultar la lectura del programa. A
menudo usaremos nombres para las variables como nodo y lista
para explicar c´omo queremos usar una variable y a veces creamos
variables adicionales para eliminar ambigu¨edades.
Podr´ıamos haber escrito imprimeAlReves sin cabeza ni cola, que lo
har´ıa mas conciso, pero posiblemente menos claro:
def imprimeAlReves(lista)
: if lista == None :
return
imprimeAlReves(lista.siguiente)
print lista,
Mirando esas dos llamadas, hemos de recordar que
imprimeAlReves trata sus argumentos como una coleccio´n y print
los trata como a un s´olo objeto.
El teorema fundamental de la ambigu¨edad indica que la
ambigu¨edad que es inherente a una referencia a un nodo:
Una variable que hace apunta a nodo puede tratar a este
nodo como un objeto o como el primero de una lista de
nodos.
cargo
next
1 cargo
next
2 3
None
cargo
next
17.7 Modificar listas 189
17.7. Modificar listas
Hay dos formas de modificar una lista enlazada. Obviamente,
podemos cambiar la carga de uno de los nodos, pero las
operaciones mas interesantes son las que an˜aden, quitan o
reordenan los nodos.
Como ejemplo, escribamos un m´etodo que quite el segundo nodo
de la lista y devuelva una referencia al nodo quitado:
def eliminaSegundo(lista):
if lista == None: return
primero = lista
segundo = lista.siguiente
# hacer que el primer noda apunte al tercero
primero.siguiente = segundo.siguiente
# separar el segundo nodo del resto de la lista
segundo.siguiente = None
return segundo
De nuevo, estamos usando variables temporales para hacer mas
legible el c´odigo. As´ı es como usamos este m´etodo:
>>> imprimeLista(nodo1)
1 2 3
>>> eliminado = elimnaSegundo(nodo1)
>>> imprimeLista(eliminado)
2
>>> imprimeLista(nodo1)
1 3
El diagrama de estado nos muestra el efecto de la operacio´n:
first second
¿Qu´e ocurrir´ıa si llam´aramos a este m´etodo y pas´aramos una
lista de un u´nico elemento (un singleton)? ¿Qu´e suceder´ıa si pas
´aramos una lista vac´ıa como argumento? ¿Hay una condicio´n
previa para este m´etodo? Si es as´ı, ser´ıa algo razonable
establecer un m´etodo para manejar una violaci´on de la condicio´n
previa.
1 Listas
17.8. Envoltorios y ayudantes
A menudo es u´til dividir una operacio´n de listas en dos m´etodos.
Por ejemplo, para imprimir una lista invertida en el formato
convencional [3, 2, 1] pode- mos usar el m´etodo imprimeAlReves
para imprimir 3, 2, pero necesitaremos un m´etodo aparte para
imprimir los corchetes y el primer nodo. Llam´emoslo
imprimeAlRevesBonito:
def imprimeAlRevesBonito(lista) :
print "[",
if lista != None :
cabeza = lista
cola = lista.siguiente
imprimeAlReves(cola)
print cabeza,
print "]",
De nuevo, vemos que es buena idea comprobar m´etodos como
´este para ver si funcionan con casos especiales como una lista vac
´ıa o un singleton.
Cuando usamos este m´etodo en algu´n otro lugar del programa,
llamamos direc- tamente a imprimeAlRevesBonito, y ´este llama a
imprimeAlReves en nuestro lugar. En cierto modo,
imprimeAlRevesBonito actu´a como un envoltorio, y utiliza a
imprimeAlReves como su ayudante.
17.9. La clase ListaEnlazada
Existen ciertos problemas delicados con la forma en que se
implementaron las listas. Inviertiendo el orden de causa y efecto,
propondremos primero una im- plementaci´on alternativa y
explicaremos luego los problemas que ´esta resuelve. En primer
lugar crearemos un nueva clase llamada ListaEnlazada. Sus atribu-
tos son un entero que contiene la longitud de la lista y una
referencia al primer nodo. Los objetos ListaEnlazada sirven de asas
para la manipulaci´on de las listas de los objetos de tipo Nodo:
class ListaEnlazada :
def init (self) :
self.longitud = 0
self.cabeza =
None
Una ventaja de la clase ListaEnlazada es que suministra un marco
natural para poner funciones-envoltorio como imprimeAlRevesBonito,
que podemos convertir en un m´etodo de la clase ListaEnlazada:
17.10 Invariantes 191
class ListaEnlazada:
...
def imprimeAlReves(self):
print "[",
if self.cabeza != None:
self.cabeza.imprimeAlReves()
print "]",
class Nodo:
...
def imprimeAlReves(self):
if self.siguiente != None:
cola = self.siguiente
cola.imprimeAlReves()
print self.carga,
Para complicar un poco m´as las cosas, renombramos
imprimeAlRevesBonito. Ahora hay dos m´etodos llamados
imprimeAlReves: uno en la clase Nodo (el ayudante), y otro en la
clase ListaEnlazada (el envoltorio). Cuando el envolto- rio llama a
self.cabeza.imprimeAlReves, esta´ llamando al ayudante, ya que
self.cabeza es un objeto de tipo Nodo.
Otra ventaja de la clase ListaEnlazada es que facilita la forma de
an˜adir o qui- tar el primer elemento de una lista. Por ejemplo,
agregaPrimero es un m´etodo para ListaEnlazada; toma un elemento
de la carga como argumento y lo coloca en el principio de la lista:
class ListaEnlazada:
...
def agregaPrimero(self, carga):
nodo = Nodo(carga)
nodo.siguiente = self.cabeza
self.cabeza = nodo
self.longitud = self.longitud + 1
Como suele ser usual, deber´ıamos comprobar ´este c´odigo para
ver si maneja casos especiales. Por ejemplo, ¿qu´e ocurrir´ıa si la
lista esta´ unicialmente vac´ıa?
17.10. Invariantes
Algunas listas esta´n “bien construidas”; otras no. Por ejemplo, si
una lista con- tiene un bucle, provocara´ que nuestros m´etodos se
cuelguen, as´ı que podr´ıamos exigir que las listas no contengan
bucles. Otro requisito es que el valor de el valor
1 Listas
de longitud en el objeto de tipo ListaEnlazada deber´ıa ser igual al
nu´mero verdadero de nodos de la lista.
A este tipo de requisitos los llamaremos invariantes porque,
idealmente de- ber´ıan cumplirse en todos los objetos en todo
momento. Especificar invariantes para objetos es una pr´actica u´til
de la programaci´on porque hace mas facil de- mostrar la idoneidad
del c´odigo, comprobar la integridad de las estructuras de datos y
la detecci´on de errores.
Una cosa que a veces confunde respecto a los invariantes es que en
ocasiones son violados. Por ejemplo, en medio de agregaPrimero,
despu´es de an˜adir el nodo paro antes de incrementar la longitud,
se viola el invariante. Se acepta este tipo de violaci´on; de hecho, a
menudo es imposible modificar un objeto sin violar un invariante
durante al menos un corto espacio de tiempo. Normalmente, se
exige que todo m´etodo que viole un invariante debe restablecerlo.
Si hay algu´n tramo significativo de c´odigo en el que el invariante
se ve violado, es importante que los comentarios lo dejen claro, de
modo que no se realicen operaciones que dependan del invariante.
17.11. Glosario
referencia incrustada: Es una referencia almacenada en un atributo de
un objeto.
lista enlazada: Estructura de datos que implementa una coleccio´n
por medio de una secuencia de nodos enlazados.
nodo: Elemento de una lista, normalmente implementado como un
objeto que contiene una referencia a otro objeto del mismo
tipo.
carga: Datos contenidos en un nodo.
enlace: Referencia incrustada usada para enlazar un objeto con otro.
condicio´n previa: Afirmaci´on que debe ser cierta para que un m
´etodo funcio- ne correctamente.
teorema fundamental de la ambigu¨edad: Una referencia a un
nodo de una lista puede tratarse como un objeto individual o
como el primero de una lista de nodos.
singleton: Lista enlazada con un solo nodo.
17.11 Glosario 193
envoltorio: M´etodo que actu´a como mediador entre un m´etodo
invocador y m´etodo ayudante, haciendo a menudo su
invocacio´n mas facil o menos proclive a errores.
ayudante: M´etodo al que no se invoca directamente por un m
´etodo llamante sino por otro m´etodo para formar parte de
una operacio´n.
invariante: Afirmaci´on que deber´ıa ser cierta para un objeto en
todo momento (excepto tal vez cuando se esta´ modificando el
objeto).
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Cap´ıtulo 18
Pilas
18.1. Tipos abstractos de datos
Los tipos de datos vistos hasta ahora han sido todos concretos, en
el sentido que se ha especificado su implementaci´on
completamente. Por ejemplo, la clase Carta representa un naipe por
medio de dos enteros. Como dijimos, esa no es la u´nica manera de
representar una carta; existen muchas implementaciones
alternativas.
Un tipo abstracto de datos, o TAD, especifica un conjunto de
operaciones (o m´etodos) y la sema´ntica de las operaciones (lo
que hacen), pero no especifica la implementaci´on de las
operaciones. Esto es lo hace lo abstracto.
¿Para qu´e sirve?
Simplifica la tarea de especificar un algoritmo si se pueden
indicar las operaciones necesarias sin preocuparse de c´omo
se ejecutara´n dichas ope- raciones.
Como suelen existir muchas maneras de implementar un TAD,
puede ser u´til desarrollar un algoritmo que se pueda usar con
cualquiera de las im- plementaciones posibles.
Los TADs mas conocidos, como el TAD Pila de este cap´ıtulo, se
implemen- tan a menudo en bibliotecas esta´ndares de manera
que se puedan escribir una vez y usarlas luego muchos
programadores.
Las operaciones ejecutadas con TADs proporcionan un
lenguaje de alto nivel comu´n para desarrollar y hablar sobre
algoritmos.
1 Pil
Cuando hablamos de TADs a menudo se hace la distinci´on entre el
c´odigo que usa el TAD, el c´odigo cliente, y el c´odigo que
implementa el TAD, el c´odigo proveedor.
18.2. El TAD Pila
En este cap´ıtulo se presentar´a un TAD comu´n, la pila. Una pila
es una coleccio´n, lo que significa que es una estructura de datos
que contiene elementos mu´ltiples. Otras colecciones que se han
visto son los diccionarios y las listas.
Un TAD se definido por medio de las operaciones que se pueden
ejecutar so- bre ´el, lo que se llama un interfaz. La interfaz para una
pila consta de estas operaciones1
:
init : Inicializar una pila nueva y vac´ıa.
push: An˜adir un elemento a la pila.
pop: Extraer un elemento de la pila. El elemento devuelto siempre
es el u´ltimo que se an˜adi´o.
isEmpty: Probar si la pila esta´ vac´ıa.
A veces a una pila se la llama una estructura “u´ltimo en entrar
primero en salir” (“last in, first out” en ingl´es), o LIFO, porque el
elemento an˜adido en u´ltimo lugar es el primero que extraemos.
18.3. Co´mo implementar pilas con listas de
Pyt- hon
Las operaciones sobre listas que posee Python son parecidas a las
operaciones que definen a una pila. La interfaz no es exactamente
la que deber´ıa de ser, pero se pueden desarrollar programas para
convertir el TAD Pila a las operaciones predefinidas.
A este programa se lo llama implementaci´on del TAD Pila. En
general, una implementaci´on es un conjunto de m´etodos que
satisfacen los prerrequisitos sint´acticos y sema´nticos de la
interfaz.
He aqu´ı una implementaci´on de el TAD Pila que utiliza una lista Python:
1
Mantenemos los nombres ingleses porque son esta´ndar en otros TADs en Python y
otros lenguajes.
18.4 Uso de push y pop 197
class Pila :
def init (self) :
self.elementos = []
def push(self, elemento) :
self.elementos.append(elemento)
def pop(self) :
return self.elementos.pop()
def isEmpty(self) :
return (self.elementos == [])
Un objeto Pila contiene un atributo llamado elementos que es una
lista de ele- mentos en la pila. El m´etodo de inicializaci´on pone
una lista vac´ıa en elementos. Para meter un elemento nuevo en la
pila, push lo apila en elementos. Para quitar un elemento de la pila,
pop utiliza el m´etodo de lista homonimo2
para quitar y devolver el
u´ltimo elemento de la lista.
Finalmente, para probar si la pila esta vac´ıa, isEmpty (est´a vac´ıa)
compara
elementos con la lista vac´ıa.
Una implementaci´on como esta, en la cual los m´etodos consisten
de llamadas a m´etodos existentes, se llama enchapado. En la vida
real, un enchapado es una capa fina de madera de alta calidad que
se utiliza en la fabricaci´on de muebles para ocultar madera de
baja calidad. Los cient´ıficos inform´aticos utilizan esta met´afora
para describir una parte de un programa que esconde los detalles
de una implementaci´on y que provee una interfaz mas simple o
mas esta´ndar.
18.4. Uso de push y pop
Una pila es una estructura gen´erica de datos, lo significa que se
puede an˜adir cualquier tipo de elementos a ella. El ejemplo mete
en la pila dos enteros y una cadena:
>>> s = Stack()
>>> s.push(54)
>>> s.push(45)
>>> s.push("+")
Se pueden utilizar isEmpty y pop para quitar e imprimir todos los
elementos en la pila:
2
del mismo nombre
1 Pil
•
while not s.isEmpty() :
print s.pop(),
La salida es + 45 54. En otras palabras, ¡se ha utilizado una pila
para imprimir los elementos en orden inverso! Reconocemos que
no es el formato esta´ndar de impresi´on de listas, pero fue muy f
´acil usar una pila para lograrlo.
Compare estas l´ıneas con la implementaci´on de imprimeAlReves
que vimos en la Secci´on 17.4. Existe un paralelo natural entre la
versio´n recurrente de imprimeAlReves y el algoritmo de pila que
acabamos de ver. La diferencia es que imprimeAlReves utiliza la pila
de tiempo de ejecucio´n para mantenerse al tanto de los nodos
mientras recorre la lista y luego los imprime cuando regresa de la
recursio´n. El algoritmo de pila hace lo mismo, pero utiliza un
objeto Pila en vez de la pila de tiempo de ejecucio´n.
18.5. Usar una pila para evaluar postfijo
Las expresiones matem´aticas en la mayor´ıa de lenguajes de
programaci´on se escriben con el operador entre los dos
operandos, de esta manera: 1+2. A este formato se le llama infijo.
Algunas calculadoras utilizan un formato alternativo llamado
postfijo. Con postfijo, el operador va despu´es de los operandos, as
´ı: 1 2 +.
La razo´n por la que el formato postfijo es u´til es que existe una
forma natural de evaluar una expresio´n en formato postfijo
utilizando una pila:
Desde el principio de la expresio´n, evalu´e los operadores y
operandos uno por uno.
• Si el t´ermino es un operando, utilice push para colocarlo
en la pila. Si el t´ermino es un operador, utilice pop con
dos operandos de la pila, ejecute la operacio´n sobre ellos,
y coloque el resultado en la pila con push.
Cuando llegue al final de la expresio´n habr´a un operando en
la pila. Ese operando es el resultado.
Para practicar, aplique este algoritmo a la expresi´on 1 2 + 3 *.
Este ejemplo demuestra una de las ventajas de el formato
postfijo—no hay necesidad de usar par´entesis para controlar el
orden de operaciones. Para obtener el mismo resultado con el
formato infijo, se tendr´ıa que escribir (1 + 2) * 3).
Para practicar, escriba una expresi´on en formato postfijo que sea
equivalente a 1 + 2 * 3.
18.6 An´alisis sint´actico 199
18.6. An´alisis sint´actico
Para implementar el algoritmo anterior, necesitamos ser capaces
de recorrer una cadena y separarla en operandos y operadores.
Este proceso es un ejemplo del an´alisis sint´actico, y los
resultados—la piezas individuales de la cadena—son tokens3
. Quiz
´as se acuerde de esas palabras del Cap´ıtulo 1.
Python posee un m´etodo llamado split (partir) en el modulo string
(cadena) y en el m´odulo re (expresiones regulares). La funci´on
string.split parte una cadena y la convierte en una lista utilizando
un s´olo cara´cter como delimitador. Por ejemplo:
>>> import string
>>> string.split("La hora ha llegado"," ")
[’La’, ’hora’, ’ha’, ’llegado’]
En este caso, el delimitador es el cara´cter espacio, y se parte la
cadena en cada espacio.
La funci´on re.split tiene m´as potente, y nos permite utilizar una
expresio´n regular en vez de un delimitador. Una expresio´n
regular es una manera de espe- cificar un conjunto de cadenas.
Por ejemplo, [A-z] es el conjunto de todas las letras y [0-9] es el
conjunto de todas las cifras. El operador ^ niega un conjunto, y
[^0-9] es el conjunto de todo lo que no es un nu´mero, lo cual es
exactamente lo que queremos utilizar para partir expresiones
en el formato postfijo:
>>> import re
>>> re.split("([^0-9])", "123+456*/")
[’123’, ’+’, ’456’, ’*’, ’’, ’/’, ’’]
F´ıjese que el orden de los argumentos es diferente del de
string.split; el delimitador viene antes de la cadena.
La lista resultante incluye los operandos 123 y 456 y los
operadores * y /. Tambi´en incluye dos cadenas vac´ıas que se
insertan despu´es de los operandos.
18.7. Evaluar un postfijo
Para evaluar una expresio´n en formato postfijo usaremos el
analizador sintactico de la seccio´n anterior y el algoritmo de la
seccio´n previa a ´esa. Para no complicar
3
Podr´ıamos traducir “token” como “cospel” o “pieza”, en ocasiones tambi´en como “s
´ımbo- lo” pero la expresio´n inglesa esta´ tan introducida en el vocabulario inform´atico
que s´olo an˜adir´ıa confusio´n.
2 Pil
las cosas, comenzaremos con un evaluador que solo implementa los
operadores
+ y *:
def evalPostfijo(expr):
import re
listaTokens = re.split("([^0-9])", expr)
pila = Pila()
for token in listaTokens:
if token == ’’ or token == ’ ’:
continue
if token == ’+’:
suma = pila.pop() + pila.pop()
pila.push(suma)
elif token == ’*’:
producto = pila.pop() * pila.pop()
pila.push(producto)
else:
pila.push(int(token))
return pila.pop()
La primera condicio´n se encarga de espacios y cadenas vac´ıas.
Las dos condi- ciones siguientes controlan los operadores.
Asumimos, por ahora, que todo lo dem´as es un operando. Por
supuesto, ser´ıa mejor verificar si la entrada tiene errores y mostrar
un mensaje con el error, pero eso se har´a despu´es.
Comprobemos con una evaluaci´on de la forma postfijo (56+47)*2):
>>> print evalPostfijo ("56 47 + 2 *")
206
Esto es suficiente.
18.8. Clientes y proveedores
Uno de los objetivos fundamentales de un TAD es el de separar los
intereses del proveedor, quien escribe el c´odigo de programa que
implementa el TAD, y los del cliente, quien utiliza el TAD. El
proveedor s´olo se preocupa de la implementaci´on y de si es
correcta o no—de acuerdo a las especificaciones del TAD—y no de c
´omo se va a utilizar.
18.9 Glosario 201
A la inversa, el cliente supone que la implementaci´on del TAD es
correcta y no se preocupa de los detalles. Cuando se usa uno de los
tipos predefinidos de Python, uno se puede dar el lujo de pensar
exclusivamente como un cliente. Por supuesto, cuando usted
implemente un TAD, tambi´en debe desarrollar c´odi- go de cliente
para probarlo. En ese case, uno toma ambos papeles, lo cual puede
causar confusio´n. Tiene que fijarse bien en del papel que esta´
tomando en todo momento.
18.9. Glosario
tipo abstracto de datos (TAD): Un tipo de datos (a menudo una
coleccio´n de objetos) que se define por un conjunto de
operaciones pero que se puede implementar de varias
maneras.
interfaz: El conjunto de operaciones que definen un TAD.
implementaci´on: El c´odigo de programa que satisface los
prerrequisitos sint´acticos y sema´nticos de un interfaz.
cliente: Un programa (o la persona que lo escribio´) que utiliza un TAD.
proveedor: Un programa (o la persona que lo escribio´) que
implementa un TAD.
enchapado: La definici´on de clase que implementa un TAD con
definiciones de m´etodos que son las invocaciones de otros m
´etodos, a veces con trans- formaciones simples. El enchapado
no ejecuta nada de gran valor, pero mejora la interfaz vista
por el cliente o la hace mas esta´ndar.
estructura de datos gen´erica: Un tipo de estructura de datos que
puede contener datos de cualquier tipo.
infijo: Un m´etodo de escribir expresiones matem´aticas con los
operadores entre los operandos.
postfijo: Un m´etodo de escribir expresiones matem´aticas con los
operadores despu´es de los operandos.
analizar sint´acticamente: Examinar una cadena de caracteres o
tokens y analizar su estructura gram´atical.
token: Un conjunto de caracteres que se tratan como una unidad y
son anali- zados sint´acticamente, como las palabras de un lenguaje
natural. delimitador: Un cara´cter utilizado para separar tokens,
como la puntuaci´on
en un lenguaje natural.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Cap´ıtulo 19
Colas
Este cap´ıtulo presenta dos TADs: la Cola y la Cola Priorizada. En la
vida real, una cola es una fila de clientes esperando un servicio
de algu´n tipo. En la mayor´ıa de los casos, el primer cliente de la
fila es el primero al que se va a servir. Sin embargo, hay
excepciones. En los aeropuertos, a veces se saca de la cola a los
clientes cuyos vuelos van a salir pronto. En los supermercados, un
cliente educado puede dejar que alguien que lleva pocos productos
pase antes. La regla que determina qui´en va primero se llama t
´actica de encolamiento. La t´actica de encolamiento m´as simple
se llama FIFO, de “first-in-first-out”, “el primero que entra es el
primero que sale”. La tactica de encolamiento mas general es el
encolamiento priorizado, en la que a cada cliente se le asigna una
prioridad y el cliente con la prioridad mas alta pasa primero, sin
importar
el orden de llegada. Decimos que es la t´actica mas general porque la
prioridad
se puede basar en cualquier cosa: a qu´e hora sale el vuelo; cua
´ntos productos lleva el cliente; cua´n importante es el cliente. Por
supuesto, no todas las tacticas de prioridad son “justas”, pero la
justicia siempre es subjetiva.
El TAD Cola y el TAD Cola Priorizada tienen el mismo conjunto de
operaciones. La diferencia esta´ en la sema´ntica de las
operaciones: una cola usa la tactica FIFO, y una cola priorizada
(como su propio nombre indica) usa una tactica de encolamiento
priorizado.
19.1. El TAD Cola
El TAD Cola se define a trav´es de las siguientes operaciones:
init : Inicializa una cola nueva vac´ıa.
2 Col
inserta: An˜ade un elemento a la cola.
quita: Elimina y devuelve un elemento de la cola. El elemento
devuelto es el primero que se an˜adi´o.
estaVacia: Comprueba si la cola esta´ vac´ıa.
19.2. Cola Enlazada
La primera implementaci´on del TAD Cola al que vamos a echar un
vistazo se llama cola enlazada porque esta´ hecha de ojetos Nodo
enlazados. He aqu´ı la definici´on de la clase:
class Cola:
def init (self):
self.longitud = 0
self.cabeza =
None
def estaVacia(self):
return (self.longitud == 0)
def inserta(self, carga):
nodo = Nodo(carga)
nodo.siguiente = None
if self.cabeza ==
None:
# si la lista est´a vac´ıa el nuevo nodo va el
primero self.cabeza = nodo
else:
# encuentra el u´ltimo nodo de la lista
ultimo = self.cabeza
while ultimo.siguiente: ultimo = ultimo.siguiente #
a~nadir el nuevo nodo
ultimo.siguiente = nodo
self.longitud = self.longitud + 1
def quita(self):
carga = self.cabeza.carga
self.cabeza = self.cabeza.siguiente
self.longitud = self.longitud - 1
return carga
Los m´etodos estaVacia y quita son id´enticos a los m´etodos
estaVacia y
quitaPrimero de ListaEnlazada. El m´etodo inserta es nuevo y un poco mas
19.3 Rendimiento t´ıpico 205
complicado.
Queremos insertar nuevos elementos al final de la lista. Si la cola
esta´ vac´ıa, simplemente hacemos que cabeza se refiera al nuevo
nodo.
En caso contrario, recorremos la lista hasta el u´ltimo nodo y lo
fijamos al final. Podemos reconocer el u´ltimo nodo porque su
atributo siguiente es None.
En un objeto Cola correctamente construido hay dos invariantes. El
valor de longitud deber´ıa ser el nu´mero de nodos en la cola, y el
u´ltimo nodo deber´ıa tener siguiente igual a None. Cr´ease que
este m´etodo cumple con ambas inva- riantes.
19.3. Rendimiento t´ıpico
Normalmente cuando invocamos un m´etodo, no nos importan los
detalles de su implementaci´on. Pero hay un “detalle” que podr´ıa
interesarnos: el rendimien- to t´ıpico del m´etodo. ¿Cua´nto tarda,
y c´omo var´ıa el tiempo de ejecucio´n al aumentar el nu´mero de
elementos de la coleccio´n?
Primero mire quita. Ah´ı no hay bucles ni llamadas a funciones,
dando a enten- der que el tiempo de ejecucio´n de este m´etodo es
siempre el mismo. Un m´etodo as´ı se llama operacio´n de tiempo
constante. En realidad, el m´etodo podr´ıa ser ligeramente m´as r
´apido cuando la lista esta´ vac´ıa porque se salta el cuerpo de la
condicio´n, pero esa diferencia no es significativa.
El rendimiento de inserta es muy diferente. En el caso general,
tenemos que recorrer la lista para encontrar el u´ltimo elemento.
Este recorrido cuesta un tiempo proporcional a la longitud de la
lista. Como el tiempo de ejecucio´n es funci´on lineal de la
longitud, este m´etodo se llama de tiempo lineal. Comparado con el
tiempo constante, es muy pobre.
19.4. Cola Enlazada Mejorada
Nos gustar´ıa una implementaci´on del TAD Cola capaz de realizar
todas las operaciones en tiempo constante. Una forma de hacerlo
es modificar la clase Cola de modo que mantenga una referencia
tanto al primero como al u´ltimo nodo, como se muestra en la
figura:
2 Col
cargo
next
1 cargo
next
2 3
cargo
next
last
3
length
head
La implementaci´on de ColaMejorada es as´ı:
class ColaMejorada:
def init (self):
self.longitud = 0
self.cabeza =
None self.ultimo
=
None
def estaVacia(self):
return (self.longitud == 0)
Hasta ahora, el u´nico cambio es el atributo ultimo. Se usa en los m
´etodos
inserta y quita:
class ColaMejorada:
...
def inserta(self, carga):
nodo = Nodo(carga)
nodo.siguiente = None
if self.longitud == 0:
# si la lista est´a vac´ıa, el nuevo nodo es
cabeza y self.cabeza = self.ultimo = nodo
else:
u´ltimo
# encontrar el u´ltimo nodo
ultimo = self.ultimo
# a~nadir el nuevo nodo
ultimo.siguiente = nodo
self.ultimo = nodo
self.longitud = self.longitud + 1
Como ultimo sigue el rastro del u´ltimo nodo, no necesitamos
buscarlo. A causa de esto, este m´etodo funciona en tiempo
constante.
Debemos pagar un precio por esa velocidad. Tenemos que an˜adir
un caso especial a quita para apuntar ultimo a None cuando
quitamos el u´ltimo nodo:
class ColaMejorada:
19.5 Cola priorizada 207
...
def quita(self):
carga = self.cabeza.carga
self.cabeza = self.cabeza.siguiente
self.longitud = self.longitud - 1
if self.longitud == 0:
self.ultimo = None
return carga
Esta implementaci´on es m´as complicada que la de la Lista
Enlazada, y es mas dif´ıcil demostrar que es correcta. La ventaja es
que hemos alcanzado la meta: tanto inserta como quita son
operaciones de tiempo constante.
Como ejercicio, escriba una implementaci´on del TAD Cola usando
una lista de Python. Compare el rendimiento de esta implementaci
´on con la de la ColaMejorada para varias longitudes de cola.
19.5. Cola priorizada
El TAD Cola Priorizada tiene el mismo interfaz que el TAD Cola,
pero diferente sema´ntica. De nuevo, el interfaz es:
init : Inicializa una cola vac´ıa nueva.
inserta: An˜ade un nuevo elemento a la cola.
quita: Elimina y devuelve un elemento de la cola. El elemento
devuelto es el de prioridad m´as alta.
estaVacia: Comprueba si la cola esta´ vac´ıa.
La diferencia sema´ntica es que el elemento eliminado de la cola
no es necesa- riamente el primero que se an˜adi´o. En su lugar, es
el elemento con la prioridad m´as alta. Lo que son las prioridades y
c´omo se comparan con las otras no se especifica en la
implementaci´on de la Cola Priorizada. Depende de los elementos
de la cola.
Por ejemplo, si los elementos de la cola tienen nombres, podemos
elegirlos en orden alfab´etico. Si son puntuaciones de bolos,
podemos ir de mayor a menor, pero si son puntuaciones de golf,
iremos de menor a mayor. Siempre que podamos comparar los
elementos de la cola, podremos encontrar y quitar el elemento con
mayor prioridad.
Esta implementaci´on de Cola Priorizada tiene como atributo una
lista de Python que contiene los elementos de la cola.
2 Col
class ColaPriorizada:
def init (self):
self.elementos = []
def estaVacia(self):
return self.elementos == []
def inserta(self, elemento):
self.elementos.append(elemento)
El m´etodo de inicializaci´on, estaVacia, e inserta son todos
calcados de las operaciones sobre listas. El u´nico m´etodo
interesante es quita:
class ColaPriorizada:
...
def quita(self):
maxi = 0
for i in range(1,len(self.elementos)):
if self.elementos[i] > self.elementos[maxi]:
maxi = i
elemento = self.elementos[maxi]
self.elementos[maxi:maxi+1] = []
return elemento
Al principio de cada iteraci´on, maxi contiene el ´ındice del
elemento mas grande (prioridad mas alta) que hemos visto hasta el
momento. Cada vez que se com- pleta el bucle, el programa
compara el i´esimo elemento con el campe´on. Si el nuevo
elemento es mayor, el valor de maxi se fija a i.
Cuando la sentencia for completa su ejecucio´n, maxi es el ´ındice
del elemento mayor. Este elemento se elimina de la lista y se
devuelve.
Vamos a probar la implementaci´on:
>>> c = ColaPriorizada()
>>> c.inserta(11)
>>> c.inserta(12)
>>> c.inserta(14)
>>> c.inserta(13)
>>> while not c.estaVacia(): print c.quita() # ver cu´al se
quita 14
13
12
11
19.6 La clase Golfista 209
Si la cola contiene nu´meros o cadenas simples, se eliminan en
orden num´erico o alfab´etico, de mayor a menor. Python puede
encontrar el entero o la cadena mayor porque puede compararlos
usando los operadores de comparacio´n inter- nos.
Si la cola contiene un tipo de objeto, debe proporcionar un m´etodo
cmp . Cuando quita usa el operador > para comparar elementos,
invoca al cmp de uno de los elementos y le pasa el otro como par
´ametro. Siempre que el m´etodo cmp trabaje adecuadamete, la
Cola Priorizada funcionar´a.
19.6. La clase Golfista
Como ejemplo de un objeto con una definici´on inusual de
prioridad, vamos a implementar una clase llamada Golfista que
mantiene los nombres y puntua- ciones de golfistas. Como es
habitual, empezamos por definir init y str :
class Golfista:
def init (self, nombre, puntos):
self.nombre = nombre
self.puntos = puntos
def str (self):
return " -16s: d" (self.nombre, self.puntos)
str usa el operador de formato para poner los nombres y las
puntuaciones en bonitas columnas.
A continuaci´on definimos una versio´n de cmp en la que la
puntuaci´on mas baja tiene la prioridad m´as alta. Como siempre,
cmp devuelve 1 si self es “mayor que” otro, -1 si self es “menor
que” otro, y 0 si son iguales.
class Golfista:
...
def cmp (self, otro):
if self.puntos < otro.puntos: return 1 # menos es m
´as if self.puntos > otro.puntos: return -1
return 0
Ya estamos listos para probar la cola priorizada con la clase Golfista:
>>> tiger = Golfista("Tiger Woods", 61)
>>> cabr = Golfista("´Angel Cabrera", 72)
>>> ola = Golfista("J.M. Olaz´abal", 69)
>>>
2 Col
>>> cp = ColaPriorizada()
>>> cp.inserta(tiger)
>>> cp.inserta(cabr)
>>> cp.inserta(ola)
>>> while not cp.estaVacia(): print cp.quita()
Tiger Woods : 61
J.M. Olaz´abal : 69
´Angel Cabrera : 72
Como ejercicio, escriba una implementaci´on del TAD Cola Prio-
rizada usando una lista enlazada. Deber´ıa usted mantener la
lista ordenada de modo que la eliminaci´on sea una operacio´n
de tiempo constante. Compare el rendimiento de esta
implementaci´on con la implementaci´on con la lista de Python.
19.7. Glosario
cola: Un conjunto ordenado de objetos esperando un servicio de algu´n tipo.
Cola: Un TAD que ejecuta las operaciones que uno podr´ıa realizar
sobre una cola.
t´actica de encolamiento: Las reglas que determinan qu´e miembro
de la cola ser´a el pr´oximo en eliminarse.
FIFO: “First In, First Out”, una t´actica de encolamiento en la que
el primer miembro en llegar es el primero en salir.
cola priorizada: Una t´actica de encolamiento en la que cada
miembro tiene una prioridad determinada por factores
externos. El miembro con mayor prioridad es el primero en
eliminarse.
Cola Priorizada: Un TAD que define las operaciones que se pueden
realizar sobre una cola priorizada.
cola enlazada: Una implementacion de una cola utilizando una lista enlazada.
tiempo constante: Una operacio´n cuyo tiempo de ejecucio´n no
depende del taman˜o de la estructura de datos.
tiempo lineal: Una operacio´n cuyo tiempo de ejecucio´n es funci
´on lineal del taman˜o de la estructrua de datos.
cargo1
leftright
cargo2
leftright
cargo3
leftright
Cap´ıtulo 20
A´rboles
Al igual que las listas enlazadas, los a´rboles esta´n hechos de
nodos. Un tipo comu´n de a´rbol es un ´arbol binario, en el que
cada nodo contiene una referencia a otros dos nodos (posiblemente
nula). Nombramos a estas referencias como suba´rboles izquierdo
y derecho. Como los nodos de las listas, los nodos de los a´rboles
tambi´en contienen una carga. Un diagrama de estado de un a´rbol
es as´ı:
tree
None None None None
Para evitar apelotonar las cosas en la figura, solemos omitir los Nones.
La parte superior del a´rbol (el nodo al que apunta tree) se llama ra
´ız. Siguiendo con la met´afora del a´rbol, los otros nodos se
llaman ramas y los nodos de los extremos con referencias nulas se
llaman hojas. Puede parecer extran˜o que dibujemos la figura con
la ra´ız arriba y las hojas abajo, pero eso no es lo mas raro.
Para empeorar las cosas, los cient´ıficos inform´aticos an˜aden a
la mezcla otra met´afofa: el a´rbol geneal´ogico. El nodo superior
se llama a veces padre y los
A
2
nodos a los que se refiere son sus hijos. Los nodos con el mismo padre se
llaman
hermanos.
Para terminar, tenemos un vocabulario geom´etrico para hablar
sobre los a´rboles. Ya hemos mencionado izquierda y derecha, pero
tambi´en esta´n “arriba” (hacia el padre/ra´ız) y “abajo” (hacia los
hijos/hojas). Tambi´en, todos los nodos que esta´n a la misma
distancia de la ra´ız forman un nivel del a´rbol.
Probablemente no sean necesarias las met´aforas arb´oreas para
hablar de a´rboles, pero ah´ı esta´n.
Igual que las listas enlazadas, los a´rboles son estructuras de datos
recursivas porque se definen recursivamente.
Un a´rbol es:
el a´rbol vac´ıo, representado por None, o
un nodo que contiene una referencia a un objeto y
dos referen- cias a a´rboles.
20.1. Crear ´arboles
El proceso de montar un a´rbol es similar al proceso de montar una
lista enlazada. Cada invocacio´n del constructor crea un solo nodo.
class Arbol:
def init (self, carga, izquierda=None, derecha=None):
self.carga = carga
self.izquierda = izquierda
self.derecha = derecha
def str (self):
return str(self.carga)
La carga puede ser de cualquier tipo, pero los par´ametros
izquierda y derecha deben ser a´rboles. Tanto izquierda como
derecha son opcionales; el valor por omisi´on es None.
Para imprimir un nodo, simplemente imprimimos la carga.
Una forma de construir un a´rbol es del fondo hacia arriba. Asigne
primero los nodos hijos:
izquierda = Arbol(2)
derecha = Arbol(3)
20.2 Recorrer ´arboles 213
Luego cree el nodo padre y vincu´lelo a los hijos:
arbol = Arbol(1, izquierda, derecha);
Podemos escribir este c´odigo m´as concisamente anidando las
invocaciones al constructor:
>>> arbol = Arbol(1, Arbol(2), Arbol(3))
En cualquier caso, el resultado es el a´rbol del principio del cap´ıtulo.
20.2. Recorrer ´arboles
Siempre que usted vea una nueva estructura de datos, su primera
pregunta deber´ıa ser: “¿C´omo la recorro?” La forma mas natural
de recorrer un a´rbol es recursivamente. Por ejemplo, si el a´rbol
contiene enteros como carga, esta funci´on nos devuelve su suma:
def total(arbol):
if arbol == None: return 0
return total(arbol.izquierda) + total(arbol.derecha) +
arbol.carga
El caso base es el a´rbol vac´ıo, que no tiene carga, as´ı que la
suma es 0. El paso recursivo hace dos llamadas recursivas para
hallar la suma de los a´rboles hijos. Cuando terminan las llamadas
recursivas, sumamos la carga del padre y devolvemos el total.
20.3. A´rboles de expresio´n
Un a´rbol es un forma natural de representar la estructura de
una expresio´n. Al contrario que otras notaciones, puede
representar el c´alculo de forma no ambigua. Por ejemplo, la
expresio´n infija 1 + 2 * 3 es ambigua a no ser que sepamos que
la multiplicaci´on se realiza antes que la suma.
E´ste a´rbol de expresio´n representa el mismo c´alculo:
A
2
cargo+
leftright
cargo
left
right
*
cargo3
leftright
cargo2
leftright
cargo1
leftright
tree
Los nodos de un a´rbol de expresio´n pueden ser operandos como 1
y 2 u operado- res como + y *. Los operandos son nodos hojas; los
nodos operadores contienen referencias a sus operandos. (Todos
estos operadores son binarios, lo que sig- nifica que tienen
exactamente dos operandos.)
As´ı podemos construir este a´rbol:
>>> arbol = Arbol(’+’, Arbol(1), Arbol(’*’, Arbol(2), Arbol(3)))
Mirando la figura, no hay duda del orden de las operaciones; la
multiplicaci´on se realiza antes para calcular el segundo operando
de la suma.
Los a´rboles de expresio´n tienen muchos usos. El ejemplo de este
cap´ıtulo usa a´rboles para traducir expresiones a postfijo, prefijo e
infijo. A´rboles similares se usan dentro de los compiladores para
analizar, optimizar y traducir programas.
20.4. Recorrido de un ´arbol
Podemos recorrer un a´rbol de expresio´n e imprimir el contenido as´ı:
def imprimeArbol(arbol):
if arbol == None: return
print arbol.carga,
imprimeArbol(arbol.izquierda)
imprimeArbol(arbol.derecha)
En otras palabras, para imprimir un a´rbol imprima primero el
contenido de la ra´ız, luego el suba´rbol izquierdo entero y despu
´es el suba´rbol derecho entero. Esta forma de recorrer un a´rbol
se llama orden prefijo, porque el contenido de la ra´ız aparece antes
del contenido de los hijos. La salida del ejemplo anterior es:
20.4 Recorrido de un ´arbol 215
>>> arbol = Arbol(’+’, Arbol(1), Arbol(’*’, Arbol(2), Arbol(3)))
>>> imprimeArbol(arbol)
+ 1 * 2 3
Este formato es diferente tanto del postfijo como del infijo; es
otra notaci´on llamada prefija, en la que los operadores aparecen
delante de sus operandos. Puede sospechar que si recorre el a´rbol
en un orden diferente obtendr´a la expre- sio´n en una notaci´on
diferente. Por ejemplo, si imprime primero los suba´rboles y luego
la ra´ız, tendr´a:
def imprimeArbolPostfijo(arbol):
if arbol == None: return
imprimeArbolPostfijo(arbol.izquierda)
imprimeArbolPostfijo(arbol.derecha)
print arbol.carga,
¡El resultado, 1 2 3 * +, esta´ en notaci´on posfija! Este orden de
recorrido se llama orden postfijo.
Finalmente, para recorrer un a´rbol en orden infijo, usted
imprime el a´rbol izquierdo, luego la ra´ız y despu´es el a´rbol
derecho:
def imprimeArbolInfijo(arbol):
if arbol == None: return
imprimeArbolInfijo(arbol.izquierda)
print arbol.carga,
imprimeArbolInfijo(arbol.derecha)
El resultado es 1 + 2 * 3, que es la expresio´n en notaci´on infija.
Para ser justos debemos sen˜alar que hemos omitido una
importante compli- cacio´n. A veces, al escribir una expresio´n
infija, debemos usar par´entesis para preservar el orden de las
operacioens. De modo que una exploracio´n de orden infijo no es
suficiente para generar una expresio´n infija.
No obstante, con unas pequen˜as mejoras, el a´rbol de expresio´n y
los tres re- corridos nos dan una forma general de traducr
expresiones de un formato a otro.
Como ejercicio, modifique imprimeArbolInfijo de modo que pon-
ga entre par´entesis cada operador con sus operandos. ¿La salida
es correcta y sin ambigu¨edades? ¿Se necesitan siempre los par
´entesis?
Si hacemos un recorrido en orden infijo y tomamos nota de en qu
´e nivel del a´rbol estamos, podemos generar una representaci´on
gr´afica de un a´rbol:
A
2
*
+ 9
3 7
def imprimeArbolSangrado(arbol, nivel=0):
if arbol == None: return
imprimeArbolSangrado(arbol.derecha, nivel+1)
print ’ ’*nivel + str(arbol.carga)
imprimeArbolSangrado(arbol.izquierda, nivel+1)
El par´ametro nivel lleva la cuenta de d´onde estamos en el a´rbol.
Por omisi´on, incialmente es 0. Cada vez que hacemos una
llamada recursiva, pasamos nivel+1 porque el nivel del hijo es
siempre uno mayor que el del padre. Sangramos cada elemento
con dos espacios por nivel. El resultado del a´rbol de ejemplo es:
>>> imprimeArbolSangrado(arbol)
3
*
2
+
1
Si mira la salida de lado, ver´a una versio´n simplificada de la figura
original.
20.5. Construir un ´arbol de expresio´n
En esta seccio´n analizamos expresiones infijas y formamos los
correspondientes a´rboles de expresio´n. Por ejemplo, la expresio
´n (3+7)*9 nos da el siguiente a´rbol:
F´ıjese en que hemos simplificando el diagrama omitiendo los
nombres de los atributos.
El analizador que vamos a escribir maneja expresiones que incluyen
nu´meros, par´entesis y los operadores + y *. Suponemos que la
cadena de entrada ya ha sido tokenizada y almacenada en una lista
de Python. La lista de tokens de (3+7)*9 es:
20.5 Construir un ´arbol de 2
[’(’, 3, ’+’, 7, ’)’, ’*’, 9, ’fin’]
El token fin es u´til para evitar que el analizador lea
mas alla´ lista.
del final de la
A modo de ejercicio, escriba una funci´on que tome una cadena con-
teniendo una expresi´on y devuelva una lista de tokens.
La primera funci´on que vamos a escribir es tomaToken, que toma
como par´ame- tros una lista de tokens y el token que esperamos.
Compara el token esperado con el primer token de la lista: si
coinciden, elimina el token de la lista y devuelve verdadero, en
caso contrario, devuelve falso:
def tomaToken(listaToken, esperado):
if listaToken[0] == esperado:
del listaToken[0]
return 1
else:
return 0
Como listaToken apunta a un objeto mutable, los cambios hechos
aqu´ı son visibles para cualquier otra variable que apunte al mismo
objeto.
La siguiente funci´on, obtieneNumero, maneja operandos. Si el
siguiente token de listaToken es un nu´mero, obtieneNumero lo
elimina y devuelve un nodo hoja que contiene el nu´mero; en caso
contrario, devuelve None.
def obtieneNumero(listaToken):
x = listaToken[0]
if type(x) != type(0): return
None del listaToken[0]
return Arbol (x, None, None)
Antes de seguir, debemos probar obtieneNumero aisladamente.
Asignamos una lista de nu´meros a listaToken, extraemos el
primero, imprimimos el resultado e imprimimos lo que quede de la
lista de tokens:
>>> listaToken = [9, 11, ’fin’]
>>> x = obtieneNumero(listaToken)
>>> imprimeArbolPostfijo(x)
9
>>> print
listaToken [11,
’fin’]
El siguiente m´etodo que necesitamos es obtieneProducto, que
construye un a´rbol de expresio´n para productos. Un producto
simple tiene dos nu´meros como operandos, como 3 * 7.
3
*
*
5
13
A
2
Aqu´ı tenemos una versio´n de obtieneProducto que maneja productos
simples.
def obtieneProducto(listaToken):
a = obtieneNumero(listaToken)
if tomaToken(listaToken,
’*’):
b = obtieneNumero(listaToken)
return Arbol (’*’, a, b)
else:
return a
Suponiendo que obtieneNumero se ejecuta con ´exito y devuelve un a
´rbol simple, asignamos el primer operando a a. Si el siguiente
cara´cter es *, obtenemos el segundo nu´mero y construimos un a
´rbol de expresio´n con a, b, y el operador. Si el siguiente cara´cter
es cualquier otra cosa, simplemente devolvemos el nodo hoja con a.
Veamos dos ejemplos:
>>> listaToken = [9, ’*’, 11, ’fin’]
>>> arbol = obtieneProducto(listaToken)
>>> imprimeArbolPostfijo(arbol)
9 11 *
>>> listaToken = [9, ’+’, 11, ’fin’]
>>> arbol = obtieneProducto(listaToken)
>>> imprimeArbolPostfijo(arbol)
9
El segundo ejemplo implica que entendemos un operando suelto
como un tipo de producto. Esta definici´on de “producto” es
contraria a la intuici´on, pero resulta ser u´til.
Ahora tenemos que v´ernoslas con productos compuestos, como 3
* 5 * 13. Tratamos esta expresio´n como un producto de
productos, es decir, 3 * (5 * 13). El a´rbol resultante es:
Con un pequen˜o cambio en obtieneProducto podemos manejar productos ar-
bitrariamente largos:
20.5 Construir un ´arbol de 2
def obtieneProducto(listaToken):
a = obtieneNumero(listaToken)
if tomaToken(listaToken,
’*’):
b = obtieneProducto(listaToken) # cambiamos esta l
´ınea return Arbol (’*’, a, b)
else:
return a
En otras palabras, un producto puede ser bien un operando aislado
o un a´rbol con * en su ra´ız, un nu´mero en la derecha y un
producto en la izquierda. Este tipo de definici´on recursiva deber´ıa
empezar a resultar familiar.
Comprobemos la nueva versio´n con un producto compuesto:
>>> listaToken = [2, ’*’, 3, ’*’, 5 , ’*’, 7, ’fin’]
>>> arbol = obtieneProducto(listaToken)
>>> imprimeArbolPostfijo(arbol)
2 3 5 7 * * *
Ahora an˜adiremos la capacidad de analizar sumas. De nuevo,
usamos una defini- cio´n poco intuitiva de “suma”. Para nosotros,
una suma puede ser un a´rbol con
+ en la ra´ız, un producto en la izquierda y una suma en la derecha.
O tambi´en, una suma puede ser simplemente un producto.
Si quiere seguir adelante con esta definici´on, tiene una bonita
propiedad: pode- mos representar cualquier expresio´n (sin par
´entesis) como una suma de produc- tos. Esta propiedad es la base
de nuestro algoritmo de analisis.
obtieneSuma intenta construir un a´rbol con un producto en la
izquierda y una suma en la derecha. Pero si no encuentra un +,
simplemente construye un pro- ducto.
def obtieneSuma(listaToken):
a = obtieneProducto(listaToken)
if tomaToken(listaToken, ’+’):
b = obtieneSuma(listaToken)
return Arbol (’+’, a, b)
else:
return a
Vamos a probarla con 9 * 11 + 5 * 7:
>>> listaToken = [9, ’*’, 11, ’+’, 5, ’*’, 7, ’fin’]
>>> arbol = obtieneSuma(listaToken)
>>> imprimeArbolPostfijo(arbol)
9 11 * 5 7 * +
A
2
Ya casi hemos acabado, pero todav´ıa tenemos que manejar
los par´entesis. En culaquier lugar de una expresio´n donde
puede haber un nu´mero, puede haber tambi´en una suma
entera entre par´entesis. Solo necesitamos modificar obtieneNumero
para manejar subexpresiones:
def obtieneNumero(listaToken):
if tomaToken(listaToken,
’(’):
x = obtieneSuma(listaToken) # obtiene la subexpresi´on
tomaToken(listaToken, ’)’) # quita el cierre de par
´entesis return x
else:
x = listaToken[0]
if type(x) != type(0): return None
listaToken[0:1] = []
return Arbol (x, None, None)
Vamos a probar este c´odigo con 9 * (11 + 5) * 7:
>>> listaToken = [9, ’*’, ’(’, 11, ’+’, 5, ’)’, ’*’, 7, ’fin’]
>>> arbol = obtieneSuma(listaToken)
>>> imprimeArbolPostfijo(arbol)
9 11 5 + 7 * *
El analizador manej´o correctamente los par´entesis; la suma
ocurre antes de la multiplicaci´on.
En la versio´n final del programa, ser´ıa bueno dar a obtieneNumero
un nombre m´as descriptivo de su nueva funci´on.
20.6. Manejar errores
En todo momento hemos supuesto que las expresiones estaban
bien formadas. Por ejemplo, cuando alcanzamos el final de una
subexpresio´n, suponemos que el cara´cter siguiente es un par
´entesis cerrado. Si hay un error y el siguiente cara´cter es
cualquier otra cosa, deber´ıamos ocuparnos de ´el.
def obtieneNumero(listaToken):
if tomaToken(listaToken,
’(’):
x = obtieneSuma(listaToken)
if not tomaToken(listaToken, ’)’):
raise ’ExpresionErronea’, ’falta un par
´entesis’ return x
20.7 El ´arbol de 2
else:
# omitido el resto de la funci´on
La sentencia raise crea una excepcio´n; en este caso creamos un nuevo tipo
de ex-
cepcio´n, llamado ExpresionErronea. Si la funci´on que llamo´ a
obtieneNumero,
o alguna de las otras funciones de la pila de llamadas, maneja la
expresio´n, el programa podr´a continuar. En caso contrario,
Python imprimir´a un mensaje de error y saldra´.
Como ejercicio, encuentre otros lugares de estas funciones donde
puedan ocurrir errores y an˜ada las sentencias raise
adecuadas. Pruebe su codigo con expresiones formadas
incorrectamente.
20.7. El ´arbol de animales
En esta seccio´n desarrollaremos un pequen˜o programa que usa
un a´rbol para representar una base de conocimientos.
El programa interactu´a con el usuario para crear un a´rbol de
preguntas y nom- bres de animales. Aqu´ı tenemos un ejemplo de
ejecucio´n:
Est´as pensando en un animal?
s Es un p´ajaro? n
C´omo se llama el animal? perro
Qu´e pregunta distinguir´ıa a un perro de un p´ajaro? Puede
volar Si el animal fuera un perro, cu´al ser´ıa la respuesta? n
Est´as pensando en un animal? s
Puede volar? n
Es un perro? n
C´omo se llama el animal? gato
Qu´e pregunta distinguir´ıa a un gato de un perro?
Ladra Si el animal fuera un gato, cu´al ser´ıa la
respuesta? n
Est´as pensando en un animal? s
Puede volar? n
Ladra? s
Es un perro? s
Soy el m´as grande!
Can it fly?
ny
Does it bark?bird
n
cat
y
dog
A
2
Est´as pensando en un animal? n
Este es el a´rbol que construye este di´alogo:
Al principio de cada ronda, el programa empieza en lo alto del a
´rbol y hace la primera pregunta. Dependiendo de la respuesta, se
mueve al hijo de la izquierda o de la derecha y sigue hasta que
llega a un nodo hoja. En ese momento, intenta adivinar. Si falla,
pide al usuario el nombre del nuevo animal y una pregunta que
distinga al intento fallido del nuevo animal. Entonces an˜ade un
nodo al a´rbol con la nueva pregunta y el nuevo animal.
E´ste es el c´odigo:
def animal():
# empezar con un nodo suelto
raiz = Arbol("p´ajaro")
# bucle hasta que el usuario salga
while 1:
print
if not si("Est´as pensando en un animal? "): break
# recorrer el
arbol = raiz
a´rbol
while arbol.tomaIzquierda() != None:
indicador = arbol.tomaCarga() + "? "
if si(indicador):
arbol = arbol.tomaDerecha()
else:
arbol = arbol.tomaIzquierda()
# intentar adivinar
20.7 El ´arbol de 2
adivina = arbol.tomaCarga()
indicador = "Es un " + adivina + "? "
if si(indicador):
print "Soy el m´as grande!"
continue
# obtener informaci´on nueva
indicador = "C´omo se llama el animal?
" animal= raw_input(indicador)
indicador = "Qu´e pregunta distinguir´ıa a un s de un
s? " pregunta = raw_input(indicador (animal,adivina))
# a~nadir informaci´on nueva
al arbol.ponCarga(pregunta)
a´rbol
indicador = "Si el animal fuera un s, cu´al ser´ıa la
respuesta? "
if si(indicador animal):
arbol.ponIzquierda(Arbol(adivina))
arbol.ponDerecha(Arbol(animal))
else:
arbol.ponIzquierda(Arbol(animal))
arbol.ponDerecha(Arbol(adivina))
La funci´on si es un auxiliar; imprime un indicador y acepta una
entrada del usuario. Si la respuesta comienza con s o S, la funci´on
devuelve verdadero:
def si(preg):
from string import lower
resp = lower(raw_input(preg))
return (resp[0] == ’s’)
La condicio´n del bucle externo es 1, lo que significa que seguira´
hasta que se ejecute la sentencia break cuando el usuario ya no
piense en ningu´n animal. El bucle while interno recorre el a´rbol
de arriba a abajo, guiado por las res- puestas del usuario.
Cuando se an˜ade un nuevo nodo al a´rbol, la pregunta sustituye a
la carga y los dos hijos son el animal nuevo y la carga original.
Una carencia del programa es que, al salir, ¡olvida todo lo que
usted le hab´ıa ensen˜ado con tanto cuidado!
Como ejercicio, piense en varias formas en las que podr´ıa guardar
el a´rbol de conocimiento en un archivo. Implemente la que piense
que es m´as f´acil.
A
2
20.8. Glosario
´arbol binario: Un a´rbol en el que cada nodo apunta a cero, uno, o dos
nodos dependientes.
ra´ız: El nodo superior de un a´rbol, sin padre.
hoja: Un nodo del extremo inferior de un a´rbol, sin hijos.
padre: El nodo que apunta a un nodo dado.
hijo: Uno de los nodos a los que apunta un
nodo. hermanos: Nodos que tienen un
padre comu´n.
nivel: El conjunto de nodos equidistante de la ra´ız.
operador binario: Un operador que toma dos operandos.
subexpresi´on: Una expresio´n entre par´entesis que actu´a como
un operando simple dentro de otra expresio´n mayor.
orden prefijo: Una forma de recorrer un a´rbol, visitando cada nodo
antes que a sus hijos.
notaci´on prefija: Una forma de escribir una expresio´n matem
´atica en la que los operadores aparecen antes que sus
operandos.
orden postfijo: Una forma de recorrer un a´rbol, visitando los hijos
de cada nodo antes del propio nodo.
orden infijo: Una forma de recorrer un a´rbol, visitando el suba
´rbol izquierdo, luego la ra´ız, y luego el suba´rbol derecho.
Ap´endice A
Depuracio´n
En un programa pueden suceder varios tipos de error, y resulta u
´til distinguirlos para localizarlos r´apidamente:
Python presenta errores de sintaxis mientras traduce el c
´odigo fuente en c´odigo binario. Normalmente indican que
hay algo err´oneo en la sintaxis del programa. Ejemplo:
omitir los dos puntos al final de una sentencia def nos da el
mensaje SyntaxError: invalid syntax, algo redundante.
El sistema de tiempo de ejecucio´n presenta los errores en
tiempo de eje- cucio´n si algo va mal mientras se ejecuta el
programa. La mayor´ıa de los mensajes de error en tiempo de
ejecucio´n incluyen informaci´on acerca de d´onde sucedio´ el
error y qu´e funciones se estaban ejecutando. Ejemplo: una
recursio´n infinita termina por provocar un error en tiempo de
ejecu- cio´n del “maximum recursion depth exceeded”
(superada la profundidad m´axima de recursio´n).
Los errores sema´nticos son problemas con un programa que
compila y se ejecuta pero no hace lo que se espera de ´el.
Ejemplo: una expresio´n puede no evaluarse en el orden
esperado, dando un resultado inesperado.
El primer paso de la depuraci´on es averiguar con qu´e tipo de
error se enfrenta. Aunque las secciones que siguen esta´n
organizadas por tipos de error, algunas t´ecnicas son aplicables en
m´as de una situacio´n.
A.1. Errores de sintaxis
Los errores de sintaxis suelen ser f´aciles de arreglar una vez
que averigua lo que son. Desgraciadamente, muchas veces los
mensajes de error no son
2 Depuraci
muy u´tiles. Los mensajes m´as comunes son SyntaxError: invalid syntax y
SyntaxError: invalid token, ninguno de los cuales es muy informativo.
Por otra parte, el mensaje le dice en qu´e lugar del programa
sucedio´ el error. En realidad, le dice d´onde not´o el problema
Python, que no es necesariamente donde esta´ el error. A veces el
error esta´ antes de la localizacio´n del mensaje de error, muchas
veces en la l´ınea anterior.
Si esta´ haciendo el programa incrementalmente, deber´ıa tener
casi localizado el error. Estar´a en la u´ltima l´ınea que an˜adio.
Si esta´ usted copiando c´odigo de un libro, comience comparando
con atenci´on su c´odigo con el del libro. Compruebe cada cara
´cter. Al mismo tiempo, recuerde que el libro podr´ıa estar
equivocado, as´ı que si ve algo que parezca un error de sintaxis,
podr´ıa serlo.
He aqu´ı algunas formas de evitar los errores de sintaxis mas habituales:
1. Asegu´rese de que no utiliza una palabra clave de Python
como nombre de variable.
2. Compruebe que tiene los dos puntos al final de la cabecera
de todas las sentencias compuestas, las for, while, if, y def.
3. Compruebe que el sangrado es consistente. Puede usted
sangrar tanto con espacios como con tabuladores, pero es
mejor no mezclarlos. Todos los niveles deber´ıan estar
anidados por la misma cantidad.
4. Asegu´rese de que todas las cadenas del c´odigo tienen su
par de comillas de apertura y cierre.
5. Si tiene cadenas que ocupan varias l´ıneas con triples
comillas (o triples ap´ostrofos), asegu´rese de que ha
terminado la cadena correctamente. Una cadena sin
terminar puede provocar un error invalid token al final de
su programa, o puede tratar la siguiente parte del
programa como una cadena hasta que llegue a la siguiente
cadena. ¡En el segundo caso, podr´ıa no presentar ningu´n
mensaje de error!
6. Un par´entesis sin cerrar—(, { o [—hace que Python continue
con la l´ınea siguiente como parte de la sentencia actual.
Generalmente aparecer´a un error casi inmediatamente en la
l´ınea siguiente.
7. Compruebe el cla´sico = donde deber´ıa haber un == en los
condicionales. Si nada funciona, siga con la seccio´n que sigue...
A.2 Errores en tiempo de ejecuci 2
A.1.1. No consigo ejecutar mi programa, no importa lo
que haga.
Si el compilador dice que hay un error pero usted no lo ve, podr´ıa
ser porque usted y el compilador no miran el mismo c´odigo.
Compruebe su entorno de programaci´on para asegurarse de que
el programa que esta´ editando es el que esta´ intentando ejecutar
Python. Si no esta´ seguro, pruebe a poner un error de sintaxis
obvio y deliberado al principio del programa. Ahora ejecute (o
importe) de nuevo. Si el compilador no encuentra el nuevo error
probalemente hay algo equivocado en el modo en que esta´
configurado su entorno.
Si esto ocurre, puede enfrentarse a ello empezando de nuevo con
un programa nuevo como “Hola, mundo”, y asegurarse de que
puede hacer que funcione un programa conocido. Luego an˜ada
gradualmente los trozos del programa nuevo al que funciona.
A.2. Errores en tiempo de ejecucio´n
Una vez que su programa es sint´acticamente correcto, Python
pude importarlo y al menos comenzar a ejecutarlo. ¿Qu´e podr´ıa ir
mal?
A.2.1. Mi programa no hace nada de nada.
Este problema es muy comu´n cuando su archivo consta de
funciones y clases pero en realidad no invoca nada para que
empiece la ejecucio´n. Esto puede ser intencionado cuando s´olo
planea importar el modulo para suministrar clases y funciones.
Sin no es intencionado, asegu´rese de que esta´ llamando a una
funci´on que inicie la ejecucio´n, o ejecute una desde el indicador
interactivo. Vea tambi´en la seccio´n “Flujo de Ejecuci´on” m´as
adelante.
A.2.2. Mi programa se cuelga.
Si un programa se para y parece no hacer nada, decimos que “se
ha colgado”. A menudo significa que se ha quedado atrapado en un
bucle infinito o en una recursio´n infinita.
Si hay un bucle en particular que le resulta sospechoso de
provocar el problema, an˜ada una sentencia print justo antes
del bucle que diga “en- trando al bucle” y otra
inmediatamente despu´es que diga “saliendo del bucle”.
2 Depuraci
Ejecute el programa. Si obtiene el primer mensaje pero el
segundo no, tiene usted un bucle infinito. Vaya a la seccio´n
“Bucle Infinito” mas adelante. Una recursio´n infinita casi
siempre har´a que el programa corra un rato y luego
presente un error de “RuntimeError: Maximum recursion
depth exceeded”. Si ocurre eso, vaya a la seccio´n “Recursi´on
Infinita” mas ade- lante.
Si no ve este error pero sospecha que hay un problema con un
m´etodo o funci´on recursivos tambi´en puede utilizar las t
´ecnicas de la seccio´n “Re- cursio´n Infinita”.
Si no funciona ninguno de estos pasos, comience a probar
otros bucles y otros m´etodos y funciones recursivos.
Si eso no funciona, es posible que no comprenda el flujo de
ejecucio´n de su programa. Vaya a la seccio´n “Flujo de
Ejecuci´on” mas adelante.
Bucle Infinito
Si cree que tiene un bucle infinito y piensa que sabe qu´e bucle
provoca el pro- blema, an˜ada una sentencia print que imprima los
valores de las variables de la condicio´n al final del bucle junto con
el valor de la condicio´n.
Por ejamplo:
while x > 0 and y < 0 :
# hacer algo con x
# hacer algo con y
print "x: ", x
print "y: ", y
print "condici´on: ", (x > 0 and y < 0)
Ahora, cuando ejecute el programa, ver´a tres l´ıneas de salida en
cada vuelta del bucle. En la u´ltima vuelta el valor de la condicio´n
deber´ıa ser false. Si el bucle sigue ejecuta´ndose, podr´a ver los
valores de x e y, y podr´a averiguar por qu´e no se actualizan
correctamente.
Recursi´on Infinita
Una recursio´n infinita casi siempre har´a que el programa se
ejecute un rato y luego provoque un error de Maximum recursion
depth exceeded.
Si sospecha que una funci´on o un m´etodo esta´ causando una
recursio´n infinita, comience por asegurarse de que hay un caso
basico. En otras palabras, deber´ıa
A.2 Errores en tiempo de ejecuci 2
haber una condicio´n que haga que la funci´on devuelva un valor
sin hacer otra llamada recursiva. Si no, necesita revisar el
algoritmo y encontrar ese caso basico. Si hay un caso b´asico pero
el programa no parece llegar hasta ´el, an˜ada una sentencia print
que imprima los par´ametros al principio de la funci´on o m´etodo.
Cuando ahora ejecute el programa, ver´a unas pocas l´ıneas
cada vez que se
invoque la funci´on o m´etodo y all´ı ver´a los par´ametros. Si los par
´ametros no se
acercan al caso b´asico, eso le dar´a alguna idea de por qu´e no lo hace.
Flujo de Ejecuci´on
Si no esta´ seguro de qu´e curso sigue el flujo de ejecucio´n en su
programa, an˜ada sentencias print al principio de cada funci´on
con un mensaje como “entrando en la funci´on turur´u”, donde
turur´u es el nombre de la funci´on.
Cuando ahora ejecute el programa, imprimir´a una traza de cada
funci´on a me- dida que las vaya invocando.
A.2.3. Cuando ejecuto el programa recibo una excepcion.
Si algo va mal durante la ejecucio´n, Python imprime un mensaje
que incluye el nombre de la excepcio´n, la l´ınea del programa
donde sucedio´ el problema y una traza inversa.
La traza inversa identifica la funci´on que se esta´ ejecutando ahora
y la funci´on que invoc´o a ´esta, y luego la funci´on que invoc´o a
´esa, y as´ı sucesivamente. En otras palabras, traza la ruta de las
llamadas a las funciones que le llevaron a donde se encuentra.
Tambi´en incluye los nu´meros de las l´ıneas de sus archivos donde
suceden todas esas llamadas.
El primer paso es examinar el lugar del programa donde sucede el
error y ver si puede adivinar lo que sucedio´. Estos son algunos de
los errores en tiempo de ejecucio´n mas comunes:
NameError: Est´a intentando usar una variable que no existe en el
entorno actual. Recuerde que las variables locales son locales.
No puede hacer referencia a ellas desde fuera de la funci´on
en la que se definen.
TypeError: Hay varias causas posibles:
Est´a intentando usar un varlor de forma inadecuada.
Ejemplo: usar como ´ındice para una cadena, lista o tupla
algo que no es un entero.
2 Depuraci
Hay una discrepancia entre los elementos de una cadena
de formato y los elementos pasados para la conversio´n.
Esto puede ocurrir tanto si el nu´mero de elementos no
coincide como si se solicita una conversio´n no v´alida.
Est´a pasando un nu´mero err´oneo de argumentos a
una funci´on o m´etodo. Con los m´etodos, f´ıjese en la
definici´on de los m´etodos y compruebe que el primer
par´ametro es self. Luego f´ıjese en la invo- cacio´n del m
´etodo; asegu´rese de que esta´ invocando el m´etodo
sobre un objeto del tipo adecuado y dandole
correctamente el resto de ar- gumentos.
KeyError: Est´a tratando de acceder a un elemento de un
diccionario con una clave que no esta´ en el diccionario.
AttributeError: Est´a intentando acceder a un atributo o m´etodo
que no exis- te.
IndexError: El ´ındice que esta´ usando para acceder a una lista,
cadena o tupla es mayor que su longitud menos uno. Justo
antes de donde aparece el error, an˜ada una sentencia print
que muestre el valor del ´ındice y la longitud del vector. ¿Es
correcto el taman˜o del vector? ¿Tiene el ´ındice un valor
correcto?
A.2.4. Puse tantas sentencias print que me ahoga la sali-
da.
Uno de los problemas de usar sentencias print para la depuraci´on
es que pue- de terminar enterrado en informaci´on. Hay dos formas
de atajar el problema: simplificar la salida o simplificar el
programa.
Para simplificar la salida, puede elimiar o comentar (convertir
en comentarios) las sentencias print que no sean de ayuda, o
combinarlas, o dar a la salida un formato que la haga m´as
comprensible.
Para simplificar el programa puede hacer varias cosas. Primero,
reducir la escala del problema en el que esta´ trabajando el
programa. Por ejemplo, si esta´ orde- nando un vector, ordene un
vector pequen˜o. Si el programa acepta entradas del usuario, dele la
entrada m´as simple que provoque el problema.
Segundo, limpie el programa. Elimine el c´odigo muerto y
reorganice el programa para hacerlo tan legible como sea posible.
Por ejemplo, si sospecha que el proble- ma esta´ en una parte del
programa con un anidamiento muy profundo, pruebe a reescribir
esa parte con una estructura mas simple. Si sospecha de una funci
´on grande, trate de trocearla en funciones menores y pru´ebelas
separadamente.
A.3 Errores sem 2
El proceso de encontrar el caso m´ınimo de prueba le llevar´a a
menudo al error. Si se encuentra con que un programa funciona en
una situacio´n pero no en otra, eso le dar´a una pista sobre lo que
ocurre.
De forma parecida, la reescritura de una porci´on de c´odigo
puede ayudarle a encontrar errores sutiles. Si hace un cambio que
a usted le parece que no afecta al programa, pero s´ı lo hace, le dar
´a una pista.
A.3. Errores sem´anticos
En cierto modo, los errores sema´nticos son los mas dif´ıciles de
corregir, porque el compilador y el sistema de ejecucio´n no
proporcionan informaci´on sobre lo que va mal. S´olo usted sabe lo
que se supone que debe hacer el programa, y s´olo usted sabe que
no lo esta´ haciendo.
El primer paso es hacer una concexio´n entre el texto del programa
y el compor- tamiento que esta´ usted viendo. Necesita una
hipotesis sobre lo que realmente esta´ haciendo el programa. Una
de las dificultades que nos encontramos para ello es la alta
velocidad de los computadores.
A menudo desear´ıa ralentizar el progrma a una velocidad
humana, y con al- gunos programas depuradores podr´a hacerlo.
Pero el tiempo que lleva colocar unas sentencias print en los
lugares adecuadoes suele ser menor que el que lleva
configurar el depurador, poner y quitar puntos de interrupcio´n y
“hacer caminar” el programa hasta donde se produce el
error.
A.3.1. Mi programa no funciona.
Deber´ıa hacerse estas preguntas:
¿Hay algo que se supone que deber´ıa hacer el programa pero
que no parece suceder? Busque la seccio´n del c´odigo que
realiza esa funci´on y asegu´rese de que se ejecuta cuando
deber´ıa.
¿Ocurre algo que no deber´ıa? Busque el programa que realiza
esa funci´on y vea si se ejecuta cuando no debe.
¿Hay una seccio´n de c´odigo que causa un efecto que no
esperaba? asegu´rese de que entiende el c´odigo en cuestio´n,
especialmente si incluye invocaciones de funciones o m´etodos
de otros m´odulos de Python. Lea la documentaci´on de las
funciones que invoca. Pru´ebelas escribiendo casos de prueba
simples y comprobando el resultado.
2
2 Depuraci
Para programar necesitar´a tener un modelo mental de c´omo
funcionan los pro- gramas. Si escribe un programa que no hace lo
que espera de ´el, muchas veces el problema no estara´ en el
programa, sino en su modelo mental.
La mejor manera de corregir su modelo mental es dividiendo el
programa en sus componentes (normalmente las funciones y m
´etodos) y probando cada compo- nente de forma independiente.
Una vez que encuentre la discrepancia entre su modelo y la
realidad, podr´a solucionar el problema.
Por supuesto, deber´ıa ir haciendo y probando componentes tal
como desarrolla el programa. Si encuentra un problema, s´olo
habr´a una pequen˜a cantidad de c´odigo nuevo del que no sabe si
esta´ correcto.
A.3.2. Tengo una expresio´n grande y peliaguda y no
hace lo que espero.
Est´a bien escribir expresio´n complejas mientras sean legibles,
pero pueden ser dif´ıciles de depurar. Suele ser una buena idea
dividir una expesio´n compleja en una serie de asignaciones de
variables temporales.
Por ejamplo:
self.manos[i].agregaCarta (self.manos[
self.encuentraVecino(i)].darCarta())
Puede reescribirse como:
vecino = self.encuentraVecino (i)
cartaElegida = self.manos[vecino].darCarta()
self.manos[i].agregaCarta (cartaElegida)
La versio´n expl´ıcita es m´as f´acil de leer porque los nombres de
variable nos facili- tan documentaci´on adicional, y es m´as f´acil de
depurar porque puede comprobar los tipos de las variables
intermedias y mostrar sus valores.
Otro problema que puede suceder con las expresiones grandes es que el
orden de
evaluaci´on puede no ser el que usted esperaba. Por ejemplo, si esta´
traduciendo
la expresio´n x
a Python, podr´ıa escribir:
y = x / 2 * math.pi;
Eso no es correcto, porque la multiplicaci´on y la divisi´on tienen la
misma prece- dencia y se evalu´an de izquierd a derecha. As´ı que
esa expresio´n calcula xπ/2. Una buena forma de depurar
expresiones es an˜adir par´entesis para hacer expl´ıci- to el orden
de evaluaci´on:
A.3 Errores sem 2
y = x / (2 * math.pi);
Siempre que no est´e seguro del orden de evaluaci´on, utilice par
´entesis. El pro- grama no s´olo ser´a correcto (en el sentido de
hacer lo que usted prentend´ıa), sino que adem´as ser´a m´as
legible para otras personas que no hayan memorizado las reglas de
precedencia.
A.3.3. Tengo una funcio´n o m´etodo que no devuelve
lo que esperaba.
Si tiene una sentencia return con una expresio´n compleja no tendr
´a la opor- tunidad de imprimir el valor de retorno antes de volver.
De nuevo, puede usar una variable temporal. Por ejemplo, en lugar
de:
return self.manos[i].eliminaCoincidencias()
podr´ıa excribir:
cant = self.manos[i].eliminaCoincidencias()
return cant
Ahora ya tiene la oportunidad de mostrar el valor de cant antes de regresar.
A.3.4. Estoy atascado de verdad y necesito ayuda.
Primero, intente alejarse del computador durante unos minutos.
Los computa- dores emiten unas ondas que afectan al cerebro
provocando estos efectos:
Frustracio´n y/o furia.
Creencias supersticiosas (“el computador me odia”) y
pensamiento magico (“el programa s´olo funciona cuando me
pongo la gorra hacia atr´as”).
Programar dando palos de ciego (el empen˜o de programar
escribiendo todos los programas posibles y eligiendo el que
hace lo correcto).
Si se encuentra afectado por alguno de estos s´ıntomas, lev´antese
y d´e un paseo. Cuando est´e calmado, piense en el programa.
¿Qu´e es lo que hace? ¿Cua´les pueden ser las causas de tal
comportamiento? ¿Cua´ndo fue la u´ltima vez que ten´ıa un
programa que funcinaba y qu´e fue lo siguiente que hizo?
A veces lleva tiempo encontrar un error. Muchas veces
encontramos errores cuando estamos lejos del computador y
divagamos. Algunos de los mejores lu- gares para encontrar
errores son los trenes, las duchas y la cma, justo antes de quedarse
dormido.
2 Depuraci
A.3.5. No, de verdad necesito ayuda.
Sucede. Incluso los mejores programadores se atascan de vez en
cuando. A veces trabaja durante tanto tiempo en un programa que
no puede ver el error. Lo que necesita es un par de ojos nuevos.
Antes de llamar a andie, asegu´rese de que ha agotado las t
´ecnicas explicadas aqu´ı. Su programa deber´ıa ser tan simple
como sea posible, y usted deber´ıa estar trabajando con la entrada
m´ınima que provoca el error. Deber´ıa tener sentencias print en
los lugares adecuados (y lo que dicen deber´ıa ser comprensible).
De- ber´ıa entender el problema lo bastante bien como para
describirlo sucintamente. Cuando llame a alguien para que le
ayude, asegu´rese de darles la informaci´on que necesitan:
Si hay un mensaje de error, ¿cua´l es y qu´e parte del programa
sen˜ala?
¿Qu´e fue lo u´ltimo que hizo antes de que apareciera el error?
¿Cua´les son las u´ltimas l´ıneas de c´odigo que escribio´, o
cua´l es el nuevo caso de prueba que no cumple?
¿Qu´e ha intentado hasta ahora y qu´e ha averiguado?
Cuando encuentre el error, t´omese un momento para pensar
acerca de lo que podr´ıa haber hecho para encontrarlo m´as r
´apido. La siguiente vez que vea algo parecido, ser´a capaz de
encontrar el error antes.
Recuerde, el objetivo no es s´olo hacer que el programa funciones.
El objetivo es aprender c´omo hacer funcionar al programa.
Ap´endice B
Crear un nuevo tipo de
datos
Los lenguajes de programaci´on orientados a objetos permiten a los
programado- res crear nuevos tipos de datos que se comporten de
manera muy parecida a los tipos de datos nativos. Exploraremos
esta posibilidad construyendo una clase Fraccion que funcione de
manera muy similar a los tipos num´ericos nativos, enteros,
enteros largos y flotantes.
Las fracciones, tambi´en conocidas como nu´meros racionales, son
valores que pueden expresrse como la proporci´on entre dos nu
´meros enteros, tal como 5/6. Al nu´mero superior se se le llama
numerador y al inferior se se le llama deno- minador.
Comenzamos definiendo la clase Fraccion con un m´etodo de
inicializaci´on que nos surta de un numerador y un demonimador
enteros:
class Fraccion:
def init (self, numerador, denominador=1):
self.numerador = numerador
self.denominador = denominador
El denominador es opcional. Una Fraccion con un s´olo par´ametro
representa un nu´mero entero. Si el numerador es n, construimos la
fracci´on n/1.
El siguente paso es escribir un m´etodo str para que
imprima las frac- ciones de forma que tenga sentido. La forma
natural de hacerlo es “numera- dor/denominador”:
2 Crear un nuevo tipo de datos
class Fraccion:
...
def str (self):
return " d/ d" (self.numerador, self.denominador)
Para probar lo que tenemos hasta ahora, lo ponemos en un fichero
llamado Fraccion.py y lo importamos desde el int´erprete de Python.
Entonces creamos un objeto fracci´on y lo imprimimos.
>>> from Fraccion import fraccion
>>> mortadela = Fraccion(5,6)
>>> print "La fracci´on es",
mortadela La fracci´on es 5/6
Como siempre, la funci´on print invoca impl´ıcitamente al m´etodo str .
B.1. Multiplicacio´n de fracciones
Nos gustar´ıa poder aplicar las operaciones normales de suma,
resta, multiplica- cio´n y divisi´on a las fracciones. Para ello,
podemos sobrecargar los operadores matem´aticos para los objetos
de clase Fraccion.
Comenzaremos con la multiplicaci´on porque es la mas facil de
implementar. Para multiplicar dos fraciones, creamos una nueva
fracci´on cuyo numerador es el producto de los numeradores de
los operandos y cuyo denominador es el producto de los
denominadores de los operandos. mul es el nombre que Python
utiliza para el m´etodo que sobrecarga al operador *:
class Fraccion:
...
def mul (self, otro):
return Fraccion(self.numerador*otro.numerador,
self.denominador*otro.denominador)
Podemos probar este m´etodo calculando el producto de dos fracciones:
>>> print Fraccion(5,6) * Fraccion(3,4)
15/24
Funciona, pero ¡podemos hacerlo mejor! Podemos ampliar el m
´etodo para ma- nejar la multiplicaci´on por un entero. Usamos la
funci´on type para ver si otro es un entero y convertirlo en una
fracci´on en tal caso.
class Fraccion:
...
B.2 Suma de fracciones 237
def mul (self, otro):
if type(otro) == type(5):
otro = Fraccion(otro)
return Fraccion(self.numerador * otro.numerador,
self.denominador * otro.denominador)
Ahora funciona la multiplicaci´on para fracciones y enteros, pero s
´olo si la frac- cio´n es el operando de la izquierda.
>>> print Fraccion(5,6) * 4
20/6
>>> print 4 * Fraccion(5,6)
TypeError: mul nor rmul defined for these operands
Para evaluar un operador binario como la multiplicaci´on, Python
comprueba primero el operando de la izquierda para ver si
proporciona un m´etodo mul que soporte el tipo del segundo
operando. En este caso, el operador nativo de multiplicaci´on del
entero no soporta fracciones.
Despu´es, Python comprueba el segundo operando para ver si
provee un m´etodo rmul que soporte el tipo del primer operando.
En este caso, no hemos provisto el m´etodo rmul , por lo que
falla.
Por otra parte, hay una forma sencilla de obtener rmul :
class Fraccion:
...
rmul = mul
Esta asignaci´on hace que el m´etodo rmul sea el mismo que
mul . Si ahora evaluamos 4 * Fraccion(5,6), Python llamar´a al m
´etodo rmul del objeto Fraccion y le pasar´a 4 como par´ametro:
>>> print 4 * Fraccion(5,6)
20/6
Dado que rmul es lo mismo que mul , y mul puede manejar un
par´ame- tro entero, ya esta´ hecho.
B.2. Suma de fracciones
La suma es m´as complicada que la multiplicaci´on, pero au´n es
llevadera. La suma de a/b y c/d es la fracci´on (a*d+c*b)/b*d.
Usando como modelo el c´odigo de la multiplicaci´on, podemos escribir add
y
radd :
2 Crear un nuevo tipo de datos
class Fraccion:
...
def add (self, otro):
if type(otro) == type(5):
otro = Fraccion(otro)
return Fraccion(self.numerador * otro.denominador +
self.denominador * otro.numerador,
self.denominador * otro.denominador)
radd = add
Podemos probar estos m´etodos con Fracciones y enteros.
>>> print Fraccion(5,6) + Fraccion(5,6)
60/36
>>> print Fraccion(5,6) + 3
23/6
>>> print 2 + Fraccion(5,6)
17/6
Los dos primeros ejemplos llaman a add ; el u´ltimo
llama a radd .
B.3. Algoritmo de Euclides
En el ejemplo anterior, computamos la suma de 5/6 + 5/6 y
obtuvimos 60/36. Es correcto, pero no es la mejor forma de
representar la respuesta. Para redu- cir la fracci´on a su expresio
´n m´as simple, hemos de dividir el numerador y el denominador
por el m´aximo comu´n divisor (MCD) de ambos, que es 12. El
resultado ser´ıa 5/3.
En general, siempre que creamos un nuevo objeto Fraccion, deber
´ıamos redu- cirlo dividiendo el numerador y el denominador por el
MCD de ambos. Si la fracci´on ya esta´ reducida, el MCD es 1.
Euclides de Alejandr´ıa (aprox. 325–265 a. C.)
prensento´ contrar el MCD de dos nu´meros
entermos m y n:
un algoritmo para en-
Si n divide a m sin resto, entonces n es el MCD. De lo
contrario, el MCD es el MCD de n y el resto de dividir m
entre n.
Esta definici´on recursiva puede expresarse concisamente como una funci
´on:
def mcd (m, n):
if m n == 0:
B.4 Comparar fracciones 239
return n
else:
return mcd(n, m n)
En la primera l´ınea del cuerpo, usamos el operador de modulo para
comprobar la divisibilidad. En la u´ltima l´ınea, lo usamos para
calcular el resto de la divisi´on. Dado que todas las operaciones
que hemos escrito creaban un nuevo objeto Fraccion para devolver
el resultado, podemos reducir todos los resultados mo- dificando el
m´etodo de inicializaci´on.
class Fraccion:
def init (self, numerador, denominador=1):
m = mcd (numerador, denominador)
self.numerador = numerador / m
self.denominador = denominador / m
Ahora siempre que creemos una Fraccion quedar´a reducida a su
forma cano´ni- ca:
>>> Fraccion(100,-36)
-25/9
Una caracter´ıstica estupenda de mcd es que si la fracci´on es
negativa, el signo menos siempre se trasladar´a al numerador.
B.4. Comparar fracciones
Supongamos que tenemos dos objetos Fraccion, a y b, y evaluamos a
== b. La implemetaci´on por defecto de == comprueba la igualdad
superficial, por lo que s´olo devuelve true si a y b son el mismo
objeto.
Queremos mas bien devolver verdadero si a y b tienen el mismo
valor —eso es, igualdad en profundidad.
Hemos de ensen˜ar a las fracciones c´omo compararse entre s´ı.
Como vimos en la Secci´on 15.4, podemos sobrecargar todos los
operadores de comparacio´n de una vez proporcionando un m
´etodo cmp .
Por convenio, el m´etodo cmp devuelve un nu´mero negativo si
self es menor que otro, zero si son lo mismo, y un nu´mero positivo
si self es mayor que otro. La forma m´as simple de comparar dos
fracciones es la multipicaci´on cruzada. Si a/b > c/d, entonces ad
> bc. Con esto en mente, aqu´ı esta´ el c´odigo para cmp :
2 Crear un nuevo tipo de datos
class Fraccion:
...
def cmp (self, otro):
dif = (self.numerador * otro.denominador
- otro.numerador *
self.denominador)
return dif
Si self es mayor que otro, entonces dif ser´a positivo. Si otro is mayor,
entonces
dif ser´a ngativo. Si son iguales, dif es cero.
B.5. Forzando la m´aquina
Por supuesto, au´n no hemos terminado. Todav´ıa hemos de
implementar la resta sobrecargando sub y la divisi´on
sobrecargando div .
Una manera de manejar estas operaciones es implementar la
negaci´on sobre- cargando neg y la inversio´n sobrecargando
invert . Entonces podemos restar negando el segundo operando
y sumando, y podemos dividir invirtiendo el segundo operando
y multiplicando.
Luego, hemos de suministrar los m´etodos rsub y rdiv .
Desgraciadamen- te, no podemos usar el mismo truco que usamos
para la suma y la multiplicaci´on, porque la resta y la divisi´on no
son conmutativas. No podemos igualar rsub y rdiv a sub y div .
En estas operaciones, el orden de los operandos tiene
importancia.
Para manejar la negaci´on unitaria, que es el uso del signo menos
con un u´nico operando, sobrecargamos el m´etodo neg .
Podemos computar potencias sobrecargando pow , pero la
implementaci´on tiene truco. Si el exponente no es un nu
´mero entero podr´ıa no ser posible representar el resultado
como una Fraccion. Por ejemplo, Fraccion(2) ** Fraccion(1,2) es la
raiz cuadrada de 2, que es un nu´mero irracional (no se puede
representar como una fracci´on). Por lo tanto, no es facil escribir
la ver- sio´n m´as general de pow .
Existe otra extensio´n a la clase Fraccion que cabr´ıa considerar.
Hasta ahora, hemos asumido que el numerador y el denominador
son enteros. Podr´ıamos considerar la posibilidad de pertimirles
que sean enteros largos.
Como ejercicio, termine la implementaci´on de la clase Fraccion
de forma que pueda manejar resta, divisio´n, exponenciacio´n y
enteros largos como numerador y denominador.
B.6 Glosario 241
B.6. Glosario
m´aximo comu´n divisor (MCD): El mayor entero positivo que
divide al nu- merador y al denominador de una fracci´on sin
que quede un resto.
reducir: Cambiar la fracci´on a su forma equivalente con un MCD
igual a 1. negaci´on unitaria: Operacio´n que computa el elemento
sim´etrico aditivo, nor- malmente denotada con un signo menos
delante. Se denomina “unitaria”
en contraste con la operacio´n binaria menos, que es la resta.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Ap´endice C
Listados Completos
de Python
C.1. Clase Punto
class Punto:
def init (self, x=0, y=0):
self.x = x
self.y = y
def str (self):
return ’(’ + str(self.x) + ’, ’ + str(self.y) + ’)’
def add (self, otro):
return Punto(self.x + otro.x, self.y + otro.y)
def sub (self, otro):
return Punto(self.x - otro.x, self.y - otro.y)
def mul (self, otro):
return self.x * otro.x + self.y * otro.y
def rmul (self, otro):
return Punto(otro * self.x, otro * self.y)
def reverse(self):
2 Listados Completos de
self.x, self.y = self.y, self.x
def delDerechoYDelReves(derecho):
from copy import copy
reves = copy(derecho)
reves.reverse()
print str(derecho) + str(reves)
C.2. Clase Hora
class Hora:
def init (self, horas=0, minutos=0, segundos=0):
self.horas = horas
self.minutos = minutos
self.segundos = segundos
def str (self):
return str(self.horas) + ":" + str(self.minutos) 
+ ":" + str(self.segundos)
def convierteASegundos(self):
minutos = self.horas * 60 + self.minutos
segundos = self.minutos * 60 + self.segundos
return segundos
def incrementa(self, segs):
segs = segs + self.segundos
self.horas = self.horas + segs/3600
segs = segs 3600
self.minutos = self.minutos + segs/60
segs = segs 60
self.segundos = segs
def haceHora(segs):
hora = Hora()
hora.horas = segs/3600
segs = segs - hora.horas * 3600
hora.minutos = segs/60
segs = segs - hora.minutos * 60
C.3 Cartas, mazos y 2
hora.segundos = segs
return hora
C.3. Cartas, mazos y juegos
import random
class Carta:
listaDePalos = ["Tr´eboles", "Diamantes",
"Corazones", "Picas"]
listaDeValores = ["nada", "As", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "Sota", "Reina", "Rey"]
def init (self, palo=0, valor=0):
self.palo = palo
self.valor = valor
def str (self):
return (self.listaDeValores[self.valor] + " de " +
self.listaDePalos[self.palo])
def cmp (self, otro):
# controlar el palo
if self.palo > otro.palo: return 1
if self.palo < otro.palo: return -1
# si son del mismo palo, controlar el valor
if self.valor > otro.valor: return 1
if self.valor < otro.valor: return -1
# los valores son iguales, es un empate
return 0
class Mazo:
def init (self):
self.cartas = []
for palo in range(4):
for valor in range(1, 14):
self.cartas.append(Carta(palo, valor))
def muestraMazo(self):
for carta in self.cartas:
print carta
2 Listados Completos de
def str (self):
s = ""
for i in range(len(self.cartas)):
s = s + " "*i + str(self.cartas[i]) + "n"
return s
def mezclar(self):
import random
nCartas = len(self.cartas)
for i in range(nCartas):
j = random.randrange(i, nCartas)
self.cartas[i], self.cartas[j] =
self.cartas[j], self.cartas[i]
def eliminaCarta(self, carta):
if carta in self.cartas:
self.cartas.remove(carta)
return 1
else: return 0
def darCarta(self):
return self.cartas.pop()
def estaVacio(self):
return (len(self.cartas) == 0)
def repartir(self, manos, nCartas=999):
nManos = len(manos)
for i in range(nCartas):
if self.estaVacio(): break # fin si se acaban las cartas
carta = self.darCarta() # da la carta superior
mano = manos[i nManos] # a qui´en le toca?
mano.agregaCarta(carta) # agrega la carta a la mano
class Mano(Mazo):
def init (self, nombre=""):
self.cartas = []
self.nombre = nombre
def agregaCarta(self,carta) :
self.cartas.append(carta)
C.3 Cartas, mazos y 2
def str (self):
s = "La mano de " + self.nombre
if self.estaVacio():
s = s + " est´a vac´ıan"
else:
s = s + " contienen"
return s + Mazo. str (self)
class JuegoDeCartas:
def init (self):
self.mazo = Mazo()
self.mazo.mezclar()
class ManoDeLaMona(Mano):
def eliminaCoincidencias(self):
cant = 0
cartasOriginales = self.cartas[:]
for carta in cartasOriginales:
empareja = Carta(3 - carta.palo, carta.valor)
if empareja in self.cartas:
self.cartas.remove(carta)
self.cartas.remove(empareja)
print "Mano s: s con s" (self.nombre,carta,empareja)
cant = cant + 1
return cant
class JuegoDeLaMona(JuegoDeCartas):
def jugar(self, nombres):
# quitamos la Reina de Tr´eboles
self.mazo.eliminaCarta(Carta(0,12))
# construimos una mano para cada jugador
self.manos = []
for nombre in nombres :
self.manos.append(ManoDeLaMona(nombre))
# repartimos los naipes
self.mazo.repartir(self.manos)
print "----- Se han repartido las cartas."
self.muestraManos()
2 Listados Completos de
# eliminamos las coincidencias iniciales
emparejadas = self.eliminaTodasLasCoincidencias()
print "----- Coincidencias eliminadas, el juego comienza."
self.muestraManos()
# se juega hasta que se han descartado las 50 cartas
turno = 0
cantManos = len(self.manos)
while emparejadas < 25:
emparejadas = emparejadas + self.jugarUnTurno(turno)
turno = (turno + 1) cantManos
print "----- El juego termin´o."
self.muestraManos()
def eliminaTodasLasCoincidencias(self):
cant = 0
for mano in self.manos:
cant = cant + mano.eliminaCoincidencias()
return cant
def jugarUnTurno(self, i):
if self.manos[i].estaVacio():
return 0
vecino = self.encuentraVecino(i)
cartaElegida = self.manos[vecino].darCarta()
self.manos[i].agregaCarta(cartaElegida)
print "Mano", self.manos[i].nombre, "eligi´o",
cartaElegida cant = self.manos[i].eliminaCoincidencias()
self.manos[i].mezclar()
return cant
def encuentraVecino(self, i):
cantManos = len(self.manos)
for proximo in range(1,cantManos):
vecino = (i + proximo) cantManos
if not self.manos[vecino].estaVacio():
return vecino
def muestraManos(self) :
for mano in self.manos :
print mano
C.4 Lists 2
C.4. Lists Enlazadas
def imprimeLista(nodo):
while nodo:
print nodo,
nodo = nodo.siguiente
print
def imprimeAlReves(lista):
if lista == None: return
cabeza = lista
cola = lista.siguiente
imprimeAlReves(cola)
print cabeza,
def imprimeAlRevesBonito(lista) :
print "[",
if lista != None :
cabeza = lista
cola = lista.siguiente
imprimeAlReves(cola)
print cabeza,
print "]",
def eliminaSegundo(lista):
if lista == None: return
primero = lista
segundo = lista.siguiente
primero.siguiente = segundo.siguiente
segundo.siguiente = None
return segundo
class Nodo:
def init (self, carga=None, siguiente=None):
self.carga = carga
self.siguiente = siguiente
def str (self):
return str(self.carga)
2 Listados Completos de
def imprimeAlReves(self):
if self.siguiente != None:
cola = self.siguiente
cola.imprimeAlReves()
print self.carga,
class ListaEnlazada :
def init (self) :
self.longitud = 0
self.cabeza =
None
def imprimeAlReves(self):
print "[",
if self.cabeza != None:
self.cabeza.imprimeAlReves()
print "]",
def agregaPrimero(self, carga):
nodo = Nodo(carga)
nodo.siguiente = self.cabeza
self.cabeza = nodo
self.longitud = self.longitud + 1
C.5. Clase Pila
class Pila : # implem. con listas de Python
def init (self) :
self.elementos = []
def push(self, elemento) :
self.elementos.append(elemento)
def pop(self) :
return self.elementos.pop()
def isEmpty(self) :
return (self.elementos == [])
def evalPostfijo(expr):
C.6 Colas y colas priorizadas 251
import re
listaTokens = re.split("([^0-9])", expr)
pila = Pila()
for token in listaTokens:
if token == ’’ or token == ’ ’:
continue
if token == ’+’:
suma = pila.pop() + pila.pop()
pila.push(suma)
elif token == ’*’:
producto = pila.pop() * pila.pop()
pila.push(producto)
else:
pila.push(int(token))
return pila.pop()
C.6. Colas y colas priorizadas
class Cola :
def init (self) :
self.longitud = 0
self.cabeza =
None
def vacia(self) :
return (self.longitud == 0)
def inserta(self, carga) :
nodo = Nodo(carga)
nodo.siguiente = None
if self.cabeza == None :
# Si la lista est´a vac´ıa nuestro nuevo nodo es el
primero self.cabeza = nodo
else :
# Encuentra el u´ltimo nodo de la lista
ultimo = self.cabeza
while ultimo.siguiente : ultimo = ultimo.siguiente #
A~nada el nuevo nodo
ultimo.siguiente = nodo
self.longitud = self.longitud + 1
2 Listados Completos de
def quita(self) :
carga = self.cabeza.carga
self.cabeza = self.cabeza.next
self.longitud = self.longitud - 1
return carga
class ColaMejorada :
def init (self) :
self.longitud = 0
self.cabeza =
None self.ultimo
=
None
def vacia(self) :
return (self.longitud == 0)
def inserta(self, carga) :
nodo = Nodo(carga)
nodo.siguiente = None
if self.longitud == 0 :
# Si la lista est´a vac´ıa nuestro nuevo nodo es el
primero self.cabeza = self.ultimo = nodo
else :
# Encuentra el ultimo nodo de la lista
ultimo = self.ultimo
# A~nade nuestro nodo nuevo
ultimo.siguiente = nodo
self.ultimo = nodo
self.longitud = self.longitud + 1
def quita(self) :
carga = self.cabeza.carga
self.cabeza = self.cabeza.siguiente
self.longitud = self.longitud - 1
if self.longitud == 0 : self.ultimo = None
return carga
class ColaPriorizada :
def init (self) :
self.elementos = []
def vacia(self) :
C.7 A´rboles 253
return self.elementos ==
[] def inserta(self,
elemento) :
self.elementos.append(elemento)
def quita(self) :
maxi = 0
for i in range(1,len(self.elementos)) :
if self.elementos[i] > self.elementos[maxi] :
maxi = i
elemento = self.elementos[maxi]
self.elementos[maxi:maxi+1] = []
return elemento
class Golfista :
def init (self, nombre, puntos) :
self.nombre = nombre
self.puntos = puntos
def str (self) :
return " -15s: d" (self.nombre, self.puntos)
def cmp (self, otro) :
if self.puntos < otro.puntos : return 1 # menos es m
´as if self.puntos > otro.puntos : return -1
return 0
C.7. A´rboles
class Arbol :
def init (self, carga, izquierda=None, derecha=None) :
self.carga = carga
self.izquierda = izquierda
self.derecha = derecha
def str (self) :
return str(self.carga)
def tomaCarga(self): return self.carga
def tomaIzquierda(self): return self.izquierda
2 Listados Completos de
def tomaDerecha(self): return self.derecha
def ajustaCarga(self, carga): self.carga = carga
def ajustaIzquierda (self, izquierda): self.left = izquierda
def ajustaDerecha(self, derecha): self.derecha = derecha
def total(arbol) :
if arbol == None : return 0
return total(arbol.izquierda) + total(arbol.derecha) + arbol.carga
def imprimeArbol(arbol):
if arbol == None: return
print arbol.carga,
imprimeArbol(arbol.izquierda)
imprimeArbol(arbol.derecha)
def imprimeArbolPosfijo(arbol):
if arbol == None: return
imprimeArbolPosfijo(arbol.izquierda)
imprimeArbolPosfijo(arbol.derecha)
print arbol.carga,
def imprimeArbolInfijo(arbol):
if arbol == None: return
imprimeArbolInfijo(arbol.izquierda)
print arbol.carga,
imprimeArbolInfijo(arbol.derecha)
def imprimeArbolSangrado(arbol, nivel=0):
if arbol == None: return
imprimeArbolSangrado(arbol.derecha, nivel+1)
print ’ ’*nivel + str(arbol.carga)
imprimeArbolSangrado(arbol.izquierda, nivel+1)
C.8. A´rboles de expresio´n
def tomaToken(listaToken, esperado):
if listaToken[0] == esperado:
listaToken[0:1] = [] # quita el token
return 1
else:
C.9 Adivina el animal 255
return 0
def obtieneProducto(listaToken) :
a = obtieneNumero(listaToken)
if tomaToken(listaToken, ’*’)
:
b = obtieneProducto(listaToken)
return Arbol(’*’, a, b)
else :
return a
def obtieneSuma(listaToken) :
a =
obtieneProducto(listaToken) if
tomaToken(listaToken, ’+’) : b
= obtieneSuma(listaToken)
return Arbol(’+’, a, b)
else :
return a
def obtieneNumero(listaToken):
if tomaToken(listaToken, ’(’) :
x = obtieneSuma(listaToken) # obtiene subexpresi´on
tomaToken(listaToken, ’)’) # se come el cierre de par
´entesis return x
else :
x = listaToken[0]
if type(x) != type(0) : return None
listaToken[0:1] = [] # quita el token
return Arbol(x, None, None) # devuelve una hoja sin el n´umero
C.9. Adivina el animal
def animal():
# empezar con un nodo suelto
raiz = Arbol("p´ajaro")
# bucle hasta que el usuario salga
while 1:
print
if not si("Est´as pensando en un animal? "): break
# recorrer el a´rbol
2 Listados Completos de
arbol = raiz
while arbol.tomaIzquierda() != None:
indicador = arbol.tomaCarga() + "? "
if si(indicador):
arbol = arbol.tomaDerecha()
else:
arbol = arbol.tomaIzquierda()
# intentar adivinar
adivina = arbol.tomaCarga()
indicador = "Es un " + adivina + "? "
if si(indicador):
print "^A<Soy el m´as grande!"
continue
# obtener informaci´on nueva
indicador = "C´omo se llama el animal?
" animal= raw_input(indicador)
indicador = "Qu´e pregunta distinguir´ıa a un s de un
s? " pregunta = raw_input(indicador (animal,adivina))
# a~nadir informaci´on nueva
al arbol.ponCarga(pregunta)
a´rbol
indicador = "Si el animal fuera un s, cu´al ser´ıa la respuesta?
" if si(indicador animal):
arbol.ponIzquierda(Arbol(adivina))
arbol.ponDerecha(Arbol(animal))
else:
arbol.ponIzquierda(Arbol(animal))
arbol.ponDerecha(Arbol(adivina))
def si(preg):
from string import lower
resp = lower(raw_input(preg))
return (resp[0:1] == ’s’)
C.10. Fraction class
class Fraccion:
def init (self, numerador, denominador=1):
m = mcd (numerador, denominador)
C.10 Fraction class 257
self.numerador = numerador / m
self.denominador = denominador /
m
def mul (self, otro):
if type(otro) == type(5):
otro = Fraccion(otro)
return Fraccion(self.numerador * otro.numerador,
self.denominador * otro.denominador)
rmul = mul
def add (self, otro):
if type(otro) == type(5):
otro = Fraccion(otro)
return Fraccion(self.numerador * otro.denominador +
self.denominador * otro.numerador,
self.denominador * otro.denominador)
radd = add
def cmp (self, otro):
if type(otro) == type(5):
otro = Fraccion(otro)
dif = (self.numerador * otro.denominador
- otro.numerador *
self.denominador)
return dif
def repr (self):
return self. str ()
def str (self):
return " d/ d" (self.numerador, self.denominador)
def mcd(m,n):
"devuelve el m´aximo com´un denominador de dos
enteros" if m n == 0:
return n
else:
return mcd(n,m n)
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
Ap´endice D
Lecturas recomendadas
Y ahora, ¿hacia d´onde ir desde aqu´ı? Hay muchas direcciones en
las que seguir, ampliando sus conocimientos de Python spec
´ıficamente y de inform´atica en general.
Los ejemplos en este libro han sido deliberadamente simples, por
lo que pueden no haber mostrado las capacidades m´as excitantes
de Python. A continuaci´on exponemos una muestra de las
extensiones de Python y sugerencias sobre sus usos.
La programaci´on de GUIs (interfaces gr´aficas de usuario,
graphic user interface en ingl´es) permite que su programa
utilice un entorno de ventanas para interactuar con el usuario
y mostrar gr´aficos.
El primer paquete que ha tenido Python para esto es Tkinter,
basado en los lenguajes interpretados Tcl y Tk de Jon
Ousterhout. Tkinter esta´ incluido en la distribuci´on de
Python.
Otra plataforma popular es wxPython, que es esencialmente
un encha- pado sobre wxWindows, un paquete de C++ que
implementa ventanas utilizando la interfaces nativas las
plataformas Windows y Unix (incluido Linux). Las ventanas y
los controles con wxPython tienen una apariencia m´as nativa
que Tkinter y son un poco mas sencillos de programar.
Cualquier tipo de programaci´on de GUIs le llevar´a a
programaci´on basada en eventos, donde es el usuario y no el
programador quien determina el flujo de la ejecucio´n. Este
estilo de programaci´on requiere de algo de tiempo para
acostumbrarse, y a veces le forzar´a a replantearse toda la
estructura del programa.
2 Lecturas
La programaci´on web integra Python en la Internet. Por
ejemplo, puede construir programas de cliente web que abran
y lean una pagina remota (casi) tan f´acilmente como si fuera
un fichero en disco. Tambi´en hay modu- los de Python que le
permiten acceder a ficheros remotamente v´ıa ftp, y m´odulos
que le permiten enviar y recibir correos electro´nicos. Python
tam- bi´en es ampliamente utilizado en el lado del servidor de
la programaci´on web para manejar los datos de entrada de
los formularios.
Las bases de datos son un poco como super ficheros en donde
los datos esta´n almacenados en esquemas predefinidos, y
las relaciones entre los datos le permiten acceder a ellos de
varias maneras. Python tiene varios m´odulos para permitir a
los usuarios conectarse a varios motores de bases de datos,
tanto Open Source como comerciales.
La programaci´on multi-procesos (multi-hilos) le permite
ejecutar varios procesos (hilos) de ejecucio´n dentro de un u
´nico programa. Si ha tenido la experiencia de usar un
navegador web para desplazarse por una pagina web mientras
el navegador continu´a cargando el resto de la misma,
entonces tiene una idea de lo que los hilos pueden hacer.
Cuando la velocidad es m´as importante se pueden escribir
extensiones para Python en un lenguaje compilado como C o
C++. Tales extensio- nes forman la base de la mayor´ıa de
modulos en la librer´ıa de Python. El mecanismo de enlazar
funciones y datos es un poco complejo. SWIG (Simplified
Wrapper and Interface Generator) es una herramienta para
hacer este proceso mucho m´as sencillo.
D.1. Libros y sitios web sobre Python
Aqu´ı tiene las recomendaciones de los autores sobre recursos para
Python en la web:
La p´agina de inicio de Python en www.python.org es el lugar
para empezar su bu´squeda de material sobre Python.
Encontrar´a ayuda, documentaci´on, enlaces a otros libros y
listas de correo de SIGs (Special Interest Group) a las que se
puede unir.
El proyecto Open Book Project www.ibiblio.com/obp contiene
no s´olo este libro en l´ınea sino tambi´en otros libros
similares para Java y C++ de Allen Downey. Adem´as esta´
Lessons in Electric Circuits de Tony R. Kuphaldt, Getting down with
..., un conjunto de tutoriales de varios temas sobre inform
´atica, escritos y editados por estudiantes de institulo, Python
for Fun, un conjuto de estudios de casos en Python de Chris
Meyers, y The Linux Cookbook de Michael Stultz, con 300
paginas de trucos y t´ecnicas.
D.2 Libros recomendados sobre inform´atica en general 261
Finalmente si acude a Google y busca con la cadena “python -
snake - monty” obtendr´a cerca de 750.000 resultados.
Y aqu´ı algunos libros que contienen m´as material sobre el lenguaje
Python:
Core Python Programming de Wesley Chun es un libro largo, mas
de 750 p´aginas. La primera parte del libro cubre las
caracter´ısticas basicas del lenguaje Python. La segunda
parte proporciona una introducci´on paso a paso a temas m
´as avanzados incluyendo muchos de los mencionados
anteriormente.
Python Essential Reference de David M. Beazley es un libro
pequen˜o, pero contiene informaci´on sobre el lenguaje en s´ı
mismo y los modulos de la librer´ıa esta´ndar. Tambi´en esta´
muy bien indexado.
Python Pocket Reference de Mark Lutz realmente cabe en el
bolsillo. Aun- que no es tan extensivo como Python Essential
Reference es una referencia u´til para los m´odulos y funciones
mas comunmente usadas. Mark Lutz tambi´en es autor de
Programming Python, uno de los primeros (y mas largos) libros
de Python y no esta´ dirigido al programador principiante. Su
siguiente libro Learning Python es mas pequen˜o y mas
accesible.
Python Programming on Win32 de Mark Hammond y Andy
Robinson es un libro que “debe tener” cualquiera que que
utilice seriamente Pyt- hon para desarrollar aplicaciones para
Windows. Entre otras cosas cubre la integraci´on de Python y
COM, construye una pequen˜a aplicaci´on con wxPython, e
incluso utiliza Python para escribir scripts para aplicaciones
tales como Word y Excel.
D.2. Libros recomendados sobre inform´atica
en general
Las siguientes sugerencias sobre lecturas adicionales incluyen
muchos de los libros favoritos de los autores. Estos tratan sobre
buenas pr´acticas de progra- maci´on e inform´atica en general.
The Practice of Programming de Kernighan y Pike cubre no s´olo
el disen˜o y dodificaci´on de algoritmos y estructuras de datos,
sino tambi´en depura- cio´n, testeo y mejora de rendimiento
de los programas. Los ejemplos esta´n principalmente en C++
y Java, sin nada de Python.
2 Lecturas
The Elements of Java Style editado por Al Vermeulen es otro libro
pequen˜o que discute algunos de los puntos m´as sutiles de la
buena programaci´on, tales como el buen uso de las
convenciones de nombres, comentarios e indentaci´on (un
poco irrelevante en Python). El libro tambi´en cubre la
programaci´on por contrato, usando aserciones para
encontrar los errores probando precondiciones y
postcondiciones, y programaci´on correcta con hilos y su
sincronizacio´n.
Programming Pearls de Jon Bentley es un libro cla´sico.
Consiste en es- tudios de caso que aparecieron originalmente
en la columna del autor en Communications of the ACM. Los
estudios tratan sobre toma y daca en programaci´on y por qu
´e suele ser mala idea desarrollar con la primera idea de un
programa. El libro es un poco mas antiguo que los anteriores
(1986), por lo que los ejemplos esta´n en lenguajes mas
antiguos. Hay muchos pro- blemas para resolver, algunos con
soluciones y otros con pistas. Este libro fue muy popular y le
siguio´ un segundo volumen.
The New Turing Omnibus de A.K Dewdney proporciona una
introduc- cio´n amigable a 66 temas de inform´atica desde
computacio´n en parelelo hasta virus inform´aticos, desde
TACs (tomograf´ıas computerizadas) hasta algoritmos gen
´eticos. Todos los temas son cortos y entretenidos. Un libro
anterior de Dewdney Aventuras Inform´aticas es una coleccio´n
de su co- lumna Juegos de ordenador en Invertigaci´on y Ciencia.
Ambos libros son ricas fuentes de ideas para proyectos.
Tortugas, Termitas y Atascos de Trafico de Mitchel Resnick trata
sobre el poder de la descentralizaci´on y de como pueden
obtenerse comporta- mientos complejos a partir de las
actividades simples de una multitud de agentes coordinados.
Introduce el lenguaje StarLogo, que permite al usua- rio
escribir programas para agentes. La ejecucio´n del programa
demuestra comportamientos complejos agregados, que suelen
ser intuitivos. La ma- yor´ıa de los programas en el libro fueron
desarrollados por estudiantes de colegio e instituto.
Programas similares pueden escribirse en Python usando gr
´aficos e hilos.
G¨odel, Escher, Bach de Douglas Hofstadter. Simplemente, si
encuentra magia en la recursio´n tambi´en la encontrar´a en
este libro superventas. Uno de los temas de Hofstadter
concierne a los “lazos extran˜os” donde los patrones se
desenvuelven y ascienden hasta que se encuentran a s´ı
mismos
de nuevo. Es una disputa de Hofstadter que tales “lazos extran˜os”
son
una parte esencial de lo que separa lo animado de lo no animado. E
´l
demuestra tales patrones en la mu´sica de Bach, las
ilustraciones de Escher y el teorema de incompletitud de
G¨odel.
Ap´endice E
GNU Free Documentation
License
Version 1.1, March 2000
Copyright Ⓧc 2000 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies of
this license document, but changing it is not allowed.
Preamble
The purpose of this License is to make a manual, textbook, or other
written do- cument “free” in the sense of freedom: to assure
everyone the effective freedom to copy and redistribute it, with or
without modifying it, either commercially or noncommercially.
Secondarily, this License preserves for the author and pu- blisher a
way to get credit for their work, while not being considered
responsible for modifications made by others.
This License is a kind of “copyleft,” which means that derivative
works of the document must themselves be free in the same sense.
It complements the GNU General Public License, which is a
copyleft license designed for free software. We have designed this
License in order to use it for manuals for free software, because
free software needs free documentation: a free program should
come with manuals providing the same freedoms that the
software does. But this
2 GNU Free Documentation
License is not limited to software manuals; it can be used for any
textual work, regardless of subject matter or whether it is
published as a printed book. We recommend this License
principally for works whose purpose is instruction or reference.
E.1. Applicability and Definitions
This License applies to any manual or other work that contains a
notice placed by the copyright holder saying it can be distributed
under the terms of this Li- cense. The “Document,” below, refers to
any such manual or work. Any member of the public is a licensee,
and is addressed as “you.”
A “Modified Version” of the Document means any work containing
the Docu- ment or a portion of it, either copied verbatim, or with
modifications and/or translated into another language.
A “Secondary Section” is a named appendix or a front-matter
section of the Document that deals exclusively with the
relationship of the publishers or aut- hors of the Document to the
Document’s overall subject (or to related matters) and contains
nothing that could fall directly within that overall subject. (For
example, if the Document is in part a textbook of mathematics, a
Secondary Section may not explain any mathematics.) The
relationship could be a matter of historical connection with the
subject or with related matters, or of legal, commercial,
philosophical, ethical, or political position regarding them.
The “Invariant Sections” are certain Secondary Sections whose
titles are de- signated, as being those of Invariant Sections, in the
notice that says that the Document is released under this License.
The “Cover Texts” are certain short passages of text that are listed,
as Front- Cover Texts or Back-Cover Texts, in the notice that says
that the Document is released under this License.
A “Transparent” copy of the Document means a machine-readable
copy, repre- sented in a format whose specification is available to
the general public, whose contents can be viewed and edited
directly and straightforwardly with generic text editors or (for
images composed of pixels) generic paint programs or (for
drawings) some widely available drawing editor, and that is
suitable for input to text formatters or for automatic translation to
a variety of formats suitable for input to text formatters. A copy
made in an otherwise Transparent file format whose markup has
been designed to thwart or discourage subsequent modifica- tion
by readers is not Transparent. A copy that is not “Transparent” is
called “Opaque.”
E.2 Verbatim Copying 265
Examples of suitable formats for Transparent copies include plain
ASCII wit- hout markup, Texinfo input format, LATEX input format,
SGML or XML using a publicly available DTD, and standard-
conforming simple HTML designed for human modification.
Opaque formats include PostScript, PDF, proprietary for- mats that
can be read and edited only by proprietary word processors, SGML
or XML for which the DTD and/or processing tools are not
generally availa- ble, and the machine-generated HTML produced
by some word processors for output purposes only.
The “Title Page” means, for a printed book, the title page itself,
plus such following pages as are needed to hold, legibly, the
material this License requires to appear in the title page. For
works in formats which do not have any title page as such, “Title
Page” means the text near the most prominent appearance of the
work’s title, preceding the beginning of the body of the text.
E.2. Verbatim Copying
You may copy and distribute the Document in any medium, either
commercially or noncommercially, provided that this License, the
copyright notices, and the license notice saying this License
applies to the Document are reproduced in all copies, and that you
add no other conditions whatsoever to those of this License. You
may not use technical measures to obstruct or control the reading
or further copying of the copies you make or distribute. However,
you may accept compensation in exchange for copies. If you
distribute a large enough number of copies you must also follow
the conditions in Section 3.
You may also lend copies, under the same conditions stated above,
and you may publicly display copies.
E.3. Copying in Quantity
If you publish printed copies of the Document numbering more
than 100, and the Document’s license notice requires Cover Texts,
you must enclose the copies in covers that carry, clearly and
legibly, all these Cover Texts: Front-Cover Texts on the front cover,
and Back-Cover Texts on the back cover. Both covers must also
clearly and legibly identify you as the publisher of these copies.
The front cover must present the full title with all words of the title
equally prominent and visible. You may add other material on the
covers in addition. Copying with changes limited to the covers, as
long as they preserve the title of the Document and satisfy these
conditions, can be treated as verbatim copying in other respects.
2 GNU Free Documentation
If the required texts for either cover are too voluminous to fit
legibly, you should put the first ones listed (as many as fit
reasonably) on the actual cover, and continue the rest onto
adjacent pages.
If you publish or distribute Opaque copies of the Document
numbering more than 100, you must either include a machine-
readable Transparent copy along with each Opaque copy, or state
in or with each Opaque copy a publicly acces- sible computer-
network location containing a complete Transparent copy of the
Document, free of added material, which the general network-
using public has access to download anonymously at no charge
using public-standard network protocols. If you use the latter
option, you must take reasonably prudent steps, when you begin
distribution of Opaque copies in quantity, to ensure that this
Transparent copy will remain thus accessible at the stated location
until at least one year after the last time you distribute an Opaque
copy (directly or through your agents or retailers) of that edition to
the public.
It is requested, but not required, that you contact the authors of
the Document well before redistributing any large number of
copies, to give them a chance to provide you with an updated
version of the Document.
E.4. Modifications
You may copy and distribute a Modified Version of the Document
under the conditions of Sections 2 and 3 above, provided that you
release the Modified Version under precisely this License, with the
Modified Version filling the role of the Document, thus licensing
distribution and modification of the Modified Version to whoever
possesses a copy of it. In addition, you must do these things in the
Modified Version:
Use in the Title Page (and on the covers, if any) a title distinct
from that of the Document, and from those of previous
versions (which should, if there were any, be listed in the
History section of the Document). You may use the same title
as a previous version if the original publisher of that version
gives permission.
List on the Title Page, as authors, one or more persons or
entities respon- sible for authorship of the modifications in the
Modified Version, together with at least five of the principal
authors of the Document (all of its prin- cipal authors, if it has
less than five).
State on the Title page the name of the publisher of the
Modified Version, as the publisher.
Preserve all the copyright notices of the Document.
E.4 Modifications 267
Add an appropriate copyright notice for your modifications
adjacent to the other copyright notices.
Include, immediately after the copyright notices, a license
notice giving the public permission to use the Modified
Version under the terms of this License, in the form shown in
the Addendum below.
Preserve in that license notice the full lists of Invariant
Sections and re- quired Cover Texts given in the Document’s
license notice.
Include an unaltered copy of this License.
Preserve the section entitled “History,” and its title, and add
to it an item stating at least the title, year, new authors, and
publisher of the Modified Version as given on the Title Page. If
there is no section entitled “History” in the Document, create
one stating the title, year, authors, and publisher of the
Document as given on its Title Page, then add an item
describing the Modified Version as stated in the previous
sentence.
Preserve the network location, if any, given in the Document
for public access to a Transparent copy of the Document, and
likewise the network locations given in the Document for
previous versions it was based on. These may be placed in the
“History” section. You may omit a network location for a work
that was published at least four years before the Do- cument
itself, or if the original publisher of the version it refers to
gives permission.
In any section entitled “Acknowledgements” or “Dedications,”
preserve the section’s title, and preserve in the section all the
substance and tone of each of the contributor
acknowledgements and/or dedications given therein.
Preserve all the Invariant Sections of the Document, unaltered
in their text and in their titles. Section numbers or the
equivalent are not considered part of the section titles.
Delete any section entitled “Endorsements.” Such a section
may not be included in the Modified Version.
Do not retitle any existing section as “Endorsements” or to
conflict in title with any Invariant Section.
If the Modified Version includes new front-matter sections or
appendices that qualify as Secondary Sections and contain no
material copied from the Docu- ment, you may at your option
designate some or all of these sections as invariant.
2 GNU Free Documentation
To do this, add their titles to the list of Invariant Sections in the
Modified Ver- sion’s license notice. These titles must be distinct
from any other section titles. You may add a section entitled
“Endorsements,” provided it contains nothing
but endorsements of your Modified Version by various parties—for
example,
statements of peer review or that the text has been approved by an
organization as the authoritative definition of a standard.
You may add a passage of up to five words as a Front-Cover Text,
and a passage of up to 25 words as a Back-Cover Text, to the end of
the list of Cover Texts in the Modified Version. Only one passage of
Front-Cover Text and one of Back- Cover Text may be added by (or
through arrangements made by) any one entity. If the Document
already includes a cover text for the same cover, previously added
by you or by arrangement made by the same entity you are acting
on behalf of, you may not add another; but you may replace the old
one, on explicit permission from the previous publisher that added
the old one.
The author(s) and publisher(s) of the Document do not by this
License give permission to use their names for publicity for or to
assert or imply endorsement of any Modified Version.
E.5. Combining Documents
You may combine the Document with other documents released
under this Li- cense, under the terms defined in Section 4 above
for modified versions, provided that you include in the combination
all of the Invariant Sections of all of the original documents,
unmodified, and list them all as Invariant Sections of your
combined work in its license notice.
The combined work need only contain one copy of this License,
and multiple identical Invariant Sections may be replaced with a
single copy. If there are multiple Invariant Sections with the same
name but different contents, make the title of each such section
unique by adding at the end of it, in parentheses, the name of the
original author or publisher of that section if known, or else a
unique number. Make the same adjustment to the section titles in
the list of Invariant Sections in the license notice of the combined
work.
In the combination, you must combine any sections entitled
“History” in the various original documents, forming one section
entitled “History”; likewise com- bine any sections entitled
“Acknowledgements,” and any sections entitled “De- dications.”
You must delete all sections entitled “Endorsements.”
E.6 Collections of Documents 269
E.6. Collections of Documents
You may make a collection consisting of the Document and other
documents released under this License, and replace the individual
copies of this License in the various documents with a single copy
that is included in the collection, provided that you follow the rules
of this License for verbatim copying of each of the documents in all
other respects.
You may extract a single document from such a collection, and
distribute it individually under this License, provided you insert a
copy of this License into the extracted document, and follow this
License in all other respects regarding verbatim copying of that
document.
E.7. Aggregation with Independent Works
A compilation of the Document or its derivatives with other
separate and in- dependent documents or works, in or on a volume
of a storage or distribution medium, does not as a whole count as a
Modified Version of the Document, provided no compilation
copyright is claimed for the compilation. Such a com- pilation is
called an “aggregate,” and this License does not apply to the other
self-contained works thus compiled with the Document, on account
of their being thus compiled, if they are not themselves derivative
works of the Document.
If the Cover Text requirement of Section 3 is applicable to these
copies of the Document, then if the Document is less than one
quarter of the entire aggregate, the Document’s Cover Texts may
be placed on covers that surround only the Document within the
aggregate. Otherwise they must appear on covers around the
whole aggregate.
E.8. Translation
Translation is considered a kind of modification, so you may
distribute transla- tions of the Document under the terms of
Section 4. Replacing Invariant Sections with translations requires
special permission from their copyright holders, but you may
include translations of some or all Invariant Sections in addition to
the original versions of these Invariant Sections. You may include a
translation of this License provided that you also include the
original English version of this License. In case of a disagreement
between the translation and the original English version of this
License, the original English version will prevail.
2 GNU Free Documentation
E.9. Termination
You may not copy, modify, sublicense, or distribute the Document
except as expressly provided for under this License. Any other
attempt to copy, modify, sublicense, or distribute the Document is
void, and will automatically terminate your rights under this
License. However, parties who have received copies, or rights,
from you under this License will not have their licenses terminated
so long as such parties remain in full compliance.
E.10. Future Revisions of This License
The Free Software Foundation may publish new, revised versions of
the GNU Free Documentation License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns. See
http:///www.gnu.org/copyleft/.
Each version of the License is given a distinguishing version
number. If the Do- cument specifies that a particular numbered
version of this License .o
r any later version.a
pplies to it, you have
the option of following the terms and conditions either of that
specified version or of any later version that has been published
(not as a draft) by the Free Software Foundation. If the Document
does not spe- cify a version number of this License, you may choose
any version ever published (not as a draft) by the Free Software
Foundation.
E.11. Addendum: How to Use This License for
Your Documents
To use this License in a document you have written, include a copy
of the License in the document and put the following copyright and
license notices just after the title page:
Copyright Ⓧc YEAR YOUR NAME. Permission is granted to copy,
distribute and/or modify this document under the terms
of the GNU Free Documentation License, Version 1.1 or
any later version pu- blished by the Free Software
Foundation; with the Invariant Sec- tions being LIST
THEIR TITLES, with the Front-Cover Texts being LIST,
and with the Back-Cover Texts being LIST. A copy of the
li- cense is included in the section entitled “GNU Free
Documentation License.”
E.11 Addendum: How to Use This License for Your Documents 271
If you have no Invariant Sections, write “with no Invariant Sections”
instead of saying which ones are invariant. If you have no Front-
Cover Texts, write “no Front-Cover Texts” instead of “Front-Cover
Texts being LIST”; likewise for Back-Cover Texts.
If your document contains nontrivial examples of program code, we
recommend releasing these examples in parallel under your choice
of free software license, such as the GNU General Public License,
to permit their use in free software.
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
´Indice alfab´etico
a´rbol, 211
expresio´n, 213, 216
recorrido, 213, 214
vac´ıo, 212
a´rbol binario, 211, 224
a´rbol de expresio´n, 213, 216
´ındice, 76, 84, 97, 109, 229
negativo, 76
Make Way for Ducklings, 77
hon, 83
Referencia de la Biblioteca
de Pyt-
, 33
abeceda´rico, 77
acceso, 86
acto de fe, 55, 186
acumulador, 167, 170, 178
aleatorio, 167
algoritmo, 10, 146, 148
alias, 92, 97
ambigu¨edad, 7, 134
teorema fundamental, 188
an´alisis sint´actico, 10
analizar, 199, 216
analizar sint´acticamente, 7,
201
andamiaje, 48, 59
anidamiento, 45
archivo, 128
texto, 121
archivo de texto, 121, 128
archivos, 119
argumento, 21, 33
argumentos, 28
asignaci´on, 12, 20, 61
tupla, 168
tuplas, 100
asignaci´on de
alias, 112
asignaci´on de tuplas, 100, 107, 168
asignaci´on mu´ltiple,
61, 73 asignmaci´on
tupla, 107
atributo, 139
clase, 163, 170
atributo de clase, 163, 170
atributos, 132
AttributeError, 230
barajar, 167
base de conocimiento,
221 bifurcaci´on
condicional, 37
bloque, 37,
45 booleana
expresio´n,
36 booleanas
funciones, 52
borrado
lista, 91
borrado en una
lista, 91 bucle, 63,
73 anidado, 165
condicio´n, 228
cuerpo, 63,
73 en una
lista, 187 for,
76
infinito, 63, 73, 228
recorrido, 76
´Indice alfab
2
variable de, 73
while, 62
bucle for, 76, 88
bucle infinito, 227, 228
buffer circular,
210 c´odigo de
objeto, 10 c
´odigo
ejecutable, 10
c´odigo fuente, 10
c´odigo muerto, 48, 59
cadena, 11, 12
inmutable, 79
longitud, 76
porci´on, 78
cadena de formato, 123,
128
cadena inmutable, 79
caja, 114
caja de funci´on,
114 cara´cter, 75
carga, 183, 192,
211
Carta, 161
caso base, 43, 45
cifrar, 162
clase, 131, 139
Carta, 161
Golfista, 209
JuegoDeLaMona, 177
ListaEnlazada, 190
ManoDeLaMona, 176
Nodo, 183
padre, 172, 175
Pila, 196
Punto, 155
clase abstracta, 210
clase hija, 171, 182
clase padre, 171, 172,
175, 182
clase Punto,
155
clasificacio´n
cara´cter, 82
clasificacio´n de
caracteres, 82 clave,
109, 117
cliente, 196, 201
clonado, 93, 112
clonar, 97
codificar, 162, 170
coercio´n, 33
tipo, 115
coercio´n de
tipos, 22
coercion, 23
coincidencia de esquemas,
107 cola, 203, 210
implementaci´on con
Lista, 203 implementaci
´on enlazada, 204
implementaci´on mejorada, 205
cola enlazada, 204, 210
cola mejorada, 205
cola priorizada, 203, 210
TAD, 207
coleccio´n, 185, 196
columna, 96
coma flotante, 20, 131
comentario, 20
comentarios, 19
comparable,
164
comparacio´n
cadenas, 78
fracci´on, 239
comparacio´n de
cadenas, 78
compilador, 225
compilar, 2, 10
composici´on, 18, 20, 24, 51, 161, 165
compresio´n, 116
comprobacio´n de
errores, 57
concatenacio´n, 77, 80
concatenar, 20
concatenation, 18
condicio´n, 63
condicio´n, 45, 228
condicio´n previa,
188, 192 condiciones
encadenadas, 38
condiciones encadenadas, 38
constructor, 131, 139, 162
contador, 80, 84
conteo, 103
´Indice alfab 2
conversio´n de
tipo, 33 conversio
´n de tipos, 22
copia profunda,
139
copia superficial,
139
copiado, 112, 137
corresponder, 162
cuelgue, 227
cuerpo, 37, 45
bucle, 63
cursor, 73
decrementar,
84 definici´on
circular, 53
recursiva, 219
definici´on circular, 53
definici´on de funci´on, 25, 33
definici´on recursiva, 219
delimitador, 97, 125, 199, 201
denominador, 235
depuraci´on, 10, 225
depuraci´on
(debugging), 4
desarrollo
incremental, 148
planificado, 148
desarrollo de
progamas
encapsulado, 67
desarrollo de
programas, 73
generalizaci´on, 67
desarrollo de prototipos, 145
desarrollo incremental, 49,
59, 148 desarrollo
incremental de progra-
mas, 226
desarrollo planificado, 148
desbordamiento, 115
determinista, 107
diagrama de estado,
13, 20 diagrama de
pila, 33 diagramas de
pila, 30, 42
diccionario, 96, 109, 117, 124,
230
m´etodos, 111
operaciones, 110
diccionarios, 109
m´etodos, 111
operaciones sobre, 110
directorio, 125, 128
disen˜o orientado a
objetos, 171 divisi´on de
enteros, 16, 20, 22
documentaci´on, 192
Doyle, Arthur Conan,
6
ejecucio´n
flujo, 229
ejecucio´n condicional, 37
elemento, 85, 97
eliminacio´n de naipes,
168 encapsulacio´n,
136, 195, 200
encapsulado,
67
encapsular, 73
enchapado, 197
encolamiento priorizado, 203
encurtido, 125, 128
enlace, 192
enteros
divisi´on, 22
largos, 115
enteros largos, 115
envoltorio,
192 error
de sintaxis, 225
en tiempo de compilacio´n,
225 en tiempo de ejecucio
´n, 43, 225
sema´ntico, 225, 231
sintaxis, 5
tiempo de ejecucio
´n, 5 error (bug), 4
error de sintaxis, 225
error en tiempo de compilacio
´n, 225 error en tiempo de
ejecucio´n, 5, 10, 43, 76, 79,
87, 100, 111,
113, 115, 120, 124, 225,
229
error sema´ntico, 5, 10, 101, 225, 231
error sintactico, 5, 10
error(bug), 10
escalera de color,
174
´Indice alfab
2
escape
secuencia de, 73
espacio en blanco, 84
estilo funcional de
programaci´on, 144,
148
estructura anidada,
161 estructura de
datos gen´erica,
196, 197
recursiva, 183, 192, 212
estructura de datos recursiva,
212 estructura gen´erica de
datos, 196,
197
estructura recursiva de datos,
183, 192
Euclides, 238
excepcio´n, 5, 10, 126, 128,
225, 229
expresio´n, 16, 20, 198
booleana, 45
grande y peliaguda, 232
expresio´n booleana, 36, 45
expresio´n regular, 199
Fibonacci function, 56
FIFO, 203, 210
fila, 96
float, 12
flujo de ejecucio´n, 27,
33, 229 formal
lenguaje, 6
forzado de tipo de datos, 115
fracci´on, 235
comparacio´n, 239
multiplicaci´on, 236
suma, 237
frangoso, 53
funci´on, 71
funci´on join, 96
funci´on split, 96
funci´on, 25, 33, 141, 150
booleana, 169
definici´on, 25
factorial, 54
funci´on booleana, 169
funci´on de
Fibonacci, 113 funci
´on factorial, 54, 57
funci´on gamma,
57
funci´on pura, 142,
148 funciones
argumentos, 28
composici´on, 24, 51
llamadas, 21
par´ametros, 28
recursivas, 42
tuplas como valor de
retorno, 101
funciones booleanas, 52
funciones matem´aticas, 23
generalizaci´on, 67
generalizaci´on, 136, 146
generalizar, 73
Golfista, 209
gr´afico de
llamadas, 114
guardi´an, 59
guion, 10
guion bajo, 14
herencia, 171,
182
histograma, 106, 107, 116
Holmes, Sherlock, 6
identidad, 134
igualdad, 134
igualdad profunda, 134, 139
igualdad superficial, 134,
139 implementaci´on
Cola, 203
imponer,
160
impresi´on
mano de cartas,
174 objeto mazo,
166
imprimir
objeto, 133, 150
incrementar, 84
IndexError, 230
indicador, 44, 45
´Indice alfab 2
infijo, 198,
201 infinito
bucle, 63
infio, 213
inmutable, 99
instancia, 133, 136, 139
objeto, 132, 150, 164
instancia de objeto, 164
instancia de un objeto,
132, 150
instanciaci´on,
132
instanciar, 139
instrucci´on, 4
int, 12
Intel, 64
intercambio, 168
interfaz, 196, 210
interpretar, 2, 10
invariante, 191, 192
invocar, 117
invocar m´etodos, 111
irracional, 240
iteraci´on, 61,
62, 73 juego
animales, 221
juego de los animales, 221
KeyError, 230
l´ogico
operador, 36
lanzar una excepcio´n, 126,
128
lenguaje, 134
alto nivel, 2
bajo nivel, 2
completo, 53
programaci´on, 1
lenguaje completo, 53
lenguaje de alto nivel, 2, 10
lenguaje de bajo nivel, 2, 10
lenguaje de programaci´on, 1
lenguaje de programaci´on
orientado
a objetos, 149, 160
lenguaje formal, 6, 10
lenguaje natural, 6, 10, 134
lenguaje seguro, 5
Linux, 6
lista, 85, 97, 183
anidada, 95, 112
bien construida, 191
bucle, 187
bucle for, 88
como par´ametro, 185
de objetos, 165
elemento, 86
enlazada, 183, 192
impresi´on, 185
imprimir hacia atr´as,
186 infinita, 187
longitud, 87
modificar, 189
mutable, 90
pertenencia, 88
porciones, 90
recorrer recursivamente, 186
recorrido, 87, 185
lista anidada, 97, 112
lista enlazada, 183, 192
lista infinita, 187
ListaEnlazada,
190 listas
anidadas, 85, 95
clonado, 93
como par´ametros, 94
operaciones con, 89
listas anidadas, 95
literalidad, 7
llamada a funci´on,
33 llamadas a
funciones, 21 local
variable,
69 locales
variables, 29
logaritmo, 64
longitud, 87
´Indice alfab
2
lowercase, 82
m´aximo comu´n divisor, 238,
241
m´etodo, 111, 117, 141, 150,
160
ayudante, 190
envoltorio, 190
inicializaci´on, 154, 165
invocacio´n, 111
lista, 116, 166
m´etodo append, 166
m´etodo ayudante, 190, 192
m´etodo de inicializaci´on,
154, 160,
165
m´etodo de lista, 116
m´etodo envoltorio,
190 m´etodos de
desarrollo
incremental, 49 m
´etodos de lista,
166
m´etodos sobre diccionarios,
111 m´odulo, 23, 33, 81
copy, 137
operador, 35
string, 83
m´odulo copy, 137
m´odulo string,
81, 83 mu´ltiple
asignaci´on, 73
manejar errores, 220
manejar una excepcio´n,
126, 128 manejo de
errores, 220
marco, 42
marco de funci
´on, 42 matem
´aticas
funciones, 23
matriz, 95
dispersa, 112
mazo, 165
McCloskey, Robert,
77 mensajes de
error, 225
mismidad, 134
modelo
mental, 232
modelo mental, 232
modifcador, 148
modificador, 143
modificar listas,
189 multiplicaci
´on fracci´on, 236
multiplicaci´on escalar, 156, 160
mutable, 79, 84, 99
lista,
90 nu
´mero
aleatorio, 101
nu´mero aleatorio, 101
NameError, 229
natural
lenguaje, 6
negaci´on, 240
negaci´on unitaria, 241
nivel, 211, 224
nodo, 183, 192, 211, 224
Nodo clase, 183
nodo de un a´rbol,
211 nodo
hermano, 224
nodo hijo, 211, 224
nodo hoja, 211, 224
nodo padre, 211, 224
nodo ra´ız, 211, 224
None, 48, 59
notaci´on de punto, 111, 151, 154
nueva l´ınea, 73
numerador, 235
objeto, 91, 97, 131,
139
mudable, 136
objeto invariante,
191
objeto mudable,
136 objetos
lista de, 165
obst´aculo al
rendiminto, 210
operaciones
con listas, 89
operaciones con listas,
89 operaciones sobre
cadenas, 17 operador,
16, 20
´Indice alfab 2
binario, 213, 224
condicional, 164
corchete, 75
formato, 123, 128, 209,
230
in, 88, 169
m´odulo, 173
sobrecarga, 156, 236
operador binario, 213, 224
operador condicional, 164
operador corchete, 75
operador de formato, 123,
128, 209,
230
operador in, 88, 169
operador l´ogico, 36
operador m´odulo, 35, 45, 173
operador matem´atico, 236
operador unitario, 240
operadores
para listas, 89
operando, 16, 20
orden, 164
orden completo, 164
orden de evaluaci
´on, 232
orden de las
operaciones, 17 orden
infijo, 214, 215, 224
orden parcial,
164
orden postfijo, 214, 215, 224
orden prefijo, 214, 224
palabra reservada, 13, 20
palabras reservadas, 14
palo, 161
papel
variable, 188
par clave-valor, 109, 117
par´ametros, 28
par´ametro, 33, 94, 133
lista, 94
pass
sentencia, 37
patr´on, 80
patr´on computacional, 80
Pentium, 64
Pila, 196
pila, 196
pista, 113, 117
plan de desarrollo, 73
poes´ıa, 8
polimorfica, 160
polimorfismo, 158
pop, 197
porci´on, 78, 84
porciones, 90
portabilidad, 10
portable, 2
postfijo, 198, 201, 213
precedencia, 20, 232
prefijo, 214, 224
print
sentencia, 9, 10
prioridad, 209
producto, 219
producto interior, 156, 160
programa, 10
desarrollo de, 73
programaci´on orientada a
objetos, 149, 171
prosa, 8
proveedor, 196, 201
pseudoaleatorio, 107
pseudoc´odigo, 238
push, 197
racional, 235
rama, 38, 45
random, 167
randrange, 167
recorrer, 84, 185, 186, 213
recorrido, 76, 80, 88, 176, 208, 214
lista, 87
recorrido de lista,
97 recorrido
eureka, 80
recta´ngulo, 135
recuento, 116
recursio´n, 213, 214
infinita, 228
´Indice alfab
2
recursio´n infinita, 227,
228
recursividad, 40, 42, 45,
53, 55
caso base, 43
infinita, 43, 57
recursividad infinita, 43,
45, 57
redimiento, 205
reducir, 238, 241
redundancia, 7
referencia, 183
incrustada, 183, 192
referencia empotrada, 211
referencia incrustada,
183, 192 referencias
alias, 92
reglas de precedencia, 17,
20 reparto de naipes, 173
repeticio´n
lista, 89
runtime error, 76
ruta, 125
secuencia, 97
secuencia aritm´etica, 66
secuencia de escape, 66,
73
secuencia geom
´etrica, 66 seguro
lenguaje, 5
sema´ntica, 5,
10
error, 5
sentencia,
20
asignaci´on, 12, 61
break, 121, 128
condicional, 45
continue, 122, 128
except, 126
print, 230
return, 40, 233
try, 126
while, 62
sentencia break, 121, 128
sentencia compuesta, 45
sentencia condicional, 45
sentencia continue, 122,
128
´Indice alfab 2
sentencia except, 126, 128
sentencia pass, 37
sentencia print, 9, 10, 230
sentencia return, 40, 233
senten
cia try,
126
senten
ciasbloque, 37
compuestas, 37
sentencias
compue
stas, 37
bloque
de
sentenci
as, 37
cabecer
a, 37
cuerpo, 37
sequencia, 85
singleton, 189, 190, 192
sintaxis, 5, 10, 226
sobrecarga, 160, 236
operador, 209
sobrecarga de operadores,
156, 160,
164, 209
solucio´n
de
problema
s, 10
subclase,
171, 175,
182
subexpresio´n, 220
suma, 219
fracci´on, 237
sustituir, 164
tactica de encolamiento,
203, 210
tablas, 64
dos dimensiones, 66
tabulador, 73
TAD, 195, 200, 201
Cola, 203
cola, 203
Cola Priorizada, 203,
207
Pila, 196
TAD Cola, 203
temporal
va
riable,
59
tempo
rales
va
riables
, 48
teore
ma
fundamental de la
ambigu¨edad, 188
|
´Indice alfab
2
teorema fundamental de la
am- bigu¨edad, 192
tesis de Turing, 53
tiempo constante, 205, 210
tiempo lineal, 205, 210
tipo, 11, 12, 20
cadena, 12
float, 12
int, 12
tipo abstracto de datos
verTAD, 195
tipo compuesto de datos,
84 tipo de datos
compuesto, 75, 131
definido por el usuario,
131, 235
diccionario, 109
tipo de datos compuesto, 75,
131 tipo de datos definido por
el usua-
rio,
131 tipo de
funci´on
modifcador, 143
pura, 142
tipo inmutable, 107
tipo mutable,
107 tipos
coercio´n, 22
comprobacio´n, 57
conversio´n,
22 tipos de
datos
enteros largos, 115
inmutables, 99
tuplas, 99
token, 199, 201, 216
traceback, 31
traza, 127
traza inversa, 43, 229
try, 128
tupla, 99, 107
tuplas, 101
Turing, Alan, 53
TypeError, 229
unidad, 10
uppercase, 82
uso de alias,
137 valor, 20,
91, 161
valor de retorno, 21, 33, 47, 59, 136
valores
tuplas, 101
valores de
retorno tuplas,
101
value, 11
variable, 12,
20
bucle, 173
papeles, 188
temporal, 59, 232
variable de bucle, 73, 173, 185
variable local, 33
variable temporal, 232
variables
locales, 69
variables locales, 29
variables temporales,
48 while
sentencia, 62
whitespace, 82
´Indice alfab 2
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
aprenda-a-pensar-como-un-programador-con-python-convertido.docx
aprenda-a-pensar-como-un-programador-con-python-convertido.docx

Más contenido relacionado

PDF
Aprende a pensar como programador con python
PDF
Aprenda a pensar como un programador con Python
PDF
Todo Sobre El Lenguaje Python
DOC
Tutorial de Programacion en Python 3.doc
DOCX
Manual de python
PDF
El tutorial de pythonnnn
PDF
Tutorial python3
PDF
Tutorial python3
Aprende a pensar como programador con python
Aprenda a pensar como un programador con Python
Todo Sobre El Lenguaje Python
Tutorial de Programacion en Python 3.doc
Manual de python
El tutorial de pythonnnn
Tutorial python3
Tutorial python3

Similar a aprenda-a-pensar-como-un-programador-con-python-convertido.docx (20)

PDF
Tutorial python2
PDF
Tutorial python2
PDF
Tutorial python2
PDF
Tutorial python
PDF
Raspberry Pi: El tutorial de Python
PDF
Manual python2
PDF
Tutorial python2
PDF
Tutorial python2
PDF
Tutorial python2
PDF
excelente Tutorial aprendizaje de Python3.pdf
PDF
INFOSAN Tutorial python3 (1)
PDF
Tutorial python
PDF
Tutorial python3
PDF
PPT
Mi lenguaje de preferencia
PPTX
Python y la POO, en una clase, UNNe-Corrientes
PDF
ippython 2024 completo Andrés Marzal y Isabel gracia
PDF
1. Presentación e instalación de Python.pdf
PDF
Aprender a-programar-con-python
PDF
PYTON.pdf
Tutorial python2
Tutorial python2
Tutorial python2
Tutorial python
Raspberry Pi: El tutorial de Python
Manual python2
Tutorial python2
Tutorial python2
Tutorial python2
excelente Tutorial aprendizaje de Python3.pdf
INFOSAN Tutorial python3 (1)
Tutorial python
Tutorial python3
Mi lenguaje de preferencia
Python y la POO, en una clase, UNNe-Corrientes
ippython 2024 completo Andrés Marzal y Isabel gracia
1. Presentación e instalación de Python.pdf
Aprender a-programar-con-python
PYTON.pdf
Publicidad

Último (20)

PDF
PLANO IE0097 PANTRICIA ANTONIA LOPEZ.pdf
PDF
6o. del 23al 27 de junio 2025_120359.pdf
PPTX
AT_04_Equipo_Gestión_Pedagógica_innovacion.pptx
PPTX
MISCELANIA - constitución política 410-5.pptx
PDF
AMTD-609_FORMATOALUMNOTRABAJOFINAL602.pdf
PDF
Semana del 30 de junio al 04 de julio de 2025.pdf
PPTX
convulsiones.pptxiiuhbutghiopkhrfxdddxfvk
PPTX
Presentationoooooooooooooooooooooooooooo
DOCX
FCE POLITICa tercer grado secundaria adolescentes
PDF
Curso online para participar en exel o deribados
PPTX
11avo sesion de clase de produccion de cuyes y conejos.....pptx
PPTX
teorias del pblamiento completo. pptx
PPTX
Conferencia orientadora Sinapsis y Receptores.pptx
PDF
Act. 2.1 Recursos Naturales y su Clasificación..pdf
DOCX
FCE POLITICA tercer grado secundaria adolescentes
PDF
Saldarriaga-Roa-Aprender-Arquitectura.pdf
PDF
HhhhjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjHu .pdf
PPTX
Medicina familiar hhvkbvhjnbjihjjhghjjbhhbv
PDF
Empresas globales que cotizan en bolsa por efectivo disponible (2025).pdf
PPTX
Presentacion de caso clinico (1).pptxhbbb
PLANO IE0097 PANTRICIA ANTONIA LOPEZ.pdf
6o. del 23al 27 de junio 2025_120359.pdf
AT_04_Equipo_Gestión_Pedagógica_innovacion.pptx
MISCELANIA - constitución política 410-5.pptx
AMTD-609_FORMATOALUMNOTRABAJOFINAL602.pdf
Semana del 30 de junio al 04 de julio de 2025.pdf
convulsiones.pptxiiuhbutghiopkhrfxdddxfvk
Presentationoooooooooooooooooooooooooooo
FCE POLITICa tercer grado secundaria adolescentes
Curso online para participar en exel o deribados
11avo sesion de clase de produccion de cuyes y conejos.....pptx
teorias del pblamiento completo. pptx
Conferencia orientadora Sinapsis y Receptores.pptx
Act. 2.1 Recursos Naturales y su Clasificación..pdf
FCE POLITICA tercer grado secundaria adolescentes
Saldarriaga-Roa-Aprender-Arquitectura.pdf
HhhhjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjjHu .pdf
Medicina familiar hhvkbvhjnbjihjjhghjjbhhbv
Empresas globales que cotizan en bolsa por efectivo disponible (2025).pdf
Presentacion de caso clinico (1).pptxhbbb
Publicidad

aprenda-a-pensar-como-un-programador-con-python-convertido.docx

  • 1. Aprenda a Pensar Como un Programador con Python
  • 3. Aprenda a Pensar Como un Programador con Python Allen Downey Jeffrey Elkner Chris Meyers Traducido por Miguel A´ngel Vilella A´ngel Arnal Iv´an Juanes Litza Amurrio Efrain Andia C´esar Ballardini Green Tea Press Wellesley, Massachusetts
  • 4. Copyright Ⓧc 2002 Allen Downey, Jeffrey Elkner, y Chris Meyers. Corregido por Shannon Turlington y Lisa Cutler. Disen˜o de la cubierta por Rebecca Gimenez. Green Tea Press 1 Grove St. P.O. Box 812901 Wellesley, MA 02482 Se permite copiar, distribuir, y/o modificar este documento bajo los t´erminos de la GNU Free Documentation License, Versio´n 1.1 o cualquier versio´n posterior publicada por la Free Software Foundation; siendo las Secciones Invariantes “Pr´ologo”, “Prefacio”, y “Lista de Colaboradores”, sin texto de cubierta, y sin texto de contracubierta. Se incluye una copia de la licencia en el ap´endice titulado “GNU Free Documentation License”. La GNU Free Documentation License esta´ disponible en www.gnu.org o escri- biendo a la Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. La forma original de este libro es c´odigo fuente LATEX. La compilacio´n de este fuente LATEX tiene el efecto de generar una representaci´on independiente del dispositivo de un libro de texto, que puede convertirse a otros formatos e im- primirse. El fuente LATEX de este libro y m´as informaci´on sobre el proyecto de Libro de Texto de C´odigo Abierto esta´n disponibles en http://www.thinkpython.com La composici´on de este libro se realizo´ utilizando LATEX y LyX. Las ilustraciones se hicieron con xfig. Todos ellos son programas gratuitos de c´odigo abierto. Historia de la impresi´on: Abril 2002: Primera edicio´n. ISBN 0- 9716775-0-6
  • 5. Pr´ologo Por David Beazley Como educador, investigador, y autor de libros, estoy encantado de ver la fi- nalizaci´on de este libro. Python es un lenguaje de programaci´on divertido y extremadamente f´acil de usar que en los u´ltimos an˜os se ha hecho muy popu- lar. Desarrollado hace diez an˜os por Guido van Rossum, su sintaxis simple y la sensacio´n general se deriva en gran parte del ABC, un lenguaje desarrollado en los 1980s para la ensen˜anza. Sin embargo, Python tambi´en se cre´o para resolver problemas reales y presenta una variedad amplia de caracter´ısticas de lenguajes de programaci´on como C+ +, Java, Modula-3 y Scheme. Debido a esto, una de las caracter ´ısticas notables de Python es su atractivo para los desarrolladores profesionales de progamaci´on, cient´ıficos, investigadores, artistas, y educadores. A pesar del atractivo de Python para muchas comunidades diferentes, puede que au´n se pregunte “¿por qu´e Python?” o “¿por qu´e ensen˜ar programaci´on con Python?”No es tarea f´acil responder a estas preguntas, en especial cuando la opini´on popular esta´ del lado de alternativas mas masoquistas como C++ y Java. Sin embargo, pienso que la respuesta mas directa es que la progrmaci´on en Python es simplemente m´as divertida y mas productiva. Cuando imparto cursos de inform´atica, quiero cubrir conceptos importantes, hacer el material interesante y enganchar a los estudiantes. Desgraciadamente, hay una tendencia en los cursos de introducci´on a la programaci´on a prestar demasiada atenci ´on a la abstracci´on matem´atica que hace que los estudiantes se frustren con problemas farragosos relacionados con detalles nimios de la sin- taxis, compilacio´n, y la aplicaci´on de reglas aparentemente arcanas. Aunque tal abstraci´on y formalismo son importantes para ingenieros profesionales de la programaci´on y estudiantes que planean continuar sus estudios de inform´atica, decidirse por este enfoque en un curso introductorio s´olo tiene ´exito en hacer aburrida la inform´atica. Cuando imparto un curso, no quiero tener un aula de estudiantes sin inspiraci´on. Quisiera verlos intentando resolver problemas in- teresantes, explorando ideas diferentes, probando enfoques no convencionales,
  • 6. vi Pr´ologo rompiendo las reglas, y aprendiendo de sus errores. Al hacerlo, no quiero perder la mitad del semestre tratando de sortear problemas con una sintaxis abstru- sa, mensajes de error del compilador incomprensibles, o los varios cientos de maneras que un programa puede generar un error de proteci´on general. Una de las razones por las que me gusta Python es por que proporciona un equi- librio muy bueno entre lo pr´actico y lo conceptual. Puesto que Python es un lenguaje interpretado, los principiantes pueden tomar el lenguaje y empezar a hacer cosas interesantes casi inmediato, sin perderse el los problemas de compila- cio´n y enlazado. Adem´as, Python viene con una gran biblioteca de modulos que se pueden usar para hacer toda clase de tareas que abarcan desde programaci´on para web a gr´aficos. Este enfoque pr´actico es una buena manera de enganchar a estudiantes y permite que completen proyectos significativos. Sin embargo, Pyt- hon tambi´en puede servir como una base excelente para intruducir conceptos importantes de inform´atica. Puesto que Python soporta completamente proce- dimientos y clases, los estudiantes pueden introducirse gradualmente en temas como abstracci´an procedural, estructuras de datos, y programaci´on orientada objetos, que son aplicables a cursos posteriores en Java o C++. Python incluso toma prestada cierta cantidad de caracter ´ısticas de lenguajes de programaci´on funcionales y puede usarse para intruducir conceptos que pudieran ser cubiertos en mas detalle en cursos de Scheme o Lisp. Leendo, el prefacio de Jeffrey, me sorprenden sus comentarios sobre que Pyt- hon le permite ver un “m´as alto nivel de ´exito y un bajo nivel de frustraci´on que puede “avanzar r´apido con mejores resultados”. Aunque estos comentarios se refieren a sus cursos introductorios, a veces uso Python por estas mismas razones en cursos de inform´atica avanzada en la Universidad de Chicago. En estos cursos me enfrento constantemente con la desalentadora tarea de cubrir un mont´on de material dif´ıcil en un agotador trimestre de nueve semanas. Aun- que es ciertamente posible para m´ı infligir mucho dolor y sufrimiento usando un lenguaje como C++, he visto a menudo que ese estilo es ineficaz, especialmente cuando el curso se trata de un asunto sin relacio´n apenas con la “programaci´on”. Encuentro que usar Python me permite dedicarme mas al asunto en cuestio´n mientras permito a los estudiantes completar proyectos u´tiles. Aunque Python es todav´ıa un lenguaje joven y en desarollo, creo que tiene un futuro brillante en la educacio´n. Este libro es un paso importante en esa direcci´on. David Beazley Universidad de Chicago Autor de Python Essential Reference
  • 7. Prefacio Por Jeff Elkner Este libro debe su existencia a la colaboraci´on hecha posible por la Internet y al movimiento de software libre. Sus tres autores, un profesor universitario, un profesor de instituto y un programador profesional, todav´ıa tienen que conocerse cara a cara, pero hemos sido capaces de colaborar estrechamente y hemos reci- bido la ayuda de mucha gente maravillosa que han donado su tiempo y esfuerzo para ayudar a mejorar este libro. Creemos que este libro es un testamento a los beneficios y futuras posibilidades de este tipo de colaboraci´on, cuyo marco han establecido Richard Stallman y la Free Software Foundation. Co´mo y por qu´e vine a usar Python En 1999, el examen de Advanced Placement (AP) de Ciencias de la Computa- cio´n del Claustro Escolar se realizo´ por primera vez en C++. Como en muchos institutos en todo el pa´ıs, la decisi´on de cambiar de lenguaje tuvo un impacto directo sobre el curriculum de inform´atica en el Insituto de Yorktown en Ar- lington, Virgina, donde doy clase. Hasta ese momento, el lenguaje de ensen˜anza era Pascal tanto en nuestro curso de primer an˜o como en el AP. Al seguir con la pr´actica anterior de dar a los estudiantes dos an˜os de exposici´on al mismo lenguaje, tomamos la decisi´on de cambiar a C++ en el aula de primer an˜o del curso 1997-98 de modo que estar´ıamos en sinton´ıa con el cambio del Claustro Escolar para el curso AP del an˜o siguiente. Dos an˜os m´as tarde, me convenc´ı de que C++ era una mala eleccio´n para iniciar a los estudiantes en la inform´atica. Aunque es un lenguaje de programaci´on muy poderoso, tambi´en es extremadamente dif´ıcil de aprender y ensen˜ar. Me encontr´e luchando constantemente con la dif´ıcil sintaxis de C++ y sus mu ´ltiples formas de hacer las cosas, y como consecuencia perd´ıa muchos estudiantes sin
  • 8. Prefacio vii i necesidad. Convencido de que deb´ıa de haber una eleccio´n mejor para el lenguaje de nuestro curso de primer an˜o, me puse a buscar una alternativa para C++. Necesitaba un lenguaje que funcionase tanto en las maquinas de nuestro labo- ratorio de Linux como en las plataformas Windows y Macintosh que la mayor´ıa de los estudiantes ten´ıan en casa. Quer´ıa que fuera de c´odigo abierto, para que los estudiantes pudieran usarlo en casa sin importar su nivel econo´mico. Quer´ıa un lenguaje utilizado por programadores profesionales, y que tuviera una co- munidad activa de desarrolladores a su alrededor. Ten´ıa que soportar tanto la programaci´on procedural como la orientada a objetos. Y lo mas importante, ten´ıa que ser f´acil de aprender y de ensen˜ar. Cuando investigu´e las opciones con estos obejetivos en mente, Python destac´o como el mejor candidato. Ped´ı a uno de los estudiantes m´as talentosos de Yorktown, Matt Ahrens, que probase Python. En dos meses, no s´olo hab´ıa aprendido el lenguaje, sino que escribio´ una aplicaci´on llamada pyTicket que permit´ıa a nuestro personal infor- mar de problemas tecnol´ogicos via Web. Sab´ıa que Matt no pod´ıa terminar una aplicaci´on de tal escala en tan poco tiempo con C++, y este logro, combinado con la positiva valoraci´on de Python por parte de Matt, suger´ıa que Python era la solucio´n que buscaba. Encontrar un libro de texto Una vez decidido a usar Python tanto en mis clases de inform´atica basica como en el an˜o siguiente, el problema m´as acuciante era la falta de un libro de texto disponible. El contenido libre vino al rescate. Anteriormente en ese an˜o, Richard Stallman me present´o a Allen Downey. Ambos hab´ıamos escrito a Richard expresando nuestro inter´es en desarrollar conenidos educativos libres. Allen ya hab´ıa escrito un libro de texto de inform´atica de primer an˜o, How to Think Like a Com- puter Scientist. Cuando le´ı ese libro, supe inmediatamente que quer´ıa usarlo en mi clase. Era el libro de inform´atica mas claro y pr ´actico que hab´ıa visto. Pon´ıa el ´enfasis en los procesos de pensamiento involucrados en la programaci´on m´as que en las caracter´ısticas de un lenguaje en particular. Su lectura me hizo inmediatamente un maestro mejor. How to Think Like a Computer Scientist no era s´olo un libro excelente, sino que se public´o bajo la licencia pu´blica GNU, lo que significaba que pod´ıa usarse y modificarse libremente para ajustarse a las necesidades de su usuario. Una vez que decid´ı usar Python, se me ocurrio´ que podr´ıa traducir la versio´n original en Java del libro de Allen al nuevo lenguaje. Aunque no hubiera sido capaz de escribir un libro de texto por mi cuenta, tener el libro de Allen para trabajar a
  • 9. i partir de ´el me hizo posible hacerlo, mostrando al mismo tiempo que el modelo cooperativo de desarrollo que tan buenos resultados hab´ıa dado en el software pod´ıa funcionar tambi´en para el contenido educativo. El trabajo en este libro durante los dos u´ltimos an˜os ha sido gratificante para mis estudiantes y para m´ı, y mis estudiantes desempen˜aron un importante papel en el proceso. Como pod´ıa hacer cambios instantaneos cuando alguien encontraba un error ortogr´afico o un pasaje dif´ıcil, los anim´e a buscar errores en el libro d´andoles un punto extra cada vez que hac´ıan una sugerencia que terminaba como un cambio en el texto. Esto tuvo el doble beneficio de animarlos a leer el texto con mas atenci´on y tener el texto revisado en profundidad por sus cr´ıticos m´as importantes: los estudiantes que lo usan para aprender inform ´atica. Para la segunda mitad del libro, acerca de la programaci´on orientada a objetos, sab´ıa que necesitar´ıa a alguien con m´as experiencia real en programaci´on de la que yo ten´ıa para hacerlo bien. El libro se estanco´ en un estado inacabado durante buena parte de un an˜o hasta que la comunidad de c´odigo abierto de nuevo proporcion´o los medios necesarios para su terminaci´on. Recib´ı un correo electro´nico de Chris Meyers expresando su inter´es en el li- bro. Chris es un programador profesional que empez´o a impartir un curso de programaci´on con Python el an˜o pasado en el Colegio de Lane Community, en Eugene, Oregon. La perspectiva de impartir el curso llev´o a Chris has- ta el libro, y empez´o a colaborar con ´el inmediatamente. Hacia el final del an˜o escolar hab´ıa creado un proyecto complementario en nuesto sitio web en http://www.ibiblio.org/obp llamado Python for Fun y estaba trabajando con algunos de mis estudiantes aventajados como profesor magistral, dirigi´endo- les m ´as all´a de donde yo pod´ıa llevarles. Presentando la programaci´on con Python El proceso de traducir y usar How to Think Like a Computer Scientist duran- te los dos u´ltimos an˜os ha confirmado la idoneidad de Python para ensen˜ar a estudiantes principiantes. Python simplifica enormemente los ejemplos de pro- gramaci´on y facilita la ensen˜anza de los conceptos importantes en programaci´on.
  • 10. { } x Prefacio El primer ejemplo del texto ilustra esta cuestio´n. Es el tradicional programa “hola, mundo”, que en la versio´n C++ del libro es as´ı: #include <iostream.h> void main() { cout << "Hola, mundo" << endl; } en la versio´n Python se convierte en: print "Hola, Mundo" Aunque es un ejemplo trivial, destacan las ventajas de Python. El curso de Informa´tica I en Yorktown no tiene prerrequisitos, as´ı que muchos de los estu- diantes que ven este ejemplo esta´n mirando su primer programa. Algunos de ellos esta´n sin duda un poco nerviosos, tras haber o´ıdo que programar compu- tadores es algo dif´ıcil de aprender. La versio´n C++ siempre me ha obligado a elegir entre dos opciones insatisfactorias: explicar las sentencias #include, void main(), , y y arriesgarme a confundir o intimidar a algunos estudiantes desde el principio, o decirles “No te preocupes de todo eso ahora, hablaremos de ello m´as tarde”, y arriesgarme a lo mismo. Los objetivos educativos en este momento del curso son exponer a los estudiantes a la idea de una sentencia de progra- maci´on y llevarles a escribir su primer programa, presentandoles de esta forma el entorno de programaci´on. La programaci´on con Python tiene exactamente lo que necesito para hacer estas cosas, y nada mas. La comparacio´n del texto explicativo de este programa para cada versio´n del libro ilustra mejor lo que esto significa para los estudiantes principiantes. Hay trece p´arrafos de explicacio´n de “¡Hola, mundo!” en la versio´n C++. En la versio´n Python s´olo hay dos. Au´n m´as importante: los once parrafos que faltan no tocan las “grandes ideas” de la programaci´on de computadores, sino las minucias de la sintaxis de C++. Encontr´e que esto mismo suced´ıa por todo el libro. P´arrafos enteros desapareciendo de la versio´n Python del texto porque la sintaxis clara de Python los hace innecesarios. El uso de un lenguaje de muy alto nivel como Python permite que el profesor deje para m´as tarde hablar sobre los detalles de bajo nivel de la maquina hasta que los estudiantes tengan el fondo necesario para entender los detalles. De este modo crea la habilidad de poner pedago´gicamente “antes lo primero”. Uno de los mejores ejemplos de ello es la manera en la cual Python maneja las variables. En C++ una variable es un nombre para un lugar que contiene una cosa. Las variables deben declararse segu´n su tipo en parte porque el taman˜o del lugar al que apuntan tiene que determinarse de antemano. As´ı, la idea de una variable esta´ ligada al hardware de la m´aquina. El concepto poderoso y fundamental de
  • 11. x lo que es una variable ya es suficientemente dif´ıcil para estudiantes principiantes (tanto de inform´atica como de a´lgebra). Octetos y direcciones no ayudan a la comprensio´n. En Python una variable es un nombre que sen˜ala una cosa. Este es un concepto mucho m ´as intuitivo para estudiantes principiantes y esta´ mas cerca del significado de “variable” que aprendieron en su clase de matem ´aticas. Este an˜o tuve muchas menos dificultades ensen˜ando lo que son las variables que en el anterior, y pas´e menos tiempo ayud´andoles con los problemas derivados de su uso. Otro ejemplo de c´omo Python ayuda en la ensen˜anza y aprendizaje de la pro- gramaci´on es en su sintaxis para las funciones. Mis estudiantes siempre han tenido una gran dificultad comprendiendo las funciones. El problema principal se centra alrededor de la diferencia entre la definici´on de una funci´on y la llama- da a una funci´on, y la distinci´on asociada entre un par ´ametro y un argumento. Python viene al rescate con una sintaxis a la que no le falta belleza. La defini- cio´n de una funci´on empieza con la palabra clave def, y simplemente digo a mis estudiantes: “cuando definas una funci´on, empieza con def, seguido del nombre de la funci´on que est´es definiendo; cuando llames a una funci´on, simplemente di (escribe) su nombre”. Los par´ametros van con las definiciones; los argumentos con las llamadas. No hay tipo de retorno, tipos de par´ametros, o par´ametro por referencia y valor de por medio, por lo que ahora soy capaz de ensen˜ar funciones en la mitad de tiempo que antes, con mejor comprensio´n. El uso de Python ha mejorado la eficacia de nuestro programa de inform´atica para todos los estudiantes. Veo un mayor nivel general de ´exito y un menor nivel de frustraci´on del que experiment´e durante los dos an˜os que ensen˜´e C++. Avanzo m ´as r´apido con mejores resultados. Mas estudiantes terminan el curso con la habilidad de crear programas u´tiles y con la actitud positiva hacia la experiencia de programaci´on que esto engendra. Formar una comunidad He recibido correos electro´nicos de todos los rincones del planeta de parte de gente que usa este libro para aprender o enesen˜ar a programar. Ha em- pezando a surgir una comunidad de usuarios, y muchas personas han contri- buido al proyecto mandando materiales a trav´es del sitio web complementario http://www.thinkpython.com. Con la publicaci´on de este libro en forma impresa, espero que continue y se acelere el crecimiento de la comunidad de usuarios. La emergencia de esta co- munidad de usuarios y la posibilidad que sugiere para colaboraciones similares entre educadores han sido para m´ı las partes mas excitantes de trabajar en este proyecto. Trabajando juntos, podemos incrementar la calidad de los materiales
  • 12. xii Prefacio disponibles para nuestro uso y ahorrar un tiempo valioso. Les invito a unirse a nuestra comunidad y espero con impaciencia saber algo de ustedes. Por favor, escriban a los autores a feedback@thinkpython.com. Jeffrey Elkner Escuela Secundaria Yortown Arlington, Virginia
  • 13. Lista de Colaboradores Parafraseando la filosof´ıa de la Free Software Foundation, este libro es libre como la libre expresio´n, pero no necesariamente gratis como la pizza gratis. Se hizo realidad a causa de una colaboraci´on que no habr´ıa sido posible sin la GNU Free Documentation License. As´ı que queremos agradecer a la Free Software Foundation por desarrollar esta licencia y, por supuesto, ponerla a nuestra disposici´on. Tambi´en nos gustar´ıa dar las gracias a los mas de cien lectores de aguda vista que se han preocupado de enviarnos sugerencias y correcciones en los dos u´lti- mos an˜os. Siguiendo el esp´ıritu del software libre, decidimos expresar nuestra gratitud en la forma de una lista de colaboradores. Desgraciadamente, esta listo no esta´ completa, pero hacemos lo que podemos para mantenerla actualizada. Si se toma el tiempo de echar un vistazo a la lista, ver ´a que cada una de las personas que aparecen le ha ahorrado a usted y a los lectores que le sucedan la confusio´n de un error t ´ecnico o una explicacio´n poco clara simplemente en- vi´andonos una nota. Pos imposible que parezca tras tantas correcciones, todav´ıa puede haber errores en el libro. Si se encontrara con una, esperamos que se tome un minuto para ponerse en contacto con nosotros. La direcci´on de correo es feedback@thinkpython.com. Si cambiamos algo a partir de su sugerencia, apa- recer´a en la siguiente versio´n de la lista de colaboradores (a no ser que pida quedar omitido). ¡Gracias! Lloyd Hugh Allen envi´o una correccio´n de la Secci´on 8.4. Yvon Boulianne envi´o una correccio´n de un error sema´ntico en el Cap´ıtulo 5. Fred Bremmer comunic´o una correccio´n de la Secci´on 2.1. Jonah Cohen escribio´ los scripts en Perl para convertir la fuente LATEX del libro en hermoso HTML.
  • 14. Lista de x Michael Conlon envi´o una correccio´n gramatical del Cap ´ıtulo 2 y una mejora del estilo del Cap´ıtulo 1, e inici´o una discusi´on sobre aspectos t´ecnicos de los int´erpretes. Benoit Girard envi´o una correccio´n de un divertido error de la Secci´on 5.6. Courtney Gleason y Katherine Smith escribieron horsebet.py, que se us´o como un caso de estudio en una versio´n temprana del libro. Su pro- grama puede encontrarse en el sitio web. Lee Harr comunic´o m´as correcciones de las que tenemos sitio para enume- rar aqu´ı, y de verdad deber´ıa aparecer como uno de los principales editores del texto. James Kaylin es un estudiante que us´o el texto. Envio´ciones. numerosas correc- David Kershaw arregl´o la funci´on catTwice que no funcionaba en la Sec- cio´n 3.10. Eddie Lam ha enviado numerosas correcciones de los Cap ´ıtulos 1, 2 y 3. Tambi´en arregl´o el Makefile de forma que crea un ´ındice la primera vez que se ejecuta y nos ayud´o a preparar un esquema de versiones. Man-Yong Lee envi´o una correccio´n del c´odigo de ejemplo de la Secci´on 2.4. David Mayo sen˜al´o que la palabra “unconscientemente”en el Cap´ıtulo 1 deb´ıa cambiarse por “subconscientemente”. Chris McAloon envi´o varias correciones de las Secciones 3.9 y 3.10. Matthew J. Moelter ha sido un colaborador durante mucho tiempo y ha enviado numerosas correcciones y sugerencias. Simon Dicon Montford inform´o de una definici´on de funci ´on faltante y varios errores tipogr´aficos en el Cap´ıtulo 3. Tambi´en encontr´o errores en la funci´on incrementa del Cap ´ıtulo 13. John Ouzts corrigio´ la definici´on de “valor de retorno”del Cap´ıtulo 3. Kevin Parks envi´o valiosos comentarios y sugerencias acerca de c´omo me- jorar la distribuci´on del libro. David Pool envi´o un error tipogr´afico en el glosario del Cap´ıtulo 1, y tambi´en amables palabras de a´nimo.
  • 15. xv Michael Schmitt envi´o una correccio´n del Cap´ıtulo sobre archivos y excep- ciones. Robin Shaw sen˜al´o un error en la Secci´on 13.1, donde la funci´on impri- meHora se usaba en un ejemplo sin haberla definido. Paul Sleigh encontr´o un error en el Cap´ıtulo 7 y un error en el script Perl de Jonah Cohen que, a partir de LATEX genera, el HTML. Craig T. Snydal esta´ poniendo a prueba el texto en un curso en la Uni- versidad de Drew. Ha contribuido con varias sugerencias y correcciones de importancia. Ian Thomas y sus estudiantes usan el texto en un curso de programaci´on. Son los primeros en probar los Cap´ıtulos de la segunda mitad del libro, y han hecho numerosas correcciones y sugerencias. Keith Verheyden envi´o una correccio´n del Cap´ıtulo 3. Peter Winstanley nos hizo saber de un persistente error en nuestro lat´ın del Cap´ıtulo 3. Chris Wrobel hizo correcciones al c´odigo del Cap´ıtulo sobre E/S de archi- vos y excepciones. Moshe Zadka ha hecho contribuciones inestimables al proyecto. Adem´as de escribir el primer borrador del Cap´ıtulo sobre diccionarios, propor- ciono´ una gu´ıa continuada en las primeras etapas del libro. Christoph Zwerschke envi´o varias correcciones y sugerencias pedago´gicas, y explico´ la diferencia entre gleich y selbe. James Mayer envi´o un cargamento de errores tipogr´aficos y ortogr´aficos, incluyendo dos en la lista de colaboradores. Hayden McAfee pill´o una inconsistencia potencialmente confusa entre dos ejemplos. A´ngel Arnal es parte de un equipo internacional de traductores que tra- bajan en la versio´n en espan˜ol del texto. Tambi´en ha encontrado varios errores en la versio´n inglesa. Tauhidul Hoque y Lex Berezhny crearon las ilustraciones del Cap´ıtulo 1 y mejoraron muchas de las otras ilustraciones. Dr. Michele Alzetta pill´o un error en el Cap´ıtulo 8 y envio´ varios comen- tarios y sugerencias pedago´gicas interesantes sobre Fibonacci y La Mona.
  • 16. Lista de x Andy Mitchell pill´o un error tipogr´afico en el Cap´ıtulo 1 y un ejemplo err´oneo en el Cap´ıtulo 2. Kalin Harvey sugirio´ una clarificacio´n al Cap´ıtulo 7 y detect´o varios errores tipogr´aficos. Christopher P. Smith encontr´o varios errores tipogr´aficos y nos esta´ ayu- dando a preparar la actualizaci´on del libro para Python 2.2. David Hutchins pill´o un error tipogr´afico en el Pr´ologo. Gregor Lingl ensen˜a Python en un instituto de Viena, Austria. Est´a tra- bajando en una traducci´on del libro al alem ´an, y pillo´ graves en el Cap´ıtulo 5. un par de errores Julie Peters encontr´o un error tipogr´afico en el Prefacio.
  • 17. ´Indice general Pr´ologo Prefacio Lista de Colaboradores 1. El Camino del Programa v vii xiii 1 1.1. El lenguaje de programaci´on Python . . . . . . . . . . . . . . . 1 1.2. ¿Qu´e es un programa? . . . . . . . . . . . . . . . . . . . . . . . 3 1.3. ¿Qu´e es la depuraci´on (debugging)? . . . . . . . . . . . . . . . . 4 1.4. Lenguajes formales y lenguajes naturales . . . . . . . . . . . . . 6 1.5. El primer programa . . . . . . . . . . . . . . . . . . . . . . . . . 8 1.6. Glosario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9 2. Variables, expresiones y sentencias 11 2.1. Valores y tipos . . . . . . . . . . . . . . . . . . . . . . . . . . . 11 2.2. Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12 2.3. Nombres de variables y palabras reservadas . . . . . . . . . . . 13 2.4. Sentencias . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14 2.5. Evaluar expresiones . . . . . . . . . . . . . . . . . . . . . . . . . 15
  • 18. Lista de x 2.6. Operadores y expresiones . . . . . . . . . . . . . . . . . . . . . 16
  • 19. ´Indice x 2.7. El orden de las operaciones..............................................17 2.8. Las operaciones sobre cadenas........................................17 2.9. Composici´on .....................................................................................18 2.10. Los comentarios................................................................19 2.11. Glosario.............................................................................19 3. Funciones 21 3.1. Llamadas a funciones.......................................................21 3.2. Conversio´n de tipos .......................................................................22 3.3. Coercio´n de tipos ............................................................................22 3.4. Funciones matem´aticas ................................................................23 3.5. Composici´on .....................................................................................24 3.6. An˜adir funciones nuevas ..............................................................24 3.7. Las definiciones y el uso...................................................26 3.8. Flujo de ejecucio´n ..........................................................................27 3.9. Par´ametros y argumentos ...........................................................28 3.10. Las variables y los par´ametros son locales ..........................29 3.11. Diagramas de pila.............................................................30 3.12. Funciones con resultado...................................................31 3.13. Glosario.............................................................................32 4. Condicionales y recursividad 35 4.1. El operador m´odulo .......................................................................35 4.2. Expresiones booleanas.....................................................36 4.3. Operadores l´ogicos ........................................................................36 4.4. Ejecuci´on condicional ...................................................................37 4.5. Ejecuci´on alternativa .....................................................................37 4.6. Condiciones encadenadas.................................................38
  • 20. ´Indice xi 4.7. Condiciones anidadas.......................................................39 4.8. La sentencia return................................................................40 4.9. Recursividad.....................................................................40 4.10. Diagramas de pila para funciones recursivas..................42 4.11. Recursividad infinita.........................................................43 4.12. Entrada por teclado..........................................................44 4.13. Glosario.............................................................................45 5. Funciones productivas 47 5.1. Valores de retorno.............................................................47 5.2. Desarrollo de programas..................................................48 5.3. Composici´on .....................................................................................51 5.4. Funciones booleanas.........................................................52 5.5. Mas recursividad..............................................................53 5.6. Acto de fe..........................................................................55 5.7. Un ejemplo m´as ...............................................................................56 5.8. Comprobaci´on de tipos .................................................................57 5.9. Glosario.............................................................................58 6. Iteracio´n 61 6.1. Asignaci´on mu´ltiple .....................................................................61 6.2. La sentencia while.................................................................62 6.3. Tablas................................................................................64 6.4. Tablas de dos dimensiones...............................................66 6.5. Encapsulado y generalizaci´on ....................................................67 6.6. Mas encapsulacio´n .........................................................................68 6.7. Variables locales...............................................................69 6.8. Mas generalizaci´on ........................................................................70 6.9. Funciones..........................................................................71 6.10. Glosario.............................................................................72
  • 21. ´Indice x 7. Cadenas 75 7.1. Un tipo de datos compuesto . . . . . . . . . . . . . . . . . . . . 75 7.2. Longitud . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 76 7.3. Recorrido y el bucle for . . . . . . . . . . . . . . . . . . . . . . 76 7.4. Porciones de cadenas . . . . . . . . . . . . . . . . . . . . . . . . 78 7.5. Comparaci´on de cadenas . . . . . . . . . . . . . . . . . . . . . . 78 7.6. Las cadenas son inmutables . . . . . . . . . . . . . . . . . . . . 79 7.7. Una funci´on “encuentra” . . . . . . . . . . . . . . . . . . . . . . 80 7.8. Bucles y conteo . . . . . . . . . . . . . . . . . . . . . . . . . . . 80 7.9. El m´odulo “string” . . . . . . . . . . . . . . . . . . . . . . . . . 81 7.10. Clasificaci´on de caracteres . . . . . . . . . . . . . . . . . . . . . 82 7.11. Glosario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 83 8. Listas 85 8.1. Valores de una lista . . . . . . . . . . . . . . . . . . . . . . . . . 85 8.2. Acceso a los elementos . . . . . . . . . . . . . . . . . . . . . . . 86 8.3. Longitud (taman˜o) de una lista . . . . . . . . . . . . . . . . . . 87 8.4. Pertenencia a una lista . . . . . . . . . . . . . . . . . . . . . . . 88 8.5. Listas y bucles for . . . . . . . . . . . . . . . . . . . . . . . . . 88 8.6. Operaciones con listas . . . . . . . . . . . . . . . . . . . . . . . 89 8.7. Porciones (slices) . . . . . . . . . . . . . . . . . . . . . . . . . . 90 8.8. Las listas son mutables . . . . . . . . . . . . . . . . . . . . . . . 90 8.9. Borrado en una lista . . . . . . . . . . . . . . . . . . . . . . . . 91 8.10. Objetos y valores . . . . . . . . . . . . . . . . . . . . . . . . . . 91 8.11. Alias (poner sobrenombres) . . . . . . . . . . . . . . . . . . . . 92 8.12. Clonar listas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 93 8.13. Listas como par´ameteros . . . . . . . . . . . . . . . . . . . 94
  • 22. ´Indice x . . . 8.14. Listas anidadas . . . . . . . . . . . . . . . . . . . . . . . . . . . 95
  • 23. ´Indice x 8.15. Matrices............................................................................95 8.16. Cadenas y listas................................................................96 8.17. Glosario.............................................................................97 9. Tuplas 99 9.1. Mutabilidad y tuplas.........................................................99 9.2. Asignaci´on de tuplas ...................................................................100 9.3. Tuplas como valor de retorno.........................................101 9.4. Nu´meros aleatorios .....................................................................101 9.5. Lista de nu´meros aleatorios .....................................................102 9.6. Conteo.............................................................................103 9.7. Muchos baldes................................................................104 9.8. Una solucio´n en una sola pasada ...........................................106 9.9. Glosario...........................................................................107 10. Diccionarios 109 10.1. Operaciones sobre diccionarios......................................110 10.2. M´etodos del diccionario ............................................................111 10.3. Asignaci´on de alias y copiado ..................................................112 10.4. Matrices dispersas..........................................................112 10.5. Pistas...............................................................................113 10.6. Enteros largos.................................................................115 10.7. Contar letras...................................................................116 10.8. Glosario...........................................................................116 11. Archivos y excepciones 119 11.1. Archivos de texto.............................................................121 11.2. Escribir variables............................................................123
  • 24. ´Indice x 11.3. Directorios......................................................................125 11.4. Encurtido........................................................................125 11.5. Excepciones....................................................................126 11.6. Glosario...........................................................................128 12. Clases y objetos 131 12.1. Tipos compuestos definidos por el usuario....................131 12.2. Atributos.........................................................................132 12.3. Instancias como par´ametro ......................................................133 12.4. Mismidad........................................................................134 12.5. Recta´ngulos ....................................................................................135 12.6. Instancias como valores de retorno................................136 12.7. Los objetos son mudables...............................................136 12.8. Copiado...........................................................................137 12.9. Glosario...........................................................................139 13. Clases y funciones 141 13.1. Hora................................................................................141 13.2. Funciones puras..............................................................142 13.3. Modificadores.................................................................143 13.4. ¿Qu´e es mejor? ..............................................................................144 13.5. Desarrollo de prototipos frente a planificaci´on ................145 13.6. Generalizacio´n ..............................................................................146 13.7. Algoritmos.......................................................................146 13.8. Glosario...........................................................................147 14. Clases y m´etodos 149 14.1. Caracter´ısticas de la orientaci´on a objetos ......................149
  • 25. ´Indice x 14.2. imprimeHora...............................................................................150 14.3. Otro ejemplo...................................................................151 14.4. Un ejemplo m´as complicado ....................................................152 14.5. Argumentos opcionales...................................................153 14.6. El m´etodo de inicializaci´on .....................................................154 14.7. Revisio´n de los Puntos ...............................................................155 14.8. Sobrecarga de operadores..............................................156 14.9. Polimorfismo...................................................................158 14.10.................................................................................Glosario 160 15. Conjuntos de objetos 161 15.1. Composici´on ...................................................................................161 15.2. Objetos Carta............................................................................161 15.3. Atributos de clase y el m´etodo.......................................str 163 15.4. Comparaci´on de naipes .............................................................164 15.5. Mazos de naipes..............................................................165 15.6. Impresio´n del mazo de naipes ................................................166 15.7. Barajar el mazo...............................................................167 15.8. Eliminaci´on y reparto de los naipes ......................................168 15.9. Glosario...........................................................................169 16. Herencia 171 16.1. Herencia..........................................................................171 16.2. Una mano de cartas........................................................172 16.3. El reparto de los naipes..................................................173 16.4. Mostremos la mano........................................................174 16.5. La clase JuegoDeCartas...........................................................175
  • 26. ´Indice x 16.6. La clase ManoDeLaMona.........................................................176 16.7. La clase JuegoDeLaMona........................................................177 16.8. Glosario...........................................................................181
  • 27. ´Indice x 17.Listas enlazadas 183 17.1. Referencias incrustadas . . . . . . . . . . . . . . . . . . . . . . . 18 3 17.2. La clase Nodo . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 3 17.3. Listas como colecciones . . . . . . . . . . . . . . . . . . . . . . . 18 5 17.4. Listas y recursividad . . . . . . . . . . . . . . . . . . . . . . . . 18 6 17.5. Listas infinitas . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 7 17.6. Teorema fundamental de la ambigu¨edad . . . . . . . . . . . . . 18 8 17.7. Modificar listas . . . . . . . . . . . . . . . . . . . . . . . . . . . 18 9 17.8. Envoltorios y ayudantes . . . . . . . . . . . . . . . . . . . . . . 19 0 17.9. La clase ListaEnlazada . . . . . . . . . . . . . . . . . . . . . . 19 0 17.10. Invariantes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 1 17.11. Glosario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 2 18.Pilas 195 18.1. Tipos abstractos de datos . . . . . . . . . . . . . . . . . . . . . 19 5 18.2. El TAD Pila . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 6 18.3. C´omo implementar pilas con listas de Python . . . . . . . . . . 19 6 18.4. Uso de push y pop . . . . . . . . . . . . . . . . . . . . . . . . . 19
  • 28. ´Indice x 7 18.5. Usar una pila para evaluar postfijo . . . . . . . . . . . . . . . . 19 8 18.6. An´alisis sint´actico . . . . . . . . . . . . . . . . . . . . . . . . . . 19 9 18.7. Evaluar un postfijo . . . . . . . . . . . . . . . . . . . . . . . . . 19 9 18.8. Clientes y proveedores . . . . . . . . . . . . . . . . . . . . . . . 20 0 18.9. Glosario . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 1 19.Colas 203 19.1. El TAD Cola . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 3 19.2. Cola Enlazada . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20 4 19.3. Rendimiento t´ıpico . . . . . . . . . . . . . . . . . . . . . . . . . 20 5
  • 29. ´Indice x 19.4. Cola Enlazada Mejorada.................................................205 19.5. Cola priorizada................................................................207 19.6. La clase Golfista..............................................................209 19.7. Glosario...........................................................................210 20. A´rboles 211 20.1. Crear a´rboles .................................................................................212 20.2. Recorrer a´rboles ..........................................................................213 20.3. A´rboles de expresio´n ................................................................213 20.4. Recorrido de un a´rbol ................................................................214 20.5. Construir un a´rbol de expresio´n ..........................................216 20.6. Manejar errores..............................................................220 20.7. El a´rbol de animales ...................................................................221 20.8. Glosario...........................................................................224 A. Depuraci´on 225 A.1. Errores de sintaxis..........................................................225 A.2. Errores en tiempo de ejecucio´n .............................................227 A.3. Errores sema´nticos ......................................................................231 B. Crear un nuevo tipo de datos 235 B.1. Multiplicaci´on de fracciones ....................................................236 B.2. Suma de fracciones.........................................................237 B.3. Algoritmo de Euclides.....................................................238 B.4. Comparar fracciones......................................................239 B.5. Forzando la m´aquina ..................................................................240 B.6. Glosario...........................................................................241
  • 30. ´Indice x C. Listados Completos de Python 243 C.1. Clase Punto.....................................................................243 C.2. Clase Hora......................................................................244 C.3. Cartas, mazos y juegos...................................................245 C.4. Lists Enlazadas...............................................................249 C.5. Clase Pila........................................................................250 C.6. Colas y colas priorizadas................................................251 C.7. A´rboles .............................................................................................253 C.8. A´rboles de expresio´n ................................................................254 C.9. Adivina el animal............................................................255 C.10. Fraction class...........................................................256 D. Lecturas recomendadas 259 D.1. Libros y sitios web sobre Python....................................260 D.2. Libros recomendados sobre inform´atica en general ......261 E. GNU Free Documentation License 263 E.1. Applicability and Definitions...........................................264 E.2. Verbatim Copying...........................................................265 E.3. Copying in Quantity........................................................265 E.4. Modifications..................................................................266 E.5. Combining Documents....................................................268 E.6. Collections of Documents...............................................269 E.7. Aggregation with Independent Works............................269 E.8. Translation......................................................................269 E.9. Termination.....................................................................270 E.10. Future Revisions of This License....................................270 E.11. Addendum: How to Use This License for Your Documents270
  • 31. ´Indice x Cap´ıtulo 1 El Camino del Programa El objetivo de este libro es ensen˜arle a pensar como lo hacen los cient´ıficos inform´aticos. Esta manera de pensar combina las mejores caracter´ısticas de la matem´atica, la ingenier´ıa, y las ciencias naturales. Como los matem´aticos, los cient´ıficos inform ´aticos usan lenguajes formales para designar ideas (espec´ıfica- mente, computaciones). Como los ingenieros, ellos disen˜an cosas, ensamblando sistemas a partir de componentes y evaluando ventajas y desventajas de cada una de las alternativas. Como los cient´ıficos, ellos observan el comportamiento de sistemas complejos, forman hip´otesis, y prueban sus predicciones. La habilidad m´as importante del cient´ıfico inform´atico es la soluci´on de pro- blemas. La solucio´n de problemas incluye poder formular problemas, pensar en la solucio´n de manera creativa, y expresar una solucio´n con claridad y precisi´on. Como se ver´a, el proceso de aprender a programar es la oportunidad perfecta para desarrollar la habilidad de resolver problemas. Por esa razo´n este cap´ıtulo se llama “El Camino del programa”. A cierto nivel, usted aprender´a a programar, lo cual es una habilidad muy u´til por s´ı misma. A otro nivel, usted utilizar´a la programaci´on para obtener algu´n resultado. Ese resultado se ver ´a m´as claramente durante el proceso. 1.1. El lenguaje de programaci´on Python El lenguaje de programaci´on que aprender´a es Python. Python es un ejemplar de un lenguaje de alto nivel; otros ejemplos de lenguajes de alto nivel son C, C++, Perl y Java.
  • 32. 2 El Camino del CODIGO FUENTE INTERPRETER SALIDA CODIGO FUENTE COMPILADOR SALIDA EJECUTOR CODIGO OBJETO Como se puede deducir de la nomenclatura “lenguaje de alto nivel”, tambi´en existen lenguajes de bajo nivel, a los que tambi ´en se califica como lengua- jes de m´aquina o lenguajes ensambladores. A prop´osito, los computadores s´olo ejecutan programas escritos en lenguajes de bajo nivel. Los programas de alto nivel tienen que traducirse antes de ejecutarse. Esta traducci ´on lleva tiempo, lo cual es una pequen˜a desventaja de los lenguajes de alto nivel. Aun as´ı las ventajas son enormes. En primer lugar, la programaci ´on en lenguajes de alto nivel es mucho m´as f´acil; escribir programas en un lenguaje de alto nivel toma menos tiempo, los programas son m´as cortos y mas faciles de leer, y es mas probable que estos programas sean correctos. En segundo lugar, los lenguajes de alto nivel son portables, lo que significa que pueden ejecutarse en tipos diferentes de computadores sin modificaci´on alguna o con pocas modificaciones. Los programas escritos en lenguajes de bajo nivel s´olo pueden ser ejecutarse en un tipo de computador y deben reescribirse para ejecutarlos en otro. Debido a estas ventajas, casi todos los programa se escriben en un lenguaje de alto nivel. Los lenguajes de bajo nivel s´olo se usan para unas pocas aplicaciones especiales. Hay dos tipos de programas que traducen lenguajes de alto nivel a lenguajes de bajo nivel: int´erpretes y compiladores. Un int´erprete lee un programa de alto nivel y lo ejecuta, lo que significa que lleva a cabo lo que indica el programa. Traduce el programa poco a poco, leyendo y ejecutando cada comando. Un compilador lee el programa y lo traduce todo al mismo tiempo, antes de ejecutar cualquiera de las instrucciones. En este caso, al programa de alto nivel se le llama el c´odigo fuente, y al programa traducido el c´odigo de obje- to o el c´odigo ejecutable. Una vez compilado el programa, puede ejecutarlo repetidamente sin volver a traducirlo. Python se considera como lenguaje interpretado porque los programas de Pyt- hon se ejecutan por medio de un int´erprete. Existen dos maneras de usar el
  • 33. 1.2 ¿Qu´e es un programa? 3 int´erprete: modo de comando y modo de guion. En modo de comando se escri- ben sentencias en el lenguaje Python y el int ´erprete muestra el resultado. $ python Python 1.5.2 (#1, Feb 1 2000, 16:32:16) Copyright 1991-1995 Stichting Mathematish Centrum, Amsterdam >>> print 1 + 1 2 La primera l´ınea de este ejemplo es el comando que pone en marcha el int´erprete Python. Las dos l´ıneas siguientes son mensajes del int´erprete. La tercera l´ınea comienza con >>>, que es la invitaci´on del int´erprete para indicar que esta´ listo. Escribimos print 1 + 1 y el int´erprete contest´o 2. Alternativamente, se puede escribir el programa en un archivo y usar el int´erpre- te para ejecutar el contenido de dicho archivo. El archivo se llama, en este ca- so, un gui´on. Por ejemplo, en un editor de texto se puede crear un archivo latoya.py que contenga esta l´ınea: print 1 + 1 Por acuerdo un´anime, los archivos que contienen programas de Python tienen nombres que terminan con .py. Para ejecutar el programa, se le tiene que indicar el nombre del guion al int´erpre- te. $ python latoya.py 2 En otros entornos de desarrollo los detalles de la ejecucio´n de programas pueden ser diferentes. Aem´as, la mayor´ıa de programas son mas interesantes que el mencionado. La mayor´ıa de ejemplos en este libro se ejecutan desde en la l´ınea de comando. La l´ınea de comando es muy apropiada para el desarrollo de programas y para pruebas r´apidas porque se pueden teclear las instrucciones de Python y se pue- den ejecutar inmediatamente. Una vez que un programa esta´ completo, puede archivarse en un gui´on para ejecutarlo o modificarlo en el futuro. 1.2. ¿Qu´e es un programa? Un programa es una secuencia de instrucciones que especifican c ´omo ejecutar una computacio´n. La computacio´n puede ser algo matem´atico, como solucionar
  • 34. 4 El Camino del un sistema de ecuaciones o determinar las ra´ıces de un polinomio, pero tambi´en puede ser una computacio´n simb´olica, como buscar y reemplazar el texto de un documento o (aunque parezca raro) compilar un programa. Las instrucciones (comandos, o´rdenes) tienen una apariencia diferente en len- guajes de programaci´on diferentes, pero existen algunas funciones basicas que se presentan en casi todo lenguaje: entrada: Recibir datos del teclado, o un archivo u otro aparato. salida: Mostrar datos en el monitor o enviar datos a un archivo u otro aparato. matem´aticas: Ejecutar operaciones b´asicas de matem´aticas como la adici´on y la multiplicaci´on. operaci´on condicional: Probar la veracidad de alguna condicio´n y ejecutar una secuencia de instrucciones apropiada. repetici´on: Ejecutar alguna acci´on repetidas veces, normalmente con alguna variaci´on. Lo crea o no, eso es todo. Todos los programas que existen, por complicados que sean, esta´n formulados exclusivamente con tales instrucciones. As´ı, una manera de describir la programaci´on es: El proceso de romper una tarea en tareas cada vez m´as pequen˜as hasta que estas tareas sean suficientemente simples para ser ejecutadas con una de estas instrucciones simples. Quiz´as esta descripcio´n sea un poco ambigua. No se preocupe. Lo explicaremos con m´as detalle con el tema de los algoritmos. 1.3. ¿Qu´e es la depuraci´on (debugging)? La programaci´on es un proceso complejo y, por ser realizado por humanos, a menudo desemboca en errores. Por razones caprichosas, esos errores se llaman bugs y el proceso de buscarlos y corregirlos se llama depuraci´on (en ingl´es “debugging”). Hay tres tipos de errores que pueden ocurrir en un programa, de sintaxis, en tiempo de ejecucio´n y sema´nticos. Es muy u´til distinguirlos para encontrarlos mas r´apido.
  • 35. 1.3 ¿Qu´e es la depuraci´on (debugging)? 5 1.3.1. Errores sint´acticos Python s´olo puede ejecutar un programa si el programa es correcto sintactica- mente. En caso contrario, es decir si el programa no es correcto sintacticamente, el proceso falla y devuelve un mensaje de error. El t´ermino sintaxis se refiere a la estructura de cualquier programa y a las reglas de esa estructura. Por ejem- plo, en espan˜ol la primera letra de toda oraci´on debe ser mayu´scula, y todas las oraciones deben terminar con un punto. esta oraci´on tiene un error sint´actico. Esta oraci´on tambi´en Para la mayor´ıa de lectores, unos pocos errores sintacticos no son significatvos, y por eso pueden leer la poes´ıa de e. e. cummings sin anunciar errores de sin- taxis. Python no es tan permisivo. Si hay aunque sea un solo error sintactico en el programa, Python mostrar´a un mensaje de error y abortar´a la ejecucio´n del programa. Durante las primeras semanas de su carrera como programador pasar´a, seguramente, mucho tiempo buscando errores sintacticos. Sin embargo, tal como adquiera experiencia tendr´a menos errores y los encontrar´a mas r´apido. 1.3.2. Errores en tiempo de ejecucio´n El segundo tipo de error es un error en tiempo de ejecucio´n. Este error no aparece hasta que se ejecuta el programa. Estos errores tambi´en se llaman excepciones porque indican que algo excepcional (y malo) ha ocurrido. Con los programas que vamos a escribir al principio, los errores en tiempo de ejecucio´n ocurrira´n con poca frecuencia, as´ı que puede pasar bastante tiempo hasta que vea uno. 1.3.3. Errores sem´anticos El tercer tipo de error es el error sem´antico. Si hay un error de logica en su programa, el programa se ejecutara´ sin ningu´n mensaje de error, pero el resul- tado no ser´a el deseado. Ser´a cualquier otra cosa. Concretamente, el programa har´a lo que usted le dijo. A veces ocurre que el programa escrito no es el programa que se ten´ıa en mente. El sentido o significado del programa (su sema ´ntica) no es correcto. Es dif´ıcil ha- llar errores de l´ogica, porque requiere trabajar al rev´es, observando el resultado del programa para averiguar lo que hace.
  • 36. 6 El Camino del 1.3.4. Depuraci´on experimental Una de las t´ecnicas m´as importantes que usted aprender´a es la depuraci´on. Aun- que a veces es frustrante, la depuraci´on es una de las partes mas intelectualmente ricas, interesantes y estimulantes de la programaci´on. La depuraci´on es una actividad parecida a la tarea de un investigador: se tie- nen que estudiar las claves para inducir los procesos y eventos llevaron a los resultados que tiene a la vista. La depuraci´on tambi´en es una ciencia experimental. Una vez que se tiene la idea de cua´l es el error, se modifica el programa y se intenta nuevamente. Si su hip´otesis fue la correcta se pueden predecir los resultados de la modificaci´on y estara´ m´as cerca de un programa correcto. Si su hipotesis fue err´onea tendr´a que idearse otra hip´otesis. Como dijo Sherlock Holmes, “Cuando se ha descartado lo imposible, lo que queda, no importa cuan inveros´ımil, debe ser la verdad.” (A. Conan Doyle, The Sign of Four) Para algunas personas, la programaci´on y la depuraci´on son lo mismo: la pro- gramaci´on es el proceso de depurar un programa gradualmente hasta que haga lo que usted quiera. La idea es que deber´ıa usted comenzar con un programa que haga algo y hacer pequen˜as modificaciones, depur´andolas sobre la marcha, de modo que siempre tenga un programa que funcione. Por ejemplo, Linux es un sistema operativo que contiee miles de l ´ıneas de c´odigo, pero Linus Torvalds lo comenzo´ como un programa para explorar el microproce- sador Intel 80836. Segu´n Larry Greenfield, “Uno de los proyectos tempranos de Linus fue un programa que alternaba la impresi´on de AAAA con BBBB. Este programa evolucion´o en Linux” (de The Linux Users’Guide Versio´n Beta 1). Otros cap´ıtulos tratar´an m´as acerca del tema de depuraci´on y otras t´ecnicas de programaci´on. 1.4. Lenguajes formales y lenguajes naturales Los lenguajes naturales son los lenguajes hablados por seres humanos, como el espan˜ol, el ingl´es y el franc´es. No los han disen˜ados personas (aunque se intente poner cierto orden en ellos), sino que se han desarrollado naturalmente. Los lenguajes formales son lenguajes disen˜ados por humanos y que tienen aplicaciones espec´ıficas. La notaci´on matem´atica, por ejemplo, es un lenguaje formal ya que se presta a la representaci´on de las relaciones entre nu´meros y s´ımbolos. Los qu´ımicos utilizan un lenguaje formal para representar la estructura qu´ımica de las mol ´eculas. Y lo m´as importante:
  • 37. 1.4 Lenguajes formales y lenguajes naturales 7 Los lenguajes de programaci´on son lenguajes formales de- sarrollados para expresar computaciones. Los lenguajes formales casi siempre tienen reglas sintacticas estrictas. Por ejem- plo, 3 + 3 = 6 es una expresio´n matem´atica correcta, pero 3 = +6$ no lo es. De la misma manera, H20 es una nomenclatura qu´ımica correcta, pero 2Zz no lo es. Existen dos clases de reglas sint´acticas, en cuanto a unidades y estructura. Las unidades son los elementos b´asicos de un lenguaje, como lo son las palabras, los nu´meros y los elementos qu´ımicos. Por ejemplo, en 3=+6$, $ no es una unidad matem´atica aceptada (al menos hasta donde nosotros sabemos. Similarmente, 2Xx no es formal porque no hay ningu´n elemento con la abreviatura Zz. La segunda clase de regla sint´actica esta´ relacionada con la estructura de un elemento; o sea, el orden de las unidades. La estructura de la sentencia 3=+6$ no se acepta porque no se puede escribir el s´ımbolo de igualdad seguido de un s´ımbolo positivo. Similarmente, las f´ormulas moleculares tienen que mostrar el nu ´mero de sub´ındice despu´es del elemento, no antes. A manera de pra´ctica, trate de producir una oracio´n con estructura aceptada pero que est´e compuesta de unidades irreconocibles. Luego escriba otra oracio´n con unidades aceptables pero con estructura no v´alida. Al leer una oraci´on, sea en un lenguaje natural o una sentencia en un lenguaje t´ecnico, se debe discernir la estructura de la oraci ´on. En un lenguaje natural este proceso, llamado an´alisis sint ´actico ocurre subconscientemente. Por ejemplo cuando usted escucha la oraci´on “El otro zapato cayo”, entiende que “el otro zapato” es el sujeto y “cay´o” es el verbo. Cuando se ha analizado la oraci´on sint´acticamente, se puede deducir el significado, o la sema´ntica, de la oraci´on. Suponiendo que sepa lo ques es un zapato y lo que es caer, entender´a el significado de la oraci´on. Aunque existen muchas cosas en comu´n entre los lenguajes naturales y los lenguajes formales—por ejemplo las unidades, la estructura, la sintaxis y la sema´ntica—tambi´en existen muchas diferencias: ambigu¨edad: Los lenguajes naturales tienen much´ısimas ambigu¨edades, que los hablantes sortean usando claves contextuales y otra informaci´on. Los lenguajes formales se disen˜an para estar completamente libres de am- bigu¨edades o tanto como sea posible, lo que quiere decir que cualquier sentencia tiene s´olo un significado, sin importar el contexto.
  • 38. 8 El Camino del redundancia: Para reducir la ambigu¨edad y los malentendidos, las lenguas na- turales utilizan bastante redundancia. Como resultado suelen ser prolijos. Los lenguajes formales son menos redundantes y mas concisos. literalidad: Los lenguajes naturales tienen muchas met´aforas y frases hechas. El significado de un dicho, por ejemplo “Estirar la pata”, es diferente al significado de sus sustantivos y verbos. En este ejemplo, la oraci´on no tiene nada que ver con un pie y significa ’morirse’. Los lenguajes formales no difieren de su significado literal. Los que aprenden a hablar un lenguaje natural—es decir, todo el mundo— muchas veces tienen dificultad en adaptarse a los lenguajes formales. A veces la diferencia entre los lenguajes formales y los naturales es comparable a la diferencia entre la prosa y la poes´ıa: Poes´ıa: Se utiliza una palabra por su cualidad auditiva tanto como por su signi- ficado. El poema, en su totalidad, produce un efecto o reaccio´n emocional. La ambigu¨edad no es solo comu ´n sino utilizada a prop´osito. Prosa: El significado literal de la palabra es mas importante y la estructura da m´as significado au´n. La prosa se presta al analisis mas que la poes´ıa, pero todav´ıa contiene ambigu¨edad. Programas: El significado de un programa es inequ´ıvoco y literal, y es enten- dido en su totalidad analizando las unidades y la estructura. He aqu´ı unas sugerencias para la lectura de un programa (y de otros lenguajes formales). Primero, recuerde que los lenguajes formales son mucho mas densos que los lenguajes naturales, y por consecuente lleva mas tiempo leerlos. Tam- bi´en, la estructura es muy importante, as´ı que entonces no es una buena idea leerlo de pies a cabeza, de izquierda a derecha. En vez de eso, aprenda a sepa- rar las diferentes partes en su mente, identificar las unidades e interpretar la estructura. Finalmente, ponga atenci´on a los detalles. Los fallos de puntuaci´on y la ortograf´ıa, que puede obviar en el lenguaje natural, pueden suponer una gran diferencia en un lenguaje formal. 1.5. El primer programa Tradicionalmente el primer programa en un lenguaje nuevo se llama “Hola, mundo” (Hello world!) porque s´olo muestra las palabras “Hola a todo el mundo”. En Python es as´ı: print "Hola, mundo"
  • 39. 1.6 Glosario 9 Este es un ejemplo de una sentencia print, la cual no imprime nada en papel, m´as bien muestra un valor. En este caso, el resultado es las palabras Hola, mundo Las comillas sen˜alan el comienzo y el final del valor; no aparecen en el resultado. Alguna gente evalu´a la calidad de un lenguaje de programaci´on por la simplici- dad del programa “Hola, mundo”. Si seguimos ese criterio, Python cumple con todas sus metas. 1.6. Glosario soluci´on de problemas: El proceso de formular un problema, hallar la solu- cio´n y expresar esa solucio´n. lenguaje de alto nivel: Un lenguaje como Python disen˜ado para ser facil de leer y escribir para la gente. lenguaje de bajo nivel: Un lenguaje de programaci´on disen˜ado para ser facil de ejecutar para un computador; tambi´en se lo llama “lenguaje de maqui- na” o “lenguaje ensamblador”. portabilidad: La cualidad de un programa que le permite ser ejecutado en m´as de un tipo de computador. interpretar: Ejecutar un programa escrito en un lenguaje de alto nivel tradu- ci´endolo l´ınea por l´ınea compilar: Traducir un programa escrito en un lenguaje de alto nivel a un len- guaje de bajo nivel todo al mismo tiempo, en preparaci´on para la ejecucio´n posterior. c´odigo fuente: Un programa escrito en un lenguaje de alto nivel antes de ser compilado. c´odigo de objeto: La salida del compilador una vez que ha traducido el pro- grama. programa ejecutable: Otro nombre para el c´odigo de objeto que esta´ listo para ejecutarse. gui´on: Un programa archivado (que va a ser interpretado). programa: Un conjunto de instrucciones que especifica una computacio´n. algoritmo: Un proceso general para resolver una clase completa de problemas.
  • 40. 1 El Camino del error (bug): Un error en un programa. depuraci´on: El proceso de hallazgo y eliminacio´n de los tres tipos de errores de programaci´on. sintaxis: La estructura de un programa. error sint´actico: Un error en un programa que hace que el programa sea im- posible de analizar sint´acticamente (e imposible de interpretar). error en tiempo de ejecuci´on: Un error que no ocurre hasta que el progra- ma ha comenzado a ejecutarse e impide que el programa continu´e. excepci´on: Otro nombre para un error en tiempo de ejecucio´n. error sem´antico: Un error en un programa que hace que ejecute algo que no era lo deseado. sem´antica: El significado de un programa. language natural: Cualquier lenguaje hablado que evolucion´o de forma natu- ral. lenguaje formal: Cualquier lenguaje disen˜ado por humanos que tiene un prop´osito espec´ıfico, como la representaci´on de ideas matem´aticas o pro- gramas de computadores; todos los lenguajes de programaci´on son lengua- jes formales. unidad: Uno de los elementos b´asicos de la estructura sintactica de un progra- ma, an´alogo a una palabra en un lenguaje natural. an´alisis sint´actico: La examinacio´n de un programa y el analisis de su estruc- tura sint´actica. sentencia print: Una instrucci´on que causa que el int´erprete Python muestre un valor en el monitor.
  • 41. Cap´ıtulo 2 Variables, expresiones y sentencias 2.1. Valores y tipos El valor es uno de los elementos fundamentales (como por ejemplo una letra o un nu´mero) que manipula un programa. Los valores que hemos visto hasta el momento son 2 (el resultado de sumar 1 + 1) y Hola, mundo. Estos valores son de distintos tipos: 2 es un entero y Hola, mundo es una cadena, llamada as´ı porque contiene una “cadena” de letras. Usted (y el int´erprete) puede identificar las cadenas porque esta´n encerradas entre comi- llas. La sentencia print tambi´en funciona con enteros: >>> print 4 4 Si no esta´ seguro del tipo que tiene un determinado valor, puede preguntarselo al int´erprete de Python. >>> type("Hola, mundo") <type ’string’> >>> type(17) <type ’int’> No es sorprendente que las cadenas sean de tipo string (cadena en ingl´es) y los enteros sean de tipo int (por integer en ingl´es). De forma menos obvia, los
  • 42. 1 Variables, expresiones y nu´meros con decimales (separados por medio de un punto en ingl ´es) son de tipo float debido a la representaci´on de estos nu ´meros en el formato llamado de coma flotante (floating-point). >>> type(3.2) <type ’float’> ¿Qu´e ocurre con los valores como "17" y "3.2"? Parecen nu´meros, pero esta´n entre comillas como las cadenas. >>> type("17") <type ’string’> >>> type("3.2") <type ’string’> Son cadenas. Cuando escriba un entero largo, podr´ıa estar tentado de usar comas entre grupos de tres d´ıgitos, como en 1,000,000. E´ste no es un entero legal en Python, pero es una expresio´n legal: >>> print 1,000,000 1 0 0 En fin, no era eso lo que quer´ıamos. Python interpreta 1,000,000 como una lista de tres nu´meros que debe imprimir. As´ı que recuerde no insertar comas en sus enteros. 1 2.2. Variables Una de las caracter´ısticas m´as potentes de los lenguajes de programaci´on es la capacidad de manipular variables. Una variable es un nombre que hace referencia a un valor. La sentencia de asignacio´n crea nuevas variables y les asigna un valor: >>> mensaje = "Que onda?" >>> n = 17 >>> pi = 3.14159 Este ejemplo muestra tres asignaciones. La primera de ellas asigna el valor "Que onda?" a una variable nueva, de nombre mensaje. La segunda le da el 1 El uso de la coma y el punto en nu´mero es en ingl´es el contrario al uso espan˜ol, como se apunt´o en una nota anterior
  • 43. mensaje n pi "Que onda?" 17 3.14159 2.3 Nombres de variables y palabras reservadas 13 valor entero 17 a n, y la tercera le da el valor de nu´mero en coma flotante 3.14159 a pi. Una forma habitual de representar variables sobre el papel es escribir el nombre con una flecha sen˜alando al valor de la variable. Este tipo de representaci´on se llama diagrama de estado, ya que muestra en qu´e estado se halla cada una de las variables (consid´erelo como el “estado de a´nimo” de la variable”). El siguiente diagrama muestra el efecto de las tres sentencias de asignaci´on anteriores: La sentencia print tambi´en funciona con variables. >>> print mensaje "Que onda?" >>> print n 17 >>> print pi 3.14159 En cada caso, el resultado es el valor de la variable. Las variables tambi´en tienen tipo. De nuevo, podemos preguntar al int´erprete lo que son. >>> type(mensaje) <type ’string’> >>> type(n) <type ’int’> >>> type(pi) <type ’float’> El tipo de una variable es el tipo del valor al que se refiere. 2.3. Nombres de variables y palabras reservadas Como norma general, los programadores eligen nombres significativos para sus variables: esto permite documentar para qu ´e se usa la variable. Los nombres de las variables pueden tener una longitud arbitraria. Pueden estar formados por letras y nu´meros, pero deben comenzar con una letra. Aunque es
  • 44. 1 Variables, expresiones y aceptable usar mayu´sculas, por convencio´n no lo hacemos. Si lo hace, recuerde que la distinci´on es importante: Bruno y bruno son dos variables diferentes. El gui´on bajo ( ) tambi´en es legal y se utiliza a menudo para separar nombres con mu´ltiples palabras, como mi nombre o precio del cafe colombiano. Si intenta darle a una variable un nombre ilegal, obtendr´a un error de sintaxis. >>> 76trombones = "gran desfile" SyntaxError: invalid syntax >>> mas$ = 1000000 SyntaxError: invalid syntax >>> class = "Curso de Programaci´on 101" SyntaxError: invalid syntax 76trombones es ilegal porque no comienza por una letra. mas$ es ilegal porque contiene un cara´cter ilegal, el signo del d´olar. Pero ¿qu´e tiene de malo class? Resulta que class es una de las palabras reservadas de Python. El lenguaje usa las palabras reservadas para definir sus reglas y estructura, y no pueden usarse como nombres de variables. Python tiene 28 palabras reservadas: Tal vez quiera mantener esta lista a mano. Si el int´erprete se queja de alguno de sus nombres de variable, y usted no sabe por qu ´e, compruebe si esta´ en esta lista. 2.4. Sentencias Una sentencia es una instrucci´on que puede ejecutar el int ´erprete de Python. Hemos visto dos tipos de sentencias: print y la asignaci´on. Cuando usted escribe una sentencia en la l´ınea de comandos, Python la ejecuta y muestra el resultado, si lo hay. El resultado de una sentencia print es un valor. Las sentencias de asignaci´on no entregan ningu´n resultado. Normalmente un gui´on contiene una secuencia de sentencias. Si hay mas de una sentencia, los resultados aparecen de uno en uno tal como se van ejecutando las sentencias. and continue else for import not raise assert def except from in or return break del exec global is pass try class elif finally if lambda print while
  • 45. 2.5 Evaluar expresiones 15 Por ejemplo, el gui´on print 1 x = 2 print x prsenta la salida 1 2 De nuevo, la sentencia de asignaci´on no produce ninguna salida. 2.5. Evaluar expresiones Una expresio´n es una combinaci´on de valroes, variables y operadores. Si teclea una expresio´n en la l´ınea de comandos, el int´erprete la evalu´a y muestra el resultado: >>> 1 + 1 2 Un valor, y tambi´en una variable, se considera una expresio´n por s´ı mismo. >>> 17 17 >>> x 2 Para complicar las cosas, evaluar una expresio´n no es del todo lo mismo que imprimir un valor. >>> mensaje = "Que onda?" >>> mensaje "Que onda?" >>> print mensaje Que onda? Cuando Python muestra el valor de una expresio´n, usa el mismo formato que usted usar´ıa para introducir un valor. En el caso de las cadenas, eso significa que incluye las comillas. Pero la sentencia print imprime el valor de la expresio´n, lo que en este caso es el contenido de la cadena. En un gui´on, una expresio´n sin m´as es una sentencia valida, pero no hace nada. El gui´on
  • 46. 1 Variables, expresiones y 17 3.2 "Hola, mundo" 1 + 1 no presenta ninguna salida. ¿Co´mo cambiar´ıa usted el guion para mostrar los valores de estas cuatro expresiones? 2.6. Operadores y expresiones Los operadores son s´ımbolos especiales que representan c´alculos simples, como la suma y la multiplicaci´on. Los valores que usa el operador se llaman operan- dos. Las siguientes expresione son legales en Python y su significado es mas o menos claro: 20+32 hora-1 hora*60+minuto minuto/60 5**2 (5+9)*(15-7) Los s´ımbolos +, -, /, y el uso de los par´entesis para el agrupamiento, se usan todos de la misma forma que en matem ´aticas. El asterisco (*) es el signo de multiplicaci´on y ** el s ´ımbolo para exponenciaci´on. Cuando aparece el nombre de una variable en el lugar de un operando, se sus- tituye con su valor antes de realizar la operacio ´n. La suma, resta, multiplicaci´on y exponenciaci´on hacen lo esperado, pero la divi- sio´n le puede sorprender. La operacio´n que sigue tiene un resultado inesperado: >>> minuto = 59 >>> minuto/60 0 El valor de la variable minuto es 59, y 59 dividido entre 60 es 0.98333 y no 0. El motivo de la discrepancia reside en que Python esta´ llevando a cabo una divisi´on de enteros. Cuando ambos operandos son enteros, el resultado ha de ser tambi ´en un entero; por convencio´n, la divisi´on de enterios simpre se redondea a la baja, incluso en casos como estos en los que el siguiente entero esta´ muy pr´oximo. Una alternativa posible en este caso es el c´alculo de un porcentaje y no el de una fracci´on: >>> minuto*100/60 98
  • 47. 2.7 El orden de las operaciones 17 De nuevo se redondea el resultado a la baja, pero al menos ahora la respuesta es aproximadamente correcta. Otra alternativa es la divisi´on de coma flotante, que veremos en el Cap´ıtulo 3. 2.7. El orden de las operaciones Cuando aparece m´as de un operador en una expresio´n, el orden de evaluaci´on depende de las reglas de precedencia. Python sigue las mismas reglas de precedencia que los propios matem´aticos para sus operaciones matem´aticas. Los ingleses usan el acr´onimo PEMDAS como regla parea recordar el orden de las operaciones: Par´entesis: tienen la precedencia m´as alta y pueden usarse para forzar que una expresio´n se evalu´e en el orden que queramos nosotros. Puesto que las expresiones entre par ´entesis se evalu´an primero, 2 * (3-1) es igual a 4, y (1+1)**(5-2) es igual a 8. Tambi´en puede usar par´entesis para que una expresio´n sea m´as legible; por ejemplo (minuto * 100) / 60, aunque el resultado no cambie de todas formas. Exponenciaci´on tiene la siguiente precedencia mas alta; as´ı pues 2**1+1 es igual a 3 y no a 4, y 3*1**3 es igual a 3 y no a 27. La Multiplicaci´on y la Divisi´on tienen la misma precedencia, que es mas alta que la de la Adici´on y la Sustracci´on, que tienen tambi´en la misma precedencia. Por tanto 2*3-1 devuelve 5 y no 4, y 2/3-1 da -1, y no 1 (recuerde que en la divisi ´on de enteros 2/3 da 0). Los operadores que tienen la misma precedencia se evalu´an de izquierda a derecha. As´ı, en la expresio´n minuto*100/60, tiene lugar primero la multiplicaci´on, devolviendo tt 5900/60, que a su vez da como resultado 98. Si las operaciones se hubiesen realizado de derecha a izquierda, el resultado habr´ıa sido 59/1 que da 59, y que es incorrecto. 2.8. Las operaciones sobre cadenas En general no es posible realizar operaciones matem´aticas con cadenas, incluso si las cadenas parecen nu´meros. Las siguientes sentencias son ilegales (suponiendo que mensaje sea de tipo string) mensaje-1 "Hola"/123 mensaje*"Hola" "15"+2
  • 48. 1 Variables, expresiones y Es curioso que el operador + funcione con cadenas, aunque no haga exactamente lo que usted esperar´ıa. Para cadenas, el operador + representa la concatena- ci´on, lo que significa que se unen los dos operandos uni´endolos extremo con extremo. Por ejemplo: fruta = "pl´atano" bizcochoBueno = " pan de leche" print fruta + bizcochoBueno La salida del programa es pl´atano pan de leche. El espacio delante de pan es parte de la cadena, y es necesario para introducir el espacio que separa las cadenas concatenadas. El operador * tambi´en funciona con cadenas; lleva a cabo la repetici´on. Por ejemplo ’Chiste’*3 es ’ChisteChisteChiste’. Uno de los operandos ha de ser una cadena, el otro ha de ser un entero. Por un lado, esta interpretaci´on de + y * cobra sentido por analog´ıa con la suma y la multimplicaci´on. Igual que 4*3 es equivalente a 4+4+4, esperamos que ’Chiste’*3 sea lo mismo que ’Chiste’+’Chiste’+’Chiste’, y as´ı es. Por otro lado, la concatenacio ´n y la repeticio´n son de alguna manera muy diferentes de la adici ´on y la multiplicaci´on de enteros. ¿Puede encontrar una propiedad que tienen la suma y la multiplicaci´on de enteros y que no tengan la concatenacio´n y la repeticio´n de cadenas? 2.9. Composicio´n Hasta ahora hemos examinado los elementos de un programa (variables, expre- siones y sentencias) por separado, sin hablar de c ´omo combinarlos. Una de las caracter´ısticas m´as u´tiles de los lenguajes de programaci´on es su capacidad de tomar pequen˜os bloques de construccio´n y ensamblarlos. Por ejemplo, sabemos c´omo sumar nu´meros y c´omo imprimirlos; resulta que pode- mos hacer las dos cosas a un tiempo: >>> print 17 + 3 20 En realidad, no debemos decir “al mismo tiempo”, puesto que en realidad la suma tiene que realizarse antes de la impresi´on, pero la cuestio´n es que cualquier expresio´n relacionada con nu ´meros, cadenas y variables puede usarse dentro de una sentencia print. Ya hemos visto un ejemplo de ello: print "N´umero de minutos desde la medianoche: ", hora*60+minuto
  • 49. 2.10 Los comentarios 19 Y tambi´en puede poner expresiones arbitrarias en la parte derecha de una sen- tencia de asignaci´on: porcentaje = (minuto * 100) / 60 Esta capacidad puede no resultar muy sorprendente, pero ya veremos otros ejemplos donde la composici´on hace posible expresar c´alculos complejos con limpieza y brevedad. ATENCIO´ N: Hay l´ımites al lugar donde pueden usarse ciertas expresiones. Por ejemplo, la parte izquierda de una sentencia de asignaci´on tiene que ser un nombre de variable, no una exrpresio ´n. Por tanto es ilegal lo siguiente: minute+1 = hour. 2.10. Los comentarios Conforme los programas van creciendo de taman˜o y complica ´ndose, se vuelven m´as complicados de leer. Los lenguajes formales son densos y con frecuencia es dif´ıcil observar un trozo de c´odigo y averiguar lo que hace, o por qu´e lo hace. Por ello es una buena idea an˜adir notas a su programa que expliquen, en un lenguaje natural, qu´e hace el programa. Estas notas se llaman comentarios y se marcan con el s´ımbolo #: # calcula el porcentaje de la hora que ha pasado ya porcentaje = (minuto * 100) / 60 En este caso, el comentario aparece en una l´ınea propia. Tambi´en puede poner comentarios al final de otras l´ıneas: porcentaje = (minuto * 100) / 60 # ojo: divisi´on de enteros Todo lo que va del # al final de la l´ınea se ignora (no tiene efecto sobre el programa). El mensaje esta´ destinado al programador, o a futuros programa- dores que podr´ıan tener que usar el c´odigo. En este caso avisa al lector sobre el sorprendente comportamiento de la divisi´on de enteros. 2.11. Glosario valor: un nu´mero o cadena (o cualquier otra cosa que se especifique poste- riormente) que puede almacenarse en una variable o calcularse en una expresio´n.
  • 50. 2 Variables, expresiones y tipo: un conjunto de valores. El tipo de un valor determina c´omo puede usarse en las expresiones. Hasta ahora, los tipos que hemos visto son enteros (tipo int), nu´meros de coma flotante (tipo float) y cadenas (tipo string). coma flotante: un formato para representar nu´meros con decimales. variable: nombre que hace referencia a un valor. sentencia: es una porci´on de c´odigo que representa una orden o acci´on. Hasta ahora, las sentencias que hemos vistos son las asignaciones y las sentencias print. asignacio´n: sentencia que asigna un valor a una variable. diagrama de estado: representaci´on gr´afica de un conjunto de variables y de los valores a los que se refiere. palabra reservada: es una palabra clave que usa el compilador para analizar sint´acticamente los programas. No pueden usarse palabras reservadas, por ejemplo if, def y while como nombres de variables. operador: un s´ımbolo especial que representa un c´alculo sencillo, como la su- ma, la multiplicaci´on o la concatenacio´n de cadenas. operando: uno de los valores sobre los que actu´a un operador. expresi´on: una combinaci´on de variables, operadores y valores. Dicha combi- naci´on representa un u´nico valor como resultado. evaluar: simplificar una expresio´n ejecutando las operaciones para entregar un valor u´nico. divisi´on de enteros: es una operacio´n que divide un entero entre otro y de- vuelve un entero. La divisi´on de enteros devuelve s´olo el nu´mero entero de veces que el numerador es divisible por en denominador, y descarta el resto. reglas de precedencia: la serie de reglas que especifican el orden en el que las expresiones con mu´tiples operadores han de evaluarse. concatenar: unir dos operandos extremo con extremo. composici´on: la capacidad de combinar expresiones sencillas y sentencias has- ta crear sentencias y expresiones compuestas, con el fin de representar c´alculos complejos de forma concisa. comentario: un segmento de informaci´on en un programa, destinado a otros programadores (o cualquiera que lea el c ´odigo fuente) y que no tiene efecto sobre la ejecucio´n del programa.
  • 51. Cap´ıtulo 3 Funciones 3.1. Llamadas a funciones Ya hemos visto un ejemplo de una llamada a una funcio´n: >>> type("32") <type ’string’> El nombre de la funci´on es type, y muestra el tipo de un valor o de una variable. El valor o variable, llamado el argumento de la funci ´on, ha de estar encerrado entre par´entesis. Es habitual decir que una funci´on “toma” un argumento y “devuelve” un resultado. El resultado se llama valor de retorno. En lugar de imprimir el valor de retorno, podemos asign´arselo a una variable. >>> nereida = type("32") >>> print nereida <type ’string’> Otro ejemplo m´as: la funci´on id toma como argumento un valor o una variable y devuelve un entero que actu´a como identificador u ´nico de ese valor. >>> id(3) 134882108 >>> yanira = 3 >>> id(yanira) 134882108 Cada valor tiene un id, que es un valor u´nico relacionado con donde se almacena en la memoria del computador. El id de una variable es el id del valor al que hace referencia.
  • 52. 2 Funcion 3.2. Conversio´n de tipos Python proporciona una coleccio´n de funciones internas que convierten valores de un tipo a otro. La funci´on int toma un valor y lo convierte a un entero, si es posible, o da un error si no es posible. >>> int("32") 32 >>> int("Hola") ValueError: invalid literal for int(): Hola int tambi´en convierte valores de coma flotante a enteros, pero recuerde que siempre redondea hacia abajo: >>> int(3.99999) 3 La funci´on float que convierte enteros y cadenas en nu´meros en coma flotante: >>> float(32) 32.0 >>> float("3.14159") 3.14159 Finalmente, esta´ la funci´on str, que convierte a tipo string: >>> str(32) ’32’ >>> str(3.14149) ’3.14149’ Pudiera parecer extran˜o que Python distinga entre el valor entero 1 y el valor de coma flotante 1.0. Tal vez representen el mismo nu ´mero, pero pertenecen a tipos distintos. El motivo es que se representan de forma distinta dentro del computador. 3.3. Coercio´n de tipos Ahora que ya sabemos convertir entre tipos, tenemos otra forma de enfrentarnos a la divisi´on de enteros. Volviendo al ejemplo del cap ´ıtulo anterior, suponga que queremos calcular qu´e fracci´on de una hora hab´ıa transcurrido. La expresio´n m´as obvia, minuto / 60, realiza una divisi´on de enteros, por lo que el resultado es siempre 0, incluso 59 minutos despu´es de la hora. Una alternativa es convetir minuto a tipo float (coma flotante) y luego efectuar una divisi´on de coma flotante:
  • 53. 3.4 Funciones matem´aticas 23 >>> minuto = 59 >>> float(minuto) / 60.0 0.983333333333 O bien podemos sacar provecho de las reglas de la conversio´n automatica de tipos, llamada coerci´on de tipos. Para los operadores matem´aticos, si uno de los operandos matem´aticos es tipo float, el otro se convierte automaticamente en float. >>> minuto = 59 >>> minuto / 60.0 0.983333333333 Al usar un denomidador que es float, obligamos a Python a hacer divisi´on de coma flotante. 3.4. Funciones matem´aticas Es posible que ya haya visto usted en matem´aticas funciones como sin (seno) y log, y que haya aprendido a evaluar expresiones como sin(pi/2) y log(1/x). Primero evalu´a la expresio´n entre par ´entesis, (el argumento). Por ejemplo, pi/2 es aproximadamente 1.571, y 1/x es 0.1 (si x es igual a 10.0). Luego evalu´a la funci´on en s´ı misma, bien mir´andola en una tabla, bien llevando a cabo diversos c´alculos. El sin (seno) de 1.571 es 1, y el log de 0.1 es -1 (suponiendo que log indique el logaritmo de base 10). Este proceso puede aplicarse repetidamente para evaluar expresiones mas com- plicadas como log(1/sin(pi/2)). Primero evaluamos el argumento de la fun- cio´n m´as interna, luego se evalu´a la funci´on, y as´ı sucesivamente. Python dispone de un m´odulo matem´atico que proporciona la mayor´ıa de las funciones matem´aticas habituales. Un m´odulo es un archivo que contiene una coleccio´n de funciones agrupadas juntas. Antes de poder usar las funciones de un modulo, tenemos que importarlo: >>>import math Para llamar a una de las funciones, tenemos que especificar el nombre del modulo y el nombre de la funci´on, separados por un punto. A esto se le llama notaci´on de punto: decibelio = math.log10 (17.0) angulo = 1.5 altura = math.sin(angulo)
  • 54. 2 Funcion La primera sentencia da a decibelio el valor del logaritmo de 17, en base 10. Hay tambi´en una funci´on llamada log que toma logaritmos en base e. La tercera sentencia halla el seno del valor de la variable angulo. sin y las otras funciones trigonom´etricas (cos, tan, etc.) toman sus argumentos en radianes. Para convertir de grados a radianes, puede dividir por 360 y multiplicar por 2*pi. Por ejemplo, para hallar el seno de 45 grados, calcule primero el a´ngulo en radianes y luego halle el seno: grados = 45 angulo = grados * 2 * math.pi / 360.0 math.sin(angulo) La constante pi tambi´en es parte del m´odulo math. Si se sabe la geometr´ıa, puede verificar el resultado compara´ndolo con el de la ra´ız cuadrada de 2, dividida entre 2. >>> math.sqrt(2) / 2.0 0.707106781187 3.5. Composicio´n Igual que con las funciones matem´aticas, las funciones de Python se pueden componer; eso quiere decir que se usa una expresio´n como parte de otra. Por ejemplo, puede usar cualquier expresio´n como argumento de una funci´on: x = math.cos(angulo + pi/2) Esta sentencia toma el valor de pi, lo divide entre dos y le an˜ade el resultado al valor de angulo. La suma se pasa luego como argumento a la funci´on cos. Tambi´en puede tomar el resultado de una funci´on y pas´arselo como argumento a otra: x = math.exp(math.log(10.0)) Esta sentencia encuentra el logaritmo en base e de 10 y luego eleva e a ese exponente. El resultado queda asignado a x. 3.6. An˜adir funciones nuevas Hasta ahora s´olo hemos usado las funciones que vienen incluidas con Python, pero tambi´en es posible an˜adir nuevas funciones. La creacio´n de nuevas funciones para resolver sus problemas partigulares es una de las cosas mas u´tiles de los lenguajes de programaci´on de prop´osito general.
  • 55. 3.6 An˜adir funciones nuevas 25 En contextos de programaci´on, funcio´n es una secuencia de instrucciones con nombre, que lleva a cabo la operacio´n deseada. Esta operacio´n se especifica en una definici´on de funcio´n. Las funciones que hemos usado hsta ahora las han definido por nosotros, y esas definiciones esta´n ocultas. Eso es bueno, ya que nos permite usar funciones sin preocuparnos sobre los detalles de sus definiciones. La sintaxis de la definici´on de una funci´on es: def NOMBRE( LISTA DE PARAMETROS ): SENTENCIAS Puede inventarse el nombre que desee para su funci´on, con la excepcio´n de que no puede usar las palabras reservadas de Python. La lista de par´ametros especifica qu´e informaci´on, en caso de haberla, ha de proporcionar para usar la funci´on nueva. Puede haber cualquier nu´mero de sentencias dentro de la funci ´on, pero tienen que estar indentadas desde el margen izquierdo. En los ejemplos de este libro se usar´a una indentaci´on de dos espacios. El primer par de funciones que escribiremos no tienen par´ametros, de manera que su sintaxis es: def nueva_linea(): print Esta funci´on se llama nueva linea. Los par´entesis vac´ıos indican que no tiene par´ametros. Contiene una u´nica sentencia, que muestra como salida un cara´cter de nueva l´ınea (es lo que sucede cuando utiliza una orden print sin argumentos). Llamamos entonces a la funci´on nueva usando la misma sintaxis que usamos para las funciones internas: print "Primera linea." nueva_linea() print "Segunda linea." The output of this program is Primera linea. Segunda linea. Observe el espacio an˜adido que hay entre las dos l´ıneas. Si quisi´eramos mas espacios, entre las l´ıneas, ¿qu´e har´ıamos? Podemos llamar varias veces a la misma funci´on: print "Primera linea." nueva_linea() nueva_linea() nueva_linea() print "Segunda linea."
  • 56. 2 Funcion O bien podemos escribir una nueva funci´on que llamaremos tresLineas, y que imprima tres nuevas l´ıneas: def tresLineas(): nueva_linea() nueva_linea() nueva_linea() print "Primera Linea." tresLineas() print "Segunda Linea." Esta funci´on contiene tres sentencias, las cuales esta´n todas indentadas con dos espacios. Puesto que la siguiente sentencia no esta´ indentada, Python sabe que no es parte de la funci´on. Observe los siguientes puntos con respecto a este programa: 1. Se puede llamar al mismo procedimiento repetidamente. De hecho es bas- tante u´til hacerlo, adem´as de habitual. 2. Se puede llamar a una funci´on desde dentro de otra funci´on: en este caso tresLineas llama a nueva linea. Hasta ahora puede no haber quedar claro por qu´e vale la pena crear todas estas funciones nuevas. En realidad hay much´ısimas razones, pero este ejemplo demuestra dos: Crear una nueva funci´on le da la oportunidad de dar un nombre a un grupo de sentencias. Las funciones simplifican su programa al ocultar c´alculos complejos detr´as de o ´rdenes sencillas, y usar palabras de su propia lengua en vez de c´odigo arcano. Crear una nueva funci´on hace que el programa sea mas pequen˜o, al elimi- nar c´odigo repetitivo. Por ejemplo, una manera de imprimir nueve l´ıneas consecutivas es llamar a tresLineas tres veces. Como actividad, escriba una funcion llamada nueveLineas que use tresLineas para imprimir nueve l´ıneas en blanco. ¿Co´mo impri- mir´ıa 27 l´ıneas nuevas? 3.7. Las definiciones y el uso Juntando los fragmentos de c´odigo de la seccio´n anterior, el programa completo queda de la siguiente manera:
  • 57. 3.8 Flujo de ejecuci´on 27 def nueva_linea(): print def tresLineas(): nueva_linea() nueva_linea() nueva_linea() print "Primera Linea." tresLineas() print "Segunda Linea." El presente programa contiene dos definiciones de funciones: nueva linea y tresLineas. Las definiciones de funciones se ejecutan como el resto de senten- cias, pero el efecto es crear una nueva funci´on. Las sentencias del interior de la funci´on no se ejecutan hasta que se llama a la funci´on, y la definici´on de la funci´on no genera salida. Como era de esperar, tiene que crear una funci´on antes de poder ejecutarla. En otras palabras, la definici´on de la funci´on tiene que ejecutarse antes de la primera vez que se la invoque. Como actividad, pruebe a ejecutar este programa moviendo las tres u´ltimas sentencias al principio del programa. Registre qu´e mensaje de error obtiene usted. Como segunda actividad, pruebe a tomar la versi´on del programa que funcionaba y a mover la defincio´n de nueva linea m´as abajo que la definicio´n de tresLineas . ¿Qu´e ocurre cuando ejecuta el programa? 3.8. Flujo de ejecucio´n Para asegurarse de que una funci´on se define antes de su primer uso, tiene que conocer el orden en el que se ejecutan las sentencias; a esto se le llama flujo de ejecuci´on. La ejecucio´n comienza siempre por la primera sentencia del programa. Las sen- tencias se ejecutan a razo´n de una cada vez, en orden, hasta que se alcanza una llamada a una funci´on. Las definiciones de funciones no alteran el flujo de ejecucio´n del programa, pero recuerde que las sentencias que hay dentro de la funci´on no se ejecutan hasta que se hace la llamada a la funci´on. Aunque no es habitual, puede definir una
  • 58. 2 Funcion funci´on dentro de otra. En este caso, la definici´on de funci´on interior no se ejecuta hasta que no se llama a la funci´on exterior. Las llamadas a funciones son como un desv´ıo en el flujo de ejecucio´n. En lugar de ir a la siguiente sentencia, el flujo salta hasta la primera l´ınea de la funci´on a la que se llama, ejecuta todas las sentencias que encuentre all´ı, y vuelve a retomar la ejecucio´n en el punto donde lo dej´o. Esto suena bastante sencillo... hasta que se acuerda de que una funci´on puede llamar a otra. Mientras estamos en medio de una funci´on, podr´ıamos vernos obligados a abandonarla e ir a ejecutar sentencias en otra funci´on mas. Pero mientras estamos en esta nueva funci´on, ¡podr´ıamos salirnos y ejecutar otra funci ´on m´as! Afortunadamente, a Python se le da bien tomar nota de donde esta´, de manera que cada vez que se completa una funci´on, el programa retoma el punto en donde lo dej´o en la funci´on que hizo la llamada. Cuando llega al final del programa, termina. ¿Cua´l es la moraleja de toda esta historia? Cuando est´e leyendo un programa, no lo lea desde la parte superior a la inferior. En lugar de eso, siga el flujo de ejecucio´n. 3.9. Par´ametros y argumentos Algunas de las funciones internas que hemos usado precisan de argumentos, los valores que controlan c´omo la funci´on lleva a cabo su tarea. Por ejemplo, si desea encontrar el seno de un nu ´mero, tiene que indicar de qu´e nu´mero se trata. As´ı pues, sin toma como argumento un valor num´erico. Algunas funciones toman m´as de un argumento, como pow, que toma dos argu- mentos: la base y el exponente. Dentro de la funci ´on, los valores que se le han pasado se asignan a variables llamadas par´ametros. He aqu´ı un ejemplo de una funci´on definida por el usuario, que toma un par´ame- tro: def imprimeDoble(paso): print paso, paso Esta funci´on toma un u´nico argumento y se lo asigna a un par ´ametro llamado paso. El valor del par´ametro (en este punto todav ´ıa no tenemos ni idea de cua´l ser´a) se imprime dos veces, seguido por un cara´cter de nueva l´ınea. El nombre paso se eligio´ para sugerir que el nombre que le d´e a un par´ametro depende de usted, pero en general es mejor que elija un nombre mas ilustrativo que paso.
  • 59. 3.10 Las variables y los par´ametros son locales 29 La funci´on imprimeDoble sirve con cualquier tipo (de dato) que se pueda im- primir: >>> imprimeDoble(’Jam´on’) Jam ´on Jam´on >>> imprimeDoble(5) 5 5 >>> imprimeDoble(3.14159) 3.14159 3.14159 En la primera llamada a la funci´on, el argumento es una cadena; en la segunda es un entero, y en la tercera es un nu´mero de coma flotante. Las mismas reglas de composici´on que se aplican a las funciones internas se aplican tambi´en a las funciones definidas por el usuario, as´ı que puede usar cualquier tipo de expresio´n como argumento de imprimeDoble. >>> imprimeDoble(’Jam´on’*4) Jam´onJam ´onJam´onJam´on Jam´onJam´onJam´onJam´on >>> imprimeDoble(math.cos(math.pi)) -1.0 -1.0 Como de costumbre, se evalu´a la expresio´n antes de ejecutar la funci´on, de modo que imprimeDoble devuelve Jam´onJam´onJam´onJam ´on Jam´onJam´onJam´onJam´on en lugar de ’Jam´on’*4’Jam´on’*4. Asimismo podemos usar una variable como argumento: >>> latoya = ’Dafne, es mitad laurel mitad ninfa’ >>> imprimeDoble(latoya) Dafne, es mitad laurel mitad ninfa. Dafne, es mitad laurel mitad ninfa. Observe un aspecto realmente importante en este caso: el nombre de la variable que pasamos como argumento (latoya) no tiene nada que ver con el nombre del par´ametro (paso). No importa c´omo se llamaba el valor en su lugar original (el lugar desde donde se invoc ´o); aqu´ı en imprimeDoble llamamos a todo el mundo paso. 3.10. Las variables y los par´ametros son locales Cuando crea una variable dentro de una funci´on, s´olo existe dentro de dicha funci´on, y no puede usarla fuera de ella. Por ejemplo, la funci´on >>> def catDoble(parte1, parte2): ... cat = parte1 + parte2 ... imprimeDoble(cat) ... >>>
  • 60. 3 Funcion chant1 chant2 "Pie Jesu domine," "Dona eis requiem." part1 part2 cat "Pie Jesu domine," "Dona eis requiem." "Pie Jesu domine, Dona eis requiem." bruce"Pie Jesu domine, Dona eis requiem." toma dos argumentos, los concatena y luego imprime el resultado dos veces. Podemos llamar a la funci´on con dos cadenas: >>> cantus1 = "Die Jesu domine, " >>> cantus2 = "Dona eis requiem." >>> catDoble(cantus1, cantus2) Die Jesu domine, Dona eis requiem. Die Jesu domine, Dona eis requiem. Cuando catDoble termina, la variable cat se destruye. Si trat´asemos de impri- mirla, obtendr´ıamos un error: >>> print cat NameError: cat Los par´ametros tambi´en son locales. Por ejemplo, una vez fuera de la funci´on imprimeDoble, no existe nada llamado paso. Si trata de usarla, Python se que- jar´a. 3.11. Diagramas de pila Para mantener el rastro de qu´e variables pueden usarse y donde, a veces es u´til dibujar un diagrama de pila. Como los diagramas de estado, los diagramas de pila muestran el valor de cada variable, pero tambi´en muestran la funci´on a la que cada variable pertenece. Cada funci´on se representa por una caja con el nombre de la funci´on junto a ´el. Los par´ametros y variables que pertenecen a una funci´on van dentro. Por ejemplo, el diagrama de stack para el programa anterior tiene este aspecto: main catTwice printTwice El orden de la pila muestra el flujo de ejecucio´n. imprimeDoble fue llamado por catDoble y a catDoble lo invoc´o main , que es un nombre especial de la
  • 61. 3.12 Funciones con resultado 31 funci´on m´as alta. Cuando crea una variable fuera de cualquier funci´on, pertenece a main . En cada caso, el par´ametro se refiere al mismo valor que el argumento corres- pondiente. As´ı que parte1 en catDoble tiene el mismo valor que cantus1 en main . Si sucede un error durante la llamada a una funci´on, Python imprime el nombre de la funci´on y el nombre de la funci´on que la llamo, y el nombre de la funci´on que llam´o a ´esa, y as´ı hasta main . Por ejemplo, si intentamos acceder a cat desde imprimeDoble, provocaremos un NameError: Traceback (innermost last): File "test.py", line 13, in main catDoble(cantus1, cantus2) File "test.py", line 5, in catDoble imprimeDoble(cat) File "test.py", line 9, in imprimeDoble print cat NameError: cat Esta lista de funciones de llama traceback (traza inversa). Le dice a usted en qu´e archivo de programa sucedio´ el error, y en qu´e l ´ınea, y qu´e funciones se ejecutaban en ese momento. Tambi´en muestra la l´ınea de c´odigo que causo´ el error. F´ıjese en la similaridad entre la traza inversa y el diagrama de pila. No es una coincidencia. 3.12. Funciones con resultado Seguramente ha notado ya que algunas de las funciones que estamos usando, igual que las funciones matem´aticas, devuelven un resultado. Otras funciones, como nueva linea, llevan a cabo una acci´on pero no devuelven un valor. Ello suscita varias preguntas: 1. ¿Qu´e sucede si llama usted a uana funci´on y no hace nada con el resultado (es decir, no lo asigna a una variable ni lo usa como parte de una expresio´n m´as amplia)? 2. ¿Qu´e sucede si usa una funci´on sin resultado como parte de una expresio´n, por ejemplo nueva linea() + 7?
  • 62. 3 Funcion 3. ¿Se pueden escribir funciones que devuelvan resultados, o debemos limi- tarnos a funcinoes simples como nueva linea e imprimeDoble? La respuesta a la tercera pregunta es “s´ı, puede escribir funciones que devuelvan valores”, y lo haremos en el cap´ıtulo 5. Como actividad final, consteste a las otras dos preguntas intentando hacerlas en la pra´ctica. Cada vez que tenga una duda sobre lo que es legal o ilegal en Python, perguntar al int ´erprete sera´ una buena manera de averiguarlo. 3.13. Glosario llamada a funcio´n: Una sentencia que ejecuta una funci´on. Est´a compuesta por el nombre de la funci´on m´as una lista de argumentos encerrados entre par´entesis. argumento: Valor que se le pasa a una funci´on cuando se la llama. El valor se asigna al par´ametro correspondiente de la funci ´on. valor de retorno: Es el resultado de una funci´on. Si se usa una llamada a funci´on a modo de expresio´n, el valor de retorno es el valor de la expresio´n. conversi´on de tipo: Una sentencia expl´ıcita que toma un valor de un tipo y calcula el valor correspondiente de otro tipo. coerci´on: Conversio´n tipos que ocurre automaticamente de acuerdo con las reglas de coercio´n de Python. m´odulo: Fichero que contiene una coleccio´n de funciones y clases relacionadas. notaci´on de punto: La sintaxis para llamar a una funci´on de otro modulo, especificando el nombre del m´odulo, seguido por un punto y el nombre de la funci´on. funcio´n: Secuencia de sentencias etiquetadas que llevan a cabo determinada operacio´n de utilidad. Las funciones pueden tomar par´ametros o no, y pueden producir un resultado o no. definici´on de funcio´n: Sentencia que crea una nueva funci´on, especificando su nombre, par´ametros y las sentencias que ejecuta. flujo de ejecuci´on: Orden en el que se ejecutan las sentencias durante la eje- cucio´n de un programa.
  • 63. 3.13 Glosario 33 par´ametro: Nombre que se usa dentro de una funci´on para referirse a el valor que se le pasa como argumento. variable local: variable definida dentro de una funci´on. Las variables locales s´olo pueden usarse dentro de su funci´on. diagrama de pila: Representaci´on gr´afica de una pila de funciones, sus varia- bles y los valores a los que se refieren. traza inversa: (traceback en ingl´es) Una lista de las funciones en curso de ejecucio´n, presentadas cuando sucede un error en tiempo de ejecucio´n. notaci´on de punto traza inversa
  • 65. Cap´ıtulo 4 Condicionales y recursividad 4.1. El operador mo´dulo El operador m´odulo funciona con enteros (y expresiones enteras), y devuelve el resto de dividir el primer operando entre el segundo. En Python, el operador de m´odulo es el signo de tanto por ciento ( ). La sintaxis es la misma de los otros operadores: >>> cociente = 7 / 3 >>> print cociente 2 >>> resto = 7 3 >>> print resto 1 As´ı, 7 dividido entre 3 da 2 con 1 de resto. El operador de m´odulo resulta ser soprendentemente u´til. Por ejemplo, puede comprobar si un nu´mero es divisible entre otro: si x y es cero, entonces x es divisible entre y. Tambi´en puede usar el operador m´odulo para extraer el d´ıgito mas a la derecha de un nu´mero. Por ejemplo, x 10 devuelve el d ´ıgito mas a la derecha de x (en base 10). De forma similar, x 100 devuelve los dos u´ltimos d´ıgitos.
  • 66. 3 Condicionales y 4.2. Expresiones booleanas Una expresi´on booleana es una expresio´n que es cierta o falsa. En Python, una expresio´n que es cierta tiene el valor 1, y una expresio ´n que es falsa tiene el valor 0. El operador == compara dos valores y entrega una expresio´n booleana: >>> 5 == 5 1 >>> 5 == 6 0 En la primera sentencia, los dos operandos son iguales, as´ı que la expresio´n se evalu´a como 1 (verdadero); en la segunda sentencia, 5 no es igual a 6, as´ı que obtenemos 0 (falso). El operador == es uno de los operadores de comparacio´n; los otros son: x != y # x no es igual a 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 Aunque probablemente estas operaciones le resulten familiares, los s´ımbolos en Python son diferentes de los matem´aticos. Un error habitual es utilizar un signo igual sencillo (=) en lugar del doble (==). Recuerde que = es un operador de asignaci´on y == es un operador de comparacio´n. Adem´as, no existen =< ni =>. 4.3. Operadores lo´gicos Hay tres operadores l´ogicos: and, or, y not. La sema´ntica (significado) de estos operadores es similar a sus significados en ingl´es. Por ejemplo, x >0 and x <10 es verdadero s´olo si x es mayor que 0 y menor que 10. n 2 == 0 or n 3 == 0 es verdadero si cualquiera de las condiciones es verda- dera, o sea, si el nu´mero es divisible por 2 o por 3. Finalmente, el operador not niega una expresio´n booleana, de forma que not(x >y) es cierto si (x >y) es falso, o sea, si x es menor o igual que y. Hablando estrictamente, los operandos de los operadores logicos deber´ıan ser expresiones booleanas, pero Python no es muy estricto. Cualqueir nu´mero que no sea cero se interpreta como “verdadero”.
  • 67. 4.4 Ejecuci´on condicional 37 >>> x = 5 >>> x and 1 1 >>> y = 0 >>> y and 1 0 En general, este tipo de cosas no se considera buen estilo. Si quiere comparar un valor con cero, deber´ıa hacerlo expl ´ıcitamente. 4.4. Ejecucio´n condicional Para escribir programas u´tiles, casi siempre necesitamos la capacidad de com- probar ciertas condiciones y cambiar el comportamiento del programa en conso- nancia. Las sentencias condicionales nos dan esta capacidad. La forma mas sencilla es la sentencia if: if x > 0: print "x es positivo" La expresio´n booleana tras el if se llama condicio´n. Si es verdadera, entonces la sentencia indentada se ejecuta. Si la condicio´n no es verdadera, no pasa nada. Como otras sentencias compuestas, if consta de una cabecera y un bloque de sentencias: CABECERA: PRIMERA SENTENCIA ... ULITMA SENTENCIA La cabecera comienza con una nueva l´ınea y termina con el signo de dos puntos. Los elementos indentados que siguen se llaman bloque de la sentencia. La primera sentencia no indentada marca el fin del bloque. Un bloque de sentencias dentro de una sentencia compuesta recibe el nombre de cuerpo de la sentencia. No hay l ´ımite a la cantidad de sentencias que pueden aparecer en el cuerpo de una sentencia if, pero debe haber al menos una. A veces, es u´til tener un cuerpo sin sentencias, (normalmente como reserva de espacio para algo de c´odigo que todav´ıa no ha escrito). En tales casos, puede usted utilizar la sentencia pass, que no hace nada. 4.5. Ejecucio´n alternativa Una segunda forma de la sentencia if es la ejecucio´n alternativa, en la que hay dos posibilidades, y la condicio´n determina cua´l de ellas se ejecuta. La sintaxis
  • 68. 3 Condicionales y tiene este aspecto: if x 2 == 0: print x, "es par" else: print x, "es impar" Si el resto cuando se divide x entre 2 es cero, entonces sabemos que x es par, y este programa muestra un mensaje a tal efecto. Si la condicio´n es falsa, se ejecuta el segundo lote de sentencias. Puesto que la condicio´n debe ser verdadera o falsa, se ejecutara´ exactamente una de las alternativas. Llamamos ramas a las posibilidades porque son ramas del flujo de ejecucio´n. Como un aparte, si piensa que querr´a comprobar con frecuencia la paridad de nu´meros, quiz´a desee “envolver” este c´odigo en una funci´on: def imprimeParidad(x): if x 2 == 0: print x, "es par" else: print x, "es impar" Ahora tiene una funci´on llamada imprimeParidad que muestra el mensaje apro- piado para cada nu´mero entero que usted le pase. Llame a esta funci´on de la manera siguiente: >>> imprimeParidad(17) >>> imprimeParidad(y+1) 4.6. Condiciones encadenadas A veces hay m´as de dos posibilidades y necesitamos mas de dos ramas. Una forma de expresar tal computacio´n es un conditional encadenado: if x < y: print x, "es menor que", y elif x > y: print x, "es mayor que", y else: print x, "y", y, "son iguales" elif es una abreviatura de ”else if”. De nuevo, s´olo se ejecutara´ una rama. No hay l´ımite al nu´mero de sentencias elif, pero s ´olo se permite una sentencia else (que puede omitirse) y debe ser la u´ltima rama de la sentencia: if eleccion == ’A’: funcionA() elif eleccion == ’B’:
  • 69. 4.7 Condiciones anidadas 39 funcionB() elif eleccion == ’C’: funcionC() else: print "Eleccion no valida." Las condiciones se comprueban en orden. Si la primera es falsa, se comprueba la siguiente, y as´ı. Si una de ellas es cierta, se ejecuta la rama correspondiente y termina la sentencia. Incluso si es cierta mas de una condicio´n, s´olo se ejecuta la primera rama verdadera. Como ejercicio, envuelva estos ejemplos en funciones llamadas compara(x, y) y resuelve(eleccion). 4.7. Condiciones anidadas Una condicio´n puede estar anidada dentro de otra. Pod´ıamos haber escrito as´ı el ejemplo de tricotom´ıa: ~~if x == y: ~~~~print x, "y", y, "son iguales" ~~else: ~~~~if x < y: ~~~~~~print x, "es menor que", y ~~~~else: ~~~~~~print x, "es mayor que", y La condicio´n externa que contiene dos ramas. La primera rama contiene una sentencia simple de salida. La segunda rama contiene otra sentencia if, que tiene dos ramas en s´ı misma. Estas dos ramas son ambas sentencias de salida de datos, aunque podr´ıan ser igualmente sentencias condicionales. Aunque la indentaci´on de las sentencias hace la estructura evidente, las condi- ciones anidadas en seguida se vuelven dif´ıciles de leer. En general es una buena idea evitarlas cuando pueda. Los operadores l´ogicos suelen facilitar un modo de simplificar las sentencias condicionales anidadas. Por ejemplo, podemos reescribir el c´odigo siguiente con un s´olo condicional: if 0 < x: if x < 10: print "x es un n´umero positivo de un d´ıgito." La sentencia print s´olo se ejecuta si conseguimos superar ambos condicionales, as´ı que podemos usar el operador and:
  • 70. 4 Condicionales y if 0 < x and x < 10: print "x es un n´umero positivo de un d´ıgito." Estos tipos de condiciones son habituales, por lo que Python nos proporciona una sintaxis alternativa similar a la notaci´on matem ´atica: if 0 < x < 10: print "x es un n´umero positivo de un d´ıgito." Esta condicio´n es sema´nticamente la misma que la expresio´n booleana compues- ta y que el condicional anidado. 4.8. La sentencia return La sentencia return le permite terminar la ejecucio´n de una funci ´on antes de alcanzar su final. Una razo´n para usarla es detectar una condicio´n de error: import math def imprimeLogaritmo(x): if x <= 0: print "Solo numeros positivos, por favor." return result = math.log(x) print "El log de x es", result La funci´on imprimeLogaritmo toma un par´ametro llamado x. Lo primero que hace es comprobar si x es menor o igual que cero, en cuyo caso muestra un mensaje de error y luego usa return para salir de la funci´on. El flujo de la ejecucio´n vuelve inmediatamente al llamante y no se ejecutan las l´ıneas restantes de la funci´on. Recuerde que para usar una funci´on del modulo math tiene que importarlo. 4.9. Recursividad Ya mencionamos que es legal que una funci´on llame a otra, y de ello hemos visto ya varios ejemplos. Olvidamos mencionar que tambi´en es legal que una funci´on se llame a s´ı misma. Puede no resultar evidente por qu´e es bueno esto, pero viene a resultar una de las cosas m´as interesantes y curiosas que puede hacer un programa. Examine por ejemplo la siguiente funci´on:
  • 71. 4.9 Recursividad 41 def cuenta_atras(n): if n == 0: print "Despegando!" else: print n cuenta_atras(n-1) cuenta atras espera que su par´ametro, n, sea un entero positivo. Si n el par´ame- tro es cero, muestra la palabra “Despegando!”. En otro caso, muestra n y luego llama a la funci´on llamada cuenta atras (ella misma) pas´andole como argu- mento n-1. ¿Qu´e sucede si llamamos a la funci´on de la siguiente manera? >>> cuenta_atras(3) La ejecucio´n de cuenta atras comienza con n=3, y puesto que n no es cero, da como salida el valor 3, y luego se llama a s´ı misma ... La ejecucio´n de cuenta atras comienza con n=2, y puesto que n no es cero, muestra el valor 2 y luego se llama a s´ı misma ... La ejecucio´n de cuenta atras comienza con n=1, y puesto que n no es cero, muestra el valor 1, y luego se llama a s´ı misma... La ejecucio´n de cuenta atras comienza con n=0, y puesto que n es cero, muestra la palabra “Des- pegando!” y luego retorna. La cuenta atras que dio n=1 retorna. La cuenta atras que dio n=2 retorna. La cuenta atras que dio n=3 retorna. Y entonces ya esta´ de vuelta en main (menudo viaje). De manera que la salida completa presenta el siguiente aspecto: 3 2 1 Despegando! Como segundo ejemplo, consideremos de nuevo las funciones nuevaLinea and tresLineas. def nuevaLinea(): print
  • 72. 4 Condicionales y def tresLineas(): nuevaLinea() nuevaLinea() nuevaLinea() Aunque todas funcionan, no ser´ıan de mucha ayuda si quisiera mostrar 2 l´ıneas nuevas o 106. Una mejor alternativa ser´a: def nLineas(n): if n > 0: print nLineas(n-1) Este programa es parecido a cuenta atras; mientras n sea mayor que cero, muestra una nueva l´ınea, y luego se llama a s´ı misma para mostrar >n-1 nuevas l´ıneas m´as. De esta manera, el nu´mero total de nuevas l´ıneas es 1 + (n-1), que si rescata su a´lgebra ver´a que es n. El proceso por el que una funci´on se llama a s´ı misma se llama recursividad, y dichas funciones se denominan recursivas. 4.10. Diagramas de pila para funciones recursi- vas El la Secci´on 3.11 utilizamos un diagrama de pila para representar el estado de un programa durante la llamada de una funci´on. El mismo tipo de diagrama puede hacer m´as f´acil interpretar una funci´on recursiva. Cada vez que se llama a una funci´on, Python crea un nuevo marco para la funci´on, que contiene sus variables locales y par ´ametros. En el caso de una funci´on recursiva, puede haber m´as de un marco en la pila al mismo tiempo. La figura muestra un diagrama de pila para cuenta atras, invocada con n = 3:
  • 73. n3 n2 n1 n0 4.11 Recursividad infinita 43 main countdown countdown countdown countdown Como es habitual, en lo alto de la pila esta´ el marco de main . Est´a vac´ıa porque no hemos ninguna variable sobre main ni le hemos pasado ningu´n par´ametro. Los cuatro marcos de cuenta atras tienen valores diferentes para el par ´ametro n. El fondo de la pila, donde n=0, se llama caso base. No hace una llamada recursiva, de manera que no hay m´as marcos. con el para´metro n=4. Como actividad, dibuje un diagrama de pila para nLineas, invocada 4.11. Recursividad infinita Si una recursio´n no alcanza nunca el caso base, seguira´ haciendo llamadas re- cursivas para siempre y nunca terminar´a. Esta circunstancia se conoce como recursividad infinita, y generalmente no se la considera una buena idea. Este es un programa m´ınimo con recursividad infinita: def recurre(): recurre() El la mayor´ıa de los entornos de programaci´on, un programa con recursividad infinita no se ejecutara´ realmente para siempre. Python informar´a de un mensaje de error cuando se alcance el nivel m´aximo de recursividad: File "<stdin>", line 2, in recurse (98 repetitions omitted) File "<stdin>", line 2, in recurse RuntimeError: Maximum recursion depth exceeded
  • 74. 4 Condicionales y Esta traza inversa es un poco mayor que la que vimos en el cap´ıtulo anterior. ¡Cuando sucede el error, hay 100 marcos recurre en la pila! Como actividad, escriba una funci´on con recursividad infinita y ejecu´tela en el int´erprete de Python. 4.12. Entrada por teclado Los programas que hemos escrito hasta ahora son un poco maleducados en el sentido de que no aceptan entradas de datos del usuario. Simplemente hacen lo mismo siempre. Python proporciona funciones internas que obtienen entradas desde el teclado. La m´as sencilla se llama raw input. Cuando llamamos a esta funci´on, el pro- grama se detiene y espera a que el usuario escriba algo. Cuando el usuario pulsa la tecla Return o Enter, el programa se reanuda y raw input devuelve lo que el usuario escribio´ como tipo string: >>> entrada = raw_input () A qu´e est´as esperando? >>> print entrada A qu´e est´as esperando? Antes de llamar a raw input es conveniente mostrar un mensaje que le pida al usuario el dato solicitado. Este mensaje se llama indicador (prompt en ingl´es). Puede proporcionarle un indicador a raw input como argumento: >>> nombre = raw_input ("C´omo te llamas? ") C ´omo te llamas? H´ector, h´eroe de los Troyanos! >>> print nombre H´ector, h´eroe de los Troyanos! Si espera que la entrada sea un entero, utilice la funci´on input. Por ejemplo: >>> indicador = ... "Cu´al es la velocidad de una golondrina sin carga?n" >>> velocidad = input (indicador) Si el usuario teclea una cadena de nu´meros, se convertira´ en un entero y se asignar´a a velocidad. Por desgracia, si el usuario escribe algo que no sea un d´ıgito, el programa dar´a un error: >>> velocidad = input (indicador) Cu´al es la velocidad de una golondrina sin carga? Se refiere usted a la golondrina europea o a la africana? SyntaxError: invalid syntax Para evitar este tipo de error, generalmente es buena idea usar raw input para obtener una cadena y usar entonces las funciones de conversio´n para convertir a otros tipos.
  • 75. 4.13 Glosario 45 4.13. Glosario operador m´odulo: Operador, sen˜alado con un signo de tanto por ciento ( ), que trabaja sobre enteros y devuelve el resto cuando un nu´mero se divide entre otro. expresi´on booleana: Una exprersio´n que es cierta o falsa. operador de comparacio´n: Uno de los operadores que comparan dos valores: ==, !=, >, <, >= y <=. operador l´ogico: Uno de los operadores que combinan expresiones booleanas: and, or y not. sentencia condicional: Sentencia que controla el flujo de ejecucio´n de un pro- grama dependiendo de cierta condicio´n. condicio´n: La expresio´n booleana de una sentencia condicional que determina qu´e rama se ejecutara´. sentencia compuesta: Estructura de Python que esta´ formado por una cabe- cera y un cuerpo. La cabecera termina en dos puntos (:). El cuerpo tiene una sangr´ıa con respecto a la cabecera. bloque: Grupo sentencias consecutivas con el mismo sangrado. cuerpo: En una sentencia compuesta, el bloque de sentencias que sigue a la cabecera de la sentencia. anidamiento: Una estructura de programa dentro de otra; por ejemplo, una sentencia condidional dentro de una o ambas ramas de otra sentencia condicional. recursividad: El proceso de volver a llamar a la funci´on que se esta ´ ejecutando en ese momento. caso base: En una funci´on recursiva, la rama de una sentencia condicional que no ocasiona una llamada recursiva. recursividad infinita: Funci´on que se llama a s´ı misma recursivamente sin alcanzar nunca el caso base. A la larga una recursio´n infinita provocara´ un error en tiempo de ejecucio ´n. indicador: indicador visual que invita al usuario a introducir datos.
  • 77. Cap´ıtulo 5 Funciones productivas 5.1. Valores de retorno Algunas de las funciones internas que hemos usado, como las funciones math o funciones matem´aticas, han producido resultados. Llamar a la funci´on genera un nuevo valor, que normalmente asignamos a una variable pasa usar como parte de una expresio´n. import math e = math.exp(1.0) altura = radio * math.sin(angulo) Pero hasta ahora, ninguna de las funciones que hemos escrito ha devuelto un valor. En este cap´ıtulo escribiremos funciones que devuelvan valores, que llamaremos funciones productivas, a falta de un nombre mejor. El primer ejemplo es area, que devuelve el a´rea de un c´ırculo con un radio dado: import math def area(radio): temporal = math.pi * radio**2 return temporal Ya hemos visto antes la sentencia return, pero en una funci´on productiva la sentencia return incluye un valor de retorno. Esta sentencia quiere decir “re- torna inmediatamente de la funci´on y usa la siguiente expresio´n como valor de retorno”. La expresio´n dada puede ser arbitrariamente complicada; as´ı pues, podr´ıamos haber escrito esta funci´on m´as concisamente:
  • 78. 4 Funciones def area(radio): return math.pi * radio**2 Por otra parte, las variables temporales como temporal suelen hacer mas f´acil el depurado. A veces es u´til disponer de varias sentencias de retorno, una en cada rama de una condicio´n: def valorAbsoluto(x): if x < 0: return -x else: return x Puesto que estas sentencias return esta´n en una condicio´n alternativa, s´olo se ejecutara´ una de ellas. En cuanto se ejecuta una de ellas, la funci´on termina sin ejecutar ninguna de las sentencias siguientes. El c´odigo que aparece despu´es de una sentencia return o en cualquier otro lugar donde el flujo de ejecucio´n no pueda llegar, recibe el nombre de c´odigo muerto. En una funci´on productiva es una buena idea asegurarse de que cualquier posible recorrido del programa alcanza una sentencia return. Por ejemplo: def valorAbsoluto(x): if x < 0: return -x elif x > 0: return x Este programa no es correcto porque si resulta que x vale 0, entonces no se cum- ple ninguna de ambas condiciones y la funci ´on termina sin alcanzar la setencia return. En este caso, el valor de retorno es un valor especial llamado None: >>> print valorAbsoluto(0) None Como actividad, escriba una funci´on comparar que devuelva 1 si x >y , 0 si x == y , y -1 si x <y . 5.2. Desarrollo de programas Llegados a este punto, tendr´ıa que poder mirar a funciones Python completas y adivinar qu´e hacen. Tambi´en, si ha hecho los ejercicios, habr´a escrito algu- nas funcioncillas. Tal como vaya escribiendo funciones mayores puede empezar
  • 79. √ 5.2 Desarrollo de programas 49 a experimentar m´as dificultades, especialmente con los errores en tiempo de ejecucio´n y los sema´nticos. Para lidiar con programas de complejidad creciente, vamos a sugerirle una t´ecni- ca que llamaremos desarrollo incremental. El objetivo del desarrollo incre- mental es sustituir largas sesiones de depuraci´on por la adici´on y prueba de pequen˜as porciones de c ´odigo en cada vez. Por ejemplo, supongamos que desea encontrar la distancia entre dos puntos, da- dos por las coordenadas (x1, y1) y (x2, y2). Por el teorema de Pitagoras, podemos escribir la distancia es: distancia = (x2 − x1)2 + (y2 − y1)2 (5.1) El primer paso es considerar qu´e aspecto tendr´ıa una funci´on distancia en Python. En otras palabras, ¿cua´les son las entradas (par´ametros) y cua´l es la salida (valor de retorno)? En este caso, los dos puntos son los par´ametros, que podemos representar usando cuatro par´ametros. El valor de retorno es la distancia, que es un valor en coma flotante. Ya podemos escribir un bosquejo de la funci´on: def distancia(x1, y1, x2, y2): return 0.0 Obviamente, la funci´on no calcula distancias; siempre devuelve cero. Pero es sint´acticamente correcta y se ejecutara´, lo que significa que podemos probarla antes de complicarla m´as. Para comprobar la nueva funci´on, tenemos que llamarla con valores de ejemplo: >>> def distancia(x1, y1, x2, y2): ... return 0.0 ... >>> distancia(1, 2, 4, 6) 0.0 >>> Elegimos estos valores de tal forma que la distancia horizontal sea igual a 3 y la distancia vertical sea igual a 4; de esa manera el resultado es 5 (la hipotenusa del tri´angulo 3-4-5). Cuando se comprueba una funci´on, es u´til saber la respuesta correcta. Hasta el momento, hemos comprobado que la funci´on es sintacticamente correc- ta, as´ı que podemos empezar a an˜adir l ´ıneas de c´odigo. Despu´es de cada cambio
  • 80. 5 Funciones − − incremental, comprobamos de nuevo la funci´on. Si en un momento dado apare- ce un error, sabremos d´onde esta´ exactamente: en la u´ltima l´ınea que hayamos an˜adido. El siguiente paso en el c´alculo es encontrar las diferencias entre x2 x1 y y2 y1. Almacenaremos dichos valores en variables temporales llamadas dx y dy y las imprimiremos. def distancia(x1, y1, x2, y2): dx = x2 - x1 dy = y2 - y1 print "dx es", dx print "dy es", dy return 0.0 Si la funci´on funciona, valga la redundancia, las salidas deber´ıan ser 3 y 4. Si es as´ı, sabemos que la funci´on recibe correctamente los par´ametros y realiza correctamente el primer c´alculo. Si no, s ´olo hay unas pocas l´ıneas que revisar. Ahora calculamos la suma de los cuadarados de dx y dy: def distancia(x1, y1, x2, y2): dx = x2 - x1 dy = y2 - y1 dalcuadrado = dx**2 + dy**2 print "dalcuadrado es: ", dalcuadrado return 0.0 F´ıjese en que hemos eliminado las sentencias print que escribimos en el pa- so anterior. Este c´odigo se llama andamiaje porque es u ´til para construir el programa pero no es parte del producto final. De nuevo querremos ejecutar el programa en este estado y comprobar la salida (que deber´ıa dar 25). Por u´ltimo, si hemos importado el m´odulo math, podemos usar la funci´on sqrt para calcular y devolver el resultado: def distancia(x1, y1, x2, y2): dx = x2 - x1 dy = y2 - y1 dalcuadrado = dx**2 + dy**2 resultado = math.sqrt(dalcuadrado) return resultado Si esto funciona correctamente, ha terminado. Si no, podr´ıa ser que quisiera usted imprimir el valor de resultado antes de la sentencia return.
  • 81. 5.3 Composici´on 51 Al principio, deber´ıa an˜adir solamente una o dos l´ıneas de c ´odigo cada vez. Conforme vaya ganando experiencia, puede que se encuentre escribiendo y de- purando trozos mayores. Sin embargo, el proceso de desarrollo incremental puede ahorrarle mucho tiempo de depurado. Los aspectos fundamentales del proceso son: 1. Comience con un programa que funcione y hagale pequen˜os cambios in- crementales. Si hay un error, sabra´ exactamente donde esta´. 2. Use variables temporales para mantener valores intermedios, de tal manera que pueda mostrarlos por pantalla y comprobarlos. 3. Una vez que el programa est´e funcionando, tal vez prefiera eliminar parte del andamiaje o aglutinar mu´ltiples sentencias en expresiones compuestas, pero s´olo si eso no hace que el programa sea dif´ıcil de leer. Como actividad, utilice el desarrollo incremental para escribir una funci´on de nombre hipotenusa que devuelva la longitud de la hipo- tenusa de un tria´ngulo rect´angulo, dando como para ´metros los dos catetos. Registre cada estado del desarrollo incremental segu´n vaya avanzando. 5.3. Composicio´n Como seguramente a estas alturas ya supondr´a, se puede llamar a una funci´on desde dentro de otra. Esta habilidad se llama composici ´on . Como ejemplo, escribiremos una funci´on que tome dos puntos, el centro del c´ırculo y un punto del per´ımetro, y calcule el a´rea del c´ırculo. Supongamos que el punto central esta´ almacenado en las variables xc e yc, y que el punto del per´ımetro lo esta´ en xp e yp. El primer paso es hallar el radio del c´ırculo, que es la distancia entre los dos puntos. Afortunadamente hay una funci´on, distancia, que realiza esta tarea: radio = distancia(xc, yc, xp, yp) El segundo paso es encontrar el a´rea de un c´ırculo con ese radio y devolverla: resultado = area(radio) return resultado Envolviendo todo esto en una funci´on, obtenemos: def area2(xc, yc, xp, yp): radio = distancia(xc, yc, xp, yp) resultado = area(radio) return resultado
  • 82. 5 Funciones Hemos llamado a esta funci´on area2 para distinguirla de la funci ´on area definida anteriormente. S´olo puede haber una u´nica funci´on con un determinado nombre dentro de un m´odulo. Las variables temporales radio y area son u´tiles para el desarrollo y el depu- rado, pero una vez que el programa esta´ funcionando, podemos hacerlo mas conciso integrando las llamadas a las funciones en una sola l´ınea: def area2(xc, yc, xp, yp): return area(distancia(xc, yc, xp, yp)) Como actividad, escriba una funci´on pendiente(x1, y1, x2, y2) que devuelva la pendiente de la l´ınea que atraviesa los puntos (x1, y1) y (x2, y2). Luego use esta funcion en una funci´on que se llame intercepta(x1, y1, x2, y2) que devuelva la [[y-intercepta]] de la l ´ınea a trav´es de los puntos (x1, y1) y (x2, y2). 5.4. Funciones booleanas Las funciones pueden devolver valores booleanos, lo que a menudo es conveniente para ocultar complicadas comprobaciones dentro de funciones. Por ejemplo: def esDivisible(x, y): if x y == 0: return 1 # it’s true else: return 0 # it’s false La funci´on lleva por nombre esDivisible. Es habitual dar a las funciones boo- leanas nombres que suenan como preguntas s´ı/no. Devuelve 1 o´ 0 para indicar si la x es o no divisibelo por y. Podemos reducir el taman˜o de la funci´on aprovechandonos del hecho de que la sentencia condicional que hay despu´es del if es en s´ı misma una expresio´n booleana. Podemos devolverla directamente, evitando a la vez la sentencia if: def esDivisible(x, y): return x y == 0 La siguiente sesio´n muestra a la nueva funci´on en acci´on: >>> esDivisible(6, 4) 0 >>> esDivisible(6, 3) 1 El uso m´as comu´n para las funciones booleanas es dentro de sentencias condi- cionales:
  • 83. 5.5 M´as recursividad 53 if esDivisible(x, y): print "x es divisible entre y" else: print "x no es divisible entre y" Puede parecer tentador escribir algo como: if esDivisible(x, y) == 1: Pero la comparacio´n extra es innecesaria. Como actividad, escriba una funcion estaEntre(x, y, z) que de- vuelva 1 en caso de que y <= x <= z y que devuelva 0 en cualquier otro caso. 5.5. M´as recursividad Hasta ahora, usted ha aprendido solamente un pequen˜o subconjunto de Python, pero puede que le interese saber que ese subconjunto es ya un lenguaje de programaci´on completo; con esto queremos decir que cualquier cosa que pueda computarse se puede expresar en este lenguaje. Cualquier programa que se haya escrito alguna vez puede reescribirse utilizando u´nicamente las caracter ´ısticas del lenguaje que ha aprendido hasta el momento (de hecho, necesitar´ıa algunas o´rdenes para controlar dispositivos como el teclado, el rato´n, los discos, etc, pero eso es todo). Probar tal afirmaci´on es un ejercicio nada trivial, completado por primera vez por Alan Turing, uno de los primeros cient´ıficos inform´aticos (algunos argumen- tar´an que era un matem´atico, pero muchos de los cient´ıficos inform´aticos pione- ros comenzaron como matem´aticos). En correspondencia, se la conoce como la tesis de Turing. Si estudia un curso de Teor´ıa de la Computaci´on, tendr´a opor- tunidad de ver la prueba. Para darle una idea de lo que puede hacer con las herramientas que ha aprendido hasta ahora, evaluaremos una serie de funciones matem´aticas que se definen recursivamente. Una definici´on recursiva es semejante a una definici´on circular, en el sentido de que la definici´on contiene una referencia a lo que se define. Una definici´on verdaderamente circular no es muy u´til: frangoso: adjetivo que describe algo que es frangoso Si usted viera esa definici´on en el diccionario, se quedar´ıa confuso. Por otra parte, si ha buscado la definici´on de la funci´on matem´atica factorial, habr´a visto algo sejemante a lo siguiente:
  • 84. 5 Funciones 0! = 1 n! = n · (n − 1)! Esta definici´on establece que el factoral de 0 es 1, y que el factorial de cualquier otro valor, n, es n multiplicado por el factorial de n − 1. As´ı pues, 3! es 3 veces 2!, que es 2 veces 1!, que es una vez 0!. Juntandolos todos, , 3! es igual a 3 veces 2 veces 1 vez 1, que es 6. Si puede escribir una definici´on recursiva de algo, normalmente podr´a escribir un programa de Python para evaluarlo. El primer paso es decidir cua´les son los par´ametros para esta funci´on. Con poco esfuerzo llegar´a a la conclusio´n de que factorial toma un u ´nico par´ametro: def factorial(n): Si resultase que el argumento fuese 0, todo lo que hemos de hacer es devolver 1: def factorial(n): if n == 0: return 1 En otro caso, y he aqu´ı la parte interesante, tenemos que hacer una llamada recursiva para hallar el factorial de n − 1 y luego multiplicarlo por n: def factorial(n): if n == 0: return 1 else: recursivo = factorial(n-1) resultado = n * recursivo return resultado El flujo de ejecucio´n de este programa es similar al de cuenta atras de la Secci´on 4.9. Si llamamos a factorial con el valor 3: Puesto que 3 no es 0, tomamos la segunda rama y calculamos el factorial de n-1... Puesto que 2 no es 0, tomamos la segunda rama y calculamos el factorial de n-1... Puesto que 1 no es 0, tomamos la segunda rama y calcu- lamos el factorial de n-1... Puesto que 0 es 0, tomamos la primera rama y devolvemos el valor 1 sin hacer mas llamadas re- cursivas.
  • 85. n0 n1recurse1return1 n2recurse1return2 n3recurse2return6 5.6 Acto de fe 55 El valor de retorno (1) se multiplica por n, que es 1, y se devuelve el resultado. El valor de retorno (1) se multiplica por n, que es 2, y se devuelve el resultado. El valor de retorno (2) se multiplica por n, que es 3, y el resultado 6, se convierte en el valor de retorno de la llamada a la funci´on que comenzo´ todo el proceso. He aqu´ı el aspecto que tiene el diagrama de pila para esta secuencia de llamadas a funci´on: main 6 factorial 2 factorial 1 factorial 1 factorial Los valores de retorno se muestran segu´n se pasan hacia la parte superior de la pila. En cada marco, el valor de retorno es el valor de resultado, que es el producto de n por recursivo. N´otese que en el u´ltimo marco las variables locales recursivo y resultado no existen porque la rama que las crea no se ejecuta. 5.6. Acto de fe Seguir el flujo de ejecucio´n es una de las maneras de leer programas; pero puede volverse r´apidamente una tarea laber ´ınitca. La alternativa es lo que llamamos el “acto de fe”. Cuando llegamos a una funci´on, en lugar de seguir el flujo de ejecucio´n, damos por sentado que la funci´on trabaja correctamente y devuelve el valor apropiado. De hecho, usted ya practica dicho salto de fe cuando usa funciones internas. Cuando llama a math.cos o a math.exp, no examina la implementaci´on de
  • 86. 5 Funciones − dichas funciones. Simplemente da por sentado que funcionan porque los que escribieron las bibliotecas internas de Python son buenos programadores. Lo mismo se aplica cuando llama a una de las funciones programadas por usted. Por ejemplo en la Secci´on 5.4, escribimos una funci´on llamada esDivisible que determina si un nu´mero es divisible por otro. Una vez que nos hayamos convencido de que dicha funci´on es correcta, comprobando y examinando el c´odigo, podremos usar la funci´on sin tener siquiera que volver a mirar el c´odigo otra vez. Lo mismo vale para los programas recursivos. Cuando llegue a la llamada recur- siva, en lugar de seguir el flujo de ejecucio´n, tendr ´ıa que dar por supuesto que la llamada recursiva funciona (es decir, devuelve el resultado correcto) y luego preguntarse: “suponiendo que puedo hallar el factorial de n 1, ¿puedo hallar el factorial de n?” En este caso, esta´ claro que s´ı puede, multiplic ´andolo por n. Por supuesto, es un tanto extran˜o dar por supuesto que la funci´on esta´ bien cuando ni siquiera ha acabado de escribirla, pero precisamente por eso se llama acto de fe. 5.7. Un ejemplo m´as En el ejemplo anterior, usamos variables temporales para ir apuntando los re- sultados y para hacer que el c´odigo fuese mas facil de depurar, pero podr´ıamos habernos ahorrado unas cuantas l´ıneas: def factorial(n): if n == 0: return 1 else: return n * factorial(n-1) De ahora en adelante, tenderemos a usar la versio´n mas concisa, pero le reco- mendamos que utilice la versio´n m´as expl´ıcita mientras se halle desarrollando c´odigo. Cuando lo tenga funcionando, lo podr´a acortar, si se siente inspirado. Despu´es de factorial, el ejemplo m´as comu´n de una funci´on matem´atica re- cursivamente definida es fibonacci, que presenta la siguiente definici´on: fibonacci(0) = 1 fibonacci(1) = 1 fibonacci(n) = fibonacci(n − 1) + fibonacci(n − 2); Traducido a Python, es como sigue:
  • 87. 5.8 Comprobaci´on de tipos 57 def fibonacci (n): if n == 0 or n == 1: return 1 else: return fibonacci(n-1) + fibonacci(n-2) Si intenta seguir el flujo de ejecucio´n aqu´ı, incluso para valores relativamente pequen˜os de n, le puede dar un dolor de cabeza. Pero si confiamos en el acto de fe, si da por supuesto que las dos llamadas recursivas funcionan correctamente, entonces estara´ claro que obtiene el resultado correcto al sumarlas juntas. 5.8. Comprobaci´on de tipos ¿Qu´e sucede si llamamos a factorial y le damos 1.5 como argumento? >>> factorial (1.5) RuntimeError: Maximum recursion depth exceeded Tiene todo el aspecto de una recursio´n infinita. Pero, ¿c´omo ha podido ocurrir? Hay una condicio´n de salida o caso base: cuando n == 0. El problema es que el valor de n yerra el caso base. En la primera llamada recursiva, el valor de n es 0.5. En la siguiente vez su valor es -0.5. A partir de ah´ı, se vuelve m´as y mas pequen˜o, pero nunca ser´a 0. Tenemos dos opciones. Podemos intentar generalizar la funci´on factorial para que trabaje con nu´meros de coma flotante, o podemos hacer que factorial compruebe el tipo de su par´ametro. La primera opcio´n se llama funci´on gamma, y esta´ m´as all´a del objetivo de este libro. As´ı pues, tomemos la segunda. Podemos usar la funci´on type para comparar el tipo del par´ametro con el tipo de un valor entero conocido (por ejemplo 1). Ya que estamos en ello, podemos asegurarnos de que el par´ametro sea positivo: def factorial (n): if type(n) != type(1): print "El factorial est´a definido s´olo para enteros." return -1 elif n < 0: print "El factorial est´a definido s´olo para enteros positivos." return -1 elif n == 0: return 1 else: return n * factorial(n-1)
  • 88. 5 Funciones Ahora tenemos tres condiciones de salida o casos base. El primero filtra los nu´meros no enteros. El segundo evita los enteros negativos. En ambos casos, se muestra un mensaje de error y se devuelve un valor especial, -1, para indicar a quien hizo la llamada a la funci´on que algo fue mal: >>> factorial (1.5) El factorial esta definido solo para enteros. -1 >>> factorial (-2) El factorial esta definido solo para enteros positivos. -1 >>> factorial ("paco") El factorial esta definido solo para enteros. -1 Si pasamos ambas comprobaciones, entonces sabemos que n es un entero positivo y podemos probar que la recursio´n termina. Este programa muestra un patr´on que se llama a veces guardi´an. Las primeras dos condicionales actu´an como guardianes, protegiendo al c´odigo que sigue de los valores que pudieran causar errores. Los guardianes hacen posible demostrar la correccio´n del c´odigo. 5.9. Glosario funcio´n productiva: Funci´on que devuelve un valor de retorno. valor de retorno: El valor obtenido como resultado de una llamada a una funci´on. variable temporal: Variable utilizada para almacenar un valor intermedio en un c´alculo complejo. c´odigo muerto: Parte de un programa que no podr´a ejecutarse nunca, a me- nudo debido a que aparece tras una sentencia de return. None: Valor especial de Python que devuelven funciones que o bien no tienen sentencia de return o bien tienen una sentencia de return sin argumento. desarrollo incremental: Un m´etodo de desarrollo de programas que busca evitar el depurado an˜adiendo y probando una pequen˜a cantidad de c´odigo en cada paso. andamiaje: El c´odigo que se usa durante el desarrollo del programa pero que no es parte de la versio´n final.
  • 89. 5.9 Glosario 59 guardi´an: Una condicio´n que comprueba y maneja circunstancias que pudieran provocar un error.
  • 91. bruce 5 7 Cap´ıtulo 6 Iteracio´n 6.1. Asignaci´on mu´ltiple Es posible que haya descubierto que es posible hacer mas de una asignaci´on a una misma variable. El efecto de la nueva asignaci´on es redirigir la variable de manera que deje de remitir al valor antiguo y empieze a remitir al valor nuevo. bruno = 5 print bruno, bruno = 7 print bruno La salida del programa es 5 7, ya que la primera vez que imprimimos bruno su valor es 5, y la segunda vez su valor es 7. La coma al final de la primera sentencia print impide que se imprima una nueva l´ınea en ese punto, por eso ambos valores aparecen en la misma l´ınea. He aqu´ı el aspecto de una asignaci´on mu´ltiple en un diagrama de estado: Cuando hay asignaciones mu´ltiples a una variable, es especialmente importante distinguir entre una sentencia de asignaci´on y una sentencia de igualdad. Puesto que Python usa el s´ımbolo = para la asignaci´on, es tentador interpretar una sentencia como a = b como sentencia de igualdad. Pero no lo es.
  • 92. 6 Itera Para empezar, la igualdad es commutativa, y la asignaci´on no lo es. Por ejemplo en matem´aticas si a = 7 entonces 7 = a. Pero en Python la sentencia a = 7 es legal, y 7 = a no lo es. Y lo que es m´as, en matem´aticas, una sentencia de igualdad es verdadera todo el tiempo. Si a = b ahora, entonces a siempre ser´a igual a b. En Python, una sentencia de asignaci´on puede hacer que dos variables sean iguales, pero no tienen por qu´e quedarse as´ı. a = 5 b = a # a y b son ahora iguales a = 3 # a y b ya no son iguales La tercera l´ınea cambia el valor de a pero no cambia el valor de b, y por lo tanto ya dejan de ser iguales. En algunos lenguajes de programaci´on, se usa para la asignaci´on un s´ımbolo distinto, como <- o como :=, para evitar la confusio´n. Aunque la asignaci´on mu´ltiple es u´til a menudo, debe usarla con cuidado. Si los valores de las variables van cambiando constantemente en distintas partes del programa, podr´ıa suceder que el c´odigo sea dif´ıcil de leer y mantener. 6.2. La sentencia while Una de las tareas para las que los computadores se usan con frecuencia es la automatizaci´on de tareas repetitivas. Repetir tareas similares o id´enticas es algo que los computadores hacen bien y las personas no hacen tan bien. Hemos visto dos programas, nLineas y cuenta atras, que usan la recursividad para llevar a cabo la repeticio´n, que tambi´en se llama iteracio´n. Por ser la iteraci´on tan habitual, Python proporciona como lenguaje varias caracter´ısticas que la hacen m ´as f´acil. La primera caracter´ıstica que vamos a considerar es la sentencia while. E´ste es el aspecto de cuenta atras con una sentencia while: def cuenta_atras(n): while n > 0: print n n = n-1 print "Despegando!" Como eliminamos la llamada recursiva, esta funci´on no es recursiva. Casi podr´ıa leer una sentencia while como si fuera ingl´es (castellano “mien- tras”). Quiere decir que “Mientras n sea mayor que cero, continu´a mostrando el valor de n y despu´es resta´ndole 1 al valor de n. Cuando llegues a cero, muestra la palabra “Despegando!”.
  • 93. 6.2 La sentencia while 63 M´as formalmente, el flujo de ejecucio´n de una sentencia while es el siguiente: 1. Evaluar la condicio´n, devolviendo 0 o 1. 2. Si la condicio´n es falsa (0), salir de la sentencia while y continuar la ejecucio´n en la siguiente sentencia. 3. Si la condicio´n es verdadera (1), ejecutar cada una de las sentencias en el cuerpo del bucle while, y luego volver al paso 1. El cuerpo esta´ formado por todas las sentencias bajo el encabezado que tienen el mismo sangrado. Este tipo de flujo de llama bucle porque el tercer paso vuelve de nuevo arriba. N´otese que si la condicio´n es falsa la primera vez que se atraviesa el bucle, las sentencias del interior del bucle no se ejecutan nunca. El cuerpo del bucle debe cambiar el valor de una o mas variables de manera que, llegado el momento, la condicio´n sea falsa y el bucle termine. En caso contrario, el bucle se repetira´ para siempre, que es lo que se llama bucle infinito. Una infinita fuente de diversio´n para los cient´ıficos inform´aticos es la observaci´on de que las instrucciones del champu´ “lavar, aclarar, repetir”, son un bucle infinito. En el caso de cuenta atras, podemos probar que el bucle terminar´a porque sabemos que el valor de n es finito, y podemos ver que el valor de n disminuye cada vez que se atraviesa el bucle (cada iteraci´on), de manera que ea la larga tenemos que llegar a cero. En otros casos no es tan facil decirlo: def secuencia(n): while n != 1: print n, if n 2 == 0: # n es par n = n/2 else: # n es impar n = n*3+1 La condicio´n de este bucle es n != 1, de manera que el bucle continuar´a hasta que n sea 1, que har´a que la condicio´n sea falsa. En cada iteraci´on, el programa muestra como salida el valor de n y luego com- prueba si es par o impar. Si es par, el valor de n se divide entre dos. Si es impar, el valor se sustituye por 3n+1. Por ejemplo, si el valor de comienzo (el argumento pasado a la secuencia) es 3, la secuencia resultante es 3, 10, 5, 16, 8, 4, 2, 1. Puesto que n a veces aumenta y a veces disminuye, no hay una prueba obvia de que n alcance alguna vez el valor 1, o de que el programa vaya a terminar. Para algunos valores particulares de n, podemos probar la terminaci´on. Por ejemplo,
  • 94. 6 Itera si el valor de inicio es una potencia de dos, entonces el valor de n ser´a par cada vez que se pasa a trav´es del bucle, hasta que lleguemos a 1. El ejemplo anterior acaba con dicha secuencia, empezando por 16. Dejando aparte valores particulares, la pregunta interesante es si podemos pro- bar que este programa terminar´a para todos los valores de n. Hasta la fecha, nadie ha sido capaz de probarlo o negarlo. Como actividad, reescriba la funci´on nLines de la secci´on 4.9 utili- zando iteracio´n en lugar de recursividad. 6.3. Tablas Una de las cosas para las que resultan buenos los bucles es para generar datos ta- bulares. Antes de que los computadores estuvieran disponibles de forma masiva, la gente ten´ıa que calcular a mano logaritmos, senos, cosenos y otras funciones matem´aticas. Para facilitarlo, los libros de matem´aticas conten´ına largas tablas donde aparec´ıan los valores de estas funciones. Confeccionar estas tablas era una tarea lenta y pesada, y el resultado estaba lleno de erratas. Cuando los computadores aparecieron en escena, una de las primeras reacciones fue “¡Qu´e bueno! Podemos usar los computadores para generar las tablas, as´ı no habr´a errores”. Resulto´ cierto (casi), pero no se vio mas alla. Poco despu´es los computadores y las calculadoras cient´ıficas se hicieron tan ubicuas que las tablas resultaron obsoletas. Bueno, casi. Resulta que para ciertas operaciones, los computadores usan tablas para obtener un valor aproximado, y luego ejecutan c´alculos para mejorar la aproximaci´on. En algunos casos, ha habido errores en las tablas subyacentes; el m´as famoso estaba en la tabla que el Pentium de Intel usaba para llevar a cabo la divisi´on de coma flotante. Aunque una tabla logar´ıtmica ya no es tan u´til como lo fuera antan˜o, todav´ıa constituye un buen ejemplo de iteraci´on. El siguiente programa muestra una secuencia de valores en la columna izquierda y sus logaritmos en la columna derecha: x = 1.0 while x < 10.0: print x, ’t’, math.log(x) x = x + 1.0 El t representa un cara´cter de tabulacio´n.
  • 95. 6.3 Tablas 65 Tal como se van mostrando en la pantalla caracteres y cadenas, un sen˜alador invisible llamado cursor lleva la cuenta de donde ir´a el pr´oximo cara´cter. Tras una sentencia print, el cursor va normalmente al principio de la l´ınea siguiente. El cara´cter de tabulaci´on hace que el cursor se desplace a la derecha hasta que alcance uno de los marcadores de tabulaci´on. Los tabuladores son u´tiles para alinear columnas de texto, como en la salida del programa anterior: 1.0 0.0 2.0 0.69314718056 3.0 1.09861228867 4.0 1.38629436112 5.0 1.60943791243 6.0 1.79175946923 7.0 1.94591014906 8.0 2.07944154168 9.0 2.19722457734 Si estos valores le parecen raros, recuerde que la funci´on log usa como base e. Debido a que las potencias de dos son muy importantes en las ciencias de la computacio´n, generalmente querremos hallar los logaritmos en relacio´n con la base dos. Para llevarlo a cabo, podemos usar la siguiente formula: lo g2 Cambiar la sentencia de salida a: x = logex loge2 (6.1) print x, ’t’, math.log(x)/math.log(2.0) devuelve Podemos ver que 1, 2, 4 y 8 son potencias de dos, porque sus logaritomos de base 2 son nu´meros enteros. Si quisi´eramos encontrar los logaritmos de otras potencias de dos, podr´ıamos modificar el programa de la siguiente manera: x = 1.0 while x < 100.0: 1.0 0.0 2.0 1.0 3.0 1.58496250072 4.0 2.0 5.0 2.32192809489 6.0 2.58496250072 7.0 2.80735492206 8.0 3.0 9.0 3.16992500144
  • 96. 6 Itera print x, ’t’, math.log(x)/math.log(2.0) x = x * 2.0 Ahora, en lugar de an˜adir algo a x cada vez que atravesamos el bucle, que devuelve una secuencia aritm´etica, multiplicamos x por algo, devolviendo una secuencia geom´etrica. El resultado es: 1.0 0.0 2.0 1.0 4.0 2.0 8.0 3.0 16.0 4.0 32.0 5.0 64.0 6.0 Debido a que usamos caracteres de tabulaci´on entre las columnas, la posici´on de la segunda columna no depende del nu´mero de d ´ıgitos de la primera columna. Las tablas logar´ıtimicas quiz´as ya no sean u´tiles, pero conocer las potencias de dos no ha dejado de serlo para los cient´ıficos inform´aticos. cias de dos hasta 65536 (es decir, 216 ). Impr´ımala y memor´ıcela. Como actividad, modifique el programa para que muestre las poten- El cara´cter de barra invertida en ’t’ indica el principio de una secuencia de escape. Las secuencias de escape se usan para representar caracteres invisibles como tabulaciones y retornos de carro. La secuencia n representa un retorno de carro. Una sentencia de escape puede aparecer en cualquier lugar de una cadena; en el ejemplo, la secuencia de escape del tabulador es la u ´nica de la cadena. ¿Co´mo cree que puede representar una barra invertida en una cadena? Como ejercicio, escriba un u´nica cadena que presente esta salida. 6.4. Tablas de dos dimensiones Una tabla de dos dimensiones es una tabla en la que Usted elige una fila y una columna y lee el valor de la intersecci´on. Un buen ejemplo es una tabla de
  • 97. 6.5 Encapsulado y generalizaci´on 67 multiplicar. Supongamos que desea imprimir una tabla de multiplicar para los valores del 1 al 6. Una buena manera de comenzar es escribir un bucle sencillo que imprima los mu´ltiplos de 2, todos en una l´ınea. i = 1 while i <= 6: print 2*i, ’t’, i = i + 1 print La primera l´ınea inicializa una variable lllamada i, que actuar´a como contador, o variable de bucle. Conforme se ejecuta el bucle, el valor de i se incrementa de 1 a 6. Cuando i vale 7, el bucle termina. Cada vez que se atraviesa el bucle, imprimimos el valor 2*i seguido por tres espacios. De nuevo, la coma de la sentencia print suprime el salto de l´ınea. Despu´es de completar el bucle, la segunda sentencia print crea una l´ınea nueva. La salida de este programa es: 2 4 6 8 10 12 Hasta ahora, bien. El siguiente paso es encapsular y generalizar. 6.5. Encapsulado y generalizaci´on Por “encapsulado” generalmente se entiende tomar una pieza de c ´odigo y envol- verla en una funci´on, permiti´endole obtener las ventajas de todo aquello para lo que valen las funciones. Hemos visto dos ejemplos de encapsulado, cuando escribimos imprimeParidad en la Secci´on 4.5 y esDivisible en la Secci´on 5.4. Por “generalizaci´on” entendemos tomar algo espec´ıfico, como imprimir los mu´lti- plos de 2, y hacerlo m´as general, como imprimir los mu´ltiplos de cualquier entero. He aqu´ı una funci´on que encapsula el bucle de la seccio´n anterior y la generaliza para imprimir mu´ltiplos de n. def imprimeMultiplos(n): i = 1 while i <= 6: print n*i, ’t’, i = i + 1 print
  • 98. 6 Itera Para encapsular, todo lo que hubimos de hacer fue an˜adir la primera l´ınea, que declara el nombre de la funci´on y la lista de par´ametros. Para generalizar, todo lo que tuvimos que hacer fue sustituir el valor 2 por el par´ametro n. Si llamamos a esta funci´on con el argumento 2, obtenemos la misma salida que antes. Con el argumento 3, la salida es: 3 6 9 12 15 18 y con argumento 4, la salida es 4 8 12 16 20 24 A estas alturas es probable que haya adivinado c´omo vamos a imprimir una tabla de multiplicaci´on: llamaremos a imprimeMultiplos repetidamente con diferentes argumentos. De hecho, podemos a usar otro bucle: i = 1 while i <= 6: imprimeMultiplos(i) i = i + 1 Observe hasta qu´e punto este bucle es similar al que hay en el interior de imprimeMultiplos. Todo lo que hicimos fue sustituir la sentencia print por una llamada a una funci´on. La salida de este programa es una tabla de multiplicaci´on: 6.6. M´as encapsulacio´n Para dar m´as ejemplos de encapsulacio´n, tomaremos el c´odigo del final de la Secci´on 6.5 y lo envolveremos en una funci´on: def imprimeTablaMult(): i = 1 while i <= 6: imprimeMultiplos(i) i = i + 1 El proceso que mostramos aqu´ı es un plan de desarrollo habitual. Se desa- rrolla gradualmente el c´odigo an˜adiendo l´ıneas fuera de cualquier funci´on o en 1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36
  • 99. 1 i2 3 n 3 i 1 2 6.7 Variables locales 69 el int´erprete. Cuando conseguimos que funcionen, se extraen y se envuelven en una funci´on. Este plan de desarrollo es especialmente si, al comenzar a escribir, no sabe c´omo dividir el programa en funciones. Este enfoque le permite disen˜arlo sobre la marcha. 6.7. Variables locales Quiz´a se est´e preguntando c´omo podemos usar la misma variable tanto en imprimeMultiplos como en imprimeTablaMult. ¿No habr´a problemas cuan- do una de las funciones cambie los valores de la variable? La respuesta es no, ya que la variable i en imprimeMultiplos y la variable i in imprimeTablaMult no son la misma variable. Las variables creadas dentro de una funci´on son locales. No puede acceder a una variable local fuera de su funci´on “hu´esped”. Eso significa que es posible tener mu´ltiples variables con el mismo nombre, siempre que no est´en en la misma funci´on. El diagrama de pila de esta funci´on muestra claramente que las dos variables llamadas i no son la misma variable. Pueden referirse a diferentes valores, y cambiar uno no afecta al otro. printMultTable printMultiples El valor de i en imprimeTablaMult va desde 1 hasta 6. En el diagrama, re- sulta ser 3. El pr´oximo recorrido del bucle ser´a 4. En cada recorrido del bucle, imprimeTablaMult llama a imprimeMultiplos con el valor actual de i como argumento. Ese valor se asigna al par´ametro n. Dentro de imprimeMultiplos, el valor de i va desde 1 hasta 6. En el diagrama, resulta ser 2. Los cambios en esta variable no tienen ningu´n efecto sobre el valor de i en imprimeTablaMult. Es habitual y perfectamente legal tener diferentes variables locales con el mismo nombre. En especial, los nombres i, j y k se suelen usar como variables de
  • 100. 7 Itera bucle. Si evita usarlas en una funci´on porque las utiliz´o en algu´n otro lugar, probablemente consiga que el programa sea mas dif´ıcil de leer. 6.8. M´as generalizaci´on Como otro ejemplo de generalizaci´on, imagine que desea un programa que im- prima una tabla de multiplicaci´on de cualquier taman˜o, y no s´olo la tabla de 6x6. Podr´ıa an˜adir un par´ametro a imprimeTablaMult: def imprimeTablaMult(mayor): i = 1 while i <= mayor: imprimeMultiplos(i) i = i + 1 Hemos sustituido el valor 6 con el par´ametro mayor. Si ahora se llama a imprimeTablaMult con el argumento 7, obtenemos: lo que es correcto, excepto por el hecho de que seguramente queremos que la tabla est´e cuadrada, con el mismo nu´mero de filas que de columnas. Para hacer- lo, an˜adimos otro par´ametro a imprimeMultiplos, a fin de especificar cua´ntas columnas tendr´ıa que tener la tabla. S´olo para fastidiar, llamaremos tambi´en a este par´ametro mayor, para demostrar que diferentes funciones pueden tener par´ametros con el mismo nombre (al igual que las variables locales). Aqu´ı tenemos el programa completo: def imprimeMultiplos(n, mayor): int i = 1 while i <= mayor: print n*i, ’t’, i = i + 1 print def imprimeTablaMult(mayor): int i = 1 while i <= mayor: 1 2 3 4 5 6 2 4 6 8 10 12 3 6 9 12 15 18 4 8 12 16 20 24 5 10 15 20 25 30 6 12 18 24 30 36 7 14 21 28 35 42
  • 101. 6.9 Funciones 71 imprimeMultiplos(i, mayor) i = i + 1 N´otese que al an˜adir un nuevo par´ametro, tuvimos que cambiar la primera l´ınea de la funci´on (el encabezado de la funci´on), y tuvimos tambi´en que cambiar el lugar donde se llama a la funci´on en imprimeTablaMult. Segu´n lo esperado, este programa genera una tabla cuadrada de 7x7: Cuando generaliza correctamente una funci´on, a menudo se encuentra con que el programa resultante tiene capacidades que Usted no pensaba. Por ejemplo, quiz´a observe que la tabla de multiplicaci´on es sim´etrica, porque ab = ba, de manera que todas las entradas de la tabla aparecen dos veces. Puede ahorrar tinta imprimiendo s´olo la mitad de la tabla. Para hacerlo, s´olo tiene que cambiar una l´ınea de imprimeTablaMult. Cambie imprimeMultiplos(i, mayor) po r imprimeMultiplos(i, i) y obtendr´a 1 2 4 3 6 9 4 8 12 16 5 10 15 20 25 6 12 18 24 30 36 7 14 21 28 35 42 49 Como actividad, siga o trace la ejecuci´on de esta nueva versi´on de imprimeTablaMult para hacerse una idea de c´omo funciona. 6.9. Funciones Hasta el momento hemos mencionado en alguna ocasio´n “todas las cosas para las que sirven las funciones”. Puede que ya se est´e preguntando qu´e cosas son exactamente. He aqu´ı algunas de las razones por las que las funciones son u´tiles: 1 2 3 4 5 6 7 2 4 6 8 10 12 14 3 6 9 12 15 18 21 4 8 12 16 20 24 28 5 10 15 20 25 30 35 6 12 18 24 30 36 42 7 14 21 28 35 42 49
  • 102. 7 Itera Al dar un nombre a una secuencia de sentencias, hace que su programa sea m´as f´acil de leer y depurar. Dividir un programa largo en funciones le permite separar partes del pro- grama, depurarlas aisladamente, y luego recomponerlas en un todo. Las funciones facilitan tanto la recursividad como la iteraci´on. Las funciones bien disen˜adas son generalmente u´tiles para mas de un pro- grama. Una vez escritas y depuradas, puden reutilizarse. 6.10. Glosario asignacio´n mu´ltiple: Hacer m´as de una asignaci´on a la misma variable du- rante la ejecucio´n de un programa. iteracio´n: La ejecucio´n repetida de un conjunto de sentencias por medio de una llamada recursiva a una funci´on o un bucle. bucle: Sentencia o grupo de sentencias que se ejecutan repetidamente hasta que se cumple una condicio´n de terminaci´on. bucle infinito: Bucle cuya condicio´n de terminaci´on nunca se cumple. cuerpo: Las sentencias que hay dentro de un bucle. variable de bucle: Variable que se usa para determinar la condicio ´n de ter- minaci´on de un bucle. tabulador: Car´acter especial que hace que el cursor se mueva hasta la siguiente marca de tabulaci´on en la l´ınea actual. nueva l´ınea: Un cara´cter especial que hace que le cursor se mueva al inicio de la siguiente l´ınea. cursor: Un marcador invisible que sigue el rastro de donde se imprimir´a el siguiente cara´cter. secuencia de escape: Car´acter de escape ( ) seguido por uno o mas caracteres imprimibles, que se usan para designar un cara´cter no imprimible. encapsular: Dividir un programa largo y complejo en componentes (como las funciones) y aislar los componentes unos de otros (por ejemplo usando variables locales).
  • 103. 6.10 Glosario 73 generalizar: Sustituir algo innecesariamente espec´ıfico (como es un valor cons- tante) con algo convenientemente general (como es una variable o par´ame- tro). La generalizaci´on hace el c´odigo mas vers´atil, mas apto para reutili- zarse y algunas veces incluso m´as f´acil de escribir. plan de desarrollo: Proceso para desarrollar un programa. En este cap´ıtulo, hemos mostrado un estilo de desarrollo basado en desarrollar c´odigo para hacer cosas simples y espec´ıficas, y luego encapsularlas y generalizarlas.
  • 105. Cap´ıtulo 7 Cadenas 7.1. Un tipo de datos compuesto Hasta el momento hemos visto tres tipos: int, float, y string. Las cadenas son cuantitativamente diferentes de los otros dos porque esta´n hechas de piezas menores: caracteres. Los tipos que comprenden piezas menores se llaman tipos de datos com- puestos. Dependiendo de qu´e estemos haciendo, podemos querer tratar un tipo compuesto como una u´nica cosa o acceder a sus partes. Esta ambigu¨edad es u´til. El operador corchete selecciona un cara´cter suelto de una cadena. >>> fruta = "banana" >>> letra = fruta[1] >>> print letra La expresio´n fruta[1] selecciona el cara´cter nu´mero 1 de fruta. La variable letra apunta al resultado. Cuando mostramos letra, nos encontramos con una sorpresa: a La primera letra de "banana" no es a. A no ser que usted sea un programador. Por perversas razones, los cient´ıficos de la computacio´n siempre empiezan a contar desde cero. La 0-sima letra (“cer´osima”) de "banana" es b. La 1-´esima (“un´esima”) es a, y la 2-´esima (“dos´esima”) letra es n. Si quiera la cer´osima letra de una cadena, simplemente pone 0, o cualquier expresio´n de valor 0, entre los corchetes:
  • 106. 7 Caden >>> letra = fruta[0] >>> print letra b A la expresio´n entre corchetes se le llama ´ındice. Un ´ındice identifica a un miembro de un conjunto ordenado, en este caso el conjunto de caracteres de la cadena. El ´ındice indica cua´l quiere usted, de ah´ı el nombre. Puede ser cualquier expresio´n entera. 7.2. Longitud La funci´on len devuelve el nu´mero de caracteres de una cadena: >>> fruta = "banana" >>> len(fruta) 6 Para obtener la u´ltima letra de una cadena puede sentirse tentado a probar algo como esto: longitud = len(fruta) ultima = fruta[longitud] # ERROR! Eso no funcionar´a. Provoca un error en tiempo de ejecucio´n IndexError: string index out of range. La razo´n es que no hay una sexta letra en "banana". Como empezamos a contar por cero, las seis letras esta´n numeradas del 0 al 5. Para obtener el u´ltimo cara´cter tenemos que restar 1 de longitud: longitud = len(fruta) ultima = fruta[longitud-1] De forma alternativa, podemos usar ´ındices negativos, que cuentan hacia atr´as desde el final de la cadena. La expresio´n fruta[-1] nos da la u´ltima letra. fruta[-2] nos da la penu´ltima, y as´ı. 7.3. Recorrido y el bucle for Muchos c´alculos incluyen el proceso de una cadena cara´cter a cara´cter. A me- nudo empiezan por el principio, seleccionan cada cara´cter por turno, hacen algo con ´el y siguen hasta el final. Este patr´on de proceso se llama recorrido. Una forma de codificar un recorrido es una sentencia while: indice = 0 while indice < len(fruta): letra = fruta[indice]
  • 107. 7.3 Recorrido y el bucle for 77 print letra indice = indice + 1 Este bucle recorre la cadena y muestra cada letra en una l ´ınea distinta. La condicio´n del bucle es indice < len(fruta), de modo que cuando indice es igual a la longitud de la cadena, la condicio´n es falsa y no se ejecuta el cuerpo del bucle. El u´ltimo cara´cter al que se accede es el que tiene el ´ındice len(fruta)-1, que es el u´ltimo cara´cter de la cadena. Como ejercicio, escriba una funci´on que tome una cadena como ar- gumento y entregue las letras en orden inverso, una por l´ınea. Es tan habitual usar un ´ındice para recorrer un conjunto de valores que Python facilita una sintaxis alternativa m´as simple: el bucle for: for car in fruta: print car Cada vez que recorremos el bucle, se asigna a la variable car el siguiente cara´cter de la cadena. El bucle continu´a hasta que no quedan caracteres. El ejemplo siguiente muestra c´omo usar la concatenacio´n junto a un bucle for para generar una serie abeceda´rica. “Abeceda´rica” es la serie o lista en la que cada uno de los elementos aparece en orden alfab´etico. Por ejemplo, en el libro de Robert McCloskey Make Way for Ducklings (Dejad paso a los patitos), los nombres de los patitos son Jack, Kack, Lack, Mack, Nack, Ouack, Pack, y Quack. Este bucle saca esos nombres en orden: prefijos = "JKLMNOPQ" sufijo = "ack" for letra in prefijos: print letra + sufijo La salida del programa es: Jack Kack Lack Mack Nack Oack Pack Qack Por supuesto, esto no es del todo correcto, porque “Ouack” y “Quack” no esta´n correctamente escritos.
  • 108. 7 Caden Como ejercicio, modifique el programa para corregir este error. 7.4. Porciones de cadenas Llamamos porci´on a un segmento de una cadena. La seleccio´n de una porci´on es similar a la seleccio´n de un cara´cter: >>> s = "Pedro, Pablo, y Mar´ıa" >>> print s[0:5] Pedro >>> print s[7:12] Pablo >>> print s[15:20] Mar ´ıa El operador [n:m] devuelve la parte de la cadena desde el en ´esimo cara´cter hasta el “em´esimo”, incluyendo el primero pero excluyendo el u´ltimo. Este com- portamiento contradice a nuestra intuici´on; tiene mas sentido si imagina los ´ındices sen˜alando entre los caracteres, como en el siguiente diagrama: indice 0 1 2 3 4 5 6 Si omite el primer ´ındice (antes de los dos puntos), la porci´on comienza al principio de la cadena. Si omite el segundo ´ındice, la porci´on llega al final de la cadena. As´ı: >>> fruta = "banana" >>> fruta[:3] ’ban’ >>> fruta[3:] ’ana’ ¿Qu´e cree usted que significa s[:]? 7.5. Comparaci´on de cadenas Los operadores de comparacio´n trabajan sobre cadenas. Para ver si dos cadenas son iguales: fruta " b a n a n a "
  • 109. 7.6 Las cadenas son inmutables 79 if palabra == "banana": print "S´ı, no tenemos bananas!" Otras operaciones de comparacio´n son u´tiles para poner palabras en orden al- fab´etico: if palabra < "banana": print "Tu palabra," + palabra + ", va antes de banana." elif palabra > "banana": print "Tu palabra," + palabra + ", va despu´es de banana." else: print "Sı, no tenemos bananas!" Sin embargo, deber´ıa usted ser consciente de que Python no maneja las mayu´scu- las y minu´sculas como lo hace la gente. Todas las mayu´suculas van antes de la minu´sculas. Como resultado de ello: Tu palabra, Zapato, va antes de banana. Una forma comu´n de abordar este problema es convertir las cadenas a un forma- to esta´ndar, como pueden ser las minu ´sculas, antes de realizar la comparacio´n. Un problema mayor es hacer que el programa se d´e cuenta de que los zapatos no son frutas. 7.6. Las cadenas son inmutables Es tentador usar el operador [] en el lado izquierdo de una asignaci´on, con la intenci´on de cambiar un cara´cter en una cadena. Por ejemplo: saludo = "Hola, mundo" saludo[0] = ’M’ # ERROR! print saludo En lugar de presentar la salida Mola, mundo, este c´odigo presenta el siguien- te error en tiempo de ejecucio´n TypeError: object doesn’t support item assignment. Las cadenas son inmutables, lo que significa que no puede cambiar una cade- na existente. Lo m´as que puede hacer es crear una nueva cadena que sea una variaci´on de la original: saludo = "Hola, mundo" nuevoSaludo = ’M’ + saludo[1:] print nuevoSaludo
  • 110. 8 Caden Aqu´ı la solucio´n es concatenar una nueva primera letra a una porci´on de saludo. Esta operacio´n no tiene efectos sobre la cadena original. 7.7. Una funcio´n “encuentra” ¿Qu´e hace la siguiente funci´on? def encuentra(cad, c): indice = 0 while indice < len(cad): if cad[indice] == c: return indice indice = indice + 1 return -1 En cierto sentido, encuentra es lo contrario del operador []. En lugar de tomar un ´ındice y extraer el cara´cter correspondiente, toma un cara´cter y encuentra el ´ındice donde aparece el cara ´cter. Si el cara´cter no se encuentra, la funci´on devuelve -1. Este es el primer ejemplo que hemos visto de una sentencia return dentro de un bucle. Si cad[indice] == c, la funci´on vuelve inmediatamente, escapando del bucle prematuramente. Si el cara´cter no aparece en la cadena, el programa sale del bucle normalmente y devuelve -1. Este patr´on de computacio´n se llama a veces un recorrido “eureka” porque en cuanto encontramos lo que buscamos, podemos gritar “¡Eureka!” y dejar de buscar. A modo de ejercicio, modifique la funci´on encuentra para que acepte un tercer para´metro, el ´ındice de la cadena donde deber ´ıa empezar a buscar. 7.8. Bucles y conteo El programa que sigue cuenta el nu´mero de veces que la letra a aparece en una cadena:
  • 111. 7.9 El m´odulo “string” 81 fruta = "banana" cuenta = 0 for car in fruta: if car == ’a’: cuenta = cuenta + 1 print cuenta Este programa muestra otro patr´on de computacio´n llamado contador. La variable cuenta se incializa a 0 y luego se incrementa cada vez que se encuentra una a. (Incrementar es aumentar en uno; es lo contario de decrementar, y sin relacio´n alguna con “excremento”, que es un nombre.) Al salir del bucle, cuenta contiene el resultado – el nu´mero total de aes. Como ejercicio, encapsule este c´odigo en una funci´on llamada cuentaLetras, y general´ıcela de forma que acepte la cadena y la letra como para´metros. Como un segundo ejercicio, reescriba esta funci´on para que en lu- gar de recorrer la cadena, use la versi´on de tres para ´metros de encuentra del anterior. 7.9. El mo´dulo “string” El m´odulo string contiene funciones u´tiles para manipular cadenas. Como es habitual, tenemos que importar el m´odulo antes de poder usarlo: >>> import string El m´odulo string incluye una funci´on llamada find que hace lo mismo que la funci´on encuentra que escribimos. Para llamarla debemos especificar el nombre del m´odulo y el nombre de la funci ´on por medio de la notaci´on de punto. >>> fruta = "banana" >>> indice = string.find(fruta, "a") >>> print indice 1 Este ejemplo demuestra uno de los beneficios de los modulos: ayudan a evitar las colisiones entre los nombres de las funciones predefinidas y las definidas por el usuario. Al usar la notaci´on de punto podr´ıamos especificar qu´e versio´n de find queremos en caso de haberle daddo un nombre en ingl´es a nuestra funci´on. En realidad, string.find es m´as general que nuestra versio´n. Para empezar, puede encontrar subcadenas, no s´olo caracteres:
  • 112. 8 Caden >>> string.find("banana", "na") 2 Adem´as, acepta un argumento adicional que especifica el ´ındice en el que deber´ıa comenzar: >>> string.find("banana", "na", 3) 4 O puede tomar dos argumentos adicionales que especifican un intervalo de ´ındi- ces: >>> string.find("sus", "s", 1, 2) -1 En este ejemplo, la bu´squeda falla porque la letra s no aparece en el intervalo de ´ındices desde 1 hasta 2 (sin incluir 2). 7.10. Clasificaci´on de caracteres A menudo viene bien examinar un cara´cter y comprobar si es una letra mayu´scu- la o minu´scula, o si es un cara´cter o un d´ıgito. El modulo string proporciona varias constantes que son u´tiles para estos menesteres. La cadena string.lowercase contiene todas las letras que el sistema conside- ra como minu´sculas. De forma similar, string.uppercase contiene todas las mayu´sculas. Pruebe lo que sigue y vea qu´e obtiene: >>> print string.lowercase >>> print string.uppercase >>> print string.digits Podemos usar estas constantes y find para clasificar caracteres. Por ejemplo, si find(lowercase, c) devuelve un valor que no sea -1, entonces c es una minu´scula: def esMinuscula(c): return find(string.lowercase, c) != -1 Alternativamente, podemos aprovecharnos del operador in, que determina si un cara´cter aparece en una cadena: def esMinuscula(c): return c in string.lowercase
  • 113. 7.11 Glosario 83 Como una alternativa m´as, podemos usar el operador de comparacio´n, aunque esta solucio´n s´olo sea pr´actica para el alfabeto ingl´es: def esMinuscula(c): return ’a’ <= c <= ’z’ Si c esta´ entre a y z, tiene que ser una minu´scula. Como ejercicio, explique qu´e versi´on de esMinuscula cree que es m´as r´apida. ¿Puede pensar en otras razones aparte de la velocidad para preferir una sobre la otra? Otra constante definida en el m´odulo string puede sorprenderle cuando la imprima: >>> print string.whitespace Los caracteres de whitespace mueven el cursor sin imprimir nada. Crean los espacios en blanco entre los caracteres visibles (al menos sobre papel blanco). La constante string.whitespace contiene todos los caracteres de espacio en blanco, inclu´ıdos espacio, tabulador (t), y salto de l´ınea (n). Hay otras funciones u´tiles en el m´odulo string, pero este libro no pretende ser un manual de referencia. Por otra parte, la Referencia de la Biblioteca de Python s´ı lo es. Junto con un mont´on m´as de documentaci´on, esta´ disponible en el sitio web de Python, www.python.org. 7.11. Glosario tipo de datos compuesto: Un tipo de datos en el que los valores esta´n hechos de componentes o elementos que son a su vez valores. recorrer: Realizar de forma iterativa una operacio´n similar sobre cada uno de los elementos de un conjunto. ´ındice: Una variable o valor usado para seleccionar un miembro de un conjunto ordenado, como puede ser un cara´cter de una cadena. porci´on: Una parte de una cadena especificada por un intervalo de ´ındices. mutable: Un tipo de datos compuesto a cuyos elementos se les puede asignar nuevos valores. contador: Una variable usada para contar algo, normalmente inicializado a cero e incrementado posteriormente.
  • 114. 8 Caden incrementar: Aumentar el valor de una variable en una unidad. decrementar: Disminuir el valor de una variable en una unidad. espacio en blanco: Cualquiera de los caracteres que mueven el cursor sin im- primir caracteres visibles. La constante string.whitespace contiene to- dos los caracterse de espacio en blanco.
  • 115. Cap´ıtulo 8 Listas Una lista es un conjunto ordenado de valores, en el cual cada valor va identifica- do por un ´ındice. Los valores que constituyen una lista son sus elementos. Las listas son similares a las cadenas de texto (strings), que son conjuntos ordenados de caracteres, excepto en que los elementos de una lista pueden ser de cualquier tipo. Las listas y las cadenas, y otras cosas que se comportan como conjuntos ordenados, se llaman secuencias. 8.1. Valores de una lista Hay varias maneras de crear una nueva lista; la mas sencilla es encerrar sus elementos entre corchetes: [10, 20, 30, 40] ["spam", "el´astico", "golondrina"] El primer ejemplo es una lista de cuatro enteros. El segundo es una lista de tres cadenas de texto. Los elementos de una lista no tienen por qu´e ser del mismo tipo. La siguiente lista contiene una cadena, un nu´mero con decimales y un entero, y, maravilla de las maravillas, otra lista: ["hola", 2.0, 5, [10, 20]] Se dice que una lista dentro de otra lista esta´ anidada. Las listas que contienen nu´meros enteros consecutivos son comunes, de manera que Python proporciona una manera sencilla de crearlas: >>> range(1,5) [1, 2, 3, 4]
  • 116. 8 List La funci´on range toma dos argumentos y devuelve una lista que contiene todos los enteros entre el primero y el segundo, ¡incluyendo el primero pero no el segundo! Hay dos formas alternativas para range. Con un solo argumento, crea una lista que empieza desde 0: >>> range(10) [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] Si hay un tercer argumento, especificara´ el espacio entre dos valores sucesivos; a esto se le llama paso (step). Este ejemplo cuenta de 1 a 10 de dos en dos (con pasos de 2). >>> range(1, 10, 2) [1, 3, 5, 7, 9] Para terminar, hay una lista especial que no contiene elementos. Se la llama lista vac´ıa y se representa []. Con todas estas maneras para crear listas, ser´ıa decepcionante que no pudi´era- mos asignar valores de listas a variables o pasar listas como par´ametros a fun- ciones. Por supuesto que podemos. vocabulario = ["mejorar", "castigar", "defenestrar"] numeros = [17, 123] vacio = [] print vocabulario, numeros, vacio [’mejorar’, ’castigar’, ’defenestrar’] [17, 123] [] 8.2. Acceso a los elementos La sintaxis para acceder a los elementos de una lista es la misma que para acceder a los caracteres de una cadena: el operador corchetes []. La expresio´n dentro de los corchetes especifica el ´ındice. Recuerde que los ´ındices siempre comienzan en cero: print numeros[0] numeros[1] = 5 El operador [] puede aparecer en cualquier parte de una expresio ´n. Cuando aparece a la izquierda de una asignaci´on, cambia uno de los elementos de la lista, de manera que el “un´esimo” elemento de numeros, que era 123, ahora es 5. Se puede usar como ´ındice cualquier expresio´n entera.
  • 117. 8.3 Longitud (taman˜o) de una lista 87 >>> numeros[3-2] 5 >>> numeros[1.0] TypeError: sequence index must be integer Si intenta acceder (leer o modificar) un elemento que no existe, obtendr´a un error en tiempo de ejecucio´n: >>> numeros[2] = 5 IndexError: list assignment index out of range Si se da un ´ındice negativo, se cuenta hacia atr´as desde el final de la lista. >>> numeros[-1] 5 >>> numeros[-2] 17 >>> numeros[-3] IndexError: list index out of range numeros[-1] es el u´ltimo elemento de la lista, numeros[-2] es el penu´ltimo, y numeros[-3] no existe. Es muy habitual usar una varible de bucle como ´ındice para una lista: jinetes = ["guerra", "hambre", "peste", "muerte"] i = 0 while i < 4: print jinetes[i] i = i + 1 Este bucle while cuenta desde 0 hasta 4. Cuando la variable de bucle vale 4, la condicio´n falla y acaba el bucle. Por tanto, el cuerpo del bucle s´olo se ejecuta cuando i es 0, 1, 2 y 3. Cada vez que recorremos el bucle, la variable i se usa como ´ındice de la lis- ta, imprimiendo el elemento i-´esimo. Esta plantilla de computacio´n se llama recorrido de lista. 8.3. Longitud (taman˜o) de una lista La funci´on len toma una lista y devuelve su taman˜o. Es una buena idea usar este valor como l´ımite superior de un bucle, en lugar de una constante. De esta manera, si el taman˜o de la lista cambia, no habr´a que estar haciendo cambios en todos los bucles; funcionar´an correctamente con cualquier taman˜o de lista. jinetes = ["guerra", "hambre", "peste", "muerte"] i = 0
  • 118. 8 List while i < len(jinetes): print jinetes[i] i = i + 1 La u´ltima vez que se ejecuta el cuerpo del bucle, i es len(jinetes) - 1, que es el ´ındice del u´ltimo elemento. Cuando i se iguala a len(jinetes), la condicio´n falla y no se ejecuta el cuerpo, lo que es una cosa buena, ya que len(jinetes) no es un ´ındice legal. Aunque una lista puede contener otra lista como elemento, la lista anidada cuenta como un elemento sencillo. El taman˜o de esta lista es 4: [’spam!’, 1, [’Brie’, ’Roquefort’, ’Pol le Veq’], [1, 2, 3]] Como ejercicio, escriba un bucle que recorra la lista anterior e im- prima la longitud de cada elemento. ¿qu´e ocurre si env´ıa un entero a len? 8.4. Pertenencia a una lista in es un operador booleano que comprueba la pertenencia a una secuencia. Lo usamos en la Secci´on 7.10 con las cadenas, pero tambi´en funciona con las listas y otras secuencias: >>> jinetes = [’guerra’, ’hambre’, ’peste’, ’muerte’] >>> ’peste’ in jinetes 1 >>> ’libertinaje’ in jinetes 0 Como “peste” es un miembro de la lista jinetes, el operador in devuelve verdadero. Como “libertinaje” no esta´ en la lista, in devuelve falso. Podemos usar not en combinaci´on con in para comprobar si un elemento no es miembro de una lista: >>> ’libertinaje’ not in jinetes 1 8.5. Listas y bucles for El bucle for que vimos en la Secci´on 7.3 tambi´en funciona con las listas. La sintaxis generalizada de un bucle for es:
  • 119. 8.6 Operaciones con listas 89 for VARIABLE in LISTA: CUERPO Esta sentencia es equivalente a: i = 0 while i < len(LISTA): VARIABLE = LISTA[i] CUERPO i = i + 1 El bucle for es m´as conciso porque podemos eliminar la variable de bucle, i. Aqu´ı tenemos el bucle anterior con un bucle for: for jinete in jinetes: print jinete M´as au´n, casi se lee igual que en espan˜ol, “Para (cada) jinete en (la lista de) jinetes, imprime (el nombre del) jinete”. Se puede usar cualquier expresio´n de lista en un bucle for: for numero in range(20): if numero 2 == 0: print numero for fruta in ["pl´atano", "manzana", "membrillo"]: print "Me gusta comer " + fruta + "s!" El primer ejemplo imprime todos los nu´meros pares entre el 0 y el 19. El segundo ejemplo expresa su entusiasmo por diferentes frutas. 8.6. Operaciones con listas El operador + concatena listas: >>> a = [1, 2, 3] >>> b = [4, 5, 6] >>> c = a + b >>> print c [1, 2, 3, 4, 5, 6] De forma similar, el operador * repite una lista un nu´mero dado de veces: >>> [0] * 4 [0, 0, 0, 0] >>> [1, 2, 3] * 3 [1, 2, 3, 1, 2, 3, 1, 2, 3] En el primer ejemplo la lista [0] contiene un solo elemento que es repetido cuatro veces. En el segundo ejemplo, la lista [1, 2, 3] se repite tres veces.
  • 120. 9 List 8.7. Porciones (slices) Las operaciones de porciones que vimos en la Secci´on 7.4 tambi ´en funcionan en sobre las listas: >>> lista = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’] >>> lista[1:3] [’b’, ’c’] >>> lista[:4] [’a’, ’b’, ’c’, ’d’] >>> lista[3:] [’d’, ’e’, ’f’] >>> lista[:] [’a’, ’b’, ’c’, ’d’, ’e’, ’f’] 8.8. Las listas son mutables A diferencia de las cadenas, las listas son mutables, lo que significa que pode- mos cambiar sus elementos. Podemos modificar uno de sus elementos usando el operador corchetes en el lado izquierdo de una asignaci´on: >>> fruta = ["pl´atano", "manzana", "membrillo"] >>> fruta[0] = "pera" >>> fruta[-1] = "naranja" >>> print fruta [’pera’, ’manzana’, ’naranja’] Con el operador de porci´on podemos reemplazar varios elementos a la vez: >>> lista = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’] >>> lista[1:3] = [’x’, ’y’] >>> print lista [’a’, ’x’, ’y’, ’d’, ’e’, ’f’] Adem´as, puede eliminar elementos de una lista asign´andoles la lista vac ´ıa: >>> lista = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’] >>> lista[1:3] = [] >>> lista [’a’, ’d’, ’e’, ’f’] Y puede an˜adir elementos a la lista embuti´endolos en una porci ´on vac´ıa en la posici´on deseada: >>> lista = [’a’, ’d’, ’f’] >>> lista[1:1] = [’b’, ’c’] >>> print lista
  • 121. 8.9 Borrado en una lista 91 [’a’, ’b’, ’c’, ’d’, ’f’] >>> lista[4:4] = [’e’] >>> print lista [’a’, ’b’, ’c’, ’d’, ’e’, ’f’] 8.9. Borrado en una lista El uso de porciones para borrar elementos de una lista puede ser extran˜o, y por ello propicio a los errores. Python nos da una alternativa que resulta mas legible. del elimina un elemento de una lista: >>> a = [’uno’, ’dos’, ’tres’] >>> del a[1] >>> a [’uno’, ’tres’] Como podr´ıa esperar, del maneja ´ındices negativos y provoca un error en tiempo de ejecucio´n sin el ´ındice esta´ fuera de l´ımites. Puede usar una porci´on como ´ındice para del: >>> lista = [’a’, ’b’, ’c’, ’d’, ’e’, ’f’] >>> del lista[1:5] >>> print lista [’a’, ’f’] Como es habitual, las porciones seleccionan todos los elementos hasta, pero no inclu´ıdo, el segundo ´ındice. 8.10. Objetos y valores Si ejecutamos estas sentencias de asignaci´on: a = "banana" b = "banana" Est´a claro que a y b apuntan ambos a cadenas con las letras "banana". Pero no podemos saber si esta´n apuntando a la misma cadena. Hay dos posibles estados:
  • 122. 9 List a b "banana" "banana" a b "banana" a b [ 1, 2, 3 ] [ 1, 2, 3 ] En un caso, a y b se refieren a dos cosas diferentes que tienen el mismo valor. En el segundo caso, se refieren a la misma cosa. Estas “cosas” tienen nombres; se les denomina objetos. Un objeto es una cosa a la que se puede referir una variable. Cada objeto tiene un identificador u´nico, que podemos obtener por medio de la funci´on id. Imprimiendo los identificadores de a y b podemos saber si apuntan al mismo objeto. >>> id(a) 135044008 >>> id(b) 135044008 En este caso, las dos veces obtenemos el mismo identificador, lo que significa que Python s´olo cre´o una cadena y ambas variables, a y b, apuntan a ella. Como las cadenas de texto son inmutables, no hay diferencia pr ´actica entre los dos posibles estados. Para tipos mutables como las listas, s´ı que importa. Curiosamente, las listas se comportan de otra manera. Cuando crea dos listas, obtiene dos objetos: >>> a = [1, 2, 3] >>> b = [1, 2, 3] >>> id(a) 135045528 >>> id(b) 135041704 De manera que el diagrama de estado ser´ıa tal como ´este: a y b tienen el mismo valor, pero no se refieren al mismo objeto. 8.11. Alias (poner sobrenombres) Como las variables apuntan a objetos, si asigna una variable a otra, ambas variables se refieren al mismo objeto:
  • 123. a b [ 1, 2, 3 ] 8.12 Clonar listas 93 >>> a = [1, 2, 3] >>> b = a En este caso, el diagrama de estados ser´ıa como ´este: Como la misma lista tiene dos nombres diferentes, a y b, podemos decir que se le ha puesto un alias. Los cambios hechos a un alias afectan al otro: >>> b[0] = 5 >>> print a [5, 2, 3] Aunque este comportamiento puede ser u´til, a veces es inesperado o indeseable. En general, es m´as seguro evitar los alias cuando trabajemos con objetos muta- bles. Por supuesto, no hay problema con los objetos inmutables. Por ello Python se toma la libertad de poner alias a las cadenas cuando ve una oportunidad de economizar. 8.12. Clonar listas Si queremos modificar una lista y mantener una copia del original, necesitaremos ser capaces de hacer una copia de la lista en s´ı, no s´olo de su referencia. Este proceso a veces se denomina clonado, para evitar la ambigu¨edad de la palabra “copia”. La forma mas f´acil de clonar una lista es por medio del operador de porci ´on: >>> a = [1, 2, 3] >>> b = [] >>> b[:] = a[:] >>> print b [1, 2, 3] La extraccio´n de una porci´on de a crea una nueva lista. En este caso, la porci´on consta de la lista completa. Ahora tenemos libertad de hacer cambios en b sin preocuparnos de a: >>> b[0] = 5 >>> print a [1, 2, 3] Como ejercicio, dibuje un diagrama de estado de a y b antes y des- pues del cambio.
  • 124. 9 List numbers list 8.13. Listas como par´ameteros Cuando se pasa una lista como argumento, en realidad se pasa una referencia a ella, no una copia de la lista. Por ejemplo, la funci´on cabeza toma una lista como par´ametro y devuelve el primer elemento. def cabeza(lista): return lista[0] As´ı es como se usa. >>> numeros = [1,2,3] >>> cabeza(numeros) 1 El par´ametro lista y la variable numeros son alias de un mismo objeto. El diagrama de estado es as´ı: main head [ 1, 2, 3 ] Como el objeto lista esta´ compartido por dos marcos, lo dibujamos entre ambos. Si la funci´on modifica una lista pasada como par ´ametro, el que hizo la llamada ver´a el cambio. borra cabeza elimina el primer elemento de una lista. def borra_cabeza(lista): del lista[0] Aqu´ı vemos el uso de borra cabeza: >>> numeros = [1,2,3] >>> borra_cabeza(numeros) >>> print numeros [2, 3] Si una funci´on devuelve una lista, devuelve una referencia a la lista. Por ejemplo, cola devuelve una lista que contiene todos los elementos de una lista dada, excepto el primero. def cola(lista): return lista[1:] Aqu´ı vemos c´omo se usa cola: >>> numeros = [1,2,3] >>> resto = cola(numeros) >>> print resto >>> [2, 3]
  • 125. 8.14 Listas anidadas 95 Como el valor de retorno se cre´o con una porci´on, es una lista. La creacio ´n de rest, as´ı como cualquier cambio posterior en rest, no afectar´a a numbers. 8.14. Listas anidadas Una lista anidada es una lista que aparece como elemento dentro de otra lista. En esta lista, el tri-´esimo elemento es una lista anidada: >>> lista = ["hola", 2.0, 5, [10, 20]] Si imprimimos lista[3], obtendremos [10, 20]. Para extraer los elementos de la lista anidada, podemos proceder en dos pasos: >>> elt = lista[3] >>> elt[0] 10 O podemos combinarlos: >>> lista[3][1] 20 Los operadores corchete se evalu´an de izquierda a derecha, as´ı que esta expresio´n saca el tri-´esimo elemento de lista y luego extrae el un´esimo elemento de ella. 8.15. Matrices Es comu´n usar listas anidadas para representar matrices. Por ejemplo, la matriz: 1 2 3 4 5 6 7 8 9 puede ser representada como: >>> matriz = [[1, 2, 3], [4, 5, 6], [7, 8, 9]] matriz es una lista con tres elementos, siendo cada elemento una fila de la matriz. Podemos elegir una fila entera de la matriz de la forma normal: >>> matriz[1] [4, 5, 6] O tomar s´olo un elemento de la matriz usando la forma de doble ´ındice: >>> matriz[1][1] 5
  • 126. 9 List El primer ´ındice escoge la fila y el segundo la columna. Aunque esta manera de representar matrices es comu´n, no es la u´nica posibilidad. Una pequen˜a variaci´on consiste en usar una lista de columnas en lugar de flas. Mas adelante veremos una alternativa m ´as radical usando un diccionario. 8.16. Cadenas y listas Dos de las funciones m´as u´tiles del m´odulo string tienen que ver con listas de cadenas. La funci´on split divide una cadena en una lista de palabras. Por defecto, cualquier nu´mero de caracteres de espacio en blanco se considera un l´ımite de palabra: >>> import string >>> cancion = "La lluvia en Sevilla..." >>> string.split(cancion) [’La’, ’lluvia’, ’en’, ’Sevilla...’] Se puede usar un argumento opcional llamado delimitador para especificar qu´e caracteres se usar´an como l´ımites de palabra. El siguiente ejemplo usa la cadena ll como delimitador: >>> string.split(cancion, ’ll’) [’La ’, ’uvia en Sevi’, ’a...’] Observe que el delimitador no aparece en la lista. La funci´on join es la inversa de split. Toma una lista de cadenas y concatena los elementos con un espacio entre cada par: >>> lista = [’La’, ’lluvia’, ’en’, ’Sevilla...’] >>> string.join(lista) ’La lluvia en Sevilla...’ Como split, join acepta un delimitador opcional que se inserta entre los ele- mentos. El delimitador por defecto es el espacio. >>> string.join(lista, ’_’) ’La_lluvia_en_Sevilla...’ A modo de ejercicio, describa la relaci´on que hay entre string.join(string.split(cancion)) y cancion. ¿Es la misma para todas las cadenas? ¿Cua´ndo ser´ıa diferente?
  • 127. 8.17 Glosario 97 8.17. Glosario lista: Una coleccio´n de objetos con nombre, en la que cada objeto es identificado por un ´ındice. ´ındice: Una variable o valor enteros que se usan para indicar un elemento de una lista. elemento: Uno de los valores de una lista (u otra secuencia). El operador cor- chete selecciona elementos de una lista. secuencia: Cualquier tipo de datos que consita en un conjunto ordenado de elementos, con cada elemento identificado por un ´ındice. lista anidada: Una lista que es elemento de otra lista. recorrido de lista: Acceso secuencial a cada elemento de una lista. objeto: Una cosa a la que se puede referir una variable. alias: Mu´ltiples variables que contienen referencias al mismo objeto. clonar: Crear un objeto nuevo que tiene el mismo valor que un objeto ya exis- tente. Copiar una referencia a un objeto crea un alias, pero no clona el objeto. delimitador: Un cara´cter o cadena utilizado para indicar donde debe cortarse una cadena.
  • 129. Cap´ıtulo 9 Tuplas 9.1. Mutabilidad y tuplas Hasta ahora, ha visto dos tipos compuestos: cadenas, que esta´n hechas de ca- racteres, y listas, que esta´n hechas de elementos de cualquier tipo. Una de las diferencias que sen˜alamos es que los elementos de una lista se pueden modifi- car, pero los caracteres de una cadena no. En otras palabras, las cadenas son inmutables y las listas son mutables. En Python hay otro tipo llamado tupla que es similar a una lista salvo en que es inmutable. Sint´acticamente, una tupla es una lista de valores separados por comas: >>> tupla = ’a’, ’b’, ’c’, ’d’, ’e’ Aunque no es necesario, la convencio´n dice que hay que encerrar las tuplas entre par´entesis: >>> tupla = (’a’, ’b’, ’c’, ’d’, ’e’) Para crear una tupla con un solo elemento, debemos incluir una coma final: >>> t1 = (’a’,) >>> type(t1) <type ’tuple’> Sin la coma, Python trata (´a’) como una cadena entre par´entesis: >>> t2 = (’a’) >>> type(t2) <type ’string’>
  • 130. 1 Tupl Dejando a un lado las cuestiones de sintaxis, las operaciones sobre las tuplas son las mismas que sobre las listas. El operador ´ındice selecciona un elemento de la tupla. >>> tupla = (’a’, ’b’, ’c’, ’d’, ’e’) >>> tupla[0] ’a’ Y el operador de porci´on selecciona un intervalo de elementos. >>> tupla[1:3] (’b’, ’c’) Pero si intentamos modificar uno de los elementos de la tupla provocaremos un error: >>> tupla[0] = ’A’ TypeError: object doesn’t support item assignment Por supuesto, incluso aunque no podamos modificar los elementos de una tupla, podemos sustituir una tupla por otra diferente: >>> tupla = (’A’,) + tupla[1:] >>> tupla (’A’, ’b’, ’c’, ’d’, ’e’) 9.2. Asignaci´on de tuplas De vez en cuando, es u´til intercambiar los valores de dos variables. Para ha- cerlo con sentencias de asignaci´on convencionales debemos usar una variable temporal. Por ejemplo, para intercambiar a y b: >>> temp = a >>> a = b >>> b = temp Si tenemos que hacer esto a menudo, esta aproximaci´on resulta aparatosa. Pyt- hon proporciona una forma de asignacio´n de tuplas que soluciona este pro- blema elegantemente: >>> a, b = b, a El lado izquierdo es una tupla de variables, el lado derecho es una tupla de valores. Cada valor se asigna a su respectiva variable. Todas las expresiones del lado derecho se evalu´an antes de las asignaciones. Esta caracter´ıstica hace de la asignaci´on de tuplas algo muy vers´atil.
  • 131. 9.3 Tuplas como valor de retorno 101 Naturalmente, el nu´mero de variables a la izquierda y el nu´mero de valores a la derecha deben ser iguales: >>> a, b, c, d = 1, 2, 3 ValueError: unpack tuple of wrong size 9.3. Tuplas como valor de retorno Las funciones pueden devolver tuplas como valor de retorno. Por ejemplo, podr´ıamos escribir una funci´on que intercambie dos par ´ametros: def intercambio(x, y): return y, x Luego podemos asignar el valor de retorno a una tupla con dos variables: a, b = intercambio(a, b) En este caso, no hay ninguna ventaja en convertir intercambio en una funci´on. De hecho, existe un peligro al intentar encapsular intercambio, y es el tentador error que sigue: def intercambio(x, y): # versi´on incorrecta x, y = y, x Si llamamos a esta funci´on as´ı: intercambio(a, b) a y x son alias del mismo valor. Cambiar x dentro de intercambio hace que x se refiera a un valor diferente, pero no tiene efecto alguno sobre a en main . De forma similar, cambiar y no tiene efecto sobre b. Esta funci´on se ejecuta sin generar un mensaje de error, pero no hace lo que intentamos. Este es un ejemplo de error sema´ntico. A modo de ejercicio, dibuje un diagrama de estados para esta funci´on de manera que pueda ver por qu´e no trabaja como usted quiere. 9.4. Nu´meros aleatorios La mayor parte de los programas hacen lo mismo cada vez que los ejecutamos, por lo que se dice que son deterministas. Normalmente el determinismo es una cosa buena, ya que esperamos que un c ´alculo nos d´e siempre el mismo
  • 132. 1 Tupl resultado. Para algunas aplicaciones, sin embargo, queremos que el computador sea impredecible. El ejemplo obvio son los juegos, pero hay mas. Hacer que un programa sea realmente no determinista resulta no ser tan sencillo, pero hay formas de que al menos parezca no determinista. Una de ellas es generar nu´meros aleatorios y usarlos para determinar el resultado del programa. Python proporciona una funci´on interna que genera nu´meros pseudoaleatorios, que no son verdaderamente aleatorios en un sentido matem´atico, pero servira ´n para nuestros prop´ositos. El m´odulo random contiene una funci´on llamada random que devuelve un nu´mero en coma flotante entre 0,0 y 1,0. Cada vez que usted llama a random obtiene el siguiente nu´mero de una larga serie. Para ver un ejemplo, ejecute este bucle: import random for i in range(10): x = random.random() print x Para generar un nu´mero aleatorio entre 0,0 y un l´ımite superior como maximo, multiplique x por maximo. Como ejercicio adicional, genere un nu´mero aleatorio entero entre Como ejercicio, genere un nu´mero aleatorio entre minimo y maximo. minimo y maximo, incluyendo ambos extremos. 9.5. Lista de nu´meros aleatorios El primer paso es generar una lista de valores aleatorios. listaAleatorios acepta un par´ametro entero y devuelve una lista de nu´meros aleatorios de la longitud dada. Comienza con una lista de n ceros. Cada vez que ejecuta el bucle, sustituye uno de los elementos con un nu´mero aleatorio. El valor de retorno es una referencia a la lista completa: def listaAleatorios(n): s = [0] * n for i in range(n): s[i] = random.random() return s Vamos a probar esta funci´on con una lista de ocho elementos. A la hora de depurar es una buena idea empezar con algo pequen˜o.
  • 133. 9.6 Conteo 103 >>> listaAleatorios(8) 0.15156642489 0.498048560109 0.810894847068 0.360371157682 0.275119183077 0.328578797631 0.759199803101 0.800367163582 Se supone que los nu´meros generados por random esta´n distribuidos uniforme- mente, lo que significa que cada valor es igualmente probable. Si dividimos el intervalo de valores posibles en “baldes” de igual taman˜o y contamos el nu´mero de veces que un valor cae en cada balde, deber´ıamos tener m´as o menos el mismo nu´mero en todos. Podemos contrastar esta teor´ıa escribiendo un programa que divida el intervalo en baldes y contando el nu´mero de valores en cada uno. 9.6. Conteo Un buen enfoque sobre problemas como ´este es dividir el problema en subpro- blemas que encajen en un esquema computacional que hayamos visto antes. En este caso, queremos recorrer una lista de nu´meros y contar el nu´mero de veces que un valor cae en un intervalo dado. Eso nos suena. En la Secci´on 7.8 escribimos un programa que recorr´ıa una cadena de texto y contaba el nu´mero de veces que aparec´ıa una letra determinada. As´ı, podemos hacerlo copiando el programa viejo y adaptandolo al problema actual. El programa original era: cuenta = 0 for car in fruta: if car == ’a’: cuenta = cuenta + 1 print cuenta El primer paso es sustituir fruta con lista y car con num. Esto no cambia el programa, s´olo lo hace m´as legible. El segundo paso es cambiar la comprobacio´n. No estamos interesados en encon- trar letras. Queremos ver si num esta´ entre los valores de minimo y maximo.
  • 134. 1 Tupl cuenta = 0 for num in lista if minimo < num < maximo: cuenta = cuenta + 1 print cuenta El u´ltimo paso es encapsular este c´odigo en una funci´on llamada enElBalde. Los par´ametros son la lista y los valores minimo y maximo. def enElBalde(lista, minimo, maximo): cuenta = 0 for num in lista: if minimo < num < maximo: cuenta = cuenta + 1 return cuenta Copiar y modificar un programa existente nos facilita escribir esta funci´on r´api- damente y nos ahorra un mont´on de tiempo de depuraci´on. Este plan de desa- rrollo se llama coincidencia de esquemas. Si se encuentra trabajando en un problema que ya soluciono´, reutilice la solucio´n. 9.7. Muchos baldes Tal como aumenta el nu´mero de baldes, enElBalde se hace un tanto dif´ıcil de manejar. Con dos baldes, no esta´ mal: bajo = enElBalde(a, 0.0, 0.5) alto = enElBalde(a, 0.5, 1) Pero con cuatro baldes ya es aparatoso. balde1 = enElBalde(a, 0.0, 0.25) balde2 = enElBalde(a, 0.25, 0.5) balde3 = enElBalde(a, 0.5, 0.75) balde4 = enElBalde(a, 0.75, 1.0) Hay dos problemas. Uno es que tenemos que inventar nuevos nombres de va- riables para cada resultado. El otro es que tenemos que calcular el intervalo de cada balde. Empezaremos por solucionar el segundo problema. Si el nu´mero de baldes es numBaldes, la anchura de cada balde es 1.0 / numBaldes. Usaremos un bucle para calcular el intervalo de cada balde. La variable del bucle, i, cuenta de 1 a numBaldes-1:
  • 135. 9.7 Muchos baldes 105 anchuraBalde = 1.0 / numBaldes for i in range(numBaldes): minimo = i * anchuraBalde maximo = minimo + anchuraBalde print minimo, "hasta", maximo Para calcular el l´ımite inferior de cada balde, multiplicamos la variable de bucle por la anchura de balde. El l´ımite superior esta´ a tan s´olo una anchuraBalde. Con numBaldes = 8, la salida es: 0.0 hasta 0.125 0.125 hasta 0.25 0.25 hasta 0.375 0.375 hasta 0.5 0.5 hasta 0.625 0.625 hasta 0.75 0.75 hasta 0.875 0.875 hasta 1.0 Puede confirmar que todos los bucles tienen la misma anchura, que no se solapan y que cubren todo el intervalo entre 0,0 y 1,0. Volvamos ahora al primer problema. Necesitamos un modo de almacenar ocho enteros, usando la variable de bucle para sen˜alarlos uno por uno. En estos mo- mentos deber´ıa usted estar pensando “¡Lista!”. Debemos crear la lista de baldes fuera del bucle, porque s´olo queremos hacer- lo una vez. Dentro del bucle, podemos llamar repetidamente a enElBalde y actualizar el i-´esimo elemento de la lista: numBaldes = 8 baldes = [0] * numBaldes anchuraBalde = 1.0 / numBaldes for i in range(numBaldes): minimo = i * anchuraBalde maximo = minimo + anchuraBalde baldes[i] = enElBalde(lista, minimo, maximo) print baldes Con una lista de 1000 valores, este c´odigo genera esta lista de baldes: [138, 124, 128, 118, 130, 117, 114, 131] Estos nu´meros son razonablemente pr´oximos a 125, que es lo que esper´abamos Por lo menos, esta´n lo bastante cerca como para que podamos pensar que el generador de nu´meros aleatorios funciona.
  • 136. 1 Tupl Como ejercicio, compruebe esta funci´on con listas m´as largas, y vea si el nu´mero de valores en cada balde tiende a equilibrarse. 9.8. Una solucio´n en una sola pasada Aunque este programa funciona, no es tan eficiente como podr´ıa ser. Cada vez que llama a enElBalde recorre la lista entera. Con el aumento del nu´mero de baldes, llega a ser un mont´on de recorridos. Ser´ıa mejor hacer una sola pasada por la lista y calcular para cada valor el ´ındice del balde en el que cae. Luego podemos incrementar el contador apropiado. En la seccio´n anterior tomamos un ´ındice, i, y lo multiplicamos por la anchuraBalde para hallar el l´ımite inferior de un balde dado. Ahora quere- mos tomar un valor del intervalo 0,0 a 1,0 y hallar el ´ındice del balde en el que cae. Como el problema es el inverso del anterior, podemos suponer que deber´ıamos dividir por anchuraBalde en lugar de multiplicar. La suposici´on es correcta. Como anchuraBalde = 1.0 / numBaldes, dividir por anchuraBalde es lo mis- mo que multiplicar por numBaldes. Si multiplicamos un nu´mero del intervalo que va de 0,0 a 1,0 por numBaldes, obtenemos un nu´mero del intervalo entre 0,0 y numBaldes. Si redondeamos ese nu´mero al entero inferior obtendremos exactamente lo que estamos buscando, un ´ındice de balde: numBaldes = 8 baldes = [0] * numBaldes for i in lista: indice = int(i * numBaldes) baldes[indice] = baldes[indice] + 1 Usamos la funci´on int para convertir un nu´mero en coma flotante en un entero. ¿Es posible que este c´alculo genere un ´ındice que est´e fuera del intervalo (tanto negativo como mayor que len(baldes)-1)? Una lista como baldes que contiene conteos del nu´mero de valores en cada intervalo se llama histograma. Como ejercicio, escriba una funcion llamada histograma que tome como para´metros una lista y un nu´mero de baldes y devuelva un histograma con el nu´mero dado de baldes.
  • 137. 9.9 Glosario 107 9.9. Glosario tipo inmutable: Un tipo en el cual los elementos no se puede modificar. Las asignaciones de elementos o porciones de tipos inmutables provocan un error. tipo mutable: Un tipo de datos en el cual los elementos pueden ser modifi- cados. Todos los tipos mutables son compuestos. Las listas y diccionarios son tipos de datos mutables, las cadenas y las tuplas no. tupla: Un tipo de secuencia que es similar a una lista excepto en que es in- mutable. Las tuplas se pueden usar donde quiera que se necesite un tipo inmutable, como puede ser la clave de un diccionario. asignacio´n de tuplas: Una asignaci´on de todos los elementos de una tupla usando una u´nica sentencia de asignaci´on. La asignaci´on de tuplas sucede m´as bien en paralelo que secuencialmente, haci´endola u´til para intercam- biar valores. determinista: Un programa que hace lo mismo todas las veces que se ejecuta. pseudoaleatorio: Una secuencia de nu´meros que parece ser aleatoria pero que en realidad es el resultado de un c´alculo determinista. histograma: Una lista de enteros en la que cada elemento cuenta el nu´mero de veces que ocurre algo. coincidencia de esquemas: Un plan de desarrollo de programas que implica la identificaci´on de un esquema computacional conocido y el copiado de la solucio´n para un problema similar.
  • 139. Cap´ıtulo 10 Diccionarios Los tipos compuestos que ha visto hasta ahora (cadenas, listas y tuplas) usan en- teros como ´ındices. Si intenta usar cualquier otro tipo como ´ındice provocara´ un error. Los diccionarios son similares a otros tipos compuestos excepto en que pueden usar como ´ındice cualquier tipo inmutable. A modo de ejemplo, crearemos un diccionario que traduzca palabras inglesas al espan˜ol. En este diccionario, los ´ındices son strings (cadenas). Una forma de crear un diccionario es empezar con el diccionario vac´ıo y an˜adir elementos. El diccionario vac´ıo se expresa como {}: >>> ing_a_esp = {} >>> ing_a_esp[’one’] = ’uno’ >>> ing_a_esp[’two’] = ’dos’ La primera asignaci´on crea un diccionario llamado ing a esp; las otras asig- naciones an˜aden nuevos elementos al diccionario. Podemos presentar el valor actual del diccionario del modo habitual: >>> print ing_a_esp {’one’: ’uno’, ’two’: ’dos’} Los elementos de un diccionario aparecen en una lista separada por comas. Cada entrada contiene un ´ındice y un valor separado por dos puntos (:). En un diccionario, los ´ındices se llaman claves, por eso los elementos se llaman pares clave-valor. Otra forma de crear un diccionario es dando una lista de pares clave-valor con la misma sintaxis que la salida del ejemplo anterior:
  • 140. 1 Diccionari >>> ing_a_esp = {’one’: ’uno’, ’two’: ’dos’, ’three’: ’tres’} Si volvemos a imprimir el valor de ing a esp, nos llevamos una sorpresa: >>> print ing_a_esp {’one’: ’uno’, ’three’: ’tres’, ’two’: ’dos’} ¡Los pares clave-valor no esta´n en orden! Afortunadamente, no necesitamos preo- cuparnos por el orden, ya que los elementos de un diccionario nunca se indexan con ´ındices enteros. En lugar de eso, usamos las claves para buscar los valores correspondientes: >>> print ing_a_esp[’two’] ’dos’ La clave ’two’ nos da el valor ’dos’ aunque aparezca en el tercer par clave- valor. 10.1. Operaciones sobre diccionarios La sentencia del elimina un par clave-valor de un diccionario. Por ejemplo, el diccionario siguiente contiene los nombres de varias frutas y el nu´mero de esas frutas en el almac´en: >>> inventario = {’manzanas’: 430, ’bananas’: 312, ... ’naranjas’: 525, ’peras’: 217} >>> print inventario {’naranjas’: 525, ’manzanas’: 430, ’peras’: 217, ’bananas’: 312} Si alguien compra todas las peras, podemos eliminar la entrada del diccionario: >>> del inventario[’peras’] >>> print inventario {’naranjas’: 525, ’manzanas’: 430, ’bananas’: 312} O si esperamos recibir m´as peras pronto, podemos simplemente cambiar el in- ventario asociado con las peras: >>> inventario[’peras’] = 0 >>> print inventario {’naranajas’: 525, ’manzanas’: 430, ’peras’: 0, ’bananas’: 312} La funci´on len tambi´en funciona con diccionarios; devuelve el nu ´mero de pares clave-valor: >>> len(inventario) 4
  • 141. 10.2 M´etodos del diccionario 111 10.2. M´etodos del diccionario Un m´etodo es similar a una funci´on, acepta par´ametros y devuelve un valor, pero la sintaxis es diferente. Por ejemplo, el m ´etodo keys acepta un diccionario y devuelve una lista con las claves que aparecen, pero en lugar de la sintaxis de la funci´on keys(ing a esp), usamos la sintaxis del m´etodo ing a esp.keys(). >>> ing_a_esp.keys() [’one’, ’three’, ’two’] Esta forma de notaci´on de punto especifica el nombre de la funci ´on, keys, y el nombre del objeto al que se va a aplicar la funci´on, ing a esp. Los par´entesis indican que este m´etodo no admite par ´ametros. La llamda a un m´etodo se denomina invocaci´on; en este caso, dir ´ıamos que estamos invocando keys sobre el objeto ing a esp. El m´etodo values es similar; devuelve una lista de los valores del diccionario: >>> ing_a_esp.values() [’uno’, ’tres’, ’dos’] El m´etodo items devuelve ambos, una lista de tuplas con los pares clave-valor del diccionario: >>> ing_a_esp.items() [(’one’,’uno’), (’three’, ’tres’), (’two’, ’dos’)] La sintaxis nos proporciona informaci´on muy u´til acerca del tipo de datos. Los corchetes indican que es una lista. Los par´entesis indican que los elementos de la lista son tuplas. Si un m´etodo acepta un argumento, usa la misma sintaxis que una llamada a una funci´on. Por ejemplo, el m´etodo has key acepta una clave y devuelve verdadero (1) si la clave aparece en el diccionario: >>> ing_a_esp.has_key(’one’) 1 >>> ing_a_esp.has_key(’deux’) 0 Si usted invoca un m´etodo sin especificar un objeto, provoca un error. En este caso, el mensaje de error no es de mucha ayuda: >>> has_key(’one’) NameError: has_key
  • 142. 1 Diccionari 10.3. Asignaci´on de alias y copiado Debe usted estar atento a los alias a causa de la mutabilidad de los diccionarios. Si dos variables se refieren al mismo objeto los cambios en una afectan a la otra. Si quiere modificar un diccionario y mantener una copia del original, use el m´etodo copy. Por ejemplo, opuestos es un diccionario que contiene pares de opuestos: >>> opuestos = {’arriba’: ’abajo’, ’derecho’: ’torcido’, ... ’verdadero’: ’falso’} >>> alias = opuestos >>> copia = opuestos.copy() alias y opuestos se refieren al mismo objeto; copia hace referencia a una copia nueva del mismo diccionario. Si modificamos alias, opuestos tambi´en resulta cambiado: >>> alias[’derecho’] = ’sentado’ >>> opuestos[’derecho’] ’sentado’ Si modificamos copia, opuestos no var´ıa: >>> copia[’derecho’] = ’privilegio’ >>> opuestos[’derecho’] ’sentado’ 10.4. Matrices dispersas En la Secci´on 8.14 usamos una lista de listas para representar una matriz. Es una buena opcio´n para una matriz en la que la mayor´ıa de los valores es diferente de cero, pero piense en una matriz como ´esta: La representaci´on de la lista contiene un monton de ceros: matriz = [ [0,0,0,1,0], [0,0,0,0,0], 0 0 0 1 0 0 0 0 0 0 0 2 0 0 0 0 0 0 0 0 0 0 0 3 0
  • 143. 10.5 Pistas 113 [0,2,0,0,0], [0,0,0,0,0], [0,0,0,3,0] ] Una posible alternativa es usar un diccionario. Como claves, podemos usar tuplas que contengan los nu´meros de fila y columna. E´sta es la representaci´on de la misma matriz por medio de un diccionario: matriz = {(0,3): 1, (2, 1): 2, (4, 3): 3} S´olo hay tres pares clave-valor, una para cada elemento de la matriz diferente de cero. Cada clave es una tupla, y cada valor es un entero. Para acceder a un elemento de la matriz, podemos usar el operador []: matriz[0,3] 1 F´ıjese en que la sintaxis para la representaci´on por medio del diccionario no es la misma de la representaci´on por medio de la lista anidada. En lugar de dos ´ındices enteros, usamos un ´ındice que es una tupla de enteros. Hay un porblema. Si apuntamos a un elemento que es cero, se produce un error porque en el diccionario no hay una entrada con esa clave: >>> matriz[1,3] KeyError: (1, 3) El m´etodo get soluciona este problema: >>> matriz.get((0,3), 0) 1 El primer argumento es la clave; el segundo argumento es el valor que debe devolver get en caso de que la clave no est´e en el diccionario: >>> matriz.get((1,3), 0) 0 get mejora sensiblemente la sema´ntica del acceso a una matriz dispersa. L´astima de sintaxis. 10.5. Pistas Si estuvo jugando con la funci´on fibonacci de la Secci´on 5.7, es posible que haya notado que cuanto m´as grande es el argumento que le da, mas tiempo le
  • 144. 1 Diccionari fibonacci n0 fibonacci n1 fibonacci n0 fibonacci n1 fibonacci n1 fibonacci n2 fibonacci n2 fibonacci n3 fibonacci n4 cuesta ejecutarse. M´as au´n, el tiempo de ejecucio´n aumenta muy r´apidamente. En nuestra m´aquina, fibonacci(20) acaba instantaneamente, fibonacci(30) tarda m´as o menos un segundo, y fibonacci(40) tarda una eternidad. Para entender por qu´e, observe este gr´afico de llamadas de fibonacci con n=4: Un gr´afico de llamadas muestra un conjunto de cajas de funci´on con l´ıneas que conectan cada caja con las cajas de las funciones a las que llama. En lo alto del gr´afico, fibonacci con n=4 llama a fibonacci con n=3 y n=2. A su vez, fibonacci con n=3 llama a fibonacci con n=2 y n=1. Y as´ı sucesivamente. Cuente cua´ntas veces se llama a fibonacci(0) y fibonacci(1). Es una solucio´n ineficaz al problema, y empeora mucho tal como crece el argumento. Una buena solucio´n es llevar un registro de los valores que ya se han calculado almacen´andolos en un diccionario. A un valor que ya ha sido calculado y alma- cenado para un uso posterior se le llama pista. Aqu´ı hay una implementaci´on de fibonacci con pistas: anteriores = {0:1, 1:1} def fibonacci(n): if anteriores.has_key(n): return anteriores[n] else: nuevoValor = fibonacci(n-1) + fibonacci(n-2) anteriores[n] = nuevoValor return nuevoValor El diccionario llamado anteriores mantiene un registro de los valores de Fibo- nacci que ya conocemos. El programa comienza con s´olo dos pares: 0 corresponde a 1 y 1 corresponde a 1.
  • 145. 10.6 Enteros largos 115 Siempre que se llama a fibonacci comprueba si el diccionario contiene el resul- tado ya calculado. Si esta´ ah´ı, la funci´on puede devolver el valor inmediatamente sin hacer m´as llamadas recursivas. Si no, tiene que calcular el nuevo valor. El nuevo valor se an˜ade al diccionario antes de que la funci´on vuelva. Con esta versio´n de fibonacci, nuestra maquina puede calcular fibonacci(40) en un abrir y cerrar de ojos. Pero cuando intentamos calcular fibonacci(50), nos encontramos con otro problema: >>> fibonacci(50) OverflowError: integer addition La respuesta, como ver´a en un momento, es 20.365.011.074. El problema es que este nu´mero es demasiado grande para caber en un entero de Python. Se desborda. Afortunadamente, hay una solucio´n facil para este problema. 10.6. Enteros largos Python proporciona un tipo llamado long int que puede manejar enteros de cualquier taman˜o. Hay dos formas de crear un valor long int. Una es escribir un entero con una L mayu´scula al final: >>> type(1L) <type ’long int’> La otra es usar la funci´on long para convertir un valor en long int. long acepta cualquier tipo num´erico e incluso cadenas de d´ıgitos: >>> long(1) 1L >>> long(3.9) 3L >>> long(’57’) 57L Todas las operaciones matem´aticas funcionan sobre los long ints, as´ı que no tenemos que hacer mucho para adaptar fibonacci: >>> previous = {0:1L, 1:1L} >>> fibonacci(50) 20365011074L Simplemente cambiando el contenido inicial de anteriores cambiamos el com- portamiento de fibonacci. Los primeros dos nu ´meros de la secuencia son long ints, as´ı que todos los nu´meros subsiguientes lo ser´an tambi´en.
  • 146. 1 Diccionari Como ejercicio, modifique factorial de forma que produzca un long int como resultado. 10.7. Contar letras En el cap´ıtulo 7 escribimos una funci´on que contaba el nu´mero de apariciones de una letra en una cadena. Una versio´n mas gen ´erica de este problema es crear un histograma de las letras de la cadena, o sea, cua´ntas veces aparece cada letra. Ese histograma podr´ıa ser u´til para comprimir un archivo de texto. Como las diferentes letras aparecen con frecuencias distintas, podemos comprimir un ar- chivo usando c´odigos cortos para las letras mas habituales y c´odigos mas largos para las que aparecen con menor frecuencia. Los diccionarios facilitan una forma elegante de generar un histograma: >>> cuentaLetras = {} >>> for letra in "Mississippi": ... cuentaLetras[letra] = cuentaLetras.get (letra, 0) + 1 ... >>> cuentaLetras {’M’: 1, ’s’: 4, ’p’: 2, ’i’: 4} >>> Inicialmente, tenemos un diccionario vac´ıo. Para cada letra de la cadena, bus- camos el recuento actual (posiblemente cero) y lo incrementamos. Al final, el diccionario contiene pares de letras y sus frecuencias. Puede ser mas atractivo mostrar el histograma en orden alfab ´etico. Podemos hacerlo con los m´etodos items y sort: >>> itemsLetras = cuentaLetras.items() >>> itemsLetras.sort() >>> print itemsLetras [(’M’, 1), (’i’, 4), (’p’, 2), (’s’, 4)] Ya hab´ıa visto usted el m´etodo items, pero sort es el primer m ´etodo aplicable a listas que hemos visto. Hay varios m´as, como append, extend, y reverse. Consulte la documentaci´on de Python para ver los detalles. 10.8. Glosario diccionario: Una coleccio´n de pares clave-valor que establece una correspon- dencia entre claves y valores. Las claves pueden ser de cualquier tipo in- mutable, los valores pueden ser de cualquier tipo.
  • 147. 10.8 Glosario 117 clave: Un valor que se usa para buscar una entrada en un diccionario. par clave-valor: Uno de los elementos de un diccionario, tambi´en llamado “asociacio´n”. m´etodo: Un tipo de funci´on al que se llama con una sintaxis diferente y al que se invoca “sobre” un objeto. invocar: Llamar a un m´etodo. pista: Almacenamiento temporal de un valor precalculado para evitar c´alculos redundantes. desbordamiento: Un resultado num´erico que es demasiado grande para re- presentarse en formato num´erico.
  • 149. Cap´ıtulo 11 Archivos y excepciones Cuando un programa se esta´ ejecutando, sus datos esta´n en la memoria. Cuando un programa termina, o se apaga el computador, los datos de la memoria desa- parecen. Para almacenar los datos de forma permanente debe usted ponerlos en un archivo. Normalmente los archivos se guardan en un disco duro, disquete o CD-ROM. Cuando hay un gran nu´mero de archivos, suelen estar organizados en directo- rios (tambi´en llamados “carpetas”). Cada archivo se identifica con un nombre u´nico, o una combinaci´on de nombre de archivo y nombre de directorio. Leyendo y escribiendo archivos, los programas pueden intercambiar informaci´on entre ellos y generar formatos imprimibles como PDF. Trabajar con archivos se parece mucho a trabajar con libros. Para usar un libro, tiene que abrirlo. Cuando ha terminado, tiene que cerrarlo. Mientras el libro esta´ abierto, puede escribir en ´el o leer de ´el. En cualquier caso, sabe en qu´e lugar del libro se encuentra. Casi siempre lee el libro segu´n su orden natural, pero tambi´en puede ir saltando de una p´agina a otra. Todo esto sirve tambi´en para los archivos. Para abrir un archivo, especifique su nombre e indique si quiere leer o escribir. La apertura de un archivo crea un objeto archivo. En este ejemplo, la variable f apunta al nuevo objeto archivo. >>> f = open("test.dat","w") >>> print f <open file ’test.dat’, mode ’w’ at fe820>
  • 150. 1 Archivos y La funci´on open toma dos argumentos. El primero es el nombre del archivo y el segundo es el modo. El modo ’w’ (write) significa que lo estamos abriendo para escribir. Si no hay un archivo llamado test.dat se creara´. Si ya hay uno, el archivo que estamos escribiendo lo reemplazara´. Al imprimir el objeto archivo, vemos el nombre del archivo, el modo y la loca- lizaci´on del objeto. Para meter datos en el archivo invocamos al m´etodo write sobre el objeto archivo: >>> f.write("Ya es hora") >>> f.write("de cerrar el archivo") El cierre del archivo le dice al sistema que hemos terminado de escribir y deja el archivo listo para leer: >>> f.close() Ya podemos abrir el archivo de nuevo, esta vez para lectura, y poner su contenido en una cadena. Esta vez el argumento de modo es ’r’ (read) para lectura: >>> f = open("test.dat","r") Si intentamos abrir un archivo que no existe, recibimos un mensaje de error: >>> f = open("test.cat","r") IOError: [Errno 2] No such file or directory: ’test.cat’ Como era de esperar, el m´etodo read lee datos del archivo. Sin argumentos, lee el archivo completo: >>> text = f.read() >>> print text Ya es horade cerrar el archivo No hay un espacio entre “hora” y “de” porque no escribimos un espacio entre las cadenas. read tambi´en puede aceptar un argumento que le indica cua´ntos caracteres leer: >>> f = open("test.dat","r") >>> print f.read(7) Ya es h Si no quedan suficientes caracteres en el archivo, read devuelve los que haya. Cuando llegamos al final del archivo, read devuelve una cadena vac´ıa:
  • 151. 11.1 Archivos de texto 121 >>> print f.read(1000006) orade cerrar el archivo >>> print f.read() >>> La siguiente funci´on copia un archivo, leyendo y escribiendo los caracteres de cincuenta en cincuenta. El primer argumento es el nombre del archivo original; el segundo es el nombre del archivo nuevo: def copiaArchivo(archViejo, archNuevo): f1 = open(archViejo, "r") f2 = open(archNuevo, "w") while 1: texto = f1.read(50) if texto == "": break f2.write(texto) f1.close() f2.close() return La sentencia break es nueva. Su ejecucio´n interrumpe el bucle; el flujo de la ejecucio´n pasa a la primera sentencia tras el bucle. En este ejmplo, el bucle while es infinito porque el valor 1 siempre es verdadero. La u´nica forma de salir del bucle es ejecutar break, lo que sucede cuando texto es una cadena vac´ıa, lo que sucede cuando llegamos al final del archivo. 11.1. Archivos de texto Un archivo de texto es un archivo que contiene caracteres imprimibles y es- pacios organizados en l´ıneas separadas por caracteres de salto de l´ınea. Como Python esta´ disen˜ado espec ´ıficamente para procesar archivos de texto, propor- ciona m ´etodos que facilitan la tarea. Para hacer una demostraci´on, crearemos un archivo de texto con tres l´ıneas de texto separadas por saltos de l´ınea: >>> f = open("test.dat","w") >>> f.write("l´ınea unonl´ınea dosnl´ınea tresn") >>> f.close() El m´etodo readline lee todos los caracteres hasta e inclusive el siguiente salto de l´ınea:
  • 152. 1 Archivos y >>> f = open("test.dat","r") >>> print f.readline() l ´ınea uno >>> readlines devuelve todas las l´ıneas que queden como una lista de cadenas: >>> print f.readlines() [’l´ınea dos012’, ’l´ınea tres012’] En este caso, la salida esta´ en forma de lista, lo que significa que las cadenas aparecen con comillas y el cara´cter de salto de l´ınea aparece como la secuencia de escape 012. Al final del archivo, readline devuelve una cadena vac´ıa y readlines devuelve una lista vac´ıa: >>> print f.readline() >>> print f.readlines() [] Lo que sigue es un ejemplo de un programa de proceso de l´ıneas. filtraArchivo hace una copia de archViejo, omitiendo las l´ıneas que comienzan por #: def filtraArchivo(archViejo, archNuevo): f1 = open(archViejo, "r") f2 = open(archNuevo, "w") while 1: texto = f1.readline() if texto == "": break if texto[0] == ’#’: continue f2.write(texto) f1.close() f2.close() return La sentencia continue termina la iteraci´on actual del bucle, pero sigue haciendo bucles. El flujo de ejecucio´n pasa al principio del bucle, comprueba la condicio´n y continu´a en consecuencia.
  • 153. 11.2 Escribir variables 123 As´ı, si texto es una cadena vac´ıa, el bucle termina. Si el primer cara´cter de texto es una almohadilla, el flujo de ejecucio´n va al principio del bucle. Solo si ambas condiciones fallan copiamos texto en el archivo nuevo. 11.2. Escribir variables El argumento de write debe ser una cadena, as´ı que si queremos poner otros valores en un archivo, tenemos que convertirlos antes en cadenas. La forma mas f´acil de hacerlo es con la funci´on str: >>> x = 52 >>> f.write (str(x)) Una alternativa es usar el operador de formato . Cuando aplica a enteros, es el operador de m´odulo. Pero cuando el primer operando es una cadena, es el operador de formato. El primer operando es la cadena de formato, y el segundo operando es una tupla de expresiones. El resultado es una cadena que contiene los valores de las expresiones, formateados de acuerdo a la cadena de formato. A modo de ejemplo simple, la secuencia de formato ’ d’ significa que la primera expresio´n de la tupla deber´ıa formatearse como un entero. Aqu´ı la letra d quiere decir “decimal”: >>> motos = 52 >>> " d" motos ’52’ El resultado es la cadena ’52’, que no debe confundirse con el valor entero 52. Una secuencia de formato puede aparecer en cualquier lugar de la cadena de formato, de modo que podemos incrustar un valor en una frase: >>> motos = 52 >>> "En julio vendimos d motos." motos ’En julio vendimos 52 motos.’ La secuencia de formato ’ f’ formatea el siguiente elemento de la tupla como un nu´mero en coma flotante, y ’ s’ formatea el siguiente elemento como una cadena: >>> "En d d´ıas ingresamos f millones de s." (34,6.1,’d´olares’) ’En 34 d´ıas ingresamose 6.100000 miliones de d´olares.’
  • 154. 1 Archivos y Por defecto, el formato de coma flotante imprime seis decimales. El nu´mero de expresiones en la tupla tiene que coincidir con el nu ´mero de secuencias de formato de la cadena. Igualmente, los tipos de las expresiones deben coincidir con las secuencias de formato: >>> " d d d" (1,2) TypeError: not enough arguments for format string >>> " d" ’d´olares’ TypeError: illegal argument type for built-in operation En el primer ejemplo, no hay suficientes expresiones; en el segundo, la expresio´n es de un tipo incorrecto. Para tener m´as control sobre el formato de los nu´meros, podemos detallar el nu´mero de d´ıgitos como parte de la secuencia de formato: >>> " 6d" 62 ’ 62’ >>> " 12f" 6.1 ’ 6.100000’ El nu´mero tras el signo de porcentaje es el nu´mero m´ınimo de espacios que ocupara´ el nu´mero. Si el valor necesita menos d ´ıgitos, se an˜aden espacios en blanco delante del nu´mero. Si el nu´mero de espacios es negativo, se an˜aden los espacios tras el nu ´mero: >>> " -6d" 62 ’62 ’ Tambi´en podemos especificar el nu´mero de decimales para los nu ´meros en coma flotante: >>> " 12.2f" 6.1 ’ 6.10’ En este ejemplo, el resultado ocupa doce espacios e incluye dos d ´ıgitos tras la coma. Este formato es u´til para imprimir cantidades de dinero con las comas alineadas. Imagine, por ejemplo, un diccionario que contiene los nombres de los estudiantes como clave y las tarifas horarias como valores. He aqu´ı una funci´on que imprime el contenido del diccionario como un informe formateado: def informe (tarifas) : estudiantes = tarifas.keys() estudiantes.sort()
  • 155. 11.3 Directorios 125 for estudiante in estudiantes : print " -20s 12.02f" (estudiante, tarifas[estudiante]) Para probar la funci´on, crearemos un pequen˜o diccionario e imprimiremos el contenido: >>> tarifas >>> informe = {’mar ´ıa’: (tarifas) 6.23, ’jos´e’: 5.45, ’jes´us’: 4.25} jos´e 5.45 jes´us 4.25 mar´ıa 6.23 Controlando la anchura de cada valor nos aseguramos de que las columnas van a quedar alineadas, siempre que los nombres tengan menos de veintiu´n caracteres y las tarifas sean menos de mil millones la hora. 11.3. Directorios Cuando usted crea un archivo nuevo abri´endolo y escribiendo, el nuevo archi- vo va al directorio en uso (aqu´el en el que etuviese al ejecutar el programa). Del mismo modo, cuando abre un archivo para leerlo, Python lo busca en el directorio en uso. Si quiere abrir un archivo de cualquier otro sitio, tiene que especificar la ruta del archivo, que es el nombre del directorio (o carpeta) donde se encuentra ´este: >>> f = open("/usr/share/dict/words","r") >>> print f.readline() Aarhus Este ejemplo abre un archivo llamado words que esta´ en un directorio llamado dict, que esta´ en share, que esta´ en usr, que esta´ en el directorio de nivel superior del sistema, llamado /. No puede usar / como parte del nombre de un archivo; esta´ reservado como delimitador entre nombres de archivo y directorios. El archivo /usr/share/dict/words contiene una lista de palabras en orden alfab´etico, la primera de las cuales es el nombre de una universidad danesa. 11.4. Encurtido Para poner valores en un archivo, debe convertirlos en cadenas. Ya ha visto c´omo hacerlo con str:
  • 156. 1 Archivos y >>> f.write (str(12.3)) >>> f.write (str([1,2,3])) El problema es que cuando vuelve usted a leer el valor, obtiene una cadena. Ha perdido la informaci´on del tipo de dato original. En realidad, no puede distinguir d´onde termina un valor y comienza el siguiente: >>> f.readline() ’12.3[1, 2, 3]’ La solucio´n es el encurtido, llamado as´ı porque “conserva” estructuras de datos. El m´odulo pickle contiene las o´rdenes necesarias. Para usarlo, importe pickle y luego abra el archivo de la forma habitual: >>> import pickle >>> f = open("test.pck","w") Para almacenar una estructura de datos, use el m´etodo dump y luego cierre el archivo de la forma habitual: >>> pickle.dump(12.3, f) >>> pickle.dump([1,2,3], f) >>> f.close() Ahora podemos abrir el archivo para leer y cargar las estructuras de datos que volcamos ah´ı: >>> f = open("test.pck","r") >>> x = pickle.load(f) >>> x 12.3 >>> type(x) <type ’float’> >>> y = pickle.load(f) >>> y [1, 2, 3] >>> type(y) <type ’list’> Cada vez que invocamos load obtenemos un valor del archivo, completo con su tipo original. 11.5. Excepciones Siempre que ocurre un error en tiempo de ejecucio´n, se crea una excepci´on. Normalmente el programa se para y Pythton presenta un mensaje de error.
  • 157. 11.5 Excepciones 127 Por ejemplo, la divisi´on por cero crea una excepcio´n: >>> print 55/0 ZeroDivisionError: integer division or modulo Un elemento no existente en una lista hace lo mismo: >>> a = [] >>> print a[5] IndexError: list index out of range O el acceso a una clave que no esta´ en el diccionario: >>> b = {} >>> print b[’qu ´e’] KeyError: qu ´e En cada caso, el mensaje de error tiene dos partes: el tipo de error antes de los dos puntos y detalles sobre el error depu´es de los dos puntos. Normalmente Python tambi´en imprime una traza de d ´onde se encontraba el programa, pero la hemos omitido en los ejemplos. A veces queremos realizar una operacio´n que podr´ıa provocar una excepcio´n, pero no queremos que se pare el programa. Podemos manejar la excepcio´n usando las sentencias try y except. Por ejemplo, podemos preguntar al usuario por el nombre de un archivo y luego intentar abrirlo. Si el archivo no existe, no queremos que el programa se pare; queremos manejar la excepcio ´n. nombreArch = raw_input(’Introduce un nombre de archivo: ’) try: f = open (nombreArch, "r") except: print ’No hay ning´un archivo que se llame’, nombreArch La sentencia try ejecuta las sentencias del primer bloque. Si no se produce nin- guna excepcio´n, pasa por alto la sentencia except. Si ocurre cualquier excepcio´n, ejecuta las sentencias de la rama except y despu´es continu´a. Podemos encapsular esta capacidad en una funci´on: existe acepta un nombre de archivo y devuelve verdadero si el archivo existe y falso si no: def existe(nombreArch): try: f = open(nombreArch) f.close()
  • 158. 1 Archivos y return 1 except: return 0 Puede usar mu´ltiples bloques except para manejar diferentes tipos de excep- ciones. El Manual de Referencia de Python contiene los detalles. Si su programa detecta una condicio´n de error, puede hacer que lance (raise en ingl´es) una excepcio´n. Aqu´ı tiene usted un ejemplo que acepta una entrada del usuario y comprueba si es 17. Suponiendo que 17 no es una entrada valida por cualquier razo´n, lanzamos una excepcio´n. def tomaNumero () : # Recuerde, los acentos est ´an x = input (’Elige un n´umero: ’) # prohibidos en los nombres if x == 17 : # de funciones y variables! raise ’ErrorN´umeroMalo’, ’17 es un mal n ´umero’ return x La sentencia raise acepta dos argumentos: el tipo de excepcio´n e informaci´on espec´ıfica acerca del error. ErrorN´umeroMalo es un nuevo tipo de excepcio´n que hemos inventado para esta aplicaci ´on. Si la funci´on llamada tomaNumero maneja el error, el programa puede continuar; en caso contrario, Python imprime el mensaje de error y sale: >>> tomaNumero () Elige un n´umero: 17 ErrorN´umeroMalo: 17 es un mal n´umero El mensaje de error incluye el tipo de excepcio´n y la informaci´on adicional que usted proporcion´o. Como ejercicio, escriba una funci´on que use tomaNumero para leer un nu´mero del teclado y que maneje la excepci´on ErrorN ´umeroMalo. 11.6. Glosario archivo: Una entidad con nombre, normalmente almacenada en un disco duro, disquete o CD-ROM, que contiene una secuencia de caracteres. directorio: Una coleccio´n, con nombre, de archivos, tambi´en llamado carpeta. ruta: Una secuencia de nombres de directorio que especifica la localizacio´n exacta de un archivo.
  • 159. 11.6 Glosario 129 archivo de texto: Un archivo que contiene caracteres imprimibles organizados en l´ıneas separadas por caracteres de salto de l ´ınea. sentencia break: Una sentencia que provoca que el flujo de ejecucio ´n salga de un bucle. sentencia continue: Una sentencia que provoca que termine la iteraci´on ac- tual de un bucle. El flujo de la ejecucio´n va al principio del bucle, evalu´a la condicio´n, y procede en consecuencia. operador de formato: El operador toma una cadena de formato y una tupla de expresiones y entrega una cadena que incluye las expresiones, forma- teadas de acuerdo con la cadena de formato. cadena de formato: Una cadena que contiene caracteres imprimibles y se- cuencias de formato que indican c´omo formatear valores. secuencia de formato: Una secuencia de caracteres que comienza con e in- dica c´omo formatear un valor. encurtir: Escribir el valor de un dato en un archivo junto con la informaci´on sobre su tipo de forma que pueda ser reconstituido mas tarde. excepci´on: Un error que ocurre en tiempo de ejecucio´n. manejar: Impedir que una excepcio´n detenga un programa utilizando las sen- tencias try y except. lanzar: Sen˜alar una excepcio´n usando la sentencia raise.
  • 161. Cap´ıtulo 12 Clases y objetos 12.1. Tipos compuestos definidos por el usuario Una vez utilizados algunos de los tipos internos de Python, estamos listos para crear un tipo definido por el usuario: el Punto. Piense en el concepto de un punto matem´atico. En dos dimensiones, un punto es dos nu´meros (coordenadas) que se tratan colectivamente como un solo objeto. En notaci´on matem ´atica, los puntos suelen escribirse entre par´entesis con una coma separando las coordenadas. Por ejemplo, (0, 0) representa el origen, y (x, y) representa el punto x unidades a la derecha e y unidades hacia arriba desde el origen. Una forma natural de representar un punto en Python es con dos valores en coma flotante. La cuestio´n es, entonces, c´omo agrupar esos dos valores en un objeto compuesto. La solucio´n r´apida y burda es utilizar una lista o tupla, y para algunas aplicaciones esa podr´ıa ser la mejor opcio´n. Una alternativa es que el usuario defina un nuevo tipo compuesto, tambi´en llamado una clase. Esta aproximaci´on exige un poco mas de esfuerzo, pero tiene sus ventajas que pronto se har´an evidentes. Una definici´on de clase se parece a esto: class Punto: pass Las definiciones de clase pueden aparecer en cualquier lugar de un programa, pero normalmente esta´n al principio (tras las sentencias import). Las reglas
  • 162. 1 Clases y 3.0 4.0 x y sint´acticas de la definici´on de clases son las mismas que para cualesquiera otras sentencias compuestas. (ver la Secci´on 4.4). Esta definici´on crea una nueva clase llamada Punto. La sentencia pass no tiene efectos; s´olo es necesaria porque una sentencia compuesta debe tener algo en su cuerpo. Al crear la clase Punto hemos creado un nuevo tipo, que tambi´en se llama Punto. Los miembros de este tipo se llaman instancias del tipo u objetos. La creacio´n de una nueva instancia se llama instanciacio ´n. Para instanciar un objeto Punto ejecutamos una funci´on que se llama (lo ha adivinado) Punto: blanco = Punto() A la variable blanco se le asigna una referencia a un nuevo objeto Punto. A una funci´on como Punto que crea un objeto nuevo se le llama constructor. 12.2. Atributos Podemos an˜adir nuevos datos a una instancia utilizando la notaci´on de punto: >>> blanco.x = 3.0 >>> blanco.y = 4.0 Esta sintaxis es similar a la sintaxis para seleccionar una variable de un modu- lo, como math.pi o string.uppercase. En este caso, sin embargo, estamos seleccionando un dato de una instancia. Estos ´ıtemes con nombre se llaman atributos. El diagrama de estados que sigue muestra el resultado de esas asignaciones: blanco La variable blanco apunta a un objeto Punto, que contiene dos atributos. Cada atributo apunta a un nu´mero en coma flotante. Podemos leer el valor de un atributo utilizando la misma sintaxis: >>> print blanco.y 4.0 >>> x = blanco.x >>> print x 3.0
  • 163. 12.3 Instancias como par´ametro 133 La expresio´n blanco.x significa, “ve al objeto al que apunta blanco y toma el valor de x”. En este caso, asignamos ese valor a una variable llamada x. No hay conflicto entre la variable x y el atributo x. El prop´osito de la notaci´on de punto es identificar de forma inequ´ıvoca a qu´e variable se refiere. Puede usted usar la notaci´on de punto como parte de cualquier expresio´n. As´ı, las sentencias que siguen son correctas: print ’(’ + str(blanco.x) + ’, ’ + str(blanco.y) + ’)’ distanciaAlCuadrado = blanco.x * blanco.x + blanco.y * blanco.y La primera l´ınea presenta (3.0, 4.0); la segunda l´ınea calcula el valor 25.0. Puede tentarle imprimir el propio valor de blanco: >>> print blanco < main .Punto instance at 80f8e70> El resultado indica que blanco es una instancia de la clase Punto que se de- fini´o en main . 80f8e70 es el identificador u´nico de este objeto, escrito en hexadecimal. Probablemente no es esta la manera mas clara de mostrar un objeto Punto. En breve ver´a c ´omo cambiarlo. Como ejercicio, cree e imprima un objeto Punto y luego use id pa- ra imprimir el identificador u´nico del objeto. Traduzca el nu ´mero hexadecimal a decimal y asegu´rese de que coinciden. 12.3. Instancias como par´ametro Puede usted pasar una instancia como par´ametro de la forma habitual. Por ejemplo: def imprimePunto(p): print ’(’ + str(p.x) + ’, ’ + str(p.y) + ’)’ imprimePunto acepta un punto como argumento y lo muestra en formato esta´ndar. Si llama a imprimePunto(blanco), el resultado es (3.0, 4.0). Como ejercicio, reescriba la funcion distancia de la Secci´on 5.2 de forma que acepte dos Puntos como para´metros en lugar de cuatro nu´meros.
  • 164. 1 Clases y 12.4. Mismidad El significado de la palabra “mismo” parece totalmente claro hasta que uno se para un poco a pensarlo, y entonces se da cuenta de que hay algo mas de lo que supon´ıa. Por ejemplo, si dice “Pepe y yo tenemos la misma moto”, lo que quiere decir es que su moto y la de usted son de la misma marca y modelo, pero que son dos motos distintas. Si dice “Pepe y yo tenemos la misma madre”, quiere decir que su madre y la de usted son la misma persona1 . As´ı que la idea de “identidad” es diferente segu´n el contexto. Cuando habla de objetos, hay una ambigu¨edad parecida. Por ejemplo, si dos Puntos son el mismo, ¿significa que contienen los mismos datos (coordenadas) o que son de verdad el mismo objeto? Para averiguar si dos referencias se refieren al mismo objeto, utilice el operador ==. Por ejemplo: >>> p1 = Punto() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = Punto() >>> p2.x = 3 >>> p2.y = 4 >>> p1 == p2 0 Aunque p1 y p2 contienen las mismas coordenadas, no son el mismo objeto. Si asignamos p1 a p2, las dos variables son alias del mismo objeto: >>> p2 = p1 >>> p1 == p2 1 Este tipo de igualdad se llama igualdad superficial porque s´olo compara las referencias, pero no el contenido de los objetos. Para comparar los contenidos de los objetos (igualdad profunda) podemos escribir una funci´on llamada mismoPunto: def mismoPunto(p1, p2) : return (p1.x == p2.x) and (p1.y == p2.y) 1 No todas las lenguas tienen el mismo problema. Por ejemplo, el alem´an tiene palabras diferentes para los diferentes tipos de identidad. “Misma moto” en este contexto ser´ıa “gleiche Motorrad” y “misma madre” ser´ıa “selbe Mutter”.
  • 165. 12.5 Rect´angulos 135 Si ahora creamos dos objetos diferentes que contienen los mismos datos podre- mos usar mismoPunto para averiguar si representan el mismo punto: >>> p1 = Punto() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = Punto() >>> p2.x = 3 >>> p2.y = 4 >>> mismoPunto(p1, p2) 1 Por supuesto, si las dos variables apuntan al mismo objeto mismoPunto devuelve verdadero. 12.5. Rect´angulos Digamos que queremos una clase que represente un recta´ngulo. La pregunta es, ¿qu´e informaci´on tenemos que proporcionar para definir un recta ´ngulo? Para simplificar las cosas, supongamos que el recta´ngulo esta´ orientado vertical u horizontalmente, nunca en diagonal. Tenemos varias posibilidades: podemos sen˜alar el centro del recta ´ngulo (dos coordenadas) y su taman˜o (anchura y altura); o podemos sen˜alar una de las esquinas y el taman˜o; o podemos sen˜alar dos esquinas opuestas. Un modo con- vencional es sen˜alar la esquina superior izquierda del recta´ngulo y el taman˜o. De nuevo, definiremos una nueva clase: class Rectangulo: # Prohibidos los acentos fuera de las cadenas! pass Y la instanciaremos: caja = Rectangulo() caja.anchura = 100.0 caja.altura = 200.0 Este c´odigo crea un nuevo objeto Rectangulo con dos atributos en coma flo- tante. ¡Para sen˜alar la esquina superior izquierda podemos incrustar un objeto dentro de otro! caja.esquina = Punto() caja.esquina.x = 0.0; caja.esquina.y = 0.0;
  • 166. 1 Clases y anchura altura esquina 100.0 200.0 0.0 0.0 x y El operador punto compone. La expresio´n caja.esquina.x significa “ve al ob- jeto al que se refiere caja y selecciona el atributo llamado esquina; entonces ve a ese objeto y selecciona el atributo llamado x”. La figura muestra el estado de este objeto: caja 12.6. Instancias como valores de retorno Las funciones pueden devolver instancias. Por ejemplo, encuentraCentro acep- ta un Rectangulo como argumento y devuelve un Punto que contiene las coor- denadas del centro del Rectangulo: def encuentraCentro(caja): p = Punto() p.x = caja.esquina.x + caja.anchura/2.0 p.y = caja.esquina.y + caja.altura/2.0 return p Para llamar a esta funci´on, pase caja como argumento y asigne el resultado a una variable: >>> centro = encuentraCentro(caja) >>> imprimePunto(centro) (50.0, 100.0) 12.7. Los objetos son mudables Podemos cambiar el estado de un objeto efectuando una asignaci ´on sobre uno de sus atributos. Por ejemplo, para cambiar el taman˜o de un recta´ngulo sin cambiar su posici´on, podemos cambiar los valores de anchura y altura: caja.anchura = caja.anchura + 50 caja.altura = caja.altura + 100 Podemos encapsular este c´odigo en un m´etodo y generalizarlo para agrandar el recta´ngulo en cualquier cantidad:
  • 167. 12.8 Copiado 137 def agrandaRect(caja, danchura, daltura) : caja.anchura = caja.anchura + danchura caja.altura = caja.altura + daltura Las variables danchura y daltura indican cua´nto debe agrandarse el recta´ngulo en cada direcci´on. Invocar este m´etodo tiene el efecto de modificar el Rectangulo que se pasa como argumento. Por ejemplo, podemos crear un nuevo Rectangulo llamado bob y pas´arselo a agrandaRect: >>> bob = Rectangulo() >>> bob.anchura = 100.0 >>> bob.altura = 200.0 >>> bob.esquina = Punto() >>> bob.esquina.x = 0.0; >>> bob.esquina.y = 0.0; >>> agrandaRect(bob, 50, 100) Mientras agrandaRect se esta´ ejecutando, el par´ametro caja es un alias de bob. Cualquier cambio que haga a caja afectar´a tambi´en a bob. A modo de ejercicio, escriba una funci´on llamada mueveRect que tome un Rectangulo y dos para´metros llamados dx y dy. Tiene que cambiar la posicio´n del rect´angulo an˜adiendo dx a la coordenada x de esquina y an˜adiendo dy a la coordenada y de esquina. 12.8. Copiado El uso de alias puede hacer que un programa sea dif´ıcil de leer, porque los cambios hechos en un lugar pueden tener efectos inesperados en otro lugar. Es dif´ıcil estar al tanto de todas las variables a las que puede apuntar un objeto dado. Copiar un objeto es, muchas veces, una alternativa a la creacio´n de un alias. El m´odulo copy contiene una funci´on llamada copy que puede duplicar cualquier objeto: >>> import copy >>> p1 = Punto() >>> p1.x = 3 >>> p1.y = 4 >>> p2 = copy.copy(p1) >>> p1 == p2
  • 168. 1 Clases y anchura altura esquina 100.0 200.0 100.0 200.0 anchura altura esquina 0.0 0.0 x y 0 >>> mismoPunto(p1, p2) 1 Una vez que hemos importado el m´odulo copy, podemos usar el m ´etodo copy para hacer un nuevo Punto. p1 y p2 no son el mismo punto, pero contienen los mismos datos. Para copiar un objeto simple como un Punto, que no contiene objetos incrusta- dos, copy es suficiente. Esto se llama copiado superficial. Para algo como un Rectangulo, que contiene una referencia a un Punto, copy no lo hace del todo bien. Copia la referencia al objeto Punto, de modo que tanto el Rectangulo viejo como el nuevo apuntan a un u´nico Punto. Si creamos una caja, b1, de la forma habitual y entonces hacemos una copia, b2, usando copy, el diagrama de estados resultante se ve as´ı: b1 b2 Es casi seguro que esto no es lo que queremos. En este caso, la invocacio´n de agrandaRect sobre uno de los Rectangulos no afectar´ıa al otro, ¡pero la invo- cacio´n de mueveRect sobre cualquiera afectaria a ambos! Este comportamiento es confuso y propicia los errores. Afortunadamente, el m´odulo copy contiene un m´etodo llamado deepcopy que copia no s´olo el objeto sino tambi´en cualesquiera objetos incrustados. No le sorprendera´ saber que esta operacio´n se llama copia profunda (deep copy). >>> b2 = copy.deepcopy(b1) Ahora b1 y b2 son objetos totalmente independientes. Podemos usar deepcopy para reescribir agrandaRect de modo que en lugar de modificar un Rectangulo existente, cree un nuevo Rectangulo que tiene la misma localizacio´n que el viejo pero nuevas dimensiones: def agrandaRect(caja, danchura, daltura) : import copy nuevaCaja = copy.deepcopy(caja) nuevaCaja.anchura = nuevaCaja.anchura + danchura nuevaCaja.altura = nuevaCaja.altura + daltura return nuevaCaja
  • 169. 12.9 Glosario 139 Como ejercicio, resscriba mueveRect de modo que cree y devuelva un nuevo Rectangulo en lugar de modificar el viejo. 12.9. Glosario clase: Un tipo compuesto definido por el usuario. Tambi´en se puede pensar en una clase como una plantilla para los objetos que son instancias de la misma. instanciar: Crear una instancia de una clase. instancia: Un objeto que pertenece a una clase. objeto: Un tipo de dato compuesto que suele usarse para representar una cosa o concepto del mundo real. constructor: Un m´etodo usado para crear nuevos objetos. atributo: Uno de los elementos de datos con nombre que constituyen una ins- tancia. igualdad superficial: Igualdad de referencias, o dos referencias que apuntan al mismo objeto. igualdad profunda: Igualdad de valores, o dos referencias que apuntan a ob- jetos que tienen el mismo valor. copia superficial: Copiar el contenido de un objeto, incluyendo cualquier refe- rencia a objetos incrustados; implementada por la funci´on copy del modulo copy. copia profunda: Copiar el contenido de un objeto as´ı como cualesquiera ob- jetos incrustados, y los incrustados en estos, y as´ı sucesivamente; imple- mentada por la funci´on deepcopy del modulo copy.
  • 171. 11 59 30 hour minute second Cap´ıtulo 13 Clases y funciones 13.1. Hora Como otro ejemplo de un tipo definido por el usuario, definiremos una clase llamada Hora que registra la hora del d´ıa. La definici´on de la clase es como sigue: class Hora: pass Podemos crear un nuevo objeto Hora y asignar atributos para contener las horas, minutos y segundos: hora = Hora() hora.horas = 11 hora.minutos = 59 hora.segundos = 30 El diagrama de estado del objeto Hora es as´ı: time A modo de ejercicio, escriba una funci´on imprimeHora que acep- te un objeto Hora como argumento y lo imprima en el formato horas:minutos:segundos.
  • 172. 1 Clases y Como un segundo ejercicio, escriba una funci´on booleana despues que tome dos objetos Hora, t1 y t2, como argumentos y devuelva verdadero (1) si t1 sigue cronolo´gicamente a t2 y falso (0) en caso contrario. 13.2. Funciones puras En las pr´oximas secciones, escribiremos dos versiones de una funci´on llamada sumaHora que calcule la suma de dos Horas. Mostrar ´an dos tipos de funciones: funciones puras y modificadores. E´ste es un esbozo de sumaHora: def sumaHora(t1, t2): suma = Hora() suma.horas = t1.horas + t2.horas suma.minutos = t1.minutos + t2.minutos suma.segundos = t1.segundos + t2.segundos return suma La funci´on crea un nuevo objeto Hora, inicializa sus atributos y devuelve una referencia al nuevo objeto. A esto se le llama funcio´n pura porque no modifica ninguno de los objetos que se le pasan y no tiene efectos laterales, como mostrar un valor o tomar una entrada del usuario. Aqu´ı tiene un ejemplo de c´omo usar esta funci´on. Crearemos dos objetos Hora: horaActual, que contiene la hora actual, y horaPan, que contiene la cantidad de tiempo que necesita un panadero para hacer pan. Luego usaremos sumaHora para averiguar cua´ndo estara´ hecho el pan. Si au´n no ha terminado de escribir imprimeHora, eche un vistazo a la Secci´on 14.2 antes de probar esto: >>> horaActual = Hora() >>> horaActual.horas = 9 >>> horaActual.minutos = 14 >>> horaActual.segundos = 30 >>> horaPan = Hora() >>> horaPan.horas = 3 >>> horaPan.minutos = 35 >>> horaPan.segundos = 0 >>> horaHecho = sumaHora(horaActual, horaPan) >>> imprimeHora(horaHecho)
  • 173. 13.3 Modificadores 143 La salida de este programa es 12:49:30, lo que es correcto. Por otra parte, hay casos en los que el resultado no es correcto. ¿Puede imaginar uno? El problema es que esta funci´on no trata los casos en los que el nu´mero de segundos o minutos suma m´as que sesenta. Cuando ocurre eso, debemos “llevar” los segundos sobrantes a la columna de los minutos o los minutos extras a la columna de las horas. He aqu´ı una versio´n corregida de la funci´on: def sumaHora(t1, t2): suma = Hora() suma.horas = t1.horas + t2.horas suma.minutos = t1.minutos + t2.minutos suma.segundos = t1.segundos + t2.segundos if suma.segundos >= 60: suma.segundos = suma.segundos - 60 suma.minutos = suma.minutos + 1 if suma.minutos >= 60: suma.minutos = suma.minutos - 60 suma.horas = suma.horas + 1 return suma Aunque esta funci´on es correcta, empieza a ser grande. Mas adelante sugeriremos una aproximaci´on alternativa que nos dar´a un c´odigo mas corto. 13.3. Modificadores Hay veces en las que es u´til que una funci´on modifique uno o mas de los objetos que recibe como par´ametros. Normalmente, el llamante conserva una referencia a los objetos que pasa, as´ı que cualquier cambio que la funci´on haga ser´a visible para el llamante. Las funciones que trabajan as´ı se llaman modificadores. incremento, que an˜ade un nu´mero dado de segundos a un objeto Hora, se escribir´ıa de forma natural como un modificador. Un esbozo r´apido de la funci´on podr´ıa ser ´este:
  • 174. 1 Clases y def incremento(hora, segundos): hora.segundos = hora.segundos + segundos if hora.segundos >= 60: hora.segundos = hora.segundos - 60 hora.minutos = hora.minutos + 1 if hora.minutos >= 60: hora.minutos = hora.minutos - 60 hora.horas = hroa.horas + 1 La primera l´ınea realiza la operacio´n b´asica, las restantes tratan con los casos especiales que vimos antes. ¿Es correcta esta funci´on? ¿Qu´e ocurre si el par´ametro segundos es mucho mayor que sesenta? En tal caso, no es suficiente con acarrear una vez; debemos seguir haci´endolo hasta que segundos sea menor que sesenta. Una solucio´n es sustituir las sentencias if por sentencias while: def incremento(hora, segundos): hora.segundos = hora.segundos + segundos while hora.segundos >= 60: hora.segundos = hora.segundos - 60 hora.minutos = hora.minutos + 1 while hora.minutos >= 60: hora.minutos = hora.minutos - 60 hora.horas = hroa.horas + 1 Ahora esta funci´on es correcta, pero no es la solucio´n mas eficiente. Como ejercicio, reescriba esta funcion de modo que no contenga tan- tos bucles. Como un segundo ejercicio, reescriba incremento como una funci ´on pura, y escriba una funcion que llame a ambas versiones. 13.4. ¿Qu´e es mejor? Todo lo que se pueda hacer con modificadores puede hacerse tambi ´en con fun- ciones puras. En realidad, algunos lenguajes de programaci´on s´olo permiten funciones puras. Hay ciertas evidencias de que los programas que usan funcio- nes puras son m ´as r´apidos de desarrollar y menos propensos a los errores que
  • 175. 13.5 Desarrollo de prototipos frente a planificaci´on 145 los programas que usan modificadores. Sin embargo, a veces los modificadores son u´tiles, y en algunos casos los programas funcionales son menos eficientes. En general, recomendamos que escriba funciones puras siempre que sea razona- ble hacerlo as´ı y recurra a los modificadores s´olo si hay una ventaja convincente. Este enfoque podr´ıa llamarse estilo funcional de programaci´on. 13.5. Desarrollo de prototipos frente a planifi- caci´on En este cap´ıtulo mostramos una aproximaci´on al desarrollo de programas a la que llamamos desarrollo de prototipos. En cada caso, escribimos un esbozo basto (o prototipo) que realizaba el c´alculo basico y luego lo probamos sobre unos cuantos casos, corrigiendo los fallos tal como los encontr´abamos. Aunque este enfoque puede ser efecitvo, puede conducirnos a c ´odigo que es innecesariamente complicado, ya que trata con muchos casos especiales, y poco fiable, porque es dif´ıcil saber si encontr´o todos los errores. Una alternativa es el desarrollo planificado, en el que una comprensio´n del problema en profundidad puede hacer la programaci´on mucho mas facil. En este caso, el enfoque es que un objeto Hora es en realidad ¡un nu´mero de tres d´ıgitos en base 60! El componente segundo es la “columna de unidades”, el componente minuto es la “columna de las sesententas” y el componente hora es la “columna de las tresmilseiscentenas”. Cuando escribimos sumaHora e incremento, en realidad esta´bamos haciendo una suma en base 60, que es por lo que deb´ıamos acarrear de una columna a la siguiente. Esta observaci´on sugiere otro enfoque para el problema. Podemos convertir un objeto Hora en un simple nu´mero y sacar provecho del hecho de que la maquina sabe la aritm´etica necesaria. La siguiente funci´on convierte un objeto Hora en un entero: def convierteASegundos(t): minutos = t.horas * 60 + t.minutos segundos = minutos * 60 + t.segundos return segundos Ahora, s´olo necesitamos una forma de convertir un entero en un objeto Hora: def haceHora(segundos): hora = Hora() hora.horas = segundos/3600
  • 176. 1 Clases y segundos = segundos - hora.horas * 3600 hora.minutos = segundos/60 segundos = segundos - hora.minutos * 60 hora.segundos = segundos return hora Puede que tenga usted que pensar un poco para convencerse de que esta t´ecni- ca para convertir de una base a otra es correcta. Suponiendo que esta´ usted convencido, puede usar estas funciones para reescribir sumaHora: def sumaHora(t1, t2): segundos = convierteASegundos(t1) + convierteASegundos(t2) return haceHora(segundos) Esta versio´n es mucho m´as corta que la original, y es mucho mas facil de de- mostrar que es correcta (suponiendo, como es habitual, que las funciones a las que llama son correctas). Como ejercicio, reescriba incremento de la misma forma. 13.6. Generalizacio´n De algu´n modo, convertir de base 60 a base 10 y de vuelta es mas dif´ıcil que simplemente manejarse con las horas. La conversio ´n de base es mas abstracta; nuestra intuici´on para tratar con las horas es mejor. Pero si tenemos la comprensio´n para tratar las horas como nu ´meros en base 60, y hacer la inversio´n de escribir las funciones de conversio´n (convierteASegundos y haceHora), obtenemos un programa que es mas corto, mas facil de leer y depurar y m´as fiable. Tambi´en es m´as f´acil an˜adir funcionalidades mas tarde. Por ejemplo, imagine restar dos Horas para hallar el intervalo entre ellas. La aproximaci´on ingenua ser´ıa implementar la resta con acarreo. Con el uso de las funciones de conversio´n ser´a m´as f ´acil y con mayor probabilidad, correcto. Ir´onicamente, a veces hacer un poblema mas complejo (o mas general) lo hace m´as f´acil (porque hay menos casos especiales y menos oportunidades de error). 13.7. Algoritmos Cuando escribe una solucio´n general para una clase de problemas, en contraste con una solucio´n espec´ıfica a un problema concreto, ha escrito un algoritmo.
  • 177. − − 13.8 Glosario 147 Mencionamos esta palabra antes pero no la definimos con precisi ´on. No es facil de definir, as´ı que probaremos un par de enfoques. Primero, piense en algo que no es un algoritmo. Cuando usted aprendi´o a mul- tiplicar nu´meros de una cifra, probablemente memoriz´o la tabla de multiplicar. En efecto, memoriz´o 100 soluciones espec´ıficas. Ese tipo de conocimiento no es algor´ıtmico. Pero si usted era “harag´an” probablemente hizo trampa aprendiendo algunos trucos. Por ejemplo, para encontrar el producto de n por 9, puede escribir n 1 como el primer d´ıgito y 10 n como el segundo d´ıgito. Este truco es una solucio´n general para multiplicar cualquier nu´mero de una cifra por 9. ¡Eso es un algoritmo! De forma similar, las t´ecnicas que aprendi´o para la suma y la resta con aca- rreo y la divisi´on larga son todas algoritmos. Una de las caracter´ısticas de los algoritmos es que no requieren inteligencia para llevarse a cabo. Son procesos mec´anicos en los que cada paso sigue al anterior de acuerdo a un conjunto simple de reglas. En nuestra opini´on, es un poco vergonzoso que los humanos pasen tanto tiempo en la escuela aprendiendo a ejecutar algoritmos que, de forma bastante similar, no exigen inteligencia. Por otra parte, el proceso de disen˜ar algoritmos es interesante, un desaf´ıo inte- lectual y una parte primordial de lo que llamamos programar. Algunas de las cosas que la gente hace naturalmente, sin dificultad ni pensa- miento consciente, son las m´as dif´ıciles de expresar algor´ıtmicamente. Entender el lenguaje natural es un buen ejemplo. Todos lo hacemos, pero hasta el momen- to nadie ha sido capaz de explicar c´omo lo hacemos, al menos no en la forma de un algoritmo. 13.8. Glosario funcio´n pura: Una funci´on que no modifica ninguno de los objetos que recibe como par´ametros. La mayor´ıa de las funciones puras son rentables. modificador: Una funci´on que modifica uno o mas de los objetos que recibe como par´ametros. La mayor´ıa de los modificadores no entregan resultado. estilo funcional de programaci´on: Un estilo de programaci´on en el que la mayor´ıa de las funciones son puras. desarrollo de prototipos: Una forma de desarrollar programas empezando con un prototipo y prob´andolo y mejor´andolo gradualmente.
  • 178. 1 Clases y desarrollo planificado: Una forma de desarrollar programas que implica una comprensio´n de alto nivel del problema y mas planificaci´on que desarrollo incremental o desarrollo de prototipos. algoritmo: Un conjunto de instrucciones para solucionar una clase de proble- mas por medio de un proceso mec´anico sin intervencio´n de inteligencia.
  • 179. Cap´ıtulo 14 Clases y m´etodos 14.1. Caracter´ısticas de la orientaci´on a objetos Python es un lenguaje de programaci´on orientado a objetos, lo que signi- fica que porporciona caracter´ısticas que apoyan la programaci´on orientada a objetos. No es f´acil definir la programaci´on orientada a objetos, pero ya hemos visto algunas de sus caracter´ısticas: Los programas se hacen a base de definiciones de objetos y definiciones de funciones, y la mayor parte de la computacio´n se expresa en t´erminos de operaciones sobre objetos. Cada definici´on de un objeto se corresponde con un objeto o concepto del mundo real, y las funciones que operan en ese objeto se corresponden con las formas en que interactu´an los objetos del mundo real. Por ejemplo, la clase Hora definida en el Cap´ıtulo 13 se corresponde con la forma en la que la gente registra la hora del d ´ıa, y las funciones que definimos se corres- ponden con el tipo de cosas que la gente hace con las horas. De forma similar, las clases Punto y Rectangulo se corresponden con los conceptos matem´aticos de un punto y un recta´ngulo. Hasta ahora, no nos hemos aprovechado de las caracter´ısticas que Python nos ofrece para dar soporte a la programaci´on orientada a objetos. Hablando estric- tamente, estas caracter´ısticas no son necesarias. En su mayor´ıa, proporcionan una sintaxis alternativa para cosas que ya hemos hecho, pero en muchos casos,
  • 180. 1 Clases y m la alternativa es m´as concisa y expresa con mas precisi´on a la estructura del programa. Por ejemplo, en el programa Hora no hay una conexio´n obvia entre la definici´on de la clase y las definiciones de las funciones que siguen. Observando bien, se hace patente que todas esas funciones toman al menos un objeto Hora como par´ametro. Esta observaci´on es la que motiva los m´etodos. Ya hemos visto varios m´etodos, como keys y values, que se invocan sobre diccionarios. Cada m´etodo esta´ aso- ciado con una clase y esta´ pensado para invocarse sobre instancias de esa clase. Los m´etodos son como las funciones, con dos diferencias: Los m´etodos se definen dentro de una definici´on de clase para explicitar la relacio´n entre la clase y el m´etodo. La sintaxis para invocar un m´etodo es diferente de la de una llamada a una funci´on. En las pr´oximas secciones tomaremos las funciones de los cap ´ıtulos anteriores y las transformaremos en m´etodos. Esta transformaci´on es puramente mec´anica; puede hacerla simplemente siguiendo una secuencia de pasos. Si se acostumbra a convertir de una forma a la otra ser´a capaz de elegir la mejor forma de hacer lo que quiere. 14.2. imprimeHora En el Cap´ıtulo 13, definimos una clase llamada Hora y escribimos una fuci´on llamada imprimeHora, que deber´ıa ser parecida a esto: class Hora: pass def imprimeHora(hora): print str(hora.horas) + ":" + str(hora.minutos) + ":" + str(hora.segundos) Para llamar a esta funci´on, pas´abamos un objeto Hora como par´ametro: >>> horaActual = Hora() >>> horaActual.horas = 9 >>> horaActual.minutos = 14 >>> horaActual.segundos = 30 >>> impriemHora(horaActual)
  • 181. 14.3 Otro ejemplo 151 Para convertir imprimeHora en un m´etodo, todo lo que necesitamos hacer es mover la definici´on de la funci´on al interior de la definici´on de la clase. F´ıjese en c´omo cambia el sangrado. class Hora: def imprimeHora(hora): print str(hora.horas) + ":" + str(hora.minutos) + ":" + str(hora.segundos) Ahora podemos invocar imprimeHora usando la notaci´on de punto. >>> horaActual.imprimeHora() Como es habitual, el objeto sobre el que se invoca el m´etodo aparece delante del punto y el nombre del m´etodo aparece tras el punto. El objeto sobre el que se invoca el m´etodo se asigna al primer par ´ametro, as´ı que en este caso horaActual se asigna al par´ametro hora. Por convenio, el primer par´ametro de un m´etodo se llama self. La razo´n de esto es un tanto rebuscada, pero se basa en una met ´afora u´til. La sintaxis para la llamada a una funci´on, imprimeHora(horaActual), sugiere que la funci´on es el agente activo. Dice algo como “¡Oye imprimeHora! Aqu´ı hay un objeto para que lo imprimas”. En programaci´on orientada a objetos, los objetos son los agentes activos. Una in- vocacio´n como horaActual.imprimeHora() dice “¡Oye horaActual! ¡Impr´ıme- te!” Este cambio de perspectiva puede ser m´as elegante, pero no es obvio que sea u´til. En los ejemplos que hemos visto hasta ahora, puede no serlo. Pero a veces transferir la responsabilidad de las funciones a los objetos hace posible escribir funciones mas vers ´atiles, y hace m´as f´acil mantener y reutilizar c´odigo. 14.3. Otro ejemplo Vamos a convertir incremento (de la Secci´on 13.3) en un m´etodo. Para aho- rrar espacio, dejaremos a un lado los m´etodos ya definidos, pero usted deber´ıa mantenerlos en su versio´n: class Hora: #aqu´ı van las definiciones anteriores de m´etodos...
  • 182. 1 Clases y m def incremento(self, segundos): self.segundos = segundos + self.segundos while self.segundos >= 60: self.segundos = self.segundos - 60 self.minutos = self.minutos + 1 while self.minutos >= 60: self.minutos = self.minutos - 60 self.horas = self.horas + 1 La transformaci´on es puramente mec´anica; hemos llevado la definici´on del m´eto- do al interior de la definici´on de la clase y hemos cambiado el nombre del primer par´ametro. Ahora podemos invocar incremento como un m´etodo. horaActual.incremento(500) De nuevo, el objeto sobre el que invocamos el m´etodo se asigna al primer par´ame- tro, self. El segundo par´ametro, segundos toma el valor de 500. Como ejercicio, convierta convertirASegundos (de la Secci´on 13.5) en un m´etodo de la clase Hora. 14.4. Un ejemplo m´as complicado La funci´on despues es ligeramente m´as complicada porque opera sobre dos objetos Hora, no s´olo sobre uno. S´olo podemos convertir uno de los par´ametros en self; el otro se queda como esta´: class Hora: #aqu´ı van las definiciones anteriores de m´etodos... def despues(self, hora2): if self.horas > hora2.horas: return 1 if self.horas < hora2.horas: return 0 if self.minutos > hora2.minutos: return 1
  • 183. 14.5 Argumentos opcionales 153 if self.minutos < hora2.minutos: return 0 if self.segundos > hora2.segundos: return 1 return 0 Invocamos este m´etodo sobre un objeto y pasamos el otro como argumento: if horaHecho.despues(horaActual): print "El pan estar´a hecho despu´es de empezar." Casi puede leer la invocacio´n como una mezcla de ingl´es y espan˜ol: “Si la hora- hecho es depu´es de la hora-actual, entonces...” 14.5. Argumentos opcionales Hemos visto funciones internas que toman un nu´mero variable de argumentos. Por ejemplo, string.find puede tomar dos, tres o cuatro argumentos. Es posible escribir funciones definidas por el usuario con listas de argumentos op- cionales. Por ejemplo, podemos modernizar nuestra propia versio´n de encuentra para que haga lo mismo que string.find. Esta es la versio´n original de la Secci´on 7.7: def encuentra(cad, c): indice = 0 while indice < len(cad): if str[indice] == c: return indice indice = indice + 1 return -1 Esta es la versio´n aumentada y mejorada: def encuentra(cad, c, comienzo=0): indice = comienzo while indice < len(cad): if str[indice] == c: return indice indice = indice + 1 return -1
  • 184. 1 Clases y m El tercer par´ametro, comienzo, es opcional porque se proporciona un valor por omisi´on, 0. Si invocamos encuentra s´olo con dos argumentos, utilizamos el valor por omisi´on y comenzamos por el principio de la cadena: >>> encuentra("arriba", "r") 1 Si le damos un tercer par´ametro, anula el predefinido: >>> encuentra("arriba", "r", 2) 2 >>> encuentra("arriba", "r", 3) -1 Como ejercicio, an˜ada un cuarto para´metro, fin, que especifique d´onde dejar de buscar. Cuidado: Este ejercicio tiene truco. El valor por omisio´n de fin de- ber´ıa ser len(cad), pero eso no funciona. Los valores por omisio ´n se evalu´an al definir la funci´on, no al llamarla. Cuando se define encuentra, cad au´n no existe, as´ı que no puede averiguar su longi- tud. 14.6. El m´etodo de inicializaci´on El m´etodo de inicializacio´n es un m´etodo especial que se invoca al crear un objeto. El nombre de este m´etodo es init (dos guiones bajos, seguidos de init y dos guiones bajos m´as). Un m ´etodo de inicializaci´on para la clase Hora es as´ı: class Hora: def init (self, horas=0, minutos=0, segundos=0): self.horas = horas self.minutos = minutos self.segundos = segundos No hay conflicto entre el atributo self.horas y el par´ametro horas. la notaci´on de punto especifica a qu´e variable nos referimos. Cuando invocamos el constructor Hora, los argumentos que damos se pasan a init: >>> horaActual = Hora(9, 14, 30) >>> horaActual.imprimeHora() >>> 9:14:30
  • 185. 14.7 Revisi´on de los Puntos 155 Como los par´ametros son opcionales, podemos omitirlos: >>> horaActual = Hora() >>> horaActual.imprimeHora() >>> 0:0:0 O dar s´olo el primer par´ametro: >>> horaActual = Hora (9) >>> horaActual.imprimeHora() >>> 9:0:0 O los dos primeros par´ametros: >>> horaActual = Hora (9, 14) >>> horaActual.imprimeHora() >>> 9:14:0 Finalmente, podemos dar un subconjunto de los par´ametros nombr ´andolos ex- plicitamente: >>> horaActual = Hora(segundos = 30, horas = 9) >>> horaActual.imprimeHora() >>> 9:0:30 14.7. Revisio´n de los Puntos Vamos a reescribir la clase Punto de la Secci´on 12.1 con un estilo mas orientado a objetos: class Punto: def init (self, x=0, y=0): self.x = x self.y = y def str (self): return ’(’ + str(self.x) + ’, ’ + str(self.y) + ’)’ El m´etodo de inicializaci´on toma los valores de x e y como par ´ametros opcio- nales; el valor por omisi´on de cada par´ametro es 0. El siguiente m´etodo, str , devuelve una representaci´on en forma de cadena de un objeto Punto. Si una clase ofrece un m ´etodo llamado str , se impone al comportamiento por defecto de la funci´on interna str de Python.
  • 186. 1 Clases y m >>> p = Punto(3, 4) >>> str(p) ’(3, 4)’ Imprimir un objeto Punto invoca impl´ıcitamente a str sobre el objeto, as´ı que definir str tambi´en cambia el comportamiento de print: >>> p = Punto(3, 4) >>> print p (3, 4) Cuando escribimos una nueva clase, casi siempre empezamos escribiendo init , que facilita el instanciar objetos, y str , que casi siempre es u´til para la depuraci´on. 14.8. Sobrecarga de operadores Algunos lenguajes hacen posible cambiar la definici´on de los operadores internos cuando se aplican a tipos definidos por el usuario. Esta caracter´ıstica se llama sobrecarga de operadores. Es especialmente u´til cuando definimos nuevos tipos matem´aticos. Por ejemplo, para suplantar al operador de suma + necesitamos proporcionar un m´etodo llamado add : class Punto: # aqu´ı van los m´etodos que ya hab´ıamos definido... def add (self, otro): return Punto(self.x + otro.x, self.y + otro.y) Como es habitual, el primer par´ametro es el objeto sobre el que se invoca el m´etodo. El segundo par´ametro se llama convenientemente otro para distinguirlo del mismo (self). Para sumar dos Puntos, creamos y devolvemos un nuevo Punto que contiene la suma de las coordenadas x y la suma de las coordenadas y. Ahora, cuando apliquemos el operador + a objetos Punto, Python invocara´ a add : >>> p1 = Punto(3, 4) >>> p2 = Punto(5, 7) >>> p3 = p1 + p2 >>> print p3 (8, 11)
  • 187. 14.8 Sobrecarga de operadores 157 La expresio´n p1 + p2 equivale a p1. add (p2), pero es obviamente mas ele- gante. Como ejercicio, an˜ada un m´etodo sub (self, otro) que sobre- cargue el operador resta y pru´ebelo. Hay varias formas de sobrecargar el comportamiento del operador multiplica- cio´n: definiendo un m´etodo llamado mul , o rmul , o ambos. Si el operando a la izquierda de * es un Punto, Python invoca a mul , lo que presupone que el otro operando es tambi´en un Punto. Calcula el producto interno de dos puntos, definido segu´n las reglas del a´lgebra lineal: def mul (self, otro): return self.x * otro.x + self.y * otro.y Si el operando a la izquierda de * es un tipo primitivo y el operando de la derecha es un Punto, Python invca a rmul , lo que realiza una multiplicaci´on escalar: def rmul (self, otro): return Punto(otro * self.x, otro * self.y) El resultado es un nuevo Punto cuyas coordenadas son mu´ltiplos de las coorde- nadas originales. Si otro es un tipo que no se puede multiplicar por un nu´mero en coma flotante, entonces rmul causara´ un error. Este ejemplo muestra ambos tipos de multiplicaci´on: >>> p1 = Punto(3, 4) >>> p2 = Punto(5, 7) >>> print p1 * p2 43 >>> print 2 * p2 (10, 14) ¿Qu´e ocurre si intentamos evaluar p2 * 2? Como el primer par ´ametro es un Punto, Python invoca a mul con 2 como el segundo par´ametro. Dentro de mul , el programa intenta acceder a la coordenada x de otro, pero no lo consigue porque un entero no tiene atributos: >>> print p2 * 2 AttributeError: ’int’ object has no attribute ’x’ Desgraciadamente, el mensaje de error es un poco opaco. Este ejemplo muestra algunas de las difucultades de la programaci´on orientada a objetos. A veces es dif´ıcil averiguar simplemente qu´e c´odigo se esta´ ejecutando.
  • 188. 1 Clases y m Para ver un ejemplo m´as completo de sobrecarga de operadores, vaya al Ap´endi- ce B. 14.9. Polimorfismo La mayor´ıa de los m´etodos que hemos escrito funcionan s´olo para un tipo es- pec´ıfico. Cuando usted crea un nuevo objeto, escribe m´etodos que operan sobre ese tipo. Pero hay ciertas operaciones que querr´a aplicar a muchos tipos, como las opera- ciones aritm´eticas de las secciones anteriores. Si muchos tipos admiten el mismo conjunto de operaciones, puede escribir funciones que trabajen sobre cualquiera de esos tipos. Por ejemplo, la operacio´n multisuma (comu´n en a´lgebra lineal) toma tres par´ametros; multiplica los dos primeros y luego suma el tercero. Podemos escri- birla en Python as´ı: def multisuma (x, y, z): return x * y + z Este m´etodo trabajar´a con cualquier valor de x e y que se pueda multiplicar y con cualquier valor de z que se pueda sumar al producto. Podemos invocarlo con valores num´ericos: >>> multisuma (3, 2, 1) 7 O con Puntos: >>> p1 = Punto(3, 4) >>> p2 = Punto(5, 7) >>> print multisuma (2, p1, p2) (11, 15) >>> print multisuma (p1, p2, 1) 44 En el primer caso, el Punto se multiplica por un escalar y luego se suma a otro Punto. En el segundo caso, el producto interior produce un valor num´erico, as´ı que el tercer par´ametro tambi´en debe ser un valor num´erico. Una funci´on como ´esta que puede tomar par´ametros con diferentes tipos se llama polim´orfica. Como un ejemplo m´as, observe el m´etodo delDerechoYDelReves, que imprime dos veces una lista, hacia adelante y hacia atr´as:
  • 189. 14.9 Polimorfismo 159 def delDerechoYDelReves(derecho): import copy reves = copy.copy(derecho) reves.reverse() print str(derecho) + str(reves) Como el m´etodo reverse es un modificador, hacemos una copia de la lista antes de darle la vuelta. As´ı, este m´etodo no modifica la lista que recibe como par´ametro. He aqu´ı un ejemplo que aplica delDerechoYDelReves a una lista: >>> miLista = [1, 2, 3, 4] >>> delDerechoYDelReves(miLista) [1, 2, 3, 4][4, 3, 2, 1] Por supuesto, pretend´ıamos aplicar esta funci´on a listas, as´ı que no es sorpren- dente que funcione. Lo sorprendente es que pudi ´eramos usarla con un Punto. Para determinar si una funci´on se puede aplicar a un nuevo tipo, aplicamos la regla fundamental del polimorfismo: Si todas las operaciones realizadas dentro de la funcio´n se pueden aplicar al tipo, la funcio´n se puede aplicar al tipo. Las operaciones del m´etodo incluyen copy, reverse y print. copy trabaja sobre cualquier objeto, y ya hemos escrito un m´etodo str para los Puntos, as´ı que todo lo que necesitamos es un m ´etodo reverse en la clase Punto: def reverse(self): self.x , self.y = self.y, self.x Ahora podemos pasar Puntos a delDerechoYDelReves: >>> p = Punto(3, 4) >>> delDerechoYDelReves(p) (3, 4)(4, 3) El mejor tipo de polimorfismo es el que no se busca, cuando usted descubre que una funci´on que hab´ıa escrito se puede aplicar a un tipo para el que nunca la hab´ıa planeado.
  • 190. 1 Clases y m 14.10. Glosario lenguaje orientado a objetos: Un lenguaje que ofrece caracter´ısticas, como clases definidas por el usuario y herencia, que facilitan la programaci´on orientada a objetos. programaci´on orientada a objetos: Un estilo de programaci´on en el que los datos y las operaciones que los manipulan esta´n organizadas en clases y m´etodos. m´etodo: Una funci´on definida dentro de una definici´on de clase y que se invoca sobre instancias de esa clase. imponer: Reemplazar una opcio´n por omisi´on. Los ejemplos incluyen el reem- plazo de un par´ametro por omisi´on con un argumento particular y el reem- plazo de un m´etodo por omisi ´on proporcionando un nuevo m´etodo con el mismo nombre. m´etodo de inicializacio´n: Un m´etodo especial que se invoca automaticamen- te al crear un nuevo objeto y que inicializa los atributos del objeto. sobrecarga de operadores: Ampliar los operadores internos (+, -, *, >, <, etc.) de modo que trabajen con tipos definidos por el usuario. producto interno: Una operacio´n definida en a´lgebra lineal que multiplica dos Puntos y entrega un valor num´erico. multiplicaci´on escalar: Una operacio´n definida en a´lgebra lineal que multi- plica cada una de las coordenadas de un Punto por un valor num´erico. polim´orfica: Una funci´on que puede operar sobra mas de un tipo. Si todas las operaciones realizadas dentro de una funci´on se pueden aplicar a un tipo, la funci´on se puede aplicar a ese tipo.
  • 191. Cap´ıtulo 15 Conjuntos de objetos 15.1. Composicio´n Hasta ahora, ya ha visto varios ejemplos de composici´on. Uno de los primeros ejemplos fue el uso de la llamada a un m´etodo como parte de una expresio´n. Otro ejemplo es la estructura anidada de las sentencias; se puede escribir una sentencia if dentro de un bucle while, dentro de otra sentencia if, y as´ı suce- sivamente. Una vez visto este patr´on, y sabiendo acerca de listas y objetos, no le deber´ıa sorprender que pueda crear listas de objetos. Tambi ´en puede crear objetos que contengan listas (en forma de atributos); puede crear listas que contengan listas; objetos que contengan objetos, y as´ı indefinidamente. En este cap´ıtulo y el siguiente, exploraremos algunos ejemplos de estas combi- naciones, y usaremos objetos Carta como ejemplo. 15.2. Objetos Carta Si no esta´ usted familiarizado con los naipes de juego comunes, puede ser un buen momento para que consiga un mazo, si no este cap´ıtulo puede que no tenga mucho sentido. Hay cincuenta y dos naipes en una baraja inglesa, cada uno de los cuales pertenece a un palo y tiene un valor; hay cuatro palos diferentes y trece valores. Los palos son Picas, Corazones, Diamantes, y Tr´eboles (en el orden descendente segu´n el bridge). Los valores son As, 2, 3, 4, 5, 6, 7, 8, 9, 10, Sota, Reina, y Rey. Dependiendo del tipo de juego que se juegue, el valor del As puede ser mayor al Rey o inferior al 2.
  • 192. 1 Conjuntos de Si queremos definir un nuevo objeto para representar un naipe, es obvio qu´e atri- butos deber´ıa tener: valor y palo. Lo que no es tan obvio es el tipo que se debe dar a los atributos. Una posibilidad es usar cadenas de caracteres que contengan palabras como "Picas" para los palos y "Reina" para los valores. Un problema de esta implementaci´on es que no ser´a f´acil comparar naipes para ver cua´l tiene mayor valor o palo. Una alternativa es usar nu´meros enteros para codificar los valores y palos. Con el t´ermino “codificar” no queremos significar lo que algunas personas pueden pensar, acerca de cifrar o traducir a un c ´odigo secreto. Lo que un programador entiende por “codificar” es “definir una correspondencia entre una secuencia de nu´meros y los elementos que se desea representar”. Por ejemplo: Picas ›→ 3 Corazones ›→ 2 Diamantes ›→ 1 Tr´eboles ›→ 0 Esta correspondencia tiene una caracter´ıstica obvia: los palos corresponden a nu´meros enteros en orden, o sea que podemos comparar los palos al comparar los nu´meros. La asociacio´n de los valores es bastante obvia; cada uno de los valores num´ericos se asocia con el entero correspondiente, y para las figuras: Sota ›→ 11 Reina ›→ 12 Rey ›→ 13 Estamos usando una notaci´on matem´atica para estas asociaciones por una razo´n: no son parte del programa Python. Son parte del disen˜o del progra- ma, pero nunca aparecen expl ´ıcitamente en el c´odigo fuente. La definici´on de clase para el tipo Carta se parecer´a a: class Carta: def init (self, palo=0, valor=0): self.palo = palo self.valor = valor Como acostumbramos, proporcionaremos un m´etodo de inicializaci ´on que toma un par´ametro opcional para cada atributo. Para crear un objeto que representa el 3 de Tr´eboles, usaremos la instrucci ´on: tresDeTreboles = Carta(0, 3) El primer argumento, 0, representa el palo de Tr´eboles.
  • 193. 15.3 Atributos de clase y el m´etodostr 163 15.3. Atributos de clase y el m´etodo str Para poder imprimir los objetos Carta de una manera facil de leer para las personas, vamos a establecer una correspondencia entre los c´odigos enteros y las palabras. Una manera natural de hacer esto es con listas de cadenas de caracteres. Asignaremos estas listas dentro de atributos de clase al principio de la definici´on de clase: class Carta: listaDePalos = ["Tr´eboles", "Diamantes", "Corazones", "Picas"] listaDeValores = ["nada", "As", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Sota", "Reina", "Rey"] # se omite el m´etodo init def str (self): return (self.listaDeValores[self.valor] + " de " + self.listaDePalos[self.palo]) Un atributo de clase se define fuera de cualquier m´etodo, y puede accederse desde cualquiera de los m´etodos de la clase. Dentro de str , podemos usar listaDePalos y listaDeValores para aso- ciar los valores num´ericos de palo y valor con cadenas de caracteres. Por ejemplo, la expresio´n self.listaDePalos[self.palo] significa “usa el atribu- to palo del objeto self como un ´ındice dentro del atributo de clase denominado listaDePalos, y selecciona la cadena apropiada”. El motivo del ‘‘nada" en el primer elemento de listaDeValores es para relleno del elemento de posici´on cero en la lista, que nunca se usar´a. Los u´nicos valores l´ıcitos para el valor van de 1 a 13. No es obligatorio que desperdiciemos este primer elemento. Podr´ıamos haber comenzado en 0 como es usual, pero es menos confuso si el 2 se codifica como 2, el 3 como 3, y as´ı sucesivamente. Con los m´etodos que tenemos hasta ahora, podemos crear e imprimir naipes: >>> carta1 = Carta(1, 11) >>> print carta1 Sota de Diamantes Los atributos de clase como listaDePalos son compartidos por todos los ob- jetos de tipo Carta. La ventaja de esto es que podemos usar cualquier objeto Carta para acceder a los atributos de clase:
  • 194. 1 Conjuntos de >>> carta2 = Carta(1, 3) >>> print carta2 3 de Diamantes >>> print carta2.listaDePalos[1] Diamantes La desventaja es que si modificamos un atributo de clase, afectaremos a cada instancia de la clase. Por ejemplo, si decidimos que “Sota de Diamantes” en realidad deber´ıa llamarse “Sota de Ballenas Bailarinas”, podr´ıamos hacer lo siguiente: >>> carta1.listaDePalos[1] = "Ballenas Bailarinas" >>> print carta1 Sota de Ballenas Bailarinas El problema es que todos los Diamantes se transformar´an en Ballenas Bailarinas: >>> print carta2 3 de Ballenas Bailarinas En general no es una buena idea modificar los atributos de clase. 15.4. Comparaci´on de naipes Para los tipos primitivos, existen operadores condicionales (< , >, ==, etc.) que comparan valores y determinan cuando uno es mayor, menor, o igual a otro. Para los tipos definidos por el usuario, podemos sustituir el comportamiento de los operadores internos si proporcionamos un m´etodo llamado cmp . Por convencio´n, cmp toma dos par´ametros, self y otro, y retorna 1 si el primer objeto es el mayor, -1 si el segundo objeto es el mayor, y 0 si ambos son iguales. Algunos tipos esta´n completamente ordenados, lo que significa que se pueden comparar dos elementos cualesquiera y decir cua´l es el mayor. Por ejemplo, los nu´meros enteros y los nu´meros en coma flotante tienen un orden completo. Algu- nos conjuntos no tienen orden, o sea, que no existe ninguna manera significativa de decir que un elemento es mayor a otro. Por ejemplo, las frutas no tienen orden, lo que explica por qu´e no se pueden comparar peras con manzanas. El conjunto de los naipes tiene un orden parcial, lo que significa que algunas veces se pueden comparar los naipes, y otras veces no. Por ejemplo, usted sabe que el 3 de Tr´eboles es mayor que el 2 de Tr´eboles y el 3 de Diamantes es mayor que el 3 de Tr ´eboles. Pero, ¿cua´l es mejor?, ¿el 3 de Tr´eboles o el 2 de Diamantes?. Uno tiene mayor valor, pero el otro tiene mayor palo.
  • 195. 15.5 Mazos de naipes 165 A los fines de hacer que los naipes sean comparables, se debe decidir qu´e es mas importante: valor o palo. Para no mentir, la seleccio´n es arbitraria. Como algo hay que elegir, diremos que el palo es m´as importante, porque un mazo nuevo viene ordenado con todos los Tr´eboles primero, luego con todos los Diamantes, y as´ı sucesivamente. Con esa decisi´on tomada, podemos escribir cmp : def cmp (self, otro): # controlar el palo if self.palo > otro.palo: return 1 if self.palo < otro.palo: return -1 # si son del mismo palo, controlar el valor if self.valor > otro.valor: return 1 if self.valor < otro.valor: return -1 # los valores son iguales, es un empate return 0 En este ordenamiento, los Ases son menores que los doses. Como ejercicio, modifique cmp de tal manera que los Ases tengan mayor valor que los Reyes. 15.5. Mazos de naipes Ahora que ya tenemos los objetos para representar las Cartas, el pr ´oximo paso l´ogico es definir una clase para representar un Mazo. Por supuesto, un mazo esta´ compuesto de naipes, as´ı que cada objeto Mazo contendr´a una lista de naipes como atributo. A continuaci´on se muestra una definici´on para la clase Mazo. El m´etodo de inicializaci´on crea el atributo cartas y genera el conjunto esta´ndar de cincuenta y dos naipes. class Mazo: def init (self): self.cartas = [] for palo in range(4): for valor in range(1, 14): self.cartas.append(Carta(palo, valor)) La forma mas f´acil de poblar el mazo es mediante un bucle anidado. El bucle exterior enumera los palos desde 0 hasta 3. El bucle interior enumera los va- lores desde 1 hasta 13. Como el bucle exterior itera cuatro veces, y el interior
  • 196. 1 Conjuntos de itera trece veces, la cantidad total de veces que se ejecuta el cuerpo interior es cincuenta y dos (trece por cuatro). Cada iteraci ´on crea una nueva instancia de Carta con el palo y valor actual, y agrega dicho naipe a la lista de cartas. El m´etodo append funciona sobre listas pero no sobre tuplas, por supuesto. 15.6. Impresio´n del mazo de naipes Como es usual, cuando definimos un nuevo tipo de objeto queremos un m´etodo que imprima el contenido del objeto. Para imprimir un Mazo, recorremos la lista e imprimimos cada Carta: class Mazo: ... def muestraMazo(self): for carta in self.cartas: print carta Desde ahora en adelante, los puntos suspensivos (...) indicar´an que hemos omitido los otros m´etodos en la clase. En lugar de escribir un m´etodo muestraMazo, podr´ıamos escribir un m´etodo str para la clase Mazo. La ventaja de str esta´ en que es mas flexible. En lugar de imprimir directamente el contenido del objeto, str genera una re- presentaci´on en forma de cadena de caracteres que las otras partes del programa pueden manipular antes de imprimir o almacenar para un uso posterior. Se presenta ahora una versio´n de str que retorna una representaci´on como cadena de caracteres de un Mazo. Para darle un toque especial, acomoda los naipes en una cascada, de tal manera que cada naipe esta´ sangrado un espacio m´as que el precedente. class Mazo: ... def str (self): s = "" for i in range(len(self.cartas)): s = s + " "*i + str(self.cartas[i]) + "n" return s Este ejemplo demuestra varias caracter´ısticas. Primero, en lugar de recorrer self.cartas y asignar cada naipe a una variable, usamos i como variable de bucle e ´ındice de la lista de naipes. Segundo, utilizamos el operador de multiplicaci´on de cadenas de caracteres para sangrar cada naipe un espacio m´as que el anterior. La expresio´n *i prooprciona una cantidad de espacios igual al valor actual de i.
  • 197. 15.7 Barajar el mazo 167 Tercero, en lugar de usar la instrucci´on print para imprimir los naipes, utiliza- mos la funci´on str. El pasar un objeto como argumento a str es equivalente a invocar el m´etodo str sobre dicho objeto. Finalmente, usamos la variable s como acumulador. Inicialmente, s es una cadena de caracteres vac´ıa. En cada pasada a trav´es del bucle, se genera una nueva cadena de caracteres que se concatena con el viejo valor de s para obtener el nuevo valor. Cuando el bucle termina, s contiene la representaci´on completa en formato de cadena de caracteres del Mazo, la cual se ve como a continuaci´on se presenta: >>> mazo = Mazo() >>> print mazo As de Tr ´eboles 2 de Tr´eboles 3 de Tr´eboles 4 de Treboles 5 de Tr´eboles 6 de Tr´eboles 7 de Tr´eboles 8 de Tr´eboles 9 de Tr´eboles 10 de Tr´eboles Sota de Tr ´eboles Reina de Tr ´eboles Rey de Tr´eboles As of Diamantes Y as´ı sucesivamente. Au´n cuando los resultados aparecen en 52 renglones, se trata de s´olo una u´nica larga cadena de caracteres que contiene los saltos de l´ınea. 15.7. Barajar el mazo Si un mazo esta´ perfectamente barajado, cualquier naipe tiene la misma proba- bilidad de aparecer en cualquier posici´on del mazo, y cualquier lugar en el mazo tiene la misma probabilidad de contener cualquier naipe. Para mezclar el mazo, utilizaremos la funci´on randrange del modulo random. Es- ta funci´on toma dos enteros como argumentos a y b, y elige un nu´mero entero en
  • 198. 1 Conjuntos de forma aleatoria en el rango a <= x <b. Como el l´ımite superior es estrictamen- te menor a b, podemos usar la longitud de la lista como el segundo argumento y de esa manera tendremos garantizado un ´ındice legal dentro de la lista. Por ejemplo, esta expresio´n selecciona el ´ındice de un naipe al azar dentro del mazo: random.randrange(0, len(self.cartas)) Una manera sencilla de mezclar el mazo es recorrer los naipes e intercambiar cada una con otra elegida al azar. Es posible que el naipe se intercambie consigo mismo, pero no es un problema. De hecho, si eliminamos esa posibilidad, el orden de los naipes no ser ´a completamente al azar: class Mazo: ... def mezclar(self): import random nCartas = len(self.cartas) for i in range(nCartas): j = random.randrange(i, nCartas) self.cartas[i], self.cartas[j] = self.cartas[j], self.cartas[i] En lugar de presuponer que hay cincuenta y dos naipes en el mazo, obtenemos la longitud real de la lista y la almacenamos en nCartas. Para cada naipe del mazo, seleccionamos un naipe al azar entre aquellos que no han sido intercambiados au´n. Luego intercambiamos el naipe actual (i) con el naipe seleccionado (j). Para intercambiar los naipes usaremos la asignaci´on de tuplas, como se describe en la Secci´on 9.2: self.cartas[i], self.cartas[j] = self.cartas[j], self.cartas[i] Como ejercicio, reescriba esta l´ınea de c´odigo sin usar una asigna- ci´on de secuencias. 15.8. Eliminacio´n y reparto de los naipes Otro m´etodo que podr´ıa ser u´til para la clase Mazo es eliminaCarta, que toma un naipe como par´ametro, lo elimina, y retorna verdadero (1) si el naipe estaba en el mazo, y falso (0) si no estaba:
  • 199. 15.9 Glosario 169 class Mazo: ... def eliminaCarta(self, carta): if carta in self.cartas: self.cartas.remove(carta) return 1 else: return 0 El operador in retorna verdadero si el primer operando esta´ en el segundo, el cual debe ser una lista o tupla. Si el primer operando es un objeto, Python usa el m´etodo cmp del objeto para determinar la igualdad entre los elementos de la lista. Como el cmp en la clase Carta verifica la igualdad en profundidad, el m ´etodo eliminaCarta tambi´en verifica igualdad en profundidad. Para repartir los naipes, queremos eliminar y devolver el naipe que ocupa la posici´on superior en el mazo. El m´etodo pop de las listas proporciona una manera conveniente de realizar esto: class Mazo: ... def darCarta(self): return self.cartas.pop() En realidad, pop elimina el u´ltimo naipe en la lista, as´ı que en efecto estamos repartiendo desde el extremo inferior del mazo. Otra operacio´n m´as que es muy probable necesitemos es la funci´on booleana estaVacio, la cual devuelve verdadero si el mazo no contiene ningu´n naipe: class Deck: ... def estaVacio(self): return (len(self.cartas) == 0) 15.9. Glosario codificar: Representar un conjunto de valores uilizando otro conjunto de valo- res, entre los cuales se construye una correspondencia. atributo de clase: Una variable que se define dentro de la definici ´on de una clase pero fuera de cualquiera de sus m´etodos. Los atributos de clase son accesibles desde cualquier m´etodo de la clase y esta´n compartidos por todas las instancias de la misma.
  • 200. 1 Conjuntos de acumulador: Una variable que se usa en un bucle para acumular una serie de valores, por ejemplo concatena´ndolos dentro de una cadena de caracteres o adicion´andolos a una suma.
  • 201. Cap´ıtulo 16 Herencia 16.1. Herencia La caracter´ıstica de un lenguaje que m´as se asocia con la programaci´on orientada a objetos es la herencia. La herencia es la capacidad de definir una nueva clase que es una versio´n modificada de otra ya existente. La principal ventaja de esta caracter´ıstica es que se pueden agregar nuevos m´eto- dos a una clase sin modificar la clase existente. Se denomina “herencia” porque la nueva clase hereda todos los m´etodos de la clase existente. Si extendemos esta mat ´efora, a la clase existente a veces se la denomina clase padre. La nueva clase puede denominarse clase hija, o tambi´en “subclase”. La herencia es una caracter´ıstica poderosa. Ciertos programas que ser´ıan compli- cados sin herencia pueden escribirse de manera simple y concisa gracias a ella. Adem´as, la herencia puede facilitar la reutilizacio´n del c´odigo, pues se puede adaptar el comportamiento de la clase padre sin tener que modificarla. En algu- nos casos, la estructura de la herencia refleja la propia estructura del problema, lo que hace que el programa sea m´as f ´acil de comprender. Por otro lado, la herencia pude hacer que los porgramas sean dif ´ıciles de leer. Cuando se llama a un m´etodo, a veces no esta´ claro donde debe uno encontrar su definici´on. El c´odigo relevante puede estar diseminado por varios modulos. Adem´as, muchas de las cosas que se hacen mediante el uso de la herencia, se pue- den lograr de forma igualmente (incluso mas) elegante sin ella. Si la estructura general del problema no nos gu´ıa hacia la herencia, dicho estilo de programaci´on puede hacer m´as mal que bien.
  • 202. 1 Herenc En este cap´ıtulo demostraremos el uso de la herencia como parte de un programa que juega a las cartas a la “Mona”. Una de nuestras metas ser´a que el c´odigo que escribamos se pueda reutilizar para implementar otros juegos de naipes. 16.2. Una mano de cartas Para casi cualquier juego de naipes, necesitamos representar una mano de cartas. Una mano es similar a un mazo, por supuesto. Ambos esta´n compuestos de un conjunto de naipes, y ambos requieren de operaciones tales como agregar y eliminar una carta. Adem´as, necesitaremos la capacidad de mezclar tanto un mazo como una mano de cartas. Una mano es diferente de un mazo en ciertos aspectos. Segu´n el juego al que se est´e jugando, podemos querer realizar ciertas operaciones sobre una mano que no tienen sentido sobre un mazo. Por ejemplo, en el poker queremos clasificar una mano (straight (consecutiva), flush (de un solo palo), etc.) y compararla con otra. En bridge necesitaremos calcular el puntaje para la mano para as´ı poder hacer la subasta. Esta situacio´n sugiere el uso de la herencia. Si Mano es una subclase de Mazo, entonces tendr´a todos los m´etodos de Mazo y le podremos agregar otros m´etodos nuevos. En la definici´on de clase, el nombre de la clase padre aparece entre par ´entesis: class Mano(Mazo): pass Esta sentencia indica que la nueva clase Mano hereda de la clase existente Mazo. El constructor de Mano inicializa los atributos para la mano, que son nombre y cartas. La cadena de caracteres nombre identifica a esta mano, probablemente mediante el nombre del jugador que la sostiene. El nombre es un par´ametro opcional con un valor por omisi´on de cadena vac´ıa. cartas es la lista de cartas de la mano, inicializada como lista vac´ıa. class Mano(Mazo): def init (self, nombre=""): self.cartas = [] self.nombre = nombre Casi para cualquier juego de naipes, es necesario agregar y quitar cartas del ma- zo. La eliminacio´n de cartas ya ha sido resuelta, pues Mano hereda eliminaCarta de Mazo. Pero deberemos escribir agregaCarta:
  • 203. 16.3 El reparto de los naipes 173 class Mano(Mazo): ... def agregaCarta(self,carta) : self.cartas.append(carta) De nuevo, los puntos suspensivos indican que hemos omitido los otrs m´etodos. El m´etodo de lista append agrega la nueva carta al final de la lista de cartas. 16.3. El reparto de los naipes Ahora que ya tenemos la clase Mano, queremos repartir las cartas del Mazo en manos. No es claramente obvio si este m´etodo debe ir en la clase Mano o en la clase Mazo, pero como opera sobre un mazo u´nico y (posiblemente) sobre varias manos, es m´as natural ponerlo en el Mazo. repartir debe ser bastante general, pues los diferentes juegos tienen distintos requerimentos. Puede que necesitemos repartir todo el mazo de una vez, o que agreguemos una carta a cada mano. repartir toma dos par´ametros, una lista (o tupla) de manos y la cantidad total de naipes a repartir. Si no hay suficientes cartas en el mazo, el m´etodo reparte todas las cartas y se detiene: class Mazo : ... def repartir(self, manos, nCartas=999): nManos = len(manos) for i in range(nCartas): if self.estaVacio(): break # fin si se acaban las cartas carta = self.darCarta() # da la carta superior mano = manos[i nManos] # a qui´en le toca? mano.agregaCarta(carta) # agrega la carta a la mano El segundo par´ametro, nCartas es opcional; el valor por omisi´on es un nu´mero muy grande, lo cual es lo mismo que decir que se repartira´n todos los naipes del mazo. La variable de bucle i va desde 0 hasta nCartas-1. A cada paso a trav´es del bucle, se elimina una carta del mazo mediante el m ´etodo de lista pop, que quita y devuelve el u´ltimo elemento de la lista. El operador m´odulo ( ) permite que podamos repartir las cartas de una en una (una carta cada vez para cada mano). Cuando i es igual a la cantidad de manos en la lista, la expresio´n i nManos salta hacia el comienzo de la lista (el ´ındice es 0).
  • 204. 1 Herenc 16.4. Mostremos la mano Para mostrar el contenido de una mano, podemos sacar partido de la existencia de los m´etodos muestraMazo y str que se heredan de Mazo. Por ejemplo: >>> mazo = Mazo() >>> mazo.mezclar() >>> mano = Mano("hugo") >>> mazo.repartir([mano], 5) >>> print mano La mano de hugo contiene 2 de Picas 3 de Picas 4 de Picas As de Corazones 9 de Tr´eboles No es una gran mano, pero tiene lo necesario como para disponer de una escalera de color. Aunque es conveniente usar la herencia de los m´etodos existentes, existe infor- maci´on adicional en una Mano que desear´ıamos mostrar al imprimirla. Para ello, podemos proporcionar a la clase Mano un m´etodo str que reemplace al de la clase Mazo: class Mano(Mazo) ... def str (self): s = "La mano de " + self.nombre if self.estaVacio(): s = s + " est´a vac´ıan" else: s = s + " contienen" return s + Mazo. str (self) Al principio s es una cadena de caracteres que identifica a la mano. Si la mano esta´ vac´ıa, el programa agrega las palabras est´a vac ´ıa y devuelve s. En caso contrario, el programa agrega la palabra contiene y la representaci´on como cadena de caracteres del Mazo, que se obtiene llamando al m´etodo str de la clase Mazo sobre la instancia self. Puede parecer extran˜o que enviemos a self, que se refiere a la Mano actual, como argumento de un m´etodo de la clase Mazo, hasta que nos damos cuenta de que una Mano es un tipo de Mazo. Los objetos Mano pueden hacer cualquier
  • 205. 16.5 La clase JuegoDeCartas 175 cosa que pueda hacer un objeto Mazo, y por ello es legal que pasemos una Mano a un m´etodo de Mazo. En general, siempre es legal usar una instancia de una subclase en el lugar de una instancia de una clase padre. 16.5. La clase JuegoDeCartas La clase JuegoDeCartas asume la responsabilidad sobre algunas obligaciones b´asicas comunes a todos los juegos, tales como la creacio´n del mazo y la mezcla de los naipes: class JuegoDeCartas: def init (self): self.mazo = Mazo() self.mazo.mezclar() Esta es la primera vez que vemos que un m´etodo de inicializaci´on realiza una actividad computacional significativa, m´as alla´ de la inicializaci´on de atributos. Para implementar juegos espec´ıficos, debemos heredar de JuegoDeCartas y agregar las caracter´ısticas del nuevo juego. Como ejemplo, escribiremos una simulaci´on para La Mona. La meta de La Mona es desembarazarse de las cartas que uno tiene en la mano. Uno se saca las cartas de encima empareja ´ndolas por valor y color. Por ejemplo, el 4 de Tr´eboles se empareja con el 4 de Picas porque ambos palos son negros. La Sota de Corazones se empareja con la Sota de Diamantes porque ambos son rojos. Para iniciar el juego, se elimina la Reina de Tr´eboles del mazo, de manera que la Reina de Picas no tiene con qui´en emparejarse. Las cincuenta y una cartas restantes se reparten entre los jugadores, de una en una. Luego del reparto, todos los jugadores emparejan y descartan tantas cartas como sea posible. Cuando no se pueden realizar m´as concordancias, el juego comienza. Por turnos, cada jugador toma una carta (sin mirarla) del vecino mas cercano de la izquierda que au´n tiene cartas. Si la carta elegida concuerda con una de la mano del jugador, se elimina dicho par. Si no, la carta se agrega a la mano del jugador. Llega el momento en el que se realizan todas las concordancias posibles, con lo que queda s´olo la Reina de Picas en la mano del perdedor. En nuestra simulaci´on inform´atica del juego, la computadora juega todas las manos. Desafortunadamente, se pierden algunos de los matices del juego real. En una partida real, el jugador que tiene la Mona realiza ciertos esfuerzos para
  • 206. 1 Herenc que su vecino la tome, por ejemplo mostr´andola prominentemente o al contrario, errando al intentar mostrarla abiertamente, o incluso puede fallar al tratar de errar en su intento de mostrarla prominentemente. La computadora simplemente toma una carta al azar de su vecino. 16.6. La clase ManoDeLaMona Una mano para jugar a La Mona requiere ciertas capacidades que esta´n mas all´a de las que posee una Mano. Definiremos una nueva clase ManoDeLaMona, que hereda de Mano y nos proporciona un m´etodo adicional denominado eliminaCoincidencias: class ManoDeLaMona(Mano): def eliminaCoincidencias(self): cant = 0 cartasOriginales = self.cartas[:] for carta in cartasOriginales: empareja = Carta(3 - carta.palo, carta.valor) if empareja in self.cartas: self.cartas.remove(carta) self.cartas.remove(empareja) print "Mano s: s con s" (self.nombre,carta,empareja) cant = cant + 1 return cant Comenzamos por hacer una copia de la lista de las cartas, de tal manera que podamos recorrer la copia mientras vamos quitando cartas de la lista original. Como self.cartas se modifica en el bucle, no vamos a querer usarla para controlar el recorrido. ¡Python puede quedar realmente confundido si se recorre una lista que esta ´ cambiando! Para cada carta de la mano, averiguamos cua´l es la carta que concordara´ con ella y la buscamos. La carta que concuerda tiene el mismo valor y el otro palo del mismo color. La expresio ´n 3 - carta.palo transforma un Tr´ebol (palo 0) en una Pica (palo 3) y un Diamante (palo 1) en un Coraz´on (palo 2). Verifique por su cuenta que las operaciones opuestas tambi´en funcionan. Si la carta que concuerda esta´ en la mano, ambas se eliminan. El siguiente ejemplo demuestra el uso de eliminaCoincidencias: >>> juego = JuegoDeCartas() >>> mano = ManoDeLaMona("hugo") >>> juego.mazo.repartir([mano], 13)
  • 207. 16.7 La clase 1 >>> print mano La mano de hugo contiene As de Picas 2 de Diamantes 7 de Picas 8 de Treboles 6 de Corazones 8 de Picas 7 de Tr´eboles Raina de Tr ´eboles 7 de Diamantes 5 de Tr´eboles Sota de Diamantes 10 de Diamantes 10 de Corazones >>> mano.eliminaCoincidencias() Mano hugo: 7 de Picas con 7 de Tr ´eboles Mano hugo: 8 de Picas con 8 de Tr´eboles Mano hugo: 10 de Diamantes con 10 de Corazones Debe usted notar que no existe un m´etodo init para la clase ManoDeLaMona. Lo heredamos de Mano. 16.7. La clase JuegoDeLaMona Ahora podemos poner nuestra atenci´on en el juego en s´ı mismo. JuegoDeLaMona es una subclase de JuegoDeCartas con un m´etodo nuevo denominado jugar que toma una lista de jugadores como par ´ametro. Como el m´etodo init se hereda de JuegoDeCartas, el nuevo objeto JuegoDeLaMona contiene un mazo recientemtente mezclado: class JuegoDeLaMona(JuegoDeCartas): def jugar(self, nombres): # quitamos la Reina de Tr´eboles self.mazo.eliminaCarta(Carta(0,12)) # construimos una mano para cada jugador self.manos = [] for nombre in nombres : self.manos.append(ManoDeLaMona(nombre))
  • 208. 1 Herenc # repartimos los naipes self.mazo.repartir(self.manos) print "----- Se han repartido las cartas." self.muestraManos() # eliminamos las coincidencias iniciales emparejadas = self.eliminaTodasLasCoincidencias() print "----- Coincidencias eliminadas, el juego comienza." self.muestraManos() # se juega hasta que se han descartado las 50 cartas turno = 0 cantManos = len(self.manos) while emparejadas < 25: emparejadas = emparejadas + self.jugarUnTurno(turno) turno = (turno + 1) cantManos print "----- El juego termin´o." self.muestraManos() Algunos de los pasos que componen el juego se han colocado en m´etodos se- parados. eliminaTodasLasCoincidencias recorre la lista de manos y llama a eliminaCoincidencias para cada una de ellas: class JuegoDeLaMona(JuegoDeCartas): ... def eliminaTodasLasCoincidencias(self): cant = 0 for mano in self.manos: cant = cant + mano.eliminaCoincidencias() return cant Como ejercicio, escriba muestraManos, el cual recorre self.manos y muestra cada mano. cant es un acumulador que va sumando la cantidad de concordancias en cada mano y devuelve el total. Cuando la cantidad total de coincidencias alcanza a las veinticinco significa que se han eliminado cincuenta cartas de las manos, lo que es lo mismo que decir que s´olo queda una carta y el juego ha terminado. La variable turno recuerda el turno de cua´l jugador se esta´ jugando. Comienza en cero y se incrementa en uno cada vez; cuando alcanza el valor cantManos, el operador de modulo lo hace volver a cero.
  • 209. 16.7 La clase 1 El m´etodo jugarUnTurno toma un par´ametro que indica de qui´en es el turno. El valor de retorno es la cantidad de concordancias que se han realizado durante ese turno: class JuegoDeLaMona(JuegoDeCartas): ... def jugarUnTurno(self, i): if self.manos[i].estaVacio(): return 0 vecino = self.encuentraVecino(i) cartaElegida = self.manos[vecino].darCarta() self.manos[i].agregaCarta(cartaElegida) print "Mano", self.manos[i].nombre, "eligi´o", cartaElegida cant = self.manos[i].eliminaCoincidencias() self.manos[i].mezclar() return cant Si la mano de un jugador esta´ vac´ıa, el jugador salio´ del juego, as ´ı que no hace nada y devuelve 0. Si no, un turno consiste en encontrar el primer jugador a la izquierda que au´n tiene cartas, tomar una carta de las que posee, y controlar si hay concordancias. Antes de volver se mezclan las cartas de la mano, de tal manera que la seleccio´n del siguiente jugador sea al azar. El m´etodo encuentraVecino comienza con el jugador que esta´ inmediatamante a la izquierda y continu´a alrededor del c´ırculo hasta que encuentra un jugador que au´n tiene cartas. class JuegoDeLaMona(JuegoDeCartas): ... def encuentraVecino(self, i): cantManos = len(self.manos) for proximo in range(1,cantManos): vecino = (i + proximo) cantManos if not self.manos[vecino].estaVacio(): return vecino Si por cualquier motivo encuentraVecino llegara a dar la vuelta completa al c´ırculo sin encontrar cartas, devolver´ıa None y eso causar´ıa un error en alguna otra parte del programa. Afortunadamante podemos probar que eso no va a suceder nunca (siempre y cuando se detecte correctamente el final del juego). Hemos omitido el m´etodo muestraManos. E´se puede escribirlo usted mismo. La siguiente salida proviene de una forma reducida del juego, en la cual solamen- te se reparten las quince cartas m´as altas (desde los dieces hacia arriba) a tres
  • 210. 1 Herenc jugadores. Con este mazo m´as pequen˜o, el juego termina tras siete coincidencias, en lugar de veinticinco. >>> import cartas >>> juego = cartas.JuegoDeLaMona() >>> juego.jugar(["Allen","Jeff","Chris"]) ----- Se han repartido las cartas. Mano Allen contiene Rey de Corazones Sota de Tr ´eboles Reina de Picas Rey de Picas 10 de Diamantes Mano Jeff contiene Reina de Corazones Sota de Picas Sota de Corazones Rey de Diamantes Reina de Diamantes Mano Chris contiene Sota de Diamantes Rey de Tr´eboles 10 de Picas 10 de Corazones 10 de Tr´eboles Mano Jeff: Reina de Corazones con Reina de Diamantes Mano Chris: 10 de Picas con 10 de Tr´eboles ----- Se eliminaron las coincidencias, el juego comienza. Mano Allen contiene Rey de Corazones Sota de Tr ´eboles Reina de Picas Rey de Picas 10 de Diamantes Mano Jeff contiene Sota de Picas Sota de Corazones Rey de Diamantes
  • 211. 16.8 Glosario 181 Mano Chris contiene Sota de Diamantes Rey de Tr´eboles 10 de Corazones Mano Allen: eligi´o Rey de Diamantes Mano Allen: Rey de Corazones con Rey de Diamantes Mano Jeff: eligi´o 10 de Corazones Mano Chris: eligi´o Sota de Tr ´eboles Mano Allen: eligi´o Sota de Corazones Mano Jeff: eligi´o Sota de Diamantes Mano Chris: eligi´o Reina de Picas Mano Allen: eligi´o Sota de Diamantes Mano Allen: Sota de Corazones con Sota de Diamantes Mano Jeff: eligi´o Rey de Tr´eboles Mano Chris: eligi´o Rey de Picas Mano Allen: eligi´o 10 de Corazones Mano Allen: 10 de Diamantes con 10 de Corazones Mano Jeff: eligi´o Reina de Picas Mano Chris: eligi´o Sota de Picas Mano Chris: Sota de Tr´eboles con Sota de Picas Mano Jeff: eligi´o Rey de Picas Mano Jeff: Rey de Tr´eboles con Rey de Picas ----- El juego termin´o. La mano de Allen est´a vac´ıa. La mano de Jeff contiene Reina de Picas La mano de Chris est´a vac´ıa. As´ı que Jeff es quien perdio´. 16.8. Glosario herencia: La capacidad de definir una nueva clase que es una versio ´n modifi- cada de una clase previamente definida. clase padre: Aquella clase de la cual la clase hija hereda.
  • 212. 1 Herenc clase hija: Una nueva clase creada heredando de una clase existente; tambi´en se la llama “subclase”.
  • 213. Cap´ıtulo 17 Listas enlazadas 17.1. Referencias incrustadas Hemos visto ejemplos de atributos que hacen referencia a otros objetos, a los que llamamos referencias incrustadas (v´ease la Secci ´on 12.8). Una estrutura de datos comu´n, la lista enlazada, saca partido de esta caracter´ıstica. Las listas enlazadas se componen de nodos, donde cada nodo contiene una referencia al pr´oximo nodo de la lista. Adem´as, cada nodo contiene una unidad de datos llamada carga Podemos considerar una lista enlazada como una estructura de datos recur- siva porque tiene una definici´on recursiva. Una lista enlazada puede ser: la lista vac´ıa, representada por None, o bien un nodo que contiene un objeto de carga y una referencia a una lista enlazada. Las estructuras recursivas de datos nos llevan a m´etodos recursivos. 17.2. La clase Nodo Como es habitual cuando se escribe una clase, comenzaremos con los m´etodos de inicializaci´on y str , para poder comprobar el mecanismo basico de crear y mostrar el nuevo tipo:
  • 214. 1 Listas cargo next 1 None cargo next 2 None cargo next 3 None class Nodo: def init (self, carga=None, siguiente=None): self.carga = carga self.siguiente = siguiente def str (self): return str(self.carga) Como es habitual, los par´ametros para el m´etodo de inicializaci ´on son opciona- les. Por defecto, la carga y el enlace, siguiente, se ponen a None. La representaci´on alfanum´erica de un nodo es u´nicamente la de la carga. Como se puede pasar cualquier valor a la funci´on str, podemos guardar cualquier valor en una lista. Para comprobar la implementaci´on en este punto, podemos crear un Nodo e imprimirlo: >>> nodo = Nodo("prueba") >>> print nodo prueba Para hacerlo m´as interesante, necesitaremos una lista que contenga mas de un nodo: >>> nodo1 = Nodo(1) >>> nodo2 = Nodo(2) >>> nodo3 = Nodo(3) Este c´odigo crea tres nodos, pero au´n no tenemos una lista porque los nodos todav´ıa no esta´n enlazados. El diagrama de estados tiene el siguiente aspecto: node1 node2 node3 Para enlazar los nodos, debemos hacer que el primer nodo haga referencia al segundo, y que ´este haga referencia al tercero: >>> nodo1.siguiente = nodo2 >>> nodo2.siguiente = nodo3 La referencia del tercer nodo ser´a None, que indica que es el final de la lista. Ahora el diagrama de estados tendr´a el siguiente aspecto:
  • 215. 17.3 Listas como colecciones 185 node1 node2 node3 cargo next 1 cargo next 2 cargo 3 next None Ahora ya sabe c´omo crear nodos y enlazarlos en listas. Lo que podr´ıa estar menos claro es por qu´e. 17.3. Listas como colecciones Las listas son utiles porque aportan un modo de ensamblar mu ´ltiples objetos dentro de una u´nica entidad, a veces llamada coleccio´n. En el ejemplo, el primer nodo de la lista sirve como referencia a la lista completa. Para pasar la lista como par´ametro, s´olo tenemos que hacer referencia al primer nodo. Por ejemplo, la funci´on imprimeLista toma como argumento un nodo simple. Empezando con la cabeza de la lista, imprime cada nodo hasta que llega al final. def imprimeLista(nodo): while nodo: print nodo, nodo = nodo.siguiente print Para llamar a este m´etodo, pasamos una referencia al primer nodo: >>> imprimeLista(nodo1) 1 2 3 Dentro de imprimeLista tenemos una referencia al primer nodo de la lista, pero no hay una variable que haga referencia a los otros nodos. Tendremos que usar el valor de siguiente de cada nodo para acceder al siguiente nodo. Para recorrer una lista enlazada es habitual usar la variable de un bucle como nodo para hacer referencia sucesivamente a cada uno de los nodos. Este diagrama muestra el valor de lista y los valores que va tomando nodo:
  • 216. 1 Listas cargo y 1 cargo y 2 3 None cargo y node1 node2 node3 node Por convenio, las listas suelen imprimirse entre corchetes con co- mas entre los elementos, como [1, 2, 3]. Como ejercicio, modifique imprimeLista para que genere una salida con este formato. 17.4. Listas y recursividad Es natural expresar muchas operaciones con listas por medio de m´etodos re- cursivos. El siguiente ejemplo es un algoritmo recursivo para imprimir una lista hacia atr´as: 1. Separar la lista en dos partes: el primer nodo (llamado cabeza) y el resto (llamado cola). 2. Imprimir la cola hacia atr´as. 3. Imprimir la cabeza. Por supuesto, el paso 2, la llamada recursiva, supone que tenemos una manera de imprimir una lista del rev´es. Pero si suponemos que la llamada recursiva funciona —el acto de fe— entonces podemos estar convencidos de que este algoritmo funciona. Todo lo que necesitamos es un caso b´asico y una forma de demostrar que para cualquier lista podemos obtener el caso b ´asico. Dada la definici´on recursiva de una lista, un caso b´asico natural es la lista vac´ıa, representada por None: def imprimeAlReves(lista): if lista == None: return cabeza = lista cola = lista.siguiente imprimeAlReves(cola) print cabeza,
  • 217. cargo next 1 2 cargo next 17.5 Listas infinitas 187 La primera l´ınea maneja el caso inicial no haciendo nada. Las dos siguientes l´ıneas dividen la lista en cabeza y cola. Las dos u ´ltimas l´ıneas imprimen la lista. La coma que hay al final de la u ´ltima l´ınea evita que Python salte de l´ınea despu´es de imprimir un nodo. Llamamos este m´etodo tal como antes invocamos a imprimeLista: >>> imprimeAlReves(nodo1) 3 2 1 El resultado es una lista invertida. Es posible que se pregunte por qu´e imprimeLista e imprimeAlReves son fun- ciones y no m´etodos de la clase Nodo. La razo´n es que queremos usar None para representar la lista vac´ıa y no es legal llamar un m´etodo sobre None. Esta limi- taci´on resulta algo inc ´omoda a la hora de escribir c´odigo para manipular listas con un estilo orientado a objetos puro. ¿Podemos demostrar que imprimeAlReves siempre acabar´a? En otras palabras, ¿siempre alcanzaremos el caso b´asico? De hecho, la respuesta es no. Algunas listas har´an que el m´etodo no funcione. 17.5. Listas infinitas No hay nada que impida a un nodo hacer referencia a un nodo anterior de la lista, incluido ´el mismo. Por ejemplo, esta figura muestra una lista con dos nodos, uno de los cuales apunta a s´ı mismo: list Si llamamos a imprimeLista sobre esta lista, entrar´a en un bucle infinito. Si lla- mamos a imprimeAlReves, lo har´a de forma infinitamente recursiva. Eeste tipo de comportamiento da lugar a que sea muy dif´ıcil trabajar con listas infinitas. Sin embargo, en ocasiones resultan u´tiles. Por ejemplo, podr´ıamos representar un nu´mero como una lista de d´ıgitos y usar una lista infinita para representar una fracci´on repetida.
  • 218. 1 Listas A pesar de todo, es un problema que no podamos probar que imprimeLista e imprimeAlReves puedan terminar correctamente. Lo mejor que podemos hacer es afirmar que “si la lista no contiene bucles, estos m´etodos podr´an terminar”. Este tipo de afirmaciones se conocen como condiciones previas. Imponen una restriccio´n sobre uno de los par´ametros y describen el comportamiento del m´etodo si la restriccio´n se satisface. Veremos mas ejemplos mas adelante. 17.6. Teorema fundamental de la ambigu¨edad Una parte de imprimeAlReves podr´ıa habernos sorprendido: cabeza = lista cola = lista.siguiente Despu´es de la primera asignaci´on, la cabeza y la cola tienen el mismo tipo y el mismo valor. Asi que, ¿para qu´e hemos creado un nueva variable? La razo´n es que las dos variables desempen˜an papeles diferentes. Pensamos en la cabeza como una referencia al primer nodo de la lista. Estos “papeles” no forman parte del programa, sino que esta ´n en la mente del programador. En general no podemos decir con s´olo mirar un programa qu´e papel desem- pen˜ar´a un variable. Esta ambigu¨edad puede ser u ´til, pero tambi´en puede difi- cultar la lectura del programa. A menudo usaremos nombres para las variables como nodo y lista para explicar c´omo queremos usar una variable y a veces creamos variables adicionales para eliminar ambigu¨edades. Podr´ıamos haber escrito imprimeAlReves sin cabeza ni cola, que lo har´ıa mas conciso, pero posiblemente menos claro: def imprimeAlReves(lista) : if lista == None : return imprimeAlReves(lista.siguiente) print lista, Mirando esas dos llamadas, hemos de recordar que imprimeAlReves trata sus argumentos como una coleccio´n y print los trata como a un s´olo objeto. El teorema fundamental de la ambigu¨edad indica que la ambigu¨edad que es inherente a una referencia a un nodo: Una variable que hace apunta a nodo puede tratar a este nodo como un objeto o como el primero de una lista de nodos.
  • 219. cargo next 1 cargo next 2 3 None cargo next 17.7 Modificar listas 189 17.7. Modificar listas Hay dos formas de modificar una lista enlazada. Obviamente, podemos cambiar la carga de uno de los nodos, pero las operaciones mas interesantes son las que an˜aden, quitan o reordenan los nodos. Como ejemplo, escribamos un m´etodo que quite el segundo nodo de la lista y devuelva una referencia al nodo quitado: def eliminaSegundo(lista): if lista == None: return primero = lista segundo = lista.siguiente # hacer que el primer noda apunte al tercero primero.siguiente = segundo.siguiente # separar el segundo nodo del resto de la lista segundo.siguiente = None return segundo De nuevo, estamos usando variables temporales para hacer mas legible el c´odigo. As´ı es como usamos este m´etodo: >>> imprimeLista(nodo1) 1 2 3 >>> eliminado = elimnaSegundo(nodo1) >>> imprimeLista(eliminado) 2 >>> imprimeLista(nodo1) 1 3 El diagrama de estado nos muestra el efecto de la operacio´n: first second ¿Qu´e ocurrir´ıa si llam´aramos a este m´etodo y pas´aramos una lista de un u´nico elemento (un singleton)? ¿Qu´e suceder´ıa si pas ´aramos una lista vac´ıa como argumento? ¿Hay una condicio´n previa para este m´etodo? Si es as´ı, ser´ıa algo razonable establecer un m´etodo para manejar una violaci´on de la condicio´n previa.
  • 220. 1 Listas 17.8. Envoltorios y ayudantes A menudo es u´til dividir una operacio´n de listas en dos m´etodos. Por ejemplo, para imprimir una lista invertida en el formato convencional [3, 2, 1] pode- mos usar el m´etodo imprimeAlReves para imprimir 3, 2, pero necesitaremos un m´etodo aparte para imprimir los corchetes y el primer nodo. Llam´emoslo imprimeAlRevesBonito: def imprimeAlRevesBonito(lista) : print "[", if lista != None : cabeza = lista cola = lista.siguiente imprimeAlReves(cola) print cabeza, print "]", De nuevo, vemos que es buena idea comprobar m´etodos como ´este para ver si funcionan con casos especiales como una lista vac ´ıa o un singleton. Cuando usamos este m´etodo en algu´n otro lugar del programa, llamamos direc- tamente a imprimeAlRevesBonito, y ´este llama a imprimeAlReves en nuestro lugar. En cierto modo, imprimeAlRevesBonito actu´a como un envoltorio, y utiliza a imprimeAlReves como su ayudante. 17.9. La clase ListaEnlazada Existen ciertos problemas delicados con la forma en que se implementaron las listas. Inviertiendo el orden de causa y efecto, propondremos primero una im- plementaci´on alternativa y explicaremos luego los problemas que ´esta resuelve. En primer lugar crearemos un nueva clase llamada ListaEnlazada. Sus atribu- tos son un entero que contiene la longitud de la lista y una referencia al primer nodo. Los objetos ListaEnlazada sirven de asas para la manipulaci´on de las listas de los objetos de tipo Nodo: class ListaEnlazada : def init (self) : self.longitud = 0 self.cabeza = None Una ventaja de la clase ListaEnlazada es que suministra un marco natural para poner funciones-envoltorio como imprimeAlRevesBonito, que podemos convertir en un m´etodo de la clase ListaEnlazada:
  • 221. 17.10 Invariantes 191 class ListaEnlazada: ... def imprimeAlReves(self): print "[", if self.cabeza != None: self.cabeza.imprimeAlReves() print "]", class Nodo: ... def imprimeAlReves(self): if self.siguiente != None: cola = self.siguiente cola.imprimeAlReves() print self.carga, Para complicar un poco m´as las cosas, renombramos imprimeAlRevesBonito. Ahora hay dos m´etodos llamados imprimeAlReves: uno en la clase Nodo (el ayudante), y otro en la clase ListaEnlazada (el envoltorio). Cuando el envolto- rio llama a self.cabeza.imprimeAlReves, esta´ llamando al ayudante, ya que self.cabeza es un objeto de tipo Nodo. Otra ventaja de la clase ListaEnlazada es que facilita la forma de an˜adir o qui- tar el primer elemento de una lista. Por ejemplo, agregaPrimero es un m´etodo para ListaEnlazada; toma un elemento de la carga como argumento y lo coloca en el principio de la lista: class ListaEnlazada: ... def agregaPrimero(self, carga): nodo = Nodo(carga) nodo.siguiente = self.cabeza self.cabeza = nodo self.longitud = self.longitud + 1 Como suele ser usual, deber´ıamos comprobar ´este c´odigo para ver si maneja casos especiales. Por ejemplo, ¿qu´e ocurrir´ıa si la lista esta´ unicialmente vac´ıa? 17.10. Invariantes Algunas listas esta´n “bien construidas”; otras no. Por ejemplo, si una lista con- tiene un bucle, provocara´ que nuestros m´etodos se cuelguen, as´ı que podr´ıamos exigir que las listas no contengan bucles. Otro requisito es que el valor de el valor
  • 222. 1 Listas de longitud en el objeto de tipo ListaEnlazada deber´ıa ser igual al nu´mero verdadero de nodos de la lista. A este tipo de requisitos los llamaremos invariantes porque, idealmente de- ber´ıan cumplirse en todos los objetos en todo momento. Especificar invariantes para objetos es una pr´actica u´til de la programaci´on porque hace mas facil de- mostrar la idoneidad del c´odigo, comprobar la integridad de las estructuras de datos y la detecci´on de errores. Una cosa que a veces confunde respecto a los invariantes es que en ocasiones son violados. Por ejemplo, en medio de agregaPrimero, despu´es de an˜adir el nodo paro antes de incrementar la longitud, se viola el invariante. Se acepta este tipo de violaci´on; de hecho, a menudo es imposible modificar un objeto sin violar un invariante durante al menos un corto espacio de tiempo. Normalmente, se exige que todo m´etodo que viole un invariante debe restablecerlo. Si hay algu´n tramo significativo de c´odigo en el que el invariante se ve violado, es importante que los comentarios lo dejen claro, de modo que no se realicen operaciones que dependan del invariante. 17.11. Glosario referencia incrustada: Es una referencia almacenada en un atributo de un objeto. lista enlazada: Estructura de datos que implementa una coleccio´n por medio de una secuencia de nodos enlazados. nodo: Elemento de una lista, normalmente implementado como un objeto que contiene una referencia a otro objeto del mismo tipo. carga: Datos contenidos en un nodo. enlace: Referencia incrustada usada para enlazar un objeto con otro. condicio´n previa: Afirmaci´on que debe ser cierta para que un m ´etodo funcio- ne correctamente. teorema fundamental de la ambigu¨edad: Una referencia a un nodo de una lista puede tratarse como un objeto individual o como el primero de una lista de nodos. singleton: Lista enlazada con un solo nodo.
  • 223. 17.11 Glosario 193 envoltorio: M´etodo que actu´a como mediador entre un m´etodo invocador y m´etodo ayudante, haciendo a menudo su invocacio´n mas facil o menos proclive a errores. ayudante: M´etodo al que no se invoca directamente por un m ´etodo llamante sino por otro m´etodo para formar parte de una operacio´n. invariante: Afirmaci´on que deber´ıa ser cierta para un objeto en todo momento (excepto tal vez cuando se esta´ modificando el objeto).
  • 225. Cap´ıtulo 18 Pilas 18.1. Tipos abstractos de datos Los tipos de datos vistos hasta ahora han sido todos concretos, en el sentido que se ha especificado su implementaci´on completamente. Por ejemplo, la clase Carta representa un naipe por medio de dos enteros. Como dijimos, esa no es la u´nica manera de representar una carta; existen muchas implementaciones alternativas. Un tipo abstracto de datos, o TAD, especifica un conjunto de operaciones (o m´etodos) y la sema´ntica de las operaciones (lo que hacen), pero no especifica la implementaci´on de las operaciones. Esto es lo hace lo abstracto. ¿Para qu´e sirve? Simplifica la tarea de especificar un algoritmo si se pueden indicar las operaciones necesarias sin preocuparse de c´omo se ejecutara´n dichas ope- raciones. Como suelen existir muchas maneras de implementar un TAD, puede ser u´til desarrollar un algoritmo que se pueda usar con cualquiera de las im- plementaciones posibles. Los TADs mas conocidos, como el TAD Pila de este cap´ıtulo, se implemen- tan a menudo en bibliotecas esta´ndares de manera que se puedan escribir una vez y usarlas luego muchos programadores. Las operaciones ejecutadas con TADs proporcionan un lenguaje de alto nivel comu´n para desarrollar y hablar sobre algoritmos.
  • 226. 1 Pil Cuando hablamos de TADs a menudo se hace la distinci´on entre el c´odigo que usa el TAD, el c´odigo cliente, y el c´odigo que implementa el TAD, el c´odigo proveedor. 18.2. El TAD Pila En este cap´ıtulo se presentar´a un TAD comu´n, la pila. Una pila es una coleccio´n, lo que significa que es una estructura de datos que contiene elementos mu´ltiples. Otras colecciones que se han visto son los diccionarios y las listas. Un TAD se definido por medio de las operaciones que se pueden ejecutar so- bre ´el, lo que se llama un interfaz. La interfaz para una pila consta de estas operaciones1 : init : Inicializar una pila nueva y vac´ıa. push: An˜adir un elemento a la pila. pop: Extraer un elemento de la pila. El elemento devuelto siempre es el u´ltimo que se an˜adi´o. isEmpty: Probar si la pila esta´ vac´ıa. A veces a una pila se la llama una estructura “u´ltimo en entrar primero en salir” (“last in, first out” en ingl´es), o LIFO, porque el elemento an˜adido en u´ltimo lugar es el primero que extraemos. 18.3. Co´mo implementar pilas con listas de Pyt- hon Las operaciones sobre listas que posee Python son parecidas a las operaciones que definen a una pila. La interfaz no es exactamente la que deber´ıa de ser, pero se pueden desarrollar programas para convertir el TAD Pila a las operaciones predefinidas. A este programa se lo llama implementaci´on del TAD Pila. En general, una implementaci´on es un conjunto de m´etodos que satisfacen los prerrequisitos sint´acticos y sema´nticos de la interfaz. He aqu´ı una implementaci´on de el TAD Pila que utiliza una lista Python: 1 Mantenemos los nombres ingleses porque son esta´ndar en otros TADs en Python y otros lenguajes.
  • 227. 18.4 Uso de push y pop 197 class Pila : def init (self) : self.elementos = [] def push(self, elemento) : self.elementos.append(elemento) def pop(self) : return self.elementos.pop() def isEmpty(self) : return (self.elementos == []) Un objeto Pila contiene un atributo llamado elementos que es una lista de ele- mentos en la pila. El m´etodo de inicializaci´on pone una lista vac´ıa en elementos. Para meter un elemento nuevo en la pila, push lo apila en elementos. Para quitar un elemento de la pila, pop utiliza el m´etodo de lista homonimo2 para quitar y devolver el u´ltimo elemento de la lista. Finalmente, para probar si la pila esta vac´ıa, isEmpty (est´a vac´ıa) compara elementos con la lista vac´ıa. Una implementaci´on como esta, en la cual los m´etodos consisten de llamadas a m´etodos existentes, se llama enchapado. En la vida real, un enchapado es una capa fina de madera de alta calidad que se utiliza en la fabricaci´on de muebles para ocultar madera de baja calidad. Los cient´ıficos inform´aticos utilizan esta met´afora para describir una parte de un programa que esconde los detalles de una implementaci´on y que provee una interfaz mas simple o mas esta´ndar. 18.4. Uso de push y pop Una pila es una estructura gen´erica de datos, lo significa que se puede an˜adir cualquier tipo de elementos a ella. El ejemplo mete en la pila dos enteros y una cadena: >>> s = Stack() >>> s.push(54) >>> s.push(45) >>> s.push("+") Se pueden utilizar isEmpty y pop para quitar e imprimir todos los elementos en la pila: 2 del mismo nombre
  • 228. 1 Pil • while not s.isEmpty() : print s.pop(), La salida es + 45 54. En otras palabras, ¡se ha utilizado una pila para imprimir los elementos en orden inverso! Reconocemos que no es el formato esta´ndar de impresi´on de listas, pero fue muy f ´acil usar una pila para lograrlo. Compare estas l´ıneas con la implementaci´on de imprimeAlReves que vimos en la Secci´on 17.4. Existe un paralelo natural entre la versio´n recurrente de imprimeAlReves y el algoritmo de pila que acabamos de ver. La diferencia es que imprimeAlReves utiliza la pila de tiempo de ejecucio´n para mantenerse al tanto de los nodos mientras recorre la lista y luego los imprime cuando regresa de la recursio´n. El algoritmo de pila hace lo mismo, pero utiliza un objeto Pila en vez de la pila de tiempo de ejecucio´n. 18.5. Usar una pila para evaluar postfijo Las expresiones matem´aticas en la mayor´ıa de lenguajes de programaci´on se escriben con el operador entre los dos operandos, de esta manera: 1+2. A este formato se le llama infijo. Algunas calculadoras utilizan un formato alternativo llamado postfijo. Con postfijo, el operador va despu´es de los operandos, as ´ı: 1 2 +. La razo´n por la que el formato postfijo es u´til es que existe una forma natural de evaluar una expresio´n en formato postfijo utilizando una pila: Desde el principio de la expresio´n, evalu´e los operadores y operandos uno por uno. • Si el t´ermino es un operando, utilice push para colocarlo en la pila. Si el t´ermino es un operador, utilice pop con dos operandos de la pila, ejecute la operacio´n sobre ellos, y coloque el resultado en la pila con push. Cuando llegue al final de la expresio´n habr´a un operando en la pila. Ese operando es el resultado. Para practicar, aplique este algoritmo a la expresi´on 1 2 + 3 *. Este ejemplo demuestra una de las ventajas de el formato postfijo—no hay necesidad de usar par´entesis para controlar el orden de operaciones. Para obtener el mismo resultado con el formato infijo, se tendr´ıa que escribir (1 + 2) * 3). Para practicar, escriba una expresi´on en formato postfijo que sea equivalente a 1 + 2 * 3.
  • 229. 18.6 An´alisis sint´actico 199 18.6. An´alisis sint´actico Para implementar el algoritmo anterior, necesitamos ser capaces de recorrer una cadena y separarla en operandos y operadores. Este proceso es un ejemplo del an´alisis sint´actico, y los resultados—la piezas individuales de la cadena—son tokens3 . Quiz ´as se acuerde de esas palabras del Cap´ıtulo 1. Python posee un m´etodo llamado split (partir) en el modulo string (cadena) y en el m´odulo re (expresiones regulares). La funci´on string.split parte una cadena y la convierte en una lista utilizando un s´olo cara´cter como delimitador. Por ejemplo: >>> import string >>> string.split("La hora ha llegado"," ") [’La’, ’hora’, ’ha’, ’llegado’] En este caso, el delimitador es el cara´cter espacio, y se parte la cadena en cada espacio. La funci´on re.split tiene m´as potente, y nos permite utilizar una expresio´n regular en vez de un delimitador. Una expresio´n regular es una manera de espe- cificar un conjunto de cadenas. Por ejemplo, [A-z] es el conjunto de todas las letras y [0-9] es el conjunto de todas las cifras. El operador ^ niega un conjunto, y [^0-9] es el conjunto de todo lo que no es un nu´mero, lo cual es exactamente lo que queremos utilizar para partir expresiones en el formato postfijo: >>> import re >>> re.split("([^0-9])", "123+456*/") [’123’, ’+’, ’456’, ’*’, ’’, ’/’, ’’] F´ıjese que el orden de los argumentos es diferente del de string.split; el delimitador viene antes de la cadena. La lista resultante incluye los operandos 123 y 456 y los operadores * y /. Tambi´en incluye dos cadenas vac´ıas que se insertan despu´es de los operandos. 18.7. Evaluar un postfijo Para evaluar una expresio´n en formato postfijo usaremos el analizador sintactico de la seccio´n anterior y el algoritmo de la seccio´n previa a ´esa. Para no complicar 3 Podr´ıamos traducir “token” como “cospel” o “pieza”, en ocasiones tambi´en como “s ´ımbo- lo” pero la expresio´n inglesa esta´ tan introducida en el vocabulario inform´atico que s´olo an˜adir´ıa confusio´n.
  • 230. 2 Pil las cosas, comenzaremos con un evaluador que solo implementa los operadores + y *: def evalPostfijo(expr): import re listaTokens = re.split("([^0-9])", expr) pila = Pila() for token in listaTokens: if token == ’’ or token == ’ ’: continue if token == ’+’: suma = pila.pop() + pila.pop() pila.push(suma) elif token == ’*’: producto = pila.pop() * pila.pop() pila.push(producto) else: pila.push(int(token)) return pila.pop() La primera condicio´n se encarga de espacios y cadenas vac´ıas. Las dos condi- ciones siguientes controlan los operadores. Asumimos, por ahora, que todo lo dem´as es un operando. Por supuesto, ser´ıa mejor verificar si la entrada tiene errores y mostrar un mensaje con el error, pero eso se har´a despu´es. Comprobemos con una evaluaci´on de la forma postfijo (56+47)*2): >>> print evalPostfijo ("56 47 + 2 *") 206 Esto es suficiente. 18.8. Clientes y proveedores Uno de los objetivos fundamentales de un TAD es el de separar los intereses del proveedor, quien escribe el c´odigo de programa que implementa el TAD, y los del cliente, quien utiliza el TAD. El proveedor s´olo se preocupa de la implementaci´on y de si es correcta o no—de acuerdo a las especificaciones del TAD—y no de c ´omo se va a utilizar.
  • 231. 18.9 Glosario 201 A la inversa, el cliente supone que la implementaci´on del TAD es correcta y no se preocupa de los detalles. Cuando se usa uno de los tipos predefinidos de Python, uno se puede dar el lujo de pensar exclusivamente como un cliente. Por supuesto, cuando usted implemente un TAD, tambi´en debe desarrollar c´odi- go de cliente para probarlo. En ese case, uno toma ambos papeles, lo cual puede causar confusio´n. Tiene que fijarse bien en del papel que esta´ tomando en todo momento. 18.9. Glosario tipo abstracto de datos (TAD): Un tipo de datos (a menudo una coleccio´n de objetos) que se define por un conjunto de operaciones pero que se puede implementar de varias maneras. interfaz: El conjunto de operaciones que definen un TAD. implementaci´on: El c´odigo de programa que satisface los prerrequisitos sint´acticos y sema´nticos de un interfaz. cliente: Un programa (o la persona que lo escribio´) que utiliza un TAD. proveedor: Un programa (o la persona que lo escribio´) que implementa un TAD. enchapado: La definici´on de clase que implementa un TAD con definiciones de m´etodos que son las invocaciones de otros m ´etodos, a veces con trans- formaciones simples. El enchapado no ejecuta nada de gran valor, pero mejora la interfaz vista por el cliente o la hace mas esta´ndar. estructura de datos gen´erica: Un tipo de estructura de datos que puede contener datos de cualquier tipo. infijo: Un m´etodo de escribir expresiones matem´aticas con los operadores entre los operandos. postfijo: Un m´etodo de escribir expresiones matem´aticas con los operadores despu´es de los operandos. analizar sint´acticamente: Examinar una cadena de caracteres o tokens y analizar su estructura gram´atical. token: Un conjunto de caracteres que se tratan como una unidad y son anali- zados sint´acticamente, como las palabras de un lenguaje natural. delimitador: Un cara´cter utilizado para separar tokens, como la puntuaci´on en un lenguaje natural.
  • 233. Cap´ıtulo 19 Colas Este cap´ıtulo presenta dos TADs: la Cola y la Cola Priorizada. En la vida real, una cola es una fila de clientes esperando un servicio de algu´n tipo. En la mayor´ıa de los casos, el primer cliente de la fila es el primero al que se va a servir. Sin embargo, hay excepciones. En los aeropuertos, a veces se saca de la cola a los clientes cuyos vuelos van a salir pronto. En los supermercados, un cliente educado puede dejar que alguien que lleva pocos productos pase antes. La regla que determina qui´en va primero se llama t ´actica de encolamiento. La t´actica de encolamiento m´as simple se llama FIFO, de “first-in-first-out”, “el primero que entra es el primero que sale”. La tactica de encolamiento mas general es el encolamiento priorizado, en la que a cada cliente se le asigna una prioridad y el cliente con la prioridad mas alta pasa primero, sin importar el orden de llegada. Decimos que es la t´actica mas general porque la prioridad se puede basar en cualquier cosa: a qu´e hora sale el vuelo; cua ´ntos productos lleva el cliente; cua´n importante es el cliente. Por supuesto, no todas las tacticas de prioridad son “justas”, pero la justicia siempre es subjetiva. El TAD Cola y el TAD Cola Priorizada tienen el mismo conjunto de operaciones. La diferencia esta´ en la sema´ntica de las operaciones: una cola usa la tactica FIFO, y una cola priorizada (como su propio nombre indica) usa una tactica de encolamiento priorizado. 19.1. El TAD Cola El TAD Cola se define a trav´es de las siguientes operaciones: init : Inicializa una cola nueva vac´ıa.
  • 234. 2 Col inserta: An˜ade un elemento a la cola. quita: Elimina y devuelve un elemento de la cola. El elemento devuelto es el primero que se an˜adi´o. estaVacia: Comprueba si la cola esta´ vac´ıa. 19.2. Cola Enlazada La primera implementaci´on del TAD Cola al que vamos a echar un vistazo se llama cola enlazada porque esta´ hecha de ojetos Nodo enlazados. He aqu´ı la definici´on de la clase: class Cola: def init (self): self.longitud = 0 self.cabeza = None def estaVacia(self): return (self.longitud == 0) def inserta(self, carga): nodo = Nodo(carga) nodo.siguiente = None if self.cabeza == None: # si la lista est´a vac´ıa el nuevo nodo va el primero self.cabeza = nodo else: # encuentra el u´ltimo nodo de la lista ultimo = self.cabeza while ultimo.siguiente: ultimo = ultimo.siguiente # a~nadir el nuevo nodo ultimo.siguiente = nodo self.longitud = self.longitud + 1 def quita(self): carga = self.cabeza.carga self.cabeza = self.cabeza.siguiente self.longitud = self.longitud - 1 return carga Los m´etodos estaVacia y quita son id´enticos a los m´etodos estaVacia y quitaPrimero de ListaEnlazada. El m´etodo inserta es nuevo y un poco mas
  • 235. 19.3 Rendimiento t´ıpico 205 complicado. Queremos insertar nuevos elementos al final de la lista. Si la cola esta´ vac´ıa, simplemente hacemos que cabeza se refiera al nuevo nodo. En caso contrario, recorremos la lista hasta el u´ltimo nodo y lo fijamos al final. Podemos reconocer el u´ltimo nodo porque su atributo siguiente es None. En un objeto Cola correctamente construido hay dos invariantes. El valor de longitud deber´ıa ser el nu´mero de nodos en la cola, y el u´ltimo nodo deber´ıa tener siguiente igual a None. Cr´ease que este m´etodo cumple con ambas inva- riantes. 19.3. Rendimiento t´ıpico Normalmente cuando invocamos un m´etodo, no nos importan los detalles de su implementaci´on. Pero hay un “detalle” que podr´ıa interesarnos: el rendimien- to t´ıpico del m´etodo. ¿Cua´nto tarda, y c´omo var´ıa el tiempo de ejecucio´n al aumentar el nu´mero de elementos de la coleccio´n? Primero mire quita. Ah´ı no hay bucles ni llamadas a funciones, dando a enten- der que el tiempo de ejecucio´n de este m´etodo es siempre el mismo. Un m´etodo as´ı se llama operacio´n de tiempo constante. En realidad, el m´etodo podr´ıa ser ligeramente m´as r ´apido cuando la lista esta´ vac´ıa porque se salta el cuerpo de la condicio´n, pero esa diferencia no es significativa. El rendimiento de inserta es muy diferente. En el caso general, tenemos que recorrer la lista para encontrar el u´ltimo elemento. Este recorrido cuesta un tiempo proporcional a la longitud de la lista. Como el tiempo de ejecucio´n es funci´on lineal de la longitud, este m´etodo se llama de tiempo lineal. Comparado con el tiempo constante, es muy pobre. 19.4. Cola Enlazada Mejorada Nos gustar´ıa una implementaci´on del TAD Cola capaz de realizar todas las operaciones en tiempo constante. Una forma de hacerlo es modificar la clase Cola de modo que mantenga una referencia tanto al primero como al u´ltimo nodo, como se muestra en la figura:
  • 236. 2 Col cargo next 1 cargo next 2 3 cargo next last 3 length head La implementaci´on de ColaMejorada es as´ı: class ColaMejorada: def init (self): self.longitud = 0 self.cabeza = None self.ultimo = None def estaVacia(self): return (self.longitud == 0) Hasta ahora, el u´nico cambio es el atributo ultimo. Se usa en los m ´etodos inserta y quita: class ColaMejorada: ... def inserta(self, carga): nodo = Nodo(carga) nodo.siguiente = None if self.longitud == 0: # si la lista est´a vac´ıa, el nuevo nodo es cabeza y self.cabeza = self.ultimo = nodo else: u´ltimo # encontrar el u´ltimo nodo ultimo = self.ultimo # a~nadir el nuevo nodo ultimo.siguiente = nodo self.ultimo = nodo self.longitud = self.longitud + 1 Como ultimo sigue el rastro del u´ltimo nodo, no necesitamos buscarlo. A causa de esto, este m´etodo funciona en tiempo constante. Debemos pagar un precio por esa velocidad. Tenemos que an˜adir un caso especial a quita para apuntar ultimo a None cuando quitamos el u´ltimo nodo: class ColaMejorada:
  • 237. 19.5 Cola priorizada 207 ... def quita(self): carga = self.cabeza.carga self.cabeza = self.cabeza.siguiente self.longitud = self.longitud - 1 if self.longitud == 0: self.ultimo = None return carga Esta implementaci´on es m´as complicada que la de la Lista Enlazada, y es mas dif´ıcil demostrar que es correcta. La ventaja es que hemos alcanzado la meta: tanto inserta como quita son operaciones de tiempo constante. Como ejercicio, escriba una implementaci´on del TAD Cola usando una lista de Python. Compare el rendimiento de esta implementaci ´on con la de la ColaMejorada para varias longitudes de cola. 19.5. Cola priorizada El TAD Cola Priorizada tiene el mismo interfaz que el TAD Cola, pero diferente sema´ntica. De nuevo, el interfaz es: init : Inicializa una cola vac´ıa nueva. inserta: An˜ade un nuevo elemento a la cola. quita: Elimina y devuelve un elemento de la cola. El elemento devuelto es el de prioridad m´as alta. estaVacia: Comprueba si la cola esta´ vac´ıa. La diferencia sema´ntica es que el elemento eliminado de la cola no es necesa- riamente el primero que se an˜adi´o. En su lugar, es el elemento con la prioridad m´as alta. Lo que son las prioridades y c´omo se comparan con las otras no se especifica en la implementaci´on de la Cola Priorizada. Depende de los elementos de la cola. Por ejemplo, si los elementos de la cola tienen nombres, podemos elegirlos en orden alfab´etico. Si son puntuaciones de bolos, podemos ir de mayor a menor, pero si son puntuaciones de golf, iremos de menor a mayor. Siempre que podamos comparar los elementos de la cola, podremos encontrar y quitar el elemento con mayor prioridad. Esta implementaci´on de Cola Priorizada tiene como atributo una lista de Python que contiene los elementos de la cola.
  • 238. 2 Col class ColaPriorizada: def init (self): self.elementos = [] def estaVacia(self): return self.elementos == [] def inserta(self, elemento): self.elementos.append(elemento) El m´etodo de inicializaci´on, estaVacia, e inserta son todos calcados de las operaciones sobre listas. El u´nico m´etodo interesante es quita: class ColaPriorizada: ... def quita(self): maxi = 0 for i in range(1,len(self.elementos)): if self.elementos[i] > self.elementos[maxi]: maxi = i elemento = self.elementos[maxi] self.elementos[maxi:maxi+1] = [] return elemento Al principio de cada iteraci´on, maxi contiene el ´ındice del elemento mas grande (prioridad mas alta) que hemos visto hasta el momento. Cada vez que se com- pleta el bucle, el programa compara el i´esimo elemento con el campe´on. Si el nuevo elemento es mayor, el valor de maxi se fija a i. Cuando la sentencia for completa su ejecucio´n, maxi es el ´ındice del elemento mayor. Este elemento se elimina de la lista y se devuelve. Vamos a probar la implementaci´on: >>> c = ColaPriorizada() >>> c.inserta(11) >>> c.inserta(12) >>> c.inserta(14) >>> c.inserta(13) >>> while not c.estaVacia(): print c.quita() # ver cu´al se quita 14 13 12 11
  • 239. 19.6 La clase Golfista 209 Si la cola contiene nu´meros o cadenas simples, se eliminan en orden num´erico o alfab´etico, de mayor a menor. Python puede encontrar el entero o la cadena mayor porque puede compararlos usando los operadores de comparacio´n inter- nos. Si la cola contiene un tipo de objeto, debe proporcionar un m´etodo cmp . Cuando quita usa el operador > para comparar elementos, invoca al cmp de uno de los elementos y le pasa el otro como par ´ametro. Siempre que el m´etodo cmp trabaje adecuadamete, la Cola Priorizada funcionar´a. 19.6. La clase Golfista Como ejemplo de un objeto con una definici´on inusual de prioridad, vamos a implementar una clase llamada Golfista que mantiene los nombres y puntua- ciones de golfistas. Como es habitual, empezamos por definir init y str : class Golfista: def init (self, nombre, puntos): self.nombre = nombre self.puntos = puntos def str (self): return " -16s: d" (self.nombre, self.puntos) str usa el operador de formato para poner los nombres y las puntuaciones en bonitas columnas. A continuaci´on definimos una versio´n de cmp en la que la puntuaci´on mas baja tiene la prioridad m´as alta. Como siempre, cmp devuelve 1 si self es “mayor que” otro, -1 si self es “menor que” otro, y 0 si son iguales. class Golfista: ... def cmp (self, otro): if self.puntos < otro.puntos: return 1 # menos es m ´as if self.puntos > otro.puntos: return -1 return 0 Ya estamos listos para probar la cola priorizada con la clase Golfista: >>> tiger = Golfista("Tiger Woods", 61) >>> cabr = Golfista("´Angel Cabrera", 72) >>> ola = Golfista("J.M. Olaz´abal", 69) >>>
  • 240. 2 Col >>> cp = ColaPriorizada() >>> cp.inserta(tiger) >>> cp.inserta(cabr) >>> cp.inserta(ola) >>> while not cp.estaVacia(): print cp.quita() Tiger Woods : 61 J.M. Olaz´abal : 69 ´Angel Cabrera : 72 Como ejercicio, escriba una implementaci´on del TAD Cola Prio- rizada usando una lista enlazada. Deber´ıa usted mantener la lista ordenada de modo que la eliminaci´on sea una operacio´n de tiempo constante. Compare el rendimiento de esta implementaci´on con la implementaci´on con la lista de Python. 19.7. Glosario cola: Un conjunto ordenado de objetos esperando un servicio de algu´n tipo. Cola: Un TAD que ejecuta las operaciones que uno podr´ıa realizar sobre una cola. t´actica de encolamiento: Las reglas que determinan qu´e miembro de la cola ser´a el pr´oximo en eliminarse. FIFO: “First In, First Out”, una t´actica de encolamiento en la que el primer miembro en llegar es el primero en salir. cola priorizada: Una t´actica de encolamiento en la que cada miembro tiene una prioridad determinada por factores externos. El miembro con mayor prioridad es el primero en eliminarse. Cola Priorizada: Un TAD que define las operaciones que se pueden realizar sobre una cola priorizada. cola enlazada: Una implementacion de una cola utilizando una lista enlazada. tiempo constante: Una operacio´n cuyo tiempo de ejecucio´n no depende del taman˜o de la estructura de datos. tiempo lineal: Una operacio´n cuyo tiempo de ejecucio´n es funci ´on lineal del taman˜o de la estructrua de datos.
  • 241. cargo1 leftright cargo2 leftright cargo3 leftright Cap´ıtulo 20 A´rboles Al igual que las listas enlazadas, los a´rboles esta´n hechos de nodos. Un tipo comu´n de a´rbol es un ´arbol binario, en el que cada nodo contiene una referencia a otros dos nodos (posiblemente nula). Nombramos a estas referencias como suba´rboles izquierdo y derecho. Como los nodos de las listas, los nodos de los a´rboles tambi´en contienen una carga. Un diagrama de estado de un a´rbol es as´ı: tree None None None None Para evitar apelotonar las cosas en la figura, solemos omitir los Nones. La parte superior del a´rbol (el nodo al que apunta tree) se llama ra ´ız. Siguiendo con la met´afora del a´rbol, los otros nodos se llaman ramas y los nodos de los extremos con referencias nulas se llaman hojas. Puede parecer extran˜o que dibujemos la figura con la ra´ız arriba y las hojas abajo, pero eso no es lo mas raro. Para empeorar las cosas, los cient´ıficos inform´aticos an˜aden a la mezcla otra met´afofa: el a´rbol geneal´ogico. El nodo superior se llama a veces padre y los
  • 242. A 2 nodos a los que se refiere son sus hijos. Los nodos con el mismo padre se llaman hermanos. Para terminar, tenemos un vocabulario geom´etrico para hablar sobre los a´rboles. Ya hemos mencionado izquierda y derecha, pero tambi´en esta´n “arriba” (hacia el padre/ra´ız) y “abajo” (hacia los hijos/hojas). Tambi´en, todos los nodos que esta´n a la misma distancia de la ra´ız forman un nivel del a´rbol. Probablemente no sean necesarias las met´aforas arb´oreas para hablar de a´rboles, pero ah´ı esta´n. Igual que las listas enlazadas, los a´rboles son estructuras de datos recursivas porque se definen recursivamente. Un a´rbol es: el a´rbol vac´ıo, representado por None, o un nodo que contiene una referencia a un objeto y dos referen- cias a a´rboles. 20.1. Crear ´arboles El proceso de montar un a´rbol es similar al proceso de montar una lista enlazada. Cada invocacio´n del constructor crea un solo nodo. class Arbol: def init (self, carga, izquierda=None, derecha=None): self.carga = carga self.izquierda = izquierda self.derecha = derecha def str (self): return str(self.carga) La carga puede ser de cualquier tipo, pero los par´ametros izquierda y derecha deben ser a´rboles. Tanto izquierda como derecha son opcionales; el valor por omisi´on es None. Para imprimir un nodo, simplemente imprimimos la carga. Una forma de construir un a´rbol es del fondo hacia arriba. Asigne primero los nodos hijos: izquierda = Arbol(2) derecha = Arbol(3)
  • 243. 20.2 Recorrer ´arboles 213 Luego cree el nodo padre y vincu´lelo a los hijos: arbol = Arbol(1, izquierda, derecha); Podemos escribir este c´odigo m´as concisamente anidando las invocaciones al constructor: >>> arbol = Arbol(1, Arbol(2), Arbol(3)) En cualquier caso, el resultado es el a´rbol del principio del cap´ıtulo. 20.2. Recorrer ´arboles Siempre que usted vea una nueva estructura de datos, su primera pregunta deber´ıa ser: “¿C´omo la recorro?” La forma mas natural de recorrer un a´rbol es recursivamente. Por ejemplo, si el a´rbol contiene enteros como carga, esta funci´on nos devuelve su suma: def total(arbol): if arbol == None: return 0 return total(arbol.izquierda) + total(arbol.derecha) + arbol.carga El caso base es el a´rbol vac´ıo, que no tiene carga, as´ı que la suma es 0. El paso recursivo hace dos llamadas recursivas para hallar la suma de los a´rboles hijos. Cuando terminan las llamadas recursivas, sumamos la carga del padre y devolvemos el total. 20.3. A´rboles de expresio´n Un a´rbol es un forma natural de representar la estructura de una expresio´n. Al contrario que otras notaciones, puede representar el c´alculo de forma no ambigua. Por ejemplo, la expresio´n infija 1 + 2 * 3 es ambigua a no ser que sepamos que la multiplicaci´on se realiza antes que la suma. E´ste a´rbol de expresio´n representa el mismo c´alculo:
  • 244. A 2 cargo+ leftright cargo left right * cargo3 leftright cargo2 leftright cargo1 leftright tree Los nodos de un a´rbol de expresio´n pueden ser operandos como 1 y 2 u operado- res como + y *. Los operandos son nodos hojas; los nodos operadores contienen referencias a sus operandos. (Todos estos operadores son binarios, lo que sig- nifica que tienen exactamente dos operandos.) As´ı podemos construir este a´rbol: >>> arbol = Arbol(’+’, Arbol(1), Arbol(’*’, Arbol(2), Arbol(3))) Mirando la figura, no hay duda del orden de las operaciones; la multiplicaci´on se realiza antes para calcular el segundo operando de la suma. Los a´rboles de expresio´n tienen muchos usos. El ejemplo de este cap´ıtulo usa a´rboles para traducir expresiones a postfijo, prefijo e infijo. A´rboles similares se usan dentro de los compiladores para analizar, optimizar y traducir programas. 20.4. Recorrido de un ´arbol Podemos recorrer un a´rbol de expresio´n e imprimir el contenido as´ı: def imprimeArbol(arbol): if arbol == None: return print arbol.carga, imprimeArbol(arbol.izquierda) imprimeArbol(arbol.derecha) En otras palabras, para imprimir un a´rbol imprima primero el contenido de la ra´ız, luego el suba´rbol izquierdo entero y despu ´es el suba´rbol derecho entero. Esta forma de recorrer un a´rbol se llama orden prefijo, porque el contenido de la ra´ız aparece antes del contenido de los hijos. La salida del ejemplo anterior es:
  • 245. 20.4 Recorrido de un ´arbol 215 >>> arbol = Arbol(’+’, Arbol(1), Arbol(’*’, Arbol(2), Arbol(3))) >>> imprimeArbol(arbol) + 1 * 2 3 Este formato es diferente tanto del postfijo como del infijo; es otra notaci´on llamada prefija, en la que los operadores aparecen delante de sus operandos. Puede sospechar que si recorre el a´rbol en un orden diferente obtendr´a la expre- sio´n en una notaci´on diferente. Por ejemplo, si imprime primero los suba´rboles y luego la ra´ız, tendr´a: def imprimeArbolPostfijo(arbol): if arbol == None: return imprimeArbolPostfijo(arbol.izquierda) imprimeArbolPostfijo(arbol.derecha) print arbol.carga, ¡El resultado, 1 2 3 * +, esta´ en notaci´on posfija! Este orden de recorrido se llama orden postfijo. Finalmente, para recorrer un a´rbol en orden infijo, usted imprime el a´rbol izquierdo, luego la ra´ız y despu´es el a´rbol derecho: def imprimeArbolInfijo(arbol): if arbol == None: return imprimeArbolInfijo(arbol.izquierda) print arbol.carga, imprimeArbolInfijo(arbol.derecha) El resultado es 1 + 2 * 3, que es la expresio´n en notaci´on infija. Para ser justos debemos sen˜alar que hemos omitido una importante compli- cacio´n. A veces, al escribir una expresio´n infija, debemos usar par´entesis para preservar el orden de las operacioens. De modo que una exploracio´n de orden infijo no es suficiente para generar una expresio´n infija. No obstante, con unas pequen˜as mejoras, el a´rbol de expresio´n y los tres re- corridos nos dan una forma general de traducr expresiones de un formato a otro. Como ejercicio, modifique imprimeArbolInfijo de modo que pon- ga entre par´entesis cada operador con sus operandos. ¿La salida es correcta y sin ambigu¨edades? ¿Se necesitan siempre los par ´entesis? Si hacemos un recorrido en orden infijo y tomamos nota de en qu ´e nivel del a´rbol estamos, podemos generar una representaci´on gr´afica de un a´rbol:
  • 246. A 2 * + 9 3 7 def imprimeArbolSangrado(arbol, nivel=0): if arbol == None: return imprimeArbolSangrado(arbol.derecha, nivel+1) print ’ ’*nivel + str(arbol.carga) imprimeArbolSangrado(arbol.izquierda, nivel+1) El par´ametro nivel lleva la cuenta de d´onde estamos en el a´rbol. Por omisi´on, incialmente es 0. Cada vez que hacemos una llamada recursiva, pasamos nivel+1 porque el nivel del hijo es siempre uno mayor que el del padre. Sangramos cada elemento con dos espacios por nivel. El resultado del a´rbol de ejemplo es: >>> imprimeArbolSangrado(arbol) 3 * 2 + 1 Si mira la salida de lado, ver´a una versio´n simplificada de la figura original. 20.5. Construir un ´arbol de expresio´n En esta seccio´n analizamos expresiones infijas y formamos los correspondientes a´rboles de expresio´n. Por ejemplo, la expresio ´n (3+7)*9 nos da el siguiente a´rbol: F´ıjese en que hemos simplificando el diagrama omitiendo los nombres de los atributos. El analizador que vamos a escribir maneja expresiones que incluyen nu´meros, par´entesis y los operadores + y *. Suponemos que la cadena de entrada ya ha sido tokenizada y almacenada en una lista de Python. La lista de tokens de (3+7)*9 es:
  • 247. 20.5 Construir un ´arbol de 2 [’(’, 3, ’+’, 7, ’)’, ’*’, 9, ’fin’] El token fin es u´til para evitar que el analizador lea mas alla´ lista. del final de la A modo de ejercicio, escriba una funci´on que tome una cadena con- teniendo una expresi´on y devuelva una lista de tokens. La primera funci´on que vamos a escribir es tomaToken, que toma como par´ame- tros una lista de tokens y el token que esperamos. Compara el token esperado con el primer token de la lista: si coinciden, elimina el token de la lista y devuelve verdadero, en caso contrario, devuelve falso: def tomaToken(listaToken, esperado): if listaToken[0] == esperado: del listaToken[0] return 1 else: return 0 Como listaToken apunta a un objeto mutable, los cambios hechos aqu´ı son visibles para cualquier otra variable que apunte al mismo objeto. La siguiente funci´on, obtieneNumero, maneja operandos. Si el siguiente token de listaToken es un nu´mero, obtieneNumero lo elimina y devuelve un nodo hoja que contiene el nu´mero; en caso contrario, devuelve None. def obtieneNumero(listaToken): x = listaToken[0] if type(x) != type(0): return None del listaToken[0] return Arbol (x, None, None) Antes de seguir, debemos probar obtieneNumero aisladamente. Asignamos una lista de nu´meros a listaToken, extraemos el primero, imprimimos el resultado e imprimimos lo que quede de la lista de tokens: >>> listaToken = [9, 11, ’fin’] >>> x = obtieneNumero(listaToken) >>> imprimeArbolPostfijo(x) 9 >>> print listaToken [11, ’fin’] El siguiente m´etodo que necesitamos es obtieneProducto, que construye un a´rbol de expresio´n para productos. Un producto simple tiene dos nu´meros como operandos, como 3 * 7.
  • 248. 3 * * 5 13 A 2 Aqu´ı tenemos una versio´n de obtieneProducto que maneja productos simples. def obtieneProducto(listaToken): a = obtieneNumero(listaToken) if tomaToken(listaToken, ’*’): b = obtieneNumero(listaToken) return Arbol (’*’, a, b) else: return a Suponiendo que obtieneNumero se ejecuta con ´exito y devuelve un a ´rbol simple, asignamos el primer operando a a. Si el siguiente cara´cter es *, obtenemos el segundo nu´mero y construimos un a ´rbol de expresio´n con a, b, y el operador. Si el siguiente cara´cter es cualquier otra cosa, simplemente devolvemos el nodo hoja con a. Veamos dos ejemplos: >>> listaToken = [9, ’*’, 11, ’fin’] >>> arbol = obtieneProducto(listaToken) >>> imprimeArbolPostfijo(arbol) 9 11 * >>> listaToken = [9, ’+’, 11, ’fin’] >>> arbol = obtieneProducto(listaToken) >>> imprimeArbolPostfijo(arbol) 9 El segundo ejemplo implica que entendemos un operando suelto como un tipo de producto. Esta definici´on de “producto” es contraria a la intuici´on, pero resulta ser u´til. Ahora tenemos que v´ernoslas con productos compuestos, como 3 * 5 * 13. Tratamos esta expresio´n como un producto de productos, es decir, 3 * (5 * 13). El a´rbol resultante es: Con un pequen˜o cambio en obtieneProducto podemos manejar productos ar- bitrariamente largos:
  • 249. 20.5 Construir un ´arbol de 2 def obtieneProducto(listaToken): a = obtieneNumero(listaToken) if tomaToken(listaToken, ’*’): b = obtieneProducto(listaToken) # cambiamos esta l ´ınea return Arbol (’*’, a, b) else: return a En otras palabras, un producto puede ser bien un operando aislado o un a´rbol con * en su ra´ız, un nu´mero en la derecha y un producto en la izquierda. Este tipo de definici´on recursiva deber´ıa empezar a resultar familiar. Comprobemos la nueva versio´n con un producto compuesto: >>> listaToken = [2, ’*’, 3, ’*’, 5 , ’*’, 7, ’fin’] >>> arbol = obtieneProducto(listaToken) >>> imprimeArbolPostfijo(arbol) 2 3 5 7 * * * Ahora an˜adiremos la capacidad de analizar sumas. De nuevo, usamos una defini- cio´n poco intuitiva de “suma”. Para nosotros, una suma puede ser un a´rbol con + en la ra´ız, un producto en la izquierda y una suma en la derecha. O tambi´en, una suma puede ser simplemente un producto. Si quiere seguir adelante con esta definici´on, tiene una bonita propiedad: pode- mos representar cualquier expresio´n (sin par ´entesis) como una suma de produc- tos. Esta propiedad es la base de nuestro algoritmo de analisis. obtieneSuma intenta construir un a´rbol con un producto en la izquierda y una suma en la derecha. Pero si no encuentra un +, simplemente construye un pro- ducto. def obtieneSuma(listaToken): a = obtieneProducto(listaToken) if tomaToken(listaToken, ’+’): b = obtieneSuma(listaToken) return Arbol (’+’, a, b) else: return a Vamos a probarla con 9 * 11 + 5 * 7: >>> listaToken = [9, ’*’, 11, ’+’, 5, ’*’, 7, ’fin’] >>> arbol = obtieneSuma(listaToken) >>> imprimeArbolPostfijo(arbol) 9 11 * 5 7 * +
  • 250. A 2 Ya casi hemos acabado, pero todav´ıa tenemos que manejar los par´entesis. En culaquier lugar de una expresio´n donde puede haber un nu´mero, puede haber tambi´en una suma entera entre par´entesis. Solo necesitamos modificar obtieneNumero para manejar subexpresiones: def obtieneNumero(listaToken): if tomaToken(listaToken, ’(’): x = obtieneSuma(listaToken) # obtiene la subexpresi´on tomaToken(listaToken, ’)’) # quita el cierre de par ´entesis return x else: x = listaToken[0] if type(x) != type(0): return None listaToken[0:1] = [] return Arbol (x, None, None) Vamos a probar este c´odigo con 9 * (11 + 5) * 7: >>> listaToken = [9, ’*’, ’(’, 11, ’+’, 5, ’)’, ’*’, 7, ’fin’] >>> arbol = obtieneSuma(listaToken) >>> imprimeArbolPostfijo(arbol) 9 11 5 + 7 * * El analizador manej´o correctamente los par´entesis; la suma ocurre antes de la multiplicaci´on. En la versio´n final del programa, ser´ıa bueno dar a obtieneNumero un nombre m´as descriptivo de su nueva funci´on. 20.6. Manejar errores En todo momento hemos supuesto que las expresiones estaban bien formadas. Por ejemplo, cuando alcanzamos el final de una subexpresio´n, suponemos que el cara´cter siguiente es un par ´entesis cerrado. Si hay un error y el siguiente cara´cter es cualquier otra cosa, deber´ıamos ocuparnos de ´el. def obtieneNumero(listaToken): if tomaToken(listaToken, ’(’): x = obtieneSuma(listaToken) if not tomaToken(listaToken, ’)’): raise ’ExpresionErronea’, ’falta un par ´entesis’ return x
  • 251. 20.7 El ´arbol de 2 else: # omitido el resto de la funci´on La sentencia raise crea una excepcio´n; en este caso creamos un nuevo tipo de ex- cepcio´n, llamado ExpresionErronea. Si la funci´on que llamo´ a obtieneNumero, o alguna de las otras funciones de la pila de llamadas, maneja la expresio´n, el programa podr´a continuar. En caso contrario, Python imprimir´a un mensaje de error y saldra´. Como ejercicio, encuentre otros lugares de estas funciones donde puedan ocurrir errores y an˜ada las sentencias raise adecuadas. Pruebe su codigo con expresiones formadas incorrectamente. 20.7. El ´arbol de animales En esta seccio´n desarrollaremos un pequen˜o programa que usa un a´rbol para representar una base de conocimientos. El programa interactu´a con el usuario para crear un a´rbol de preguntas y nom- bres de animales. Aqu´ı tenemos un ejemplo de ejecucio´n: Est´as pensando en un animal? s Es un p´ajaro? n C´omo se llama el animal? perro Qu´e pregunta distinguir´ıa a un perro de un p´ajaro? Puede volar Si el animal fuera un perro, cu´al ser´ıa la respuesta? n Est´as pensando en un animal? s Puede volar? n Es un perro? n C´omo se llama el animal? gato Qu´e pregunta distinguir´ıa a un gato de un perro? Ladra Si el animal fuera un gato, cu´al ser´ıa la respuesta? n Est´as pensando en un animal? s Puede volar? n Ladra? s Es un perro? s Soy el m´as grande!
  • 252. Can it fly? ny Does it bark?bird n cat y dog A 2 Est´as pensando en un animal? n Este es el a´rbol que construye este di´alogo: Al principio de cada ronda, el programa empieza en lo alto del a ´rbol y hace la primera pregunta. Dependiendo de la respuesta, se mueve al hijo de la izquierda o de la derecha y sigue hasta que llega a un nodo hoja. En ese momento, intenta adivinar. Si falla, pide al usuario el nombre del nuevo animal y una pregunta que distinga al intento fallido del nuevo animal. Entonces an˜ade un nodo al a´rbol con la nueva pregunta y el nuevo animal. E´ste es el c´odigo: def animal(): # empezar con un nodo suelto raiz = Arbol("p´ajaro") # bucle hasta que el usuario salga while 1: print if not si("Est´as pensando en un animal? "): break # recorrer el arbol = raiz a´rbol while arbol.tomaIzquierda() != None: indicador = arbol.tomaCarga() + "? " if si(indicador): arbol = arbol.tomaDerecha() else: arbol = arbol.tomaIzquierda() # intentar adivinar
  • 253. 20.7 El ´arbol de 2 adivina = arbol.tomaCarga() indicador = "Es un " + adivina + "? " if si(indicador): print "Soy el m´as grande!" continue # obtener informaci´on nueva indicador = "C´omo se llama el animal? " animal= raw_input(indicador) indicador = "Qu´e pregunta distinguir´ıa a un s de un s? " pregunta = raw_input(indicador (animal,adivina)) # a~nadir informaci´on nueva al arbol.ponCarga(pregunta) a´rbol indicador = "Si el animal fuera un s, cu´al ser´ıa la respuesta? " if si(indicador animal): arbol.ponIzquierda(Arbol(adivina)) arbol.ponDerecha(Arbol(animal)) else: arbol.ponIzquierda(Arbol(animal)) arbol.ponDerecha(Arbol(adivina)) La funci´on si es un auxiliar; imprime un indicador y acepta una entrada del usuario. Si la respuesta comienza con s o S, la funci´on devuelve verdadero: def si(preg): from string import lower resp = lower(raw_input(preg)) return (resp[0] == ’s’) La condicio´n del bucle externo es 1, lo que significa que seguira´ hasta que se ejecute la sentencia break cuando el usuario ya no piense en ningu´n animal. El bucle while interno recorre el a´rbol de arriba a abajo, guiado por las res- puestas del usuario. Cuando se an˜ade un nuevo nodo al a´rbol, la pregunta sustituye a la carga y los dos hijos son el animal nuevo y la carga original. Una carencia del programa es que, al salir, ¡olvida todo lo que usted le hab´ıa ensen˜ado con tanto cuidado! Como ejercicio, piense en varias formas en las que podr´ıa guardar el a´rbol de conocimiento en un archivo. Implemente la que piense que es m´as f´acil.
  • 254. A 2 20.8. Glosario ´arbol binario: Un a´rbol en el que cada nodo apunta a cero, uno, o dos nodos dependientes. ra´ız: El nodo superior de un a´rbol, sin padre. hoja: Un nodo del extremo inferior de un a´rbol, sin hijos. padre: El nodo que apunta a un nodo dado. hijo: Uno de los nodos a los que apunta un nodo. hermanos: Nodos que tienen un padre comu´n. nivel: El conjunto de nodos equidistante de la ra´ız. operador binario: Un operador que toma dos operandos. subexpresi´on: Una expresio´n entre par´entesis que actu´a como un operando simple dentro de otra expresio´n mayor. orden prefijo: Una forma de recorrer un a´rbol, visitando cada nodo antes que a sus hijos. notaci´on prefija: Una forma de escribir una expresio´n matem ´atica en la que los operadores aparecen antes que sus operandos. orden postfijo: Una forma de recorrer un a´rbol, visitando los hijos de cada nodo antes del propio nodo. orden infijo: Una forma de recorrer un a´rbol, visitando el suba ´rbol izquierdo, luego la ra´ız, y luego el suba´rbol derecho.
  • 255. Ap´endice A Depuracio´n En un programa pueden suceder varios tipos de error, y resulta u ´til distinguirlos para localizarlos r´apidamente: Python presenta errores de sintaxis mientras traduce el c ´odigo fuente en c´odigo binario. Normalmente indican que hay algo err´oneo en la sintaxis del programa. Ejemplo: omitir los dos puntos al final de una sentencia def nos da el mensaje SyntaxError: invalid syntax, algo redundante. El sistema de tiempo de ejecucio´n presenta los errores en tiempo de eje- cucio´n si algo va mal mientras se ejecuta el programa. La mayor´ıa de los mensajes de error en tiempo de ejecucio´n incluyen informaci´on acerca de d´onde sucedio´ el error y qu´e funciones se estaban ejecutando. Ejemplo: una recursio´n infinita termina por provocar un error en tiempo de ejecu- cio´n del “maximum recursion depth exceeded” (superada la profundidad m´axima de recursio´n). Los errores sema´nticos son problemas con un programa que compila y se ejecuta pero no hace lo que se espera de ´el. Ejemplo: una expresio´n puede no evaluarse en el orden esperado, dando un resultado inesperado. El primer paso de la depuraci´on es averiguar con qu´e tipo de error se enfrenta. Aunque las secciones que siguen esta´n organizadas por tipos de error, algunas t´ecnicas son aplicables en m´as de una situacio´n. A.1. Errores de sintaxis Los errores de sintaxis suelen ser f´aciles de arreglar una vez que averigua lo que son. Desgraciadamente, muchas veces los mensajes de error no son
  • 256. 2 Depuraci muy u´tiles. Los mensajes m´as comunes son SyntaxError: invalid syntax y SyntaxError: invalid token, ninguno de los cuales es muy informativo. Por otra parte, el mensaje le dice en qu´e lugar del programa sucedio´ el error. En realidad, le dice d´onde not´o el problema Python, que no es necesariamente donde esta´ el error. A veces el error esta´ antes de la localizacio´n del mensaje de error, muchas veces en la l´ınea anterior. Si esta´ haciendo el programa incrementalmente, deber´ıa tener casi localizado el error. Estar´a en la u´ltima l´ınea que an˜adio. Si esta´ usted copiando c´odigo de un libro, comience comparando con atenci´on su c´odigo con el del libro. Compruebe cada cara ´cter. Al mismo tiempo, recuerde que el libro podr´ıa estar equivocado, as´ı que si ve algo que parezca un error de sintaxis, podr´ıa serlo. He aqu´ı algunas formas de evitar los errores de sintaxis mas habituales: 1. Asegu´rese de que no utiliza una palabra clave de Python como nombre de variable. 2. Compruebe que tiene los dos puntos al final de la cabecera de todas las sentencias compuestas, las for, while, if, y def. 3. Compruebe que el sangrado es consistente. Puede usted sangrar tanto con espacios como con tabuladores, pero es mejor no mezclarlos. Todos los niveles deber´ıan estar anidados por la misma cantidad. 4. Asegu´rese de que todas las cadenas del c´odigo tienen su par de comillas de apertura y cierre. 5. Si tiene cadenas que ocupan varias l´ıneas con triples comillas (o triples ap´ostrofos), asegu´rese de que ha terminado la cadena correctamente. Una cadena sin terminar puede provocar un error invalid token al final de su programa, o puede tratar la siguiente parte del programa como una cadena hasta que llegue a la siguiente cadena. ¡En el segundo caso, podr´ıa no presentar ningu´n mensaje de error! 6. Un par´entesis sin cerrar—(, { o [—hace que Python continue con la l´ınea siguiente como parte de la sentencia actual. Generalmente aparecer´a un error casi inmediatamente en la l´ınea siguiente. 7. Compruebe el cla´sico = donde deber´ıa haber un == en los condicionales. Si nada funciona, siga con la seccio´n que sigue...
  • 257. A.2 Errores en tiempo de ejecuci 2 A.1.1. No consigo ejecutar mi programa, no importa lo que haga. Si el compilador dice que hay un error pero usted no lo ve, podr´ıa ser porque usted y el compilador no miran el mismo c´odigo. Compruebe su entorno de programaci´on para asegurarse de que el programa que esta´ editando es el que esta´ intentando ejecutar Python. Si no esta´ seguro, pruebe a poner un error de sintaxis obvio y deliberado al principio del programa. Ahora ejecute (o importe) de nuevo. Si el compilador no encuentra el nuevo error probalemente hay algo equivocado en el modo en que esta´ configurado su entorno. Si esto ocurre, puede enfrentarse a ello empezando de nuevo con un programa nuevo como “Hola, mundo”, y asegurarse de que puede hacer que funcione un programa conocido. Luego an˜ada gradualmente los trozos del programa nuevo al que funciona. A.2. Errores en tiempo de ejecucio´n Una vez que su programa es sint´acticamente correcto, Python pude importarlo y al menos comenzar a ejecutarlo. ¿Qu´e podr´ıa ir mal? A.2.1. Mi programa no hace nada de nada. Este problema es muy comu´n cuando su archivo consta de funciones y clases pero en realidad no invoca nada para que empiece la ejecucio´n. Esto puede ser intencionado cuando s´olo planea importar el modulo para suministrar clases y funciones. Sin no es intencionado, asegu´rese de que esta´ llamando a una funci´on que inicie la ejecucio´n, o ejecute una desde el indicador interactivo. Vea tambi´en la seccio´n “Flujo de Ejecuci´on” m´as adelante. A.2.2. Mi programa se cuelga. Si un programa se para y parece no hacer nada, decimos que “se ha colgado”. A menudo significa que se ha quedado atrapado en un bucle infinito o en una recursio´n infinita. Si hay un bucle en particular que le resulta sospechoso de provocar el problema, an˜ada una sentencia print justo antes del bucle que diga “en- trando al bucle” y otra inmediatamente despu´es que diga “saliendo del bucle”.
  • 258. 2 Depuraci Ejecute el programa. Si obtiene el primer mensaje pero el segundo no, tiene usted un bucle infinito. Vaya a la seccio´n “Bucle Infinito” mas adelante. Una recursio´n infinita casi siempre har´a que el programa corra un rato y luego presente un error de “RuntimeError: Maximum recursion depth exceeded”. Si ocurre eso, vaya a la seccio´n “Recursi´on Infinita” mas ade- lante. Si no ve este error pero sospecha que hay un problema con un m´etodo o funci´on recursivos tambi´en puede utilizar las t ´ecnicas de la seccio´n “Re- cursio´n Infinita”. Si no funciona ninguno de estos pasos, comience a probar otros bucles y otros m´etodos y funciones recursivos. Si eso no funciona, es posible que no comprenda el flujo de ejecucio´n de su programa. Vaya a la seccio´n “Flujo de Ejecuci´on” mas adelante. Bucle Infinito Si cree que tiene un bucle infinito y piensa que sabe qu´e bucle provoca el pro- blema, an˜ada una sentencia print que imprima los valores de las variables de la condicio´n al final del bucle junto con el valor de la condicio´n. Por ejamplo: while x > 0 and y < 0 : # hacer algo con x # hacer algo con y print "x: ", x print "y: ", y print "condici´on: ", (x > 0 and y < 0) Ahora, cuando ejecute el programa, ver´a tres l´ıneas de salida en cada vuelta del bucle. En la u´ltima vuelta el valor de la condicio´n deber´ıa ser false. Si el bucle sigue ejecuta´ndose, podr´a ver los valores de x e y, y podr´a averiguar por qu´e no se actualizan correctamente. Recursi´on Infinita Una recursio´n infinita casi siempre har´a que el programa se ejecute un rato y luego provoque un error de Maximum recursion depth exceeded. Si sospecha que una funci´on o un m´etodo esta´ causando una recursio´n infinita, comience por asegurarse de que hay un caso basico. En otras palabras, deber´ıa
  • 259. A.2 Errores en tiempo de ejecuci 2 haber una condicio´n que haga que la funci´on devuelva un valor sin hacer otra llamada recursiva. Si no, necesita revisar el algoritmo y encontrar ese caso basico. Si hay un caso b´asico pero el programa no parece llegar hasta ´el, an˜ada una sentencia print que imprima los par´ametros al principio de la funci´on o m´etodo. Cuando ahora ejecute el programa, ver´a unas pocas l´ıneas cada vez que se invoque la funci´on o m´etodo y all´ı ver´a los par´ametros. Si los par ´ametros no se acercan al caso b´asico, eso le dar´a alguna idea de por qu´e no lo hace. Flujo de Ejecuci´on Si no esta´ seguro de qu´e curso sigue el flujo de ejecucio´n en su programa, an˜ada sentencias print al principio de cada funci´on con un mensaje como “entrando en la funci´on turur´u”, donde turur´u es el nombre de la funci´on. Cuando ahora ejecute el programa, imprimir´a una traza de cada funci´on a me- dida que las vaya invocando. A.2.3. Cuando ejecuto el programa recibo una excepcion. Si algo va mal durante la ejecucio´n, Python imprime un mensaje que incluye el nombre de la excepcio´n, la l´ınea del programa donde sucedio´ el problema y una traza inversa. La traza inversa identifica la funci´on que se esta´ ejecutando ahora y la funci´on que invoc´o a ´esta, y luego la funci´on que invoc´o a ´esa, y as´ı sucesivamente. En otras palabras, traza la ruta de las llamadas a las funciones que le llevaron a donde se encuentra. Tambi´en incluye los nu´meros de las l´ıneas de sus archivos donde suceden todas esas llamadas. El primer paso es examinar el lugar del programa donde sucede el error y ver si puede adivinar lo que sucedio´. Estos son algunos de los errores en tiempo de ejecucio´n mas comunes: NameError: Est´a intentando usar una variable que no existe en el entorno actual. Recuerde que las variables locales son locales. No puede hacer referencia a ellas desde fuera de la funci´on en la que se definen. TypeError: Hay varias causas posibles: Est´a intentando usar un varlor de forma inadecuada. Ejemplo: usar como ´ındice para una cadena, lista o tupla algo que no es un entero.
  • 260. 2 Depuraci Hay una discrepancia entre los elementos de una cadena de formato y los elementos pasados para la conversio´n. Esto puede ocurrir tanto si el nu´mero de elementos no coincide como si se solicita una conversio´n no v´alida. Est´a pasando un nu´mero err´oneo de argumentos a una funci´on o m´etodo. Con los m´etodos, f´ıjese en la definici´on de los m´etodos y compruebe que el primer par´ametro es self. Luego f´ıjese en la invo- cacio´n del m ´etodo; asegu´rese de que esta´ invocando el m´etodo sobre un objeto del tipo adecuado y dandole correctamente el resto de ar- gumentos. KeyError: Est´a tratando de acceder a un elemento de un diccionario con una clave que no esta´ en el diccionario. AttributeError: Est´a intentando acceder a un atributo o m´etodo que no exis- te. IndexError: El ´ındice que esta´ usando para acceder a una lista, cadena o tupla es mayor que su longitud menos uno. Justo antes de donde aparece el error, an˜ada una sentencia print que muestre el valor del ´ındice y la longitud del vector. ¿Es correcto el taman˜o del vector? ¿Tiene el ´ındice un valor correcto? A.2.4. Puse tantas sentencias print que me ahoga la sali- da. Uno de los problemas de usar sentencias print para la depuraci´on es que pue- de terminar enterrado en informaci´on. Hay dos formas de atajar el problema: simplificar la salida o simplificar el programa. Para simplificar la salida, puede elimiar o comentar (convertir en comentarios) las sentencias print que no sean de ayuda, o combinarlas, o dar a la salida un formato que la haga m´as comprensible. Para simplificar el programa puede hacer varias cosas. Primero, reducir la escala del problema en el que esta´ trabajando el programa. Por ejemplo, si esta´ orde- nando un vector, ordene un vector pequen˜o. Si el programa acepta entradas del usuario, dele la entrada m´as simple que provoque el problema. Segundo, limpie el programa. Elimine el c´odigo muerto y reorganice el programa para hacerlo tan legible como sea posible. Por ejemplo, si sospecha que el proble- ma esta´ en una parte del programa con un anidamiento muy profundo, pruebe a reescribir esa parte con una estructura mas simple. Si sospecha de una funci ´on grande, trate de trocearla en funciones menores y pru´ebelas separadamente.
  • 261. A.3 Errores sem 2 El proceso de encontrar el caso m´ınimo de prueba le llevar´a a menudo al error. Si se encuentra con que un programa funciona en una situacio´n pero no en otra, eso le dar´a una pista sobre lo que ocurre. De forma parecida, la reescritura de una porci´on de c´odigo puede ayudarle a encontrar errores sutiles. Si hace un cambio que a usted le parece que no afecta al programa, pero s´ı lo hace, le dar ´a una pista. A.3. Errores sem´anticos En cierto modo, los errores sema´nticos son los mas dif´ıciles de corregir, porque el compilador y el sistema de ejecucio´n no proporcionan informaci´on sobre lo que va mal. S´olo usted sabe lo que se supone que debe hacer el programa, y s´olo usted sabe que no lo esta´ haciendo. El primer paso es hacer una concexio´n entre el texto del programa y el compor- tamiento que esta´ usted viendo. Necesita una hipotesis sobre lo que realmente esta´ haciendo el programa. Una de las dificultades que nos encontramos para ello es la alta velocidad de los computadores. A menudo desear´ıa ralentizar el progrma a una velocidad humana, y con al- gunos programas depuradores podr´a hacerlo. Pero el tiempo que lleva colocar unas sentencias print en los lugares adecuadoes suele ser menor que el que lleva configurar el depurador, poner y quitar puntos de interrupcio´n y “hacer caminar” el programa hasta donde se produce el error. A.3.1. Mi programa no funciona. Deber´ıa hacerse estas preguntas: ¿Hay algo que se supone que deber´ıa hacer el programa pero que no parece suceder? Busque la seccio´n del c´odigo que realiza esa funci´on y asegu´rese de que se ejecuta cuando deber´ıa. ¿Ocurre algo que no deber´ıa? Busque el programa que realiza esa funci´on y vea si se ejecuta cuando no debe. ¿Hay una seccio´n de c´odigo que causa un efecto que no esperaba? asegu´rese de que entiende el c´odigo en cuestio´n, especialmente si incluye invocaciones de funciones o m´etodos de otros m´odulos de Python. Lea la documentaci´on de las funciones que invoca. Pru´ebelas escribiendo casos de prueba simples y comprobando el resultado.
  • 262. 2 2 Depuraci Para programar necesitar´a tener un modelo mental de c´omo funcionan los pro- gramas. Si escribe un programa que no hace lo que espera de ´el, muchas veces el problema no estara´ en el programa, sino en su modelo mental. La mejor manera de corregir su modelo mental es dividiendo el programa en sus componentes (normalmente las funciones y m ´etodos) y probando cada compo- nente de forma independiente. Una vez que encuentre la discrepancia entre su modelo y la realidad, podr´a solucionar el problema. Por supuesto, deber´ıa ir haciendo y probando componentes tal como desarrolla el programa. Si encuentra un problema, s´olo habr´a una pequen˜a cantidad de c´odigo nuevo del que no sabe si esta´ correcto. A.3.2. Tengo una expresio´n grande y peliaguda y no hace lo que espero. Est´a bien escribir expresio´n complejas mientras sean legibles, pero pueden ser dif´ıciles de depurar. Suele ser una buena idea dividir una expesio´n compleja en una serie de asignaciones de variables temporales. Por ejamplo: self.manos[i].agregaCarta (self.manos[ self.encuentraVecino(i)].darCarta()) Puede reescribirse como: vecino = self.encuentraVecino (i) cartaElegida = self.manos[vecino].darCarta() self.manos[i].agregaCarta (cartaElegida) La versio´n expl´ıcita es m´as f´acil de leer porque los nombres de variable nos facili- tan documentaci´on adicional, y es m´as f´acil de depurar porque puede comprobar los tipos de las variables intermedias y mostrar sus valores. Otro problema que puede suceder con las expresiones grandes es que el orden de evaluaci´on puede no ser el que usted esperaba. Por ejemplo, si esta´ traduciendo la expresio´n x a Python, podr´ıa escribir: y = x / 2 * math.pi; Eso no es correcto, porque la multiplicaci´on y la divisi´on tienen la misma prece- dencia y se evalu´an de izquierd a derecha. As´ı que esa expresio´n calcula xπ/2. Una buena forma de depurar expresiones es an˜adir par´entesis para hacer expl´ıci- to el orden de evaluaci´on:
  • 263. A.3 Errores sem 2 y = x / (2 * math.pi); Siempre que no est´e seguro del orden de evaluaci´on, utilice par ´entesis. El pro- grama no s´olo ser´a correcto (en el sentido de hacer lo que usted prentend´ıa), sino que adem´as ser´a m´as legible para otras personas que no hayan memorizado las reglas de precedencia. A.3.3. Tengo una funcio´n o m´etodo que no devuelve lo que esperaba. Si tiene una sentencia return con una expresio´n compleja no tendr ´a la opor- tunidad de imprimir el valor de retorno antes de volver. De nuevo, puede usar una variable temporal. Por ejemplo, en lugar de: return self.manos[i].eliminaCoincidencias() podr´ıa excribir: cant = self.manos[i].eliminaCoincidencias() return cant Ahora ya tiene la oportunidad de mostrar el valor de cant antes de regresar. A.3.4. Estoy atascado de verdad y necesito ayuda. Primero, intente alejarse del computador durante unos minutos. Los computa- dores emiten unas ondas que afectan al cerebro provocando estos efectos: Frustracio´n y/o furia. Creencias supersticiosas (“el computador me odia”) y pensamiento magico (“el programa s´olo funciona cuando me pongo la gorra hacia atr´as”). Programar dando palos de ciego (el empen˜o de programar escribiendo todos los programas posibles y eligiendo el que hace lo correcto). Si se encuentra afectado por alguno de estos s´ıntomas, lev´antese y d´e un paseo. Cuando est´e calmado, piense en el programa. ¿Qu´e es lo que hace? ¿Cua´les pueden ser las causas de tal comportamiento? ¿Cua´ndo fue la u´ltima vez que ten´ıa un programa que funcinaba y qu´e fue lo siguiente que hizo? A veces lleva tiempo encontrar un error. Muchas veces encontramos errores cuando estamos lejos del computador y divagamos. Algunos de los mejores lu- gares para encontrar errores son los trenes, las duchas y la cma, justo antes de quedarse dormido.
  • 264. 2 Depuraci A.3.5. No, de verdad necesito ayuda. Sucede. Incluso los mejores programadores se atascan de vez en cuando. A veces trabaja durante tanto tiempo en un programa que no puede ver el error. Lo que necesita es un par de ojos nuevos. Antes de llamar a andie, asegu´rese de que ha agotado las t ´ecnicas explicadas aqu´ı. Su programa deber´ıa ser tan simple como sea posible, y usted deber´ıa estar trabajando con la entrada m´ınima que provoca el error. Deber´ıa tener sentencias print en los lugares adecuados (y lo que dicen deber´ıa ser comprensible). De- ber´ıa entender el problema lo bastante bien como para describirlo sucintamente. Cuando llame a alguien para que le ayude, asegu´rese de darles la informaci´on que necesitan: Si hay un mensaje de error, ¿cua´l es y qu´e parte del programa sen˜ala? ¿Qu´e fue lo u´ltimo que hizo antes de que apareciera el error? ¿Cua´les son las u´ltimas l´ıneas de c´odigo que escribio´, o cua´l es el nuevo caso de prueba que no cumple? ¿Qu´e ha intentado hasta ahora y qu´e ha averiguado? Cuando encuentre el error, t´omese un momento para pensar acerca de lo que podr´ıa haber hecho para encontrarlo m´as r ´apido. La siguiente vez que vea algo parecido, ser´a capaz de encontrar el error antes. Recuerde, el objetivo no es s´olo hacer que el programa funciones. El objetivo es aprender c´omo hacer funcionar al programa.
  • 265. Ap´endice B Crear un nuevo tipo de datos Los lenguajes de programaci´on orientados a objetos permiten a los programado- res crear nuevos tipos de datos que se comporten de manera muy parecida a los tipos de datos nativos. Exploraremos esta posibilidad construyendo una clase Fraccion que funcione de manera muy similar a los tipos num´ericos nativos, enteros, enteros largos y flotantes. Las fracciones, tambi´en conocidas como nu´meros racionales, son valores que pueden expresrse como la proporci´on entre dos nu ´meros enteros, tal como 5/6. Al nu´mero superior se se le llama numerador y al inferior se se le llama deno- minador. Comenzamos definiendo la clase Fraccion con un m´etodo de inicializaci´on que nos surta de un numerador y un demonimador enteros: class Fraccion: def init (self, numerador, denominador=1): self.numerador = numerador self.denominador = denominador El denominador es opcional. Una Fraccion con un s´olo par´ametro representa un nu´mero entero. Si el numerador es n, construimos la fracci´on n/1. El siguente paso es escribir un m´etodo str para que imprima las frac- ciones de forma que tenga sentido. La forma natural de hacerlo es “numera- dor/denominador”:
  • 266. 2 Crear un nuevo tipo de datos class Fraccion: ... def str (self): return " d/ d" (self.numerador, self.denominador) Para probar lo que tenemos hasta ahora, lo ponemos en un fichero llamado Fraccion.py y lo importamos desde el int´erprete de Python. Entonces creamos un objeto fracci´on y lo imprimimos. >>> from Fraccion import fraccion >>> mortadela = Fraccion(5,6) >>> print "La fracci´on es", mortadela La fracci´on es 5/6 Como siempre, la funci´on print invoca impl´ıcitamente al m´etodo str . B.1. Multiplicacio´n de fracciones Nos gustar´ıa poder aplicar las operaciones normales de suma, resta, multiplica- cio´n y divisi´on a las fracciones. Para ello, podemos sobrecargar los operadores matem´aticos para los objetos de clase Fraccion. Comenzaremos con la multiplicaci´on porque es la mas facil de implementar. Para multiplicar dos fraciones, creamos una nueva fracci´on cuyo numerador es el producto de los numeradores de los operandos y cuyo denominador es el producto de los denominadores de los operandos. mul es el nombre que Python utiliza para el m´etodo que sobrecarga al operador *: class Fraccion: ... def mul (self, otro): return Fraccion(self.numerador*otro.numerador, self.denominador*otro.denominador) Podemos probar este m´etodo calculando el producto de dos fracciones: >>> print Fraccion(5,6) * Fraccion(3,4) 15/24 Funciona, pero ¡podemos hacerlo mejor! Podemos ampliar el m ´etodo para ma- nejar la multiplicaci´on por un entero. Usamos la funci´on type para ver si otro es un entero y convertirlo en una fracci´on en tal caso. class Fraccion: ...
  • 267. B.2 Suma de fracciones 237 def mul (self, otro): if type(otro) == type(5): otro = Fraccion(otro) return Fraccion(self.numerador * otro.numerador, self.denominador * otro.denominador) Ahora funciona la multiplicaci´on para fracciones y enteros, pero s ´olo si la frac- cio´n es el operando de la izquierda. >>> print Fraccion(5,6) * 4 20/6 >>> print 4 * Fraccion(5,6) TypeError: mul nor rmul defined for these operands Para evaluar un operador binario como la multiplicaci´on, Python comprueba primero el operando de la izquierda para ver si proporciona un m´etodo mul que soporte el tipo del segundo operando. En este caso, el operador nativo de multiplicaci´on del entero no soporta fracciones. Despu´es, Python comprueba el segundo operando para ver si provee un m´etodo rmul que soporte el tipo del primer operando. En este caso, no hemos provisto el m´etodo rmul , por lo que falla. Por otra parte, hay una forma sencilla de obtener rmul : class Fraccion: ... rmul = mul Esta asignaci´on hace que el m´etodo rmul sea el mismo que mul . Si ahora evaluamos 4 * Fraccion(5,6), Python llamar´a al m ´etodo rmul del objeto Fraccion y le pasar´a 4 como par´ametro: >>> print 4 * Fraccion(5,6) 20/6 Dado que rmul es lo mismo que mul , y mul puede manejar un par´ame- tro entero, ya esta´ hecho. B.2. Suma de fracciones La suma es m´as complicada que la multiplicaci´on, pero au´n es llevadera. La suma de a/b y c/d es la fracci´on (a*d+c*b)/b*d. Usando como modelo el c´odigo de la multiplicaci´on, podemos escribir add y radd :
  • 268. 2 Crear un nuevo tipo de datos class Fraccion: ... def add (self, otro): if type(otro) == type(5): otro = Fraccion(otro) return Fraccion(self.numerador * otro.denominador + self.denominador * otro.numerador, self.denominador * otro.denominador) radd = add Podemos probar estos m´etodos con Fracciones y enteros. >>> print Fraccion(5,6) + Fraccion(5,6) 60/36 >>> print Fraccion(5,6) + 3 23/6 >>> print 2 + Fraccion(5,6) 17/6 Los dos primeros ejemplos llaman a add ; el u´ltimo llama a radd . B.3. Algoritmo de Euclides En el ejemplo anterior, computamos la suma de 5/6 + 5/6 y obtuvimos 60/36. Es correcto, pero no es la mejor forma de representar la respuesta. Para redu- cir la fracci´on a su expresio ´n m´as simple, hemos de dividir el numerador y el denominador por el m´aximo comu´n divisor (MCD) de ambos, que es 12. El resultado ser´ıa 5/3. En general, siempre que creamos un nuevo objeto Fraccion, deber ´ıamos redu- cirlo dividiendo el numerador y el denominador por el MCD de ambos. Si la fracci´on ya esta´ reducida, el MCD es 1. Euclides de Alejandr´ıa (aprox. 325–265 a. C.) prensento´ contrar el MCD de dos nu´meros entermos m y n: un algoritmo para en- Si n divide a m sin resto, entonces n es el MCD. De lo contrario, el MCD es el MCD de n y el resto de dividir m entre n. Esta definici´on recursiva puede expresarse concisamente como una funci ´on: def mcd (m, n): if m n == 0:
  • 269. B.4 Comparar fracciones 239 return n else: return mcd(n, m n) En la primera l´ınea del cuerpo, usamos el operador de modulo para comprobar la divisibilidad. En la u´ltima l´ınea, lo usamos para calcular el resto de la divisi´on. Dado que todas las operaciones que hemos escrito creaban un nuevo objeto Fraccion para devolver el resultado, podemos reducir todos los resultados mo- dificando el m´etodo de inicializaci´on. class Fraccion: def init (self, numerador, denominador=1): m = mcd (numerador, denominador) self.numerador = numerador / m self.denominador = denominador / m Ahora siempre que creemos una Fraccion quedar´a reducida a su forma cano´ni- ca: >>> Fraccion(100,-36) -25/9 Una caracter´ıstica estupenda de mcd es que si la fracci´on es negativa, el signo menos siempre se trasladar´a al numerador. B.4. Comparar fracciones Supongamos que tenemos dos objetos Fraccion, a y b, y evaluamos a == b. La implemetaci´on por defecto de == comprueba la igualdad superficial, por lo que s´olo devuelve true si a y b son el mismo objeto. Queremos mas bien devolver verdadero si a y b tienen el mismo valor —eso es, igualdad en profundidad. Hemos de ensen˜ar a las fracciones c´omo compararse entre s´ı. Como vimos en la Secci´on 15.4, podemos sobrecargar todos los operadores de comparacio´n de una vez proporcionando un m ´etodo cmp . Por convenio, el m´etodo cmp devuelve un nu´mero negativo si self es menor que otro, zero si son lo mismo, y un nu´mero positivo si self es mayor que otro. La forma m´as simple de comparar dos fracciones es la multipicaci´on cruzada. Si a/b > c/d, entonces ad > bc. Con esto en mente, aqu´ı esta´ el c´odigo para cmp :
  • 270. 2 Crear un nuevo tipo de datos class Fraccion: ... def cmp (self, otro): dif = (self.numerador * otro.denominador - otro.numerador * self.denominador) return dif Si self es mayor que otro, entonces dif ser´a positivo. Si otro is mayor, entonces dif ser´a ngativo. Si son iguales, dif es cero. B.5. Forzando la m´aquina Por supuesto, au´n no hemos terminado. Todav´ıa hemos de implementar la resta sobrecargando sub y la divisi´on sobrecargando div . Una manera de manejar estas operaciones es implementar la negaci´on sobre- cargando neg y la inversio´n sobrecargando invert . Entonces podemos restar negando el segundo operando y sumando, y podemos dividir invirtiendo el segundo operando y multiplicando. Luego, hemos de suministrar los m´etodos rsub y rdiv . Desgraciadamen- te, no podemos usar el mismo truco que usamos para la suma y la multiplicaci´on, porque la resta y la divisi´on no son conmutativas. No podemos igualar rsub y rdiv a sub y div . En estas operaciones, el orden de los operandos tiene importancia. Para manejar la negaci´on unitaria, que es el uso del signo menos con un u´nico operando, sobrecargamos el m´etodo neg . Podemos computar potencias sobrecargando pow , pero la implementaci´on tiene truco. Si el exponente no es un nu ´mero entero podr´ıa no ser posible representar el resultado como una Fraccion. Por ejemplo, Fraccion(2) ** Fraccion(1,2) es la raiz cuadrada de 2, que es un nu´mero irracional (no se puede representar como una fracci´on). Por lo tanto, no es facil escribir la ver- sio´n m´as general de pow . Existe otra extensio´n a la clase Fraccion que cabr´ıa considerar. Hasta ahora, hemos asumido que el numerador y el denominador son enteros. Podr´ıamos considerar la posibilidad de pertimirles que sean enteros largos. Como ejercicio, termine la implementaci´on de la clase Fraccion de forma que pueda manejar resta, divisio´n, exponenciacio´n y enteros largos como numerador y denominador.
  • 271. B.6 Glosario 241 B.6. Glosario m´aximo comu´n divisor (MCD): El mayor entero positivo que divide al nu- merador y al denominador de una fracci´on sin que quede un resto. reducir: Cambiar la fracci´on a su forma equivalente con un MCD igual a 1. negaci´on unitaria: Operacio´n que computa el elemento sim´etrico aditivo, nor- malmente denotada con un signo menos delante. Se denomina “unitaria” en contraste con la operacio´n binaria menos, que es la resta.
  • 273. Ap´endice C Listados Completos de Python C.1. Clase Punto class Punto: def init (self, x=0, y=0): self.x = x self.y = y def str (self): return ’(’ + str(self.x) + ’, ’ + str(self.y) + ’)’ def add (self, otro): return Punto(self.x + otro.x, self.y + otro.y) def sub (self, otro): return Punto(self.x - otro.x, self.y - otro.y) def mul (self, otro): return self.x * otro.x + self.y * otro.y def rmul (self, otro): return Punto(otro * self.x, otro * self.y) def reverse(self):
  • 274. 2 Listados Completos de self.x, self.y = self.y, self.x def delDerechoYDelReves(derecho): from copy import copy reves = copy(derecho) reves.reverse() print str(derecho) + str(reves) C.2. Clase Hora class Hora: def init (self, horas=0, minutos=0, segundos=0): self.horas = horas self.minutos = minutos self.segundos = segundos def str (self): return str(self.horas) + ":" + str(self.minutos) + ":" + str(self.segundos) def convierteASegundos(self): minutos = self.horas * 60 + self.minutos segundos = self.minutos * 60 + self.segundos return segundos def incrementa(self, segs): segs = segs + self.segundos self.horas = self.horas + segs/3600 segs = segs 3600 self.minutos = self.minutos + segs/60 segs = segs 60 self.segundos = segs def haceHora(segs): hora = Hora() hora.horas = segs/3600 segs = segs - hora.horas * 3600 hora.minutos = segs/60 segs = segs - hora.minutos * 60
  • 275. C.3 Cartas, mazos y 2 hora.segundos = segs return hora C.3. Cartas, mazos y juegos import random class Carta: listaDePalos = ["Tr´eboles", "Diamantes", "Corazones", "Picas"] listaDeValores = ["nada", "As", "2", "3", "4", "5", "6", "7", "8", "9", "10", "Sota", "Reina", "Rey"] def init (self, palo=0, valor=0): self.palo = palo self.valor = valor def str (self): return (self.listaDeValores[self.valor] + " de " + self.listaDePalos[self.palo]) def cmp (self, otro): # controlar el palo if self.palo > otro.palo: return 1 if self.palo < otro.palo: return -1 # si son del mismo palo, controlar el valor if self.valor > otro.valor: return 1 if self.valor < otro.valor: return -1 # los valores son iguales, es un empate return 0 class Mazo: def init (self): self.cartas = [] for palo in range(4): for valor in range(1, 14): self.cartas.append(Carta(palo, valor)) def muestraMazo(self): for carta in self.cartas: print carta
  • 276. 2 Listados Completos de def str (self): s = "" for i in range(len(self.cartas)): s = s + " "*i + str(self.cartas[i]) + "n" return s def mezclar(self): import random nCartas = len(self.cartas) for i in range(nCartas): j = random.randrange(i, nCartas) self.cartas[i], self.cartas[j] = self.cartas[j], self.cartas[i] def eliminaCarta(self, carta): if carta in self.cartas: self.cartas.remove(carta) return 1 else: return 0 def darCarta(self): return self.cartas.pop() def estaVacio(self): return (len(self.cartas) == 0) def repartir(self, manos, nCartas=999): nManos = len(manos) for i in range(nCartas): if self.estaVacio(): break # fin si se acaban las cartas carta = self.darCarta() # da la carta superior mano = manos[i nManos] # a qui´en le toca? mano.agregaCarta(carta) # agrega la carta a la mano class Mano(Mazo): def init (self, nombre=""): self.cartas = [] self.nombre = nombre def agregaCarta(self,carta) : self.cartas.append(carta)
  • 277. C.3 Cartas, mazos y 2 def str (self): s = "La mano de " + self.nombre if self.estaVacio(): s = s + " est´a vac´ıan" else: s = s + " contienen" return s + Mazo. str (self) class JuegoDeCartas: def init (self): self.mazo = Mazo() self.mazo.mezclar() class ManoDeLaMona(Mano): def eliminaCoincidencias(self): cant = 0 cartasOriginales = self.cartas[:] for carta in cartasOriginales: empareja = Carta(3 - carta.palo, carta.valor) if empareja in self.cartas: self.cartas.remove(carta) self.cartas.remove(empareja) print "Mano s: s con s" (self.nombre,carta,empareja) cant = cant + 1 return cant class JuegoDeLaMona(JuegoDeCartas): def jugar(self, nombres): # quitamos la Reina de Tr´eboles self.mazo.eliminaCarta(Carta(0,12)) # construimos una mano para cada jugador self.manos = [] for nombre in nombres : self.manos.append(ManoDeLaMona(nombre)) # repartimos los naipes self.mazo.repartir(self.manos) print "----- Se han repartido las cartas." self.muestraManos()
  • 278. 2 Listados Completos de # eliminamos las coincidencias iniciales emparejadas = self.eliminaTodasLasCoincidencias() print "----- Coincidencias eliminadas, el juego comienza." self.muestraManos() # se juega hasta que se han descartado las 50 cartas turno = 0 cantManos = len(self.manos) while emparejadas < 25: emparejadas = emparejadas + self.jugarUnTurno(turno) turno = (turno + 1) cantManos print "----- El juego termin´o." self.muestraManos() def eliminaTodasLasCoincidencias(self): cant = 0 for mano in self.manos: cant = cant + mano.eliminaCoincidencias() return cant def jugarUnTurno(self, i): if self.manos[i].estaVacio(): return 0 vecino = self.encuentraVecino(i) cartaElegida = self.manos[vecino].darCarta() self.manos[i].agregaCarta(cartaElegida) print "Mano", self.manos[i].nombre, "eligi´o", cartaElegida cant = self.manos[i].eliminaCoincidencias() self.manos[i].mezclar() return cant def encuentraVecino(self, i): cantManos = len(self.manos) for proximo in range(1,cantManos): vecino = (i + proximo) cantManos if not self.manos[vecino].estaVacio(): return vecino def muestraManos(self) : for mano in self.manos : print mano
  • 279. C.4 Lists 2 C.4. Lists Enlazadas def imprimeLista(nodo): while nodo: print nodo, nodo = nodo.siguiente print def imprimeAlReves(lista): if lista == None: return cabeza = lista cola = lista.siguiente imprimeAlReves(cola) print cabeza, def imprimeAlRevesBonito(lista) : print "[", if lista != None : cabeza = lista cola = lista.siguiente imprimeAlReves(cola) print cabeza, print "]", def eliminaSegundo(lista): if lista == None: return primero = lista segundo = lista.siguiente primero.siguiente = segundo.siguiente segundo.siguiente = None return segundo class Nodo: def init (self, carga=None, siguiente=None): self.carga = carga self.siguiente = siguiente def str (self): return str(self.carga)
  • 280. 2 Listados Completos de def imprimeAlReves(self): if self.siguiente != None: cola = self.siguiente cola.imprimeAlReves() print self.carga, class ListaEnlazada : def init (self) : self.longitud = 0 self.cabeza = None def imprimeAlReves(self): print "[", if self.cabeza != None: self.cabeza.imprimeAlReves() print "]", def agregaPrimero(self, carga): nodo = Nodo(carga) nodo.siguiente = self.cabeza self.cabeza = nodo self.longitud = self.longitud + 1 C.5. Clase Pila class Pila : # implem. con listas de Python def init (self) : self.elementos = [] def push(self, elemento) : self.elementos.append(elemento) def pop(self) : return self.elementos.pop() def isEmpty(self) : return (self.elementos == []) def evalPostfijo(expr):
  • 281. C.6 Colas y colas priorizadas 251 import re listaTokens = re.split("([^0-9])", expr) pila = Pila() for token in listaTokens: if token == ’’ or token == ’ ’: continue if token == ’+’: suma = pila.pop() + pila.pop() pila.push(suma) elif token == ’*’: producto = pila.pop() * pila.pop() pila.push(producto) else: pila.push(int(token)) return pila.pop() C.6. Colas y colas priorizadas class Cola : def init (self) : self.longitud = 0 self.cabeza = None def vacia(self) : return (self.longitud == 0) def inserta(self, carga) : nodo = Nodo(carga) nodo.siguiente = None if self.cabeza == None : # Si la lista est´a vac´ıa nuestro nuevo nodo es el primero self.cabeza = nodo else : # Encuentra el u´ltimo nodo de la lista ultimo = self.cabeza while ultimo.siguiente : ultimo = ultimo.siguiente # A~nada el nuevo nodo ultimo.siguiente = nodo self.longitud = self.longitud + 1
  • 282. 2 Listados Completos de def quita(self) : carga = self.cabeza.carga self.cabeza = self.cabeza.next self.longitud = self.longitud - 1 return carga class ColaMejorada : def init (self) : self.longitud = 0 self.cabeza = None self.ultimo = None def vacia(self) : return (self.longitud == 0) def inserta(self, carga) : nodo = Nodo(carga) nodo.siguiente = None if self.longitud == 0 : # Si la lista est´a vac´ıa nuestro nuevo nodo es el primero self.cabeza = self.ultimo = nodo else : # Encuentra el ultimo nodo de la lista ultimo = self.ultimo # A~nade nuestro nodo nuevo ultimo.siguiente = nodo self.ultimo = nodo self.longitud = self.longitud + 1 def quita(self) : carga = self.cabeza.carga self.cabeza = self.cabeza.siguiente self.longitud = self.longitud - 1 if self.longitud == 0 : self.ultimo = None return carga class ColaPriorizada : def init (self) : self.elementos = [] def vacia(self) :
  • 283. C.7 A´rboles 253 return self.elementos == [] def inserta(self, elemento) : self.elementos.append(elemento) def quita(self) : maxi = 0 for i in range(1,len(self.elementos)) : if self.elementos[i] > self.elementos[maxi] : maxi = i elemento = self.elementos[maxi] self.elementos[maxi:maxi+1] = [] return elemento class Golfista : def init (self, nombre, puntos) : self.nombre = nombre self.puntos = puntos def str (self) : return " -15s: d" (self.nombre, self.puntos) def cmp (self, otro) : if self.puntos < otro.puntos : return 1 # menos es m ´as if self.puntos > otro.puntos : return -1 return 0 C.7. A´rboles class Arbol : def init (self, carga, izquierda=None, derecha=None) : self.carga = carga self.izquierda = izquierda self.derecha = derecha def str (self) : return str(self.carga) def tomaCarga(self): return self.carga def tomaIzquierda(self): return self.izquierda
  • 284. 2 Listados Completos de def tomaDerecha(self): return self.derecha def ajustaCarga(self, carga): self.carga = carga def ajustaIzquierda (self, izquierda): self.left = izquierda def ajustaDerecha(self, derecha): self.derecha = derecha def total(arbol) : if arbol == None : return 0 return total(arbol.izquierda) + total(arbol.derecha) + arbol.carga def imprimeArbol(arbol): if arbol == None: return print arbol.carga, imprimeArbol(arbol.izquierda) imprimeArbol(arbol.derecha) def imprimeArbolPosfijo(arbol): if arbol == None: return imprimeArbolPosfijo(arbol.izquierda) imprimeArbolPosfijo(arbol.derecha) print arbol.carga, def imprimeArbolInfijo(arbol): if arbol == None: return imprimeArbolInfijo(arbol.izquierda) print arbol.carga, imprimeArbolInfijo(arbol.derecha) def imprimeArbolSangrado(arbol, nivel=0): if arbol == None: return imprimeArbolSangrado(arbol.derecha, nivel+1) print ’ ’*nivel + str(arbol.carga) imprimeArbolSangrado(arbol.izquierda, nivel+1) C.8. A´rboles de expresio´n def tomaToken(listaToken, esperado): if listaToken[0] == esperado: listaToken[0:1] = [] # quita el token return 1 else:
  • 285. C.9 Adivina el animal 255 return 0 def obtieneProducto(listaToken) : a = obtieneNumero(listaToken) if tomaToken(listaToken, ’*’) : b = obtieneProducto(listaToken) return Arbol(’*’, a, b) else : return a def obtieneSuma(listaToken) : a = obtieneProducto(listaToken) if tomaToken(listaToken, ’+’) : b = obtieneSuma(listaToken) return Arbol(’+’, a, b) else : return a def obtieneNumero(listaToken): if tomaToken(listaToken, ’(’) : x = obtieneSuma(listaToken) # obtiene subexpresi´on tomaToken(listaToken, ’)’) # se come el cierre de par ´entesis return x else : x = listaToken[0] if type(x) != type(0) : return None listaToken[0:1] = [] # quita el token return Arbol(x, None, None) # devuelve una hoja sin el n´umero C.9. Adivina el animal def animal(): # empezar con un nodo suelto raiz = Arbol("p´ajaro") # bucle hasta que el usuario salga while 1: print if not si("Est´as pensando en un animal? "): break # recorrer el a´rbol
  • 286. 2 Listados Completos de arbol = raiz while arbol.tomaIzquierda() != None: indicador = arbol.tomaCarga() + "? " if si(indicador): arbol = arbol.tomaDerecha() else: arbol = arbol.tomaIzquierda() # intentar adivinar adivina = arbol.tomaCarga() indicador = "Es un " + adivina + "? " if si(indicador): print "^A<Soy el m´as grande!" continue # obtener informaci´on nueva indicador = "C´omo se llama el animal? " animal= raw_input(indicador) indicador = "Qu´e pregunta distinguir´ıa a un s de un s? " pregunta = raw_input(indicador (animal,adivina)) # a~nadir informaci´on nueva al arbol.ponCarga(pregunta) a´rbol indicador = "Si el animal fuera un s, cu´al ser´ıa la respuesta? " if si(indicador animal): arbol.ponIzquierda(Arbol(adivina)) arbol.ponDerecha(Arbol(animal)) else: arbol.ponIzquierda(Arbol(animal)) arbol.ponDerecha(Arbol(adivina)) def si(preg): from string import lower resp = lower(raw_input(preg)) return (resp[0:1] == ’s’) C.10. Fraction class class Fraccion: def init (self, numerador, denominador=1): m = mcd (numerador, denominador)
  • 287. C.10 Fraction class 257 self.numerador = numerador / m self.denominador = denominador / m def mul (self, otro): if type(otro) == type(5): otro = Fraccion(otro) return Fraccion(self.numerador * otro.numerador, self.denominador * otro.denominador) rmul = mul def add (self, otro): if type(otro) == type(5): otro = Fraccion(otro) return Fraccion(self.numerador * otro.denominador + self.denominador * otro.numerador, self.denominador * otro.denominador) radd = add def cmp (self, otro): if type(otro) == type(5): otro = Fraccion(otro) dif = (self.numerador * otro.denominador - otro.numerador * self.denominador) return dif def repr (self): return self. str () def str (self): return " d/ d" (self.numerador, self.denominador) def mcd(m,n): "devuelve el m´aximo com´un denominador de dos enteros" if m n == 0: return n else: return mcd(n,m n)
  • 289. Ap´endice D Lecturas recomendadas Y ahora, ¿hacia d´onde ir desde aqu´ı? Hay muchas direcciones en las que seguir, ampliando sus conocimientos de Python spec ´ıficamente y de inform´atica en general. Los ejemplos en este libro han sido deliberadamente simples, por lo que pueden no haber mostrado las capacidades m´as excitantes de Python. A continuaci´on exponemos una muestra de las extensiones de Python y sugerencias sobre sus usos. La programaci´on de GUIs (interfaces gr´aficas de usuario, graphic user interface en ingl´es) permite que su programa utilice un entorno de ventanas para interactuar con el usuario y mostrar gr´aficos. El primer paquete que ha tenido Python para esto es Tkinter, basado en los lenguajes interpretados Tcl y Tk de Jon Ousterhout. Tkinter esta´ incluido en la distribuci´on de Python. Otra plataforma popular es wxPython, que es esencialmente un encha- pado sobre wxWindows, un paquete de C++ que implementa ventanas utilizando la interfaces nativas las plataformas Windows y Unix (incluido Linux). Las ventanas y los controles con wxPython tienen una apariencia m´as nativa que Tkinter y son un poco mas sencillos de programar. Cualquier tipo de programaci´on de GUIs le llevar´a a programaci´on basada en eventos, donde es el usuario y no el programador quien determina el flujo de la ejecucio´n. Este estilo de programaci´on requiere de algo de tiempo para acostumbrarse, y a veces le forzar´a a replantearse toda la estructura del programa.
  • 290. 2 Lecturas La programaci´on web integra Python en la Internet. Por ejemplo, puede construir programas de cliente web que abran y lean una pagina remota (casi) tan f´acilmente como si fuera un fichero en disco. Tambi´en hay modu- los de Python que le permiten acceder a ficheros remotamente v´ıa ftp, y m´odulos que le permiten enviar y recibir correos electro´nicos. Python tam- bi´en es ampliamente utilizado en el lado del servidor de la programaci´on web para manejar los datos de entrada de los formularios. Las bases de datos son un poco como super ficheros en donde los datos esta´n almacenados en esquemas predefinidos, y las relaciones entre los datos le permiten acceder a ellos de varias maneras. Python tiene varios m´odulos para permitir a los usuarios conectarse a varios motores de bases de datos, tanto Open Source como comerciales. La programaci´on multi-procesos (multi-hilos) le permite ejecutar varios procesos (hilos) de ejecucio´n dentro de un u ´nico programa. Si ha tenido la experiencia de usar un navegador web para desplazarse por una pagina web mientras el navegador continu´a cargando el resto de la misma, entonces tiene una idea de lo que los hilos pueden hacer. Cuando la velocidad es m´as importante se pueden escribir extensiones para Python en un lenguaje compilado como C o C++. Tales extensio- nes forman la base de la mayor´ıa de modulos en la librer´ıa de Python. El mecanismo de enlazar funciones y datos es un poco complejo. SWIG (Simplified Wrapper and Interface Generator) es una herramienta para hacer este proceso mucho m´as sencillo. D.1. Libros y sitios web sobre Python Aqu´ı tiene las recomendaciones de los autores sobre recursos para Python en la web: La p´agina de inicio de Python en www.python.org es el lugar para empezar su bu´squeda de material sobre Python. Encontrar´a ayuda, documentaci´on, enlaces a otros libros y listas de correo de SIGs (Special Interest Group) a las que se puede unir. El proyecto Open Book Project www.ibiblio.com/obp contiene no s´olo este libro en l´ınea sino tambi´en otros libros similares para Java y C++ de Allen Downey. Adem´as esta´ Lessons in Electric Circuits de Tony R. Kuphaldt, Getting down with ..., un conjunto de tutoriales de varios temas sobre inform ´atica, escritos y editados por estudiantes de institulo, Python for Fun, un conjuto de estudios de casos en Python de Chris Meyers, y The Linux Cookbook de Michael Stultz, con 300 paginas de trucos y t´ecnicas.
  • 291. D.2 Libros recomendados sobre inform´atica en general 261 Finalmente si acude a Google y busca con la cadena “python - snake - monty” obtendr´a cerca de 750.000 resultados. Y aqu´ı algunos libros que contienen m´as material sobre el lenguaje Python: Core Python Programming de Wesley Chun es un libro largo, mas de 750 p´aginas. La primera parte del libro cubre las caracter´ısticas basicas del lenguaje Python. La segunda parte proporciona una introducci´on paso a paso a temas m ´as avanzados incluyendo muchos de los mencionados anteriormente. Python Essential Reference de David M. Beazley es un libro pequen˜o, pero contiene informaci´on sobre el lenguaje en s´ı mismo y los modulos de la librer´ıa esta´ndar. Tambi´en esta´ muy bien indexado. Python Pocket Reference de Mark Lutz realmente cabe en el bolsillo. Aun- que no es tan extensivo como Python Essential Reference es una referencia u´til para los m´odulos y funciones mas comunmente usadas. Mark Lutz tambi´en es autor de Programming Python, uno de los primeros (y mas largos) libros de Python y no esta´ dirigido al programador principiante. Su siguiente libro Learning Python es mas pequen˜o y mas accesible. Python Programming on Win32 de Mark Hammond y Andy Robinson es un libro que “debe tener” cualquiera que que utilice seriamente Pyt- hon para desarrollar aplicaciones para Windows. Entre otras cosas cubre la integraci´on de Python y COM, construye una pequen˜a aplicaci´on con wxPython, e incluso utiliza Python para escribir scripts para aplicaciones tales como Word y Excel. D.2. Libros recomendados sobre inform´atica en general Las siguientes sugerencias sobre lecturas adicionales incluyen muchos de los libros favoritos de los autores. Estos tratan sobre buenas pr´acticas de progra- maci´on e inform´atica en general. The Practice of Programming de Kernighan y Pike cubre no s´olo el disen˜o y dodificaci´on de algoritmos y estructuras de datos, sino tambi´en depura- cio´n, testeo y mejora de rendimiento de los programas. Los ejemplos esta´n principalmente en C++ y Java, sin nada de Python.
  • 292. 2 Lecturas The Elements of Java Style editado por Al Vermeulen es otro libro pequen˜o que discute algunos de los puntos m´as sutiles de la buena programaci´on, tales como el buen uso de las convenciones de nombres, comentarios e indentaci´on (un poco irrelevante en Python). El libro tambi´en cubre la programaci´on por contrato, usando aserciones para encontrar los errores probando precondiciones y postcondiciones, y programaci´on correcta con hilos y su sincronizacio´n. Programming Pearls de Jon Bentley es un libro cla´sico. Consiste en es- tudios de caso que aparecieron originalmente en la columna del autor en Communications of the ACM. Los estudios tratan sobre toma y daca en programaci´on y por qu ´e suele ser mala idea desarrollar con la primera idea de un programa. El libro es un poco mas antiguo que los anteriores (1986), por lo que los ejemplos esta´n en lenguajes mas antiguos. Hay muchos pro- blemas para resolver, algunos con soluciones y otros con pistas. Este libro fue muy popular y le siguio´ un segundo volumen. The New Turing Omnibus de A.K Dewdney proporciona una introduc- cio´n amigable a 66 temas de inform´atica desde computacio´n en parelelo hasta virus inform´aticos, desde TACs (tomograf´ıas computerizadas) hasta algoritmos gen ´eticos. Todos los temas son cortos y entretenidos. Un libro anterior de Dewdney Aventuras Inform´aticas es una coleccio´n de su co- lumna Juegos de ordenador en Invertigaci´on y Ciencia. Ambos libros son ricas fuentes de ideas para proyectos. Tortugas, Termitas y Atascos de Trafico de Mitchel Resnick trata sobre el poder de la descentralizaci´on y de como pueden obtenerse comporta- mientos complejos a partir de las actividades simples de una multitud de agentes coordinados. Introduce el lenguaje StarLogo, que permite al usua- rio escribir programas para agentes. La ejecucio´n del programa demuestra comportamientos complejos agregados, que suelen ser intuitivos. La ma- yor´ıa de los programas en el libro fueron desarrollados por estudiantes de colegio e instituto. Programas similares pueden escribirse en Python usando gr ´aficos e hilos. G¨odel, Escher, Bach de Douglas Hofstadter. Simplemente, si encuentra magia en la recursio´n tambi´en la encontrar´a en este libro superventas. Uno de los temas de Hofstadter concierne a los “lazos extran˜os” donde los patrones se desenvuelven y ascienden hasta que se encuentran a s´ı mismos de nuevo. Es una disputa de Hofstadter que tales “lazos extran˜os” son una parte esencial de lo que separa lo animado de lo no animado. E ´l demuestra tales patrones en la mu´sica de Bach, las ilustraciones de Escher y el teorema de incompletitud de G¨odel.
  • 293. Ap´endice E GNU Free Documentation License Version 1.1, March 2000 Copyright Ⓧc 2000 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The purpose of this License is to make a manual, textbook, or other written do- cument “free” in the sense of freedom: to assure everyone the effective freedom to copy and redistribute it, with or without modifying it, either commercially or noncommercially. Secondarily, this License preserves for the author and pu- blisher a way to get credit for their work, while not being considered responsible for modifications made by others. This License is a kind of “copyleft,” which means that derivative works of the document must themselves be free in the same sense. It complements the GNU General Public License, which is a copyleft license designed for free software. We have designed this License in order to use it for manuals for free software, because free software needs free documentation: a free program should come with manuals providing the same freedoms that the software does. But this
  • 294. 2 GNU Free Documentation License is not limited to software manuals; it can be used for any textual work, regardless of subject matter or whether it is published as a printed book. We recommend this License principally for works whose purpose is instruction or reference. E.1. Applicability and Definitions This License applies to any manual or other work that contains a notice placed by the copyright holder saying it can be distributed under the terms of this Li- cense. The “Document,” below, refers to any such manual or work. Any member of the public is a licensee, and is addressed as “you.” A “Modified Version” of the Document means any work containing the Docu- ment or a portion of it, either copied verbatim, or with modifications and/or translated into another language. A “Secondary Section” is a named appendix or a front-matter section of the Document that deals exclusively with the relationship of the publishers or aut- hors of the Document to the Document’s overall subject (or to related matters) and contains nothing that could fall directly within that overall subject. (For example, if the Document is in part a textbook of mathematics, a Secondary Section may not explain any mathematics.) The relationship could be a matter of historical connection with the subject or with related matters, or of legal, commercial, philosophical, ethical, or political position regarding them. The “Invariant Sections” are certain Secondary Sections whose titles are de- signated, as being those of Invariant Sections, in the notice that says that the Document is released under this License. The “Cover Texts” are certain short passages of text that are listed, as Front- Cover Texts or Back-Cover Texts, in the notice that says that the Document is released under this License. A “Transparent” copy of the Document means a machine-readable copy, repre- sented in a format whose specification is available to the general public, whose contents can be viewed and edited directly and straightforwardly with generic text editors or (for images composed of pixels) generic paint programs or (for drawings) some widely available drawing editor, and that is suitable for input to text formatters or for automatic translation to a variety of formats suitable for input to text formatters. A copy made in an otherwise Transparent file format whose markup has been designed to thwart or discourage subsequent modifica- tion by readers is not Transparent. A copy that is not “Transparent” is called “Opaque.”
  • 295. E.2 Verbatim Copying 265 Examples of suitable formats for Transparent copies include plain ASCII wit- hout markup, Texinfo input format, LATEX input format, SGML or XML using a publicly available DTD, and standard- conforming simple HTML designed for human modification. Opaque formats include PostScript, PDF, proprietary for- mats that can be read and edited only by proprietary word processors, SGML or XML for which the DTD and/or processing tools are not generally availa- ble, and the machine-generated HTML produced by some word processors for output purposes only. The “Title Page” means, for a printed book, the title page itself, plus such following pages as are needed to hold, legibly, the material this License requires to appear in the title page. For works in formats which do not have any title page as such, “Title Page” means the text near the most prominent appearance of the work’s title, preceding the beginning of the body of the text. E.2. Verbatim Copying You may copy and distribute the Document in any medium, either commercially or noncommercially, provided that this License, the copyright notices, and the license notice saying this License applies to the Document are reproduced in all copies, and that you add no other conditions whatsoever to those of this License. You may not use technical measures to obstruct or control the reading or further copying of the copies you make or distribute. However, you may accept compensation in exchange for copies. If you distribute a large enough number of copies you must also follow the conditions in Section 3. You may also lend copies, under the same conditions stated above, and you may publicly display copies. E.3. Copying in Quantity If you publish printed copies of the Document numbering more than 100, and the Document’s license notice requires Cover Texts, you must enclose the copies in covers that carry, clearly and legibly, all these Cover Texts: Front-Cover Texts on the front cover, and Back-Cover Texts on the back cover. Both covers must also clearly and legibly identify you as the publisher of these copies. The front cover must present the full title with all words of the title equally prominent and visible. You may add other material on the covers in addition. Copying with changes limited to the covers, as long as they preserve the title of the Document and satisfy these conditions, can be treated as verbatim copying in other respects.
  • 296. 2 GNU Free Documentation If the required texts for either cover are too voluminous to fit legibly, you should put the first ones listed (as many as fit reasonably) on the actual cover, and continue the rest onto adjacent pages. If you publish or distribute Opaque copies of the Document numbering more than 100, you must either include a machine- readable Transparent copy along with each Opaque copy, or state in or with each Opaque copy a publicly acces- sible computer- network location containing a complete Transparent copy of the Document, free of added material, which the general network- using public has access to download anonymously at no charge using public-standard network protocols. If you use the latter option, you must take reasonably prudent steps, when you begin distribution of Opaque copies in quantity, to ensure that this Transparent copy will remain thus accessible at the stated location until at least one year after the last time you distribute an Opaque copy (directly or through your agents or retailers) of that edition to the public. It is requested, but not required, that you contact the authors of the Document well before redistributing any large number of copies, to give them a chance to provide you with an updated version of the Document. E.4. Modifications You may copy and distribute a Modified Version of the Document under the conditions of Sections 2 and 3 above, provided that you release the Modified Version under precisely this License, with the Modified Version filling the role of the Document, thus licensing distribution and modification of the Modified Version to whoever possesses a copy of it. In addition, you must do these things in the Modified Version: Use in the Title Page (and on the covers, if any) a title distinct from that of the Document, and from those of previous versions (which should, if there were any, be listed in the History section of the Document). You may use the same title as a previous version if the original publisher of that version gives permission. List on the Title Page, as authors, one or more persons or entities respon- sible for authorship of the modifications in the Modified Version, together with at least five of the principal authors of the Document (all of its prin- cipal authors, if it has less than five). State on the Title page the name of the publisher of the Modified Version, as the publisher. Preserve all the copyright notices of the Document.
  • 297. E.4 Modifications 267 Add an appropriate copyright notice for your modifications adjacent to the other copyright notices. Include, immediately after the copyright notices, a license notice giving the public permission to use the Modified Version under the terms of this License, in the form shown in the Addendum below. Preserve in that license notice the full lists of Invariant Sections and re- quired Cover Texts given in the Document’s license notice. Include an unaltered copy of this License. Preserve the section entitled “History,” and its title, and add to it an item stating at least the title, year, new authors, and publisher of the Modified Version as given on the Title Page. If there is no section entitled “History” in the Document, create one stating the title, year, authors, and publisher of the Document as given on its Title Page, then add an item describing the Modified Version as stated in the previous sentence. Preserve the network location, if any, given in the Document for public access to a Transparent copy of the Document, and likewise the network locations given in the Document for previous versions it was based on. These may be placed in the “History” section. You may omit a network location for a work that was published at least four years before the Do- cument itself, or if the original publisher of the version it refers to gives permission. In any section entitled “Acknowledgements” or “Dedications,” preserve the section’s title, and preserve in the section all the substance and tone of each of the contributor acknowledgements and/or dedications given therein. Preserve all the Invariant Sections of the Document, unaltered in their text and in their titles. Section numbers or the equivalent are not considered part of the section titles. Delete any section entitled “Endorsements.” Such a section may not be included in the Modified Version. Do not retitle any existing section as “Endorsements” or to conflict in title with any Invariant Section. If the Modified Version includes new front-matter sections or appendices that qualify as Secondary Sections and contain no material copied from the Docu- ment, you may at your option designate some or all of these sections as invariant.
  • 298. 2 GNU Free Documentation To do this, add their titles to the list of Invariant Sections in the Modified Ver- sion’s license notice. These titles must be distinct from any other section titles. You may add a section entitled “Endorsements,” provided it contains nothing but endorsements of your Modified Version by various parties—for example, statements of peer review or that the text has been approved by an organization as the authoritative definition of a standard. You may add a passage of up to five words as a Front-Cover Text, and a passage of up to 25 words as a Back-Cover Text, to the end of the list of Cover Texts in the Modified Version. Only one passage of Front-Cover Text and one of Back- Cover Text may be added by (or through arrangements made by) any one entity. If the Document already includes a cover text for the same cover, previously added by you or by arrangement made by the same entity you are acting on behalf of, you may not add another; but you may replace the old one, on explicit permission from the previous publisher that added the old one. The author(s) and publisher(s) of the Document do not by this License give permission to use their names for publicity for or to assert or imply endorsement of any Modified Version. E.5. Combining Documents You may combine the Document with other documents released under this Li- cense, under the terms defined in Section 4 above for modified versions, provided that you include in the combination all of the Invariant Sections of all of the original documents, unmodified, and list them all as Invariant Sections of your combined work in its license notice. The combined work need only contain one copy of this License, and multiple identical Invariant Sections may be replaced with a single copy. If there are multiple Invariant Sections with the same name but different contents, make the title of each such section unique by adding at the end of it, in parentheses, the name of the original author or publisher of that section if known, or else a unique number. Make the same adjustment to the section titles in the list of Invariant Sections in the license notice of the combined work. In the combination, you must combine any sections entitled “History” in the various original documents, forming one section entitled “History”; likewise com- bine any sections entitled “Acknowledgements,” and any sections entitled “De- dications.” You must delete all sections entitled “Endorsements.”
  • 299. E.6 Collections of Documents 269 E.6. Collections of Documents You may make a collection consisting of the Document and other documents released under this License, and replace the individual copies of this License in the various documents with a single copy that is included in the collection, provided that you follow the rules of this License for verbatim copying of each of the documents in all other respects. You may extract a single document from such a collection, and distribute it individually under this License, provided you insert a copy of this License into the extracted document, and follow this License in all other respects regarding verbatim copying of that document. E.7. Aggregation with Independent Works A compilation of the Document or its derivatives with other separate and in- dependent documents or works, in or on a volume of a storage or distribution medium, does not as a whole count as a Modified Version of the Document, provided no compilation copyright is claimed for the compilation. Such a com- pilation is called an “aggregate,” and this License does not apply to the other self-contained works thus compiled with the Document, on account of their being thus compiled, if they are not themselves derivative works of the Document. If the Cover Text requirement of Section 3 is applicable to these copies of the Document, then if the Document is less than one quarter of the entire aggregate, the Document’s Cover Texts may be placed on covers that surround only the Document within the aggregate. Otherwise they must appear on covers around the whole aggregate. E.8. Translation Translation is considered a kind of modification, so you may distribute transla- tions of the Document under the terms of Section 4. Replacing Invariant Sections with translations requires special permission from their copyright holders, but you may include translations of some or all Invariant Sections in addition to the original versions of these Invariant Sections. You may include a translation of this License provided that you also include the original English version of this License. In case of a disagreement between the translation and the original English version of this License, the original English version will prevail.
  • 300. 2 GNU Free Documentation E.9. Termination You may not copy, modify, sublicense, or distribute the Document except as expressly provided for under this License. Any other attempt to copy, modify, sublicense, or distribute the Document is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. E.10. Future Revisions of This License The Free Software Foundation may publish new, revised versions of the GNU Free Documentation License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. See http:///www.gnu.org/copyleft/. Each version of the License is given a distinguishing version number. If the Do- cument specifies that a particular numbered version of this License .o r any later version.a pplies to it, you have the option of following the terms and conditions either of that specified version or of any later version that has been published (not as a draft) by the Free Software Foundation. If the Document does not spe- cify a version number of this License, you may choose any version ever published (not as a draft) by the Free Software Foundation. E.11. Addendum: How to Use This License for Your Documents To use this License in a document you have written, include a copy of the License in the document and put the following copyright and license notices just after the title page: Copyright Ⓧc YEAR YOUR NAME. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version pu- blished by the Free Software Foundation; with the Invariant Sec- tions being LIST THEIR TITLES, with the Front-Cover Texts being LIST, and with the Back-Cover Texts being LIST. A copy of the li- cense is included in the section entitled “GNU Free Documentation License.”
  • 301. E.11 Addendum: How to Use This License for Your Documents 271 If you have no Invariant Sections, write “with no Invariant Sections” instead of saying which ones are invariant. If you have no Front- Cover Texts, write “no Front-Cover Texts” instead of “Front-Cover Texts being LIST”; likewise for Back-Cover Texts. If your document contains nontrivial examples of program code, we recommend releasing these examples in parallel under your choice of free software license, such as the GNU General Public License, to permit their use in free software.
  • 303. ´Indice alfab´etico a´rbol, 211 expresio´n, 213, 216 recorrido, 213, 214 vac´ıo, 212 a´rbol binario, 211, 224 a´rbol de expresio´n, 213, 216 ´ındice, 76, 84, 97, 109, 229 negativo, 76 Make Way for Ducklings, 77 hon, 83 Referencia de la Biblioteca de Pyt- , 33 abeceda´rico, 77 acceso, 86 acto de fe, 55, 186 acumulador, 167, 170, 178 aleatorio, 167 algoritmo, 10, 146, 148 alias, 92, 97 ambigu¨edad, 7, 134 teorema fundamental, 188 an´alisis sint´actico, 10 analizar, 199, 216 analizar sint´acticamente, 7, 201 andamiaje, 48, 59 anidamiento, 45 archivo, 128 texto, 121 archivo de texto, 121, 128 archivos, 119 argumento, 21, 33 argumentos, 28 asignaci´on, 12, 20, 61 tupla, 168 tuplas, 100 asignaci´on de alias, 112 asignaci´on de tuplas, 100, 107, 168 asignaci´on mu´ltiple, 61, 73 asignmaci´on tupla, 107 atributo, 139 clase, 163, 170 atributo de clase, 163, 170 atributos, 132 AttributeError, 230 barajar, 167 base de conocimiento, 221 bifurcaci´on condicional, 37 bloque, 37, 45 booleana expresio´n, 36 booleanas funciones, 52 borrado lista, 91 borrado en una lista, 91 bucle, 63, 73 anidado, 165 condicio´n, 228 cuerpo, 63, 73 en una lista, 187 for, 76 infinito, 63, 73, 228 recorrido, 76
  • 304. ´Indice alfab 2 variable de, 73 while, 62 bucle for, 76, 88 bucle infinito, 227, 228 buffer circular, 210 c´odigo de objeto, 10 c ´odigo ejecutable, 10 c´odigo fuente, 10 c´odigo muerto, 48, 59 cadena, 11, 12 inmutable, 79 longitud, 76 porci´on, 78 cadena de formato, 123, 128 cadena inmutable, 79 caja, 114 caja de funci´on, 114 cara´cter, 75 carga, 183, 192, 211 Carta, 161 caso base, 43, 45 cifrar, 162 clase, 131, 139 Carta, 161 Golfista, 209 JuegoDeLaMona, 177 ListaEnlazada, 190 ManoDeLaMona, 176 Nodo, 183 padre, 172, 175 Pila, 196 Punto, 155 clase abstracta, 210 clase hija, 171, 182 clase padre, 171, 172, 175, 182 clase Punto, 155 clasificacio´n cara´cter, 82 clasificacio´n de caracteres, 82 clave, 109, 117 cliente, 196, 201 clonado, 93, 112 clonar, 97 codificar, 162, 170 coercio´n, 33 tipo, 115 coercio´n de tipos, 22 coercion, 23 coincidencia de esquemas, 107 cola, 203, 210 implementaci´on con Lista, 203 implementaci ´on enlazada, 204 implementaci´on mejorada, 205 cola enlazada, 204, 210 cola mejorada, 205 cola priorizada, 203, 210 TAD, 207 coleccio´n, 185, 196 columna, 96 coma flotante, 20, 131 comentario, 20 comentarios, 19 comparable, 164 comparacio´n cadenas, 78 fracci´on, 239 comparacio´n de cadenas, 78 compilador, 225 compilar, 2, 10 composici´on, 18, 20, 24, 51, 161, 165 compresio´n, 116 comprobacio´n de errores, 57 concatenacio´n, 77, 80 concatenar, 20 concatenation, 18 condicio´n, 63 condicio´n, 45, 228 condicio´n previa, 188, 192 condiciones encadenadas, 38 condiciones encadenadas, 38 constructor, 131, 139, 162 contador, 80, 84 conteo, 103
  • 305. ´Indice alfab 2 conversio´n de tipo, 33 conversio ´n de tipos, 22 copia profunda, 139 copia superficial, 139 copiado, 112, 137 corresponder, 162 cuelgue, 227 cuerpo, 37, 45 bucle, 63 cursor, 73 decrementar, 84 definici´on circular, 53 recursiva, 219 definici´on circular, 53 definici´on de funci´on, 25, 33 definici´on recursiva, 219 delimitador, 97, 125, 199, 201 denominador, 235 depuraci´on, 10, 225 depuraci´on (debugging), 4 desarrollo incremental, 148 planificado, 148 desarrollo de progamas encapsulado, 67 desarrollo de programas, 73 generalizaci´on, 67 desarrollo de prototipos, 145 desarrollo incremental, 49, 59, 148 desarrollo incremental de progra- mas, 226 desarrollo planificado, 148 desbordamiento, 115 determinista, 107 diagrama de estado, 13, 20 diagrama de pila, 33 diagramas de pila, 30, 42 diccionario, 96, 109, 117, 124, 230 m´etodos, 111 operaciones, 110 diccionarios, 109 m´etodos, 111 operaciones sobre, 110 directorio, 125, 128 disen˜o orientado a objetos, 171 divisi´on de enteros, 16, 20, 22 documentaci´on, 192 Doyle, Arthur Conan, 6 ejecucio´n flujo, 229 ejecucio´n condicional, 37 elemento, 85, 97 eliminacio´n de naipes, 168 encapsulacio´n, 136, 195, 200 encapsulado, 67 encapsular, 73 enchapado, 197 encolamiento priorizado, 203 encurtido, 125, 128 enlace, 192 enteros divisi´on, 22 largos, 115 enteros largos, 115 envoltorio, 192 error de sintaxis, 225 en tiempo de compilacio´n, 225 en tiempo de ejecucio ´n, 43, 225 sema´ntico, 225, 231 sintaxis, 5 tiempo de ejecucio ´n, 5 error (bug), 4 error de sintaxis, 225 error en tiempo de compilacio ´n, 225 error en tiempo de ejecucio´n, 5, 10, 43, 76, 79, 87, 100, 111, 113, 115, 120, 124, 225, 229 error sema´ntico, 5, 10, 101, 225, 231 error sintactico, 5, 10 error(bug), 10 escalera de color, 174
  • 306. ´Indice alfab 2 escape secuencia de, 73 espacio en blanco, 84 estilo funcional de programaci´on, 144, 148 estructura anidada, 161 estructura de datos gen´erica, 196, 197 recursiva, 183, 192, 212 estructura de datos recursiva, 212 estructura gen´erica de datos, 196, 197 estructura recursiva de datos, 183, 192 Euclides, 238 excepcio´n, 5, 10, 126, 128, 225, 229 expresio´n, 16, 20, 198 booleana, 45 grande y peliaguda, 232 expresio´n booleana, 36, 45 expresio´n regular, 199 Fibonacci function, 56 FIFO, 203, 210 fila, 96 float, 12 flujo de ejecucio´n, 27, 33, 229 formal lenguaje, 6 forzado de tipo de datos, 115 fracci´on, 235 comparacio´n, 239 multiplicaci´on, 236 suma, 237 frangoso, 53 funci´on, 71 funci´on join, 96 funci´on split, 96 funci´on, 25, 33, 141, 150 booleana, 169 definici´on, 25 factorial, 54 funci´on booleana, 169 funci´on de Fibonacci, 113 funci ´on factorial, 54, 57 funci´on gamma, 57 funci´on pura, 142, 148 funciones argumentos, 28 composici´on, 24, 51 llamadas, 21 par´ametros, 28 recursivas, 42 tuplas como valor de retorno, 101 funciones booleanas, 52 funciones matem´aticas, 23 generalizaci´on, 67 generalizaci´on, 136, 146 generalizar, 73 Golfista, 209 gr´afico de llamadas, 114 guardi´an, 59 guion, 10 guion bajo, 14 herencia, 171, 182 histograma, 106, 107, 116 Holmes, Sherlock, 6 identidad, 134 igualdad, 134 igualdad profunda, 134, 139 igualdad superficial, 134, 139 implementaci´on Cola, 203 imponer, 160 impresi´on mano de cartas, 174 objeto mazo, 166 imprimir objeto, 133, 150 incrementar, 84 IndexError, 230 indicador, 44, 45
  • 307. ´Indice alfab 2 infijo, 198, 201 infinito bucle, 63 infio, 213 inmutable, 99 instancia, 133, 136, 139 objeto, 132, 150, 164 instancia de objeto, 164 instancia de un objeto, 132, 150 instanciaci´on, 132 instanciar, 139 instrucci´on, 4 int, 12 Intel, 64 intercambio, 168 interfaz, 196, 210 interpretar, 2, 10 invariante, 191, 192 invocar, 117 invocar m´etodos, 111 irracional, 240 iteraci´on, 61, 62, 73 juego animales, 221 juego de los animales, 221 KeyError, 230 l´ogico operador, 36 lanzar una excepcio´n, 126, 128 lenguaje, 134 alto nivel, 2 bajo nivel, 2 completo, 53 programaci´on, 1 lenguaje completo, 53 lenguaje de alto nivel, 2, 10 lenguaje de bajo nivel, 2, 10 lenguaje de programaci´on, 1 lenguaje de programaci´on orientado a objetos, 149, 160 lenguaje formal, 6, 10 lenguaje natural, 6, 10, 134 lenguaje seguro, 5 Linux, 6 lista, 85, 97, 183 anidada, 95, 112 bien construida, 191 bucle, 187 bucle for, 88 como par´ametro, 185 de objetos, 165 elemento, 86 enlazada, 183, 192 impresi´on, 185 imprimir hacia atr´as, 186 infinita, 187 longitud, 87 modificar, 189 mutable, 90 pertenencia, 88 porciones, 90 recorrer recursivamente, 186 recorrido, 87, 185 lista anidada, 97, 112 lista enlazada, 183, 192 lista infinita, 187 ListaEnlazada, 190 listas anidadas, 85, 95 clonado, 93 como par´ametros, 94 operaciones con, 89 listas anidadas, 95 literalidad, 7 llamada a funci´on, 33 llamadas a funciones, 21 local variable, 69 locales variables, 29 logaritmo, 64 longitud, 87
  • 308. ´Indice alfab 2 lowercase, 82 m´aximo comu´n divisor, 238, 241 m´etodo, 111, 117, 141, 150, 160 ayudante, 190 envoltorio, 190 inicializaci´on, 154, 165 invocacio´n, 111 lista, 116, 166 m´etodo append, 166 m´etodo ayudante, 190, 192 m´etodo de inicializaci´on, 154, 160, 165 m´etodo de lista, 116 m´etodo envoltorio, 190 m´etodos de desarrollo incremental, 49 m ´etodos de lista, 166 m´etodos sobre diccionarios, 111 m´odulo, 23, 33, 81 copy, 137 operador, 35 string, 83 m´odulo copy, 137 m´odulo string, 81, 83 mu´ltiple asignaci´on, 73 manejar errores, 220 manejar una excepcio´n, 126, 128 manejo de errores, 220 marco, 42 marco de funci ´on, 42 matem ´aticas funciones, 23 matriz, 95 dispersa, 112 mazo, 165 McCloskey, Robert, 77 mensajes de error, 225 mismidad, 134 modelo mental, 232 modelo mental, 232 modifcador, 148 modificador, 143 modificar listas, 189 multiplicaci ´on fracci´on, 236 multiplicaci´on escalar, 156, 160 mutable, 79, 84, 99 lista, 90 nu ´mero aleatorio, 101 nu´mero aleatorio, 101 NameError, 229 natural lenguaje, 6 negaci´on, 240 negaci´on unitaria, 241 nivel, 211, 224 nodo, 183, 192, 211, 224 Nodo clase, 183 nodo de un a´rbol, 211 nodo hermano, 224 nodo hijo, 211, 224 nodo hoja, 211, 224 nodo padre, 211, 224 nodo ra´ız, 211, 224 None, 48, 59 notaci´on de punto, 111, 151, 154 nueva l´ınea, 73 numerador, 235 objeto, 91, 97, 131, 139 mudable, 136 objeto invariante, 191 objeto mudable, 136 objetos lista de, 165 obst´aculo al rendiminto, 210 operaciones con listas, 89 operaciones con listas, 89 operaciones sobre cadenas, 17 operador, 16, 20
  • 309. ´Indice alfab 2 binario, 213, 224 condicional, 164 corchete, 75 formato, 123, 128, 209, 230 in, 88, 169 m´odulo, 173 sobrecarga, 156, 236 operador binario, 213, 224 operador condicional, 164 operador corchete, 75 operador de formato, 123, 128, 209, 230 operador in, 88, 169 operador l´ogico, 36 operador m´odulo, 35, 45, 173 operador matem´atico, 236 operador unitario, 240 operadores para listas, 89 operando, 16, 20 orden, 164 orden completo, 164 orden de evaluaci ´on, 232 orden de las operaciones, 17 orden infijo, 214, 215, 224 orden parcial, 164 orden postfijo, 214, 215, 224 orden prefijo, 214, 224 palabra reservada, 13, 20 palabras reservadas, 14 palo, 161 papel variable, 188 par clave-valor, 109, 117 par´ametros, 28 par´ametro, 33, 94, 133 lista, 94 pass sentencia, 37 patr´on, 80 patr´on computacional, 80 Pentium, 64 Pila, 196 pila, 196 pista, 113, 117 plan de desarrollo, 73 poes´ıa, 8 polimorfica, 160 polimorfismo, 158 pop, 197 porci´on, 78, 84 porciones, 90 portabilidad, 10 portable, 2 postfijo, 198, 201, 213 precedencia, 20, 232 prefijo, 214, 224 print sentencia, 9, 10 prioridad, 209 producto, 219 producto interior, 156, 160 programa, 10 desarrollo de, 73 programaci´on orientada a objetos, 149, 171 prosa, 8 proveedor, 196, 201 pseudoaleatorio, 107 pseudoc´odigo, 238 push, 197 racional, 235 rama, 38, 45 random, 167 randrange, 167 recorrer, 84, 185, 186, 213 recorrido, 76, 80, 88, 176, 208, 214 lista, 87 recorrido de lista, 97 recorrido eureka, 80 recta´ngulo, 135 recuento, 116 recursio´n, 213, 214 infinita, 228
  • 310. ´Indice alfab 2 recursio´n infinita, 227, 228 recursividad, 40, 42, 45, 53, 55 caso base, 43 infinita, 43, 57 recursividad infinita, 43, 45, 57 redimiento, 205 reducir, 238, 241 redundancia, 7 referencia, 183 incrustada, 183, 192 referencia empotrada, 211 referencia incrustada, 183, 192 referencias alias, 92 reglas de precedencia, 17, 20 reparto de naipes, 173 repeticio´n lista, 89 runtime error, 76 ruta, 125 secuencia, 97 secuencia aritm´etica, 66 secuencia de escape, 66, 73 secuencia geom ´etrica, 66 seguro lenguaje, 5 sema´ntica, 5, 10 error, 5 sentencia, 20 asignaci´on, 12, 61 break, 121, 128 condicional, 45 continue, 122, 128 except, 126 print, 230 return, 40, 233 try, 126 while, 62 sentencia break, 121, 128 sentencia compuesta, 45 sentencia condicional, 45 sentencia continue, 122, 128
  • 311. ´Indice alfab 2 sentencia except, 126, 128 sentencia pass, 37 sentencia print, 9, 10, 230 sentencia return, 40, 233 senten cia try, 126 senten ciasbloque, 37 compuestas, 37 sentencias compue stas, 37 bloque de sentenci as, 37 cabecer a, 37 cuerpo, 37 sequencia, 85 singleton, 189, 190, 192 sintaxis, 5, 10, 226 sobrecarga, 160, 236 operador, 209 sobrecarga de operadores, 156, 160, 164, 209 solucio´n de problema s, 10 subclase, 171, 175, 182 subexpresio´n, 220 suma, 219 fracci´on, 237 sustituir, 164 tactica de encolamiento, 203, 210 tablas, 64 dos dimensiones, 66 tabulador, 73 TAD, 195, 200, 201 Cola, 203 cola, 203 Cola Priorizada, 203, 207 Pila, 196 TAD Cola, 203 temporal va riable, 59 tempo rales va riables , 48 teore ma fundamental de la ambigu¨edad, 188
  • 312. | ´Indice alfab 2 teorema fundamental de la am- bigu¨edad, 192 tesis de Turing, 53 tiempo constante, 205, 210 tiempo lineal, 205, 210 tipo, 11, 12, 20 cadena, 12 float, 12 int, 12 tipo abstracto de datos verTAD, 195 tipo compuesto de datos, 84 tipo de datos compuesto, 75, 131 definido por el usuario, 131, 235 diccionario, 109 tipo de datos compuesto, 75, 131 tipo de datos definido por el usua- rio, 131 tipo de funci´on modifcador, 143 pura, 142 tipo inmutable, 107 tipo mutable, 107 tipos coercio´n, 22 comprobacio´n, 57 conversio´n, 22 tipos de datos enteros largos, 115 inmutables, 99 tuplas, 99 token, 199, 201, 216 traceback, 31 traza, 127 traza inversa, 43, 229 try, 128 tupla, 99, 107 tuplas, 101 Turing, Alan, 53 TypeError, 229 unidad, 10 uppercase, 82 uso de alias, 137 valor, 20, 91, 161 valor de retorno, 21, 33, 47, 59, 136 valores tuplas, 101 valores de retorno tuplas, 101 value, 11 variable, 12, 20 bucle, 173 papeles, 188 temporal, 59, 232 variable de bucle, 73, 173, 185 variable local, 33 variable temporal, 232 variables locales, 69 variables locales, 29 variables temporales, 48 while sentencia, 62 whitespace, 82