Apunte Computacion 1 FCFM

Descargar como pdf o txt
Descargar como pdf o txt
Está en la página 1de 26

Índice

1 Expresiones y Tipos de Datos Básicos 1


1.1 ¿Qué es un Algoritmo? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2 ¿Qué es un Programa? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3 Tipos de Datos Básicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3.1 Enteros (int) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3.2 Reales (float) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Apunte CC1001 1.3.3 Texto (str) . . . . . . . . . . .
1.4 Programas Simples . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
3
3
1.4.1 Evaluación de expresiones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4.2 Variables . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.5 Errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5
Francisco Gutiérrez Figueroa 1.5.1 Errores de sintaxis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Vanessa Peña Araya 1.5.2 Errores de nombre . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
Mauricio Quezada Veas 1.5.3 Errores de tipo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6
1.5.4 Errores de valor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
1.5.5 Errores lógicos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 7
Version of September 11, 2013
2 Funciones 8
2.1 Variables y Funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.1.1 Definición de funciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8
2.1.2 Indentación y subordinación de instrucciones . . . . . . . . . . . . . . . . . . . . 9
2.1.3 Alcance de una variable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10
2.2 Problemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3 Un Poco Más Sobre Errores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.1 Errores de ejecución . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.3.2 Errores de indentación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12

3 Receta de Diseño 13
3.1 Entender el Propósito de la Función . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
3.2 Dar Ejemplos de Uso de la Función . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.3 Especificar el Cuerpo de la Función . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14
3.4 Probar la Función . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15

4 Módulos y Programas 17
4.1 Descomponiendo un programa en funciones . . . . . . . . . . . . . . . . . . . . . . . . . 18
4.2 Módulos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
4.3 Programas Interactivos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 22

1
ÍNDICE 2

5 Expresiones y Funciones Condicionales 25


5.1 Valores Booleanos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25
5.2 Funciones sobre Booleanos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27
5.3 Condiciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 28
5.4 Bloques de Código Condicionales en Python . . . . . . . . . . . . . . . . . . . . . . . . . 31
5.5 Diseñando Funciones Condicionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 32
5.5.1 Análisis de los datos y definición . . . . . . . . . . . . .
5.5.2 Dar ejemplos de uso la función . . . . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
32
32
Capı́tulo 1
5.5.3 El cuerpo de la función: el diseño de las condiciones . . . . . . . . . . . . . . . . 32
5.5.4 El cuerpo de la función: la respuesta de cada condición . . . . . . . . . . . . . . 33
5.5.5 Simplificando las condiciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 33 Expresiones y Tipos de Datos
6 Recursión 36
6.1 Potencias, factoriales y sucesiones .
6.2 Torres de Hanoi . . . . . . . . . . .
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
.
36
38
Básicos
6.3 Copo de nieve de Koch . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 40
6.4 Receta de diseño para la recursión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 44

7 Testeo y depuración de programas 46 Estas notas pretenden ser un complemento a las cátedras dictadas en el contexto del curso CC1001
7.1 Afirmaciones o Assertions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47 (Computación I), obligatorio para alumnos de Primer Año del Plan Común de Ingenierı́a y Ciencias,
7.2 Testear con números reales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48 dictado por la Facultad de Ciencias Fı́sicas y Matemáticas en la Universidad de Chile.

El objetivo principal de este curso no es formar programadores, sino desarrollar en los alumnos
una base común en razonamiento algorı́tmico y lógico, ası́ como una capacidad de modelamiento y
abstracción, necesarios para trabajar una habilidad general en la resolución de problemas. Estos
problemas no necesariamente estarán acotados en el contexto de las Ciencias de la Computación, sino
en el ámbito cotidiano y de la Ingenierı́a y Ciencias en general.

1.1 ¿Qué es un Algoritmo?


Un algoritmo es una secuencia finita de pasos que permiten ejecutar cualquier tarea (como por ejemplo,
hacer un huevo frito). La palabra algoritmo viene de la transcripción latina del nombre de Abu Abdallah
Muhammad ibn Musa al-Khwarizmi, un famoso matemático, astrónomo y geógrafo persa del siglo IX,
padre del álgebra y quien introdujo el concepto matemático de algoritmo.

Podemos considerar que definir un algoritmo es la primera etapa en la resolución de un problema.


Generalmente, procedemos como sigue:

Identificar un problema
Contextualizar los elementos que definen dicho problema
Relacionar mediante pasos de ejecución los elementos para resolver el problema

Tal como vimos anteriormente, un algoritmo es la representación natural, paso a paso, de cómo
podemos resolver un problema. Esto generalmente se conoce como una técnica de diseño top-down
(o de arriba hacia abajo). En otras palabras, partimos de un problema concreto y lo rompemos
en unidades elementales que se pueden resolver paso a paso mediante alguna estrategia conocida de
antemano.

Veamos a continuación un ejemplo: supongamos que queremos cocinar un huevo frito para acompañar
un almuerzo.
1
1.2. ¿QUÉ ES UN PROGRAMA? 2 1.4. PROGRAMAS SIMPLES 3

La definición del algoritmo que resuelve este problema serı́a: 1.3.2 Reales (float)
El tipo de datos real permite representar una aproximación decimal de números reales. La
Problema: hacer un huevo frito aproximación depende de los recursos disponibles del computador, por lo que todas las operaciones
entre valores reales son aproximaciones. Los números reales se escriben separando la parte entera de
Elementos: huevo, aceite, cocina, fósforo, sartén la parte decimal con un punto. Por ejemplo: 0.303456
Pasos de ejecución:
1.3.3 Texto (str)
1. Encender un fósforo El tipo de datos texto permite representar cadenas de caracteres indicadas entre comillas simples o
2. Con el fósforo, prender un quemador en la cocina dobles. Por ejemplo: ‘hola’, ‘103’, ‘Mi mascota es un ~ nandú!’. Al respecto, hay que recalcar que
3. Colocar la sartén sobre el quemador de la cocina la expresión ‘103’ es de tipo texto (porque está entre comillas), aun cuando su contenido se puede
entender como un número.
4. Poner unas gotas de aceite sobre la sartén
5. Tomar un huevo y quebrarlo
6. Colocar el huevo quebrado sobre la sartén
1.4 Programas Simples
7. Esperar hasta que el huevo esté listo Como vimos, un programa es una representación ejecutable de un algoritmo. Esta representación está
definida por una expresión que al ser evaluada genera un valor. A continuación veremos cómo definir
y evaluar programas simples.

1.2 ¿Qué es un Programa? 1.4.1 Evaluación de expresiones


Un programa es una especificación ejecutable de un algoritmo. Para representar un programa en Una expresión es una combinación entre valores y operadores que son evaluados durante la ejecución
un computador, utilizamos un lenguaje de programación. En el contexto de este curso, usaremos el de un programa. Por ejemplo, la expresión 1 + 1 es una expresión aritmética, tal que al evaluarla
lenguaje Python por su facilidad de aprendizaje. produce el valor 2.

Para comenzar a trabajar con Python utilizaremos su intérprete. El intérprete de Python es una Con los tipos de datos explicados anteriormente, podemos realizar operaciones entre ellos utilizando
aplicación que lee expresiones que se escriben, las evalúa y luego imprime en pantalla el resultado operadores especı́ficos en cada caso. Ası́, para datos numéricos (enteros y reales), podemos usar los
obtenido. operadores de suma (+), resta (-), multiplicación (*) y división (/). La prioridad de estos operadores
es la misma usada en álgebra: primero se evalúan los operadores multiplicativos de izquierda a derecha
Es importante recalcar que utilizaremos en este curso a Python como una herramienta y no un fin. según orden de aparición (* y /), y luego los aditivos (+, -). En el caso de querer imponer una
No hay que olvidar que el objetivo es aprender a resolver problemas, utilizando un computador como evaluación en particular que no siga el orden preestablecido, podemos indicarlo utilizando paréntesis.
apoyo para realizar esta tarea. Por ejemplo:

>>> 3 + 5
1.3 Tipos de Datos Básicos 8
Un tipo de datos es un atributo que indica al computador (y/o al programador) algo sobre la clase >>> 3 + 2 * 5
de datos sobre los que se va a procesar. Esto incluye imponer restricciones en los datos, tales como 13
qué valores pueden tomar y qué operaciones se pueden realizar. Todos los valores que aparecen en un >>> (3 + 2) * 5
programa tienen un tipo. A continuación, revisaremos algunos de los tipos de datos básicos con los 25
que vamos a trabajar en este curso:
En Python, se definen dos operadores adicionales para operaciones matemáticas recurrentes: elevar
a potencia (**) y calcular el resto de una división entera (%). La prioridad del operador % es la misma
1.3.1 Enteros (int) que la de los operadores multiplicativos, mientras que la del operador ** es mayor. Ası́:
El tipo de datos entero representa un subconjunto finito de los números enteros. El número mayor
que puede representar depende del tamaño del espacio usado por el dato y la posibilidad (o no) de >>> 2 ** 3
representar números negativos. Las tı́picas operaciones aritméticas que se pueden realizar con estos 8
datos son: suma, resta, multiplicación y división. Por ejemplo: ..., -3, -2, -1, 0, 1, 2, 3, ... >>> 4 ** 0.5
2.0
>>> 10 % 3
1

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
1.4. PROGRAMAS SIMPLES 4 1.5. ERRORES 5

Para operar con valores de tipo texto, en Python utilizamos generalmente dos operadores: si Para crear variables en un programa podemos utilizar cualquier letra del alfabeto, o bien, una
queremos unir (concatenar) dos cadenas de texto, lo indicamos con el operador +; por otro lado, si combinación de letras, números y el sı́mbolo siempre que el primer carácter no sea un número. Para
queremos repetir una cadena de texto, lo indicamos con el operador *. Por ejemplo: asignar una variable a una expresión (o al resultado de ésta), utilizamos el operador =.

>>> ‘abra’ + ‘cadabra’ Notemos que es importante el orden en que se realiza la definición de variables y expresiones: la
’abracadabra’ sintaxis correcta es variable = expresión, y no al revés. En Python se evalúa la expresión y se
>>> ‘ja’ * 3 define la variable con el valor resultante. Por ejemplo:
’jajaja’
>>> a = 8 # la variable a contiene el valor 8
Finalmente, es importante notar que en Python los valores a los que se evalúa una expresión >>> b = 12 # la variable b contiene el valor 12
dependen del tipo de los operandos. Ası́, si la expresión está compuesta únicamente de números >>> a # mostramos el valor de a
enteros, el resultado obtenido también será de tipo entero. Por ejemplo: 8
>>> a + b # creamos una expresión y mostramos su valor
>>> 1 / 2 20
0 >>> c = a + 2 * b # creamos una expresión, se evalúa y se define c con su valor
>>> c # mostramos el valor de c
En efecto, dado que 1 y 2 son valores de tipo entero, la división entre ellos también lo será (y, 32
por ende, se transforma el valor obtenido al tipo entero). Si queremos calcular el valor real de dicha >>> a = 10 # redefinimos a
operación, debemos forzar a que al menos uno de los dos operandos sea real. Ası́: >>> a + b
22
>>> 1.0 / 2.0 >>> c #el valor de c no cambia, pues lo calculamos antes de la redefinición de a
0.5 32
>>> 1 / 2.0
0.5 En Python, el sı́mbolo # sirve para introducir un comentario. Los comentarios son explicaciones
>>> 1.0 / 2 que da el programador y que no son procesadas por el intérprete. Es importante utilizar comentarios
0.5 para introducir aclaraciones relevantes en el código. Por otro lado, no es recomendable abusar de ellos!
Es mejor utilizar nombres de variables que tengan un significado propio (relevante a la expresión que
Finalmente, si queremos juntar valores de tipo texto con valores de tipo numérico, debemos convertir están calculando), y utilizar los comentarios sólo en situaciones especiales.
estos últimos previamente a valores de tipo texto. Por ejemplo, si queremos transformar un valor n a
un valor equivalente de tipo texto, utilizamos la funcin de Python str. De igual manera, si tenemos Veamos un mal ejemplo:
un valor de tipo texto que se puede entender como número, por ejemplo ’103’, podemos convertir el
valor en tipo numérico usando las funciones int y float. Ası́: >>> a = 8
>>> b = 12
>>> ‘‘En el curso hay ’’ + str(100) + ‘‘ alumnos’’ >>> c = a * b
’En el curso hay 100 alumnos’ En este ejemplo notamos que queremos calcular el área de un rectángulo, pero los nombres de las
variables no son los indicados. En este caso, las variables a, b y c pueden representar cualquier cosa.
>>> ‘‘100’’ + ‘‘1’’ Serı́a mucho más adecuado escribir:
’1001’
>>> int(‘‘100’’) + 1 >>> ancho = 8
101 >>> largo = 12
>>> area = ancho * largo
1.4.2 Variables
Una variable es el nombre que se le da a un valor o a una expresión. El valor de una variable está 1.5 Errores
dado por la definición más reciente del nombre. Para evaluar una expresión con variables, usamos una
semántica por sustitución, esto es, reemplazamos en la expresión los valores asociados al nombre por Hasta ahora hemos visto únicamente expresiones correctas, tal que al evaluarlas obtenemos un
aquellos que están definidos por la variable. resultado. ¿Qué es lo que imprime el intérprete en este caso?

Por ejemplo, si la variable x tiene el valor 8, al evaluar la expresión doble = 2 * x, entonces la >>> dia = 13
variable doble contendrá el valor 16 (pues doble ! 2 * 8 ! 16). >>> mes = ‘‘agosto’’
>>> ‘‘Hoy es ’’ + dia + ‘‘ de ’’ + mes
Traceback (most recent call last):

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
1.5. ERRORES 6 1.5. ERRORES 7

File "<stdin>", line 1, in <module> 1.5.4 Errores de valor


TypeError: cannot concatenate ’str’ and ’int’ objects
Un error de valor ocurre cuando, a pesar de tener una expresión bien formada, se aplican operaciones
sobre valores que no corresponden al tipo en el que fueron definidas. Por ejemplo:
En este caso, el intérprete nos dice que hemos cometido un error en nuestro programa: “cannot
concatenate ’str’ and ’int’ objects”, y es de tipo TypeError. Esto en español significa que
>>> nombre = ‘Juan Soto’
estamos intentando unir valores de tipo texto con valores de tipo entero. Tal como lo vimos
>>> int(nombre)
anteriormente, esto es incompatible para el intérprete por lo que debemos corregir la instrucción.
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
>>> dia = 13
ValueError: invalid literal for int() with base 10: ’Juan Soto’
>>> mes = ‘agosto’
>>> ‘Hoy es ’ + str(dia) + ‘ de ’ + mes
’Hoy es 13 de agosto’ 1.5.5 Errores lógicos
En un programa no todos los errores pueden ser detectados por el computador. Los errores lógicos
Existen distintos tipos de errores. Es útil conocerlos para no cometerlos, y eventualmente para son aquellos que se producen por descuido del programador al escribir las instrucciones y pueden
saber cómo corregirlos. provocar resultados muy inesperados. Por ejemplo, estos errores se pueden producir al darle un nombre
incorrecto o ambiguo a una variable o a otras situaciones más complejas.
1.5.1 Errores de sintaxis
Lamentablemente, el intérprete no nos indicará cuándo o en qué lı́nea se producen estos errores,
Un error de sintaxis se produce cuando el código no sigue las especificaciones propias del lenguaje.
por lo que la mejor manera de enfrentarlos es evitarlos y seguir una metodologı́a limpia y robusta que
Estos errores se detectan en el intérprete antes de que se ejecute, y se muestran con un mensaje de
nos permita asegurar a cabalidad que lo que estamos escribiendo efectivamente es lo que esperamos
error indicando el lugar “aproximado” en el que se detectó la falta. Por ejemplo:
que el computador ejecute. Por ejemplo:
>>> numero = 15
>>> numero = 15
>>> antecesor = (numero - 1))
>>> doble = 3 * numero
File "<stdin>", line 1
>>> doble #esperarı́amos 30, luego debe haber algún error en el código
antecesor = (numero - 1))
45
^
SyntaxError: invalid syntax

1.5.2 Errores de nombre


Un error de nombre se produce cuando utilizamos una variable que no se ha definido anteriormente
en el programa. Por ejemplo:

>>> lado1 = 15
>>> area = lado1 * lado2
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
NameError: name ’lado2’ is not defined

1.5.3 Errores de tipo


Un error de tipo ocurre cuando aplicamos operaciones sobre tipos que no son compatibles. Por ejemplo:

>>> dia = 13
>>> mes = ‘agosto’
>>> ‘Hoy es ’ + dia + ‘ de ’ + mes
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: cannot concatenate ’str’ and ’int’ objects

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
2.1. VARIABLES Y FUNCIONES 9

La segunda lı́nea define la salida de la función. La palabra clave return indica el fin de la función,
y la expresión que la sucede será evaluada y retornada como la salida.

Para utilizar una función, debemos invocarla con el valor de sus argumentos. En nuestro ejemplo,
para calcular el área de un cı́rculo con radio igual a 5, invocamos a la función de la siguiente manera:

Capı́tulo 2 >>> areaCirculo(5)


78.5

Funciones1
Internamente, lo que sucede es similar al reemplazo de variables que se explicó anteriormente. La
última lı́nea de la función es evaluada de la siguiente manera:
areaCirculo(5) ! 3.14 * 5 ** 2 ! 3.14 * 25 ! 78.5
Una función puede estar compuesta tanto de operadores básicos como también de otras funciones
previamente definidas. Es decir, dada la definición de una función, ésta puede ser utilizada a su vez
La clase anterior vimos cómo crear expresiones sencillas que operan con números. El objetivo de esta por otras funciones.
clase es ir un paso más allá y desarrollar pequeños trozos de código que implementen operaciones como
un conjunto. Al igual que en matemática, a una estructura que recibe valores de entrada y genera un Imaginemos que queremos definir una función que calcule el área de un anillo. Dado que el área de
valor de salida, en computación la llamamos función. un anillo se calcula restando área del cı́rculo externo con el área del cı́rculo interno, podemos utilizar
nuestra función areaCirculo para implementar esta nueva función.

2.1 Variables y Funciones


En los cursos de matemática aprendimos a relacionar cantidades mediantes expresiones con variables.
Por ejemplo, conocemos bien la relación entre el área de un cı́rculo y su radio. Si el radio de un cı́rculo
está dado por la variable “r”, entonces su área se calcula mediante la expresión:

areaCirculo = 3.14 · r2
Ası́, si tenemos un cı́rculo cuyo radio tiene valor igual a 5, sabemos que sustituyendo la variable
“r ” con este valor, obtenemos su área: Para esto, debemos crear una función que reciba dos argumentos, el radio del cı́rculo externo y el
radio del cı́rculo interno, calcule el área de ambos cı́rculos y finalmente los reste. Luego, la función
areaCirculo = 3.14 · 52 = 3.14 · 25 = 78.5 que calcula el área de un anillo queda definida de la siguiente manera:

2.1.1 Definición de funciones def areaAnillo(exterior, interior):


return areaCirculo(exterior) - areaCirculo(interior)
Al igual que en matemática, en computación una función es una regla que cumple con las mismas
caracterı́sticas, puesto que describe cómo producir información en base a otra que está dada como Para utilizar nuestra función, podemos tomar como ejemplo un anillo con radio externo igual a 5
entrada. Luego, es importante que cada función sea nombrada de forma en que sea clara la relación e interno igual a 3:
entre su nombre y el objetivo que cumple. El ejemplo anterior puede transformarse en una función
cuyo nombre serı́a areaCirculo y en Python estarı́a definida como sigue: >>> areaAnillo(5, 3)
50.24
def areaCirculo(radio):
return 3.14 * radio ** 2 En efecto, esta evaluación corresponde a:

La primera lı́nea define la función, asignándole un nombre y los argumentos que recibe. Notemos areaAnillo(5, 3) ! areaCirculo(5) - areaCirculo(3)
que para declarar una función debemos utilizar la palabra clave def y terminar la declaración con el ! 3.14 * 5 ** 2 - 3.14 * 3 ** 2
sı́mbolo dos puntos (:). Los argumentos de una función son la información de entrada que ésta recibe, ! 3.14 * 25 - 3.14 * 9
y que serán utilizados para evaluar las expresiones que la definen. En nuestro ejemplo, la función ! 50.24
recibe un sólo argumento, que es el valor del radio del cı́rculo que será utilizado para calcular su área.
2.1.2 Indentación y subordinación de instrucciones
1 Traducido al español y adaptado de: M. Felleisen et al.: How to Design Programs, MIT Press. Disponible en: Es importante notar que en Python la indentación (cantidad de tabulaciones hacia la derecha) de
www.htdp.org
cada instrucción determina el alcance al que pertenece. En el caso de nuestra función de ejemplo
8 APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
2.1. VARIABLES Y FUNCIONES 10 2.2. PROBLEMAS 11

areaCirculo, podemos ver que la primera lı́nea está al margen izquierdo mientras que la segunda está 2.2 Problemas
separado del margen izquierdo por una tabulación. Esto indica que la segunda lı́nea está subordinada a
la función, lo que se traduce que esta instrucción es parte de la ella. Ası́, si una función está compuesta Rara vez los problemas vienen formulados de tal manera que basta con traducir una fórmula
por muchas instrucciones, cada lı́nea debe esta indentada al menos una tabulación hacia la derecha matemática para desarrollar una función. En efecto, tı́picamente se tiene una descripción informal
más que la lı́nea que indica el nombre y argumentos de la función. Notemos entonces que la función sobre una situación, la que puede incluir información ambigua o simplemente poco relevante. Ası́,
de nuestro ejemplo puede ser reescrita de la siguiente manera: la primera etapa de todo programador es extraer la información relevante de un problema y luego
traducirlo en expresiones apropiadas para poder desarrollar un bloque de código. Consideremos el
def areaCirculo(radio): siguiente ejemplo:
pi = 3.14
return pi * radio * radio “Genera S.A. le paga 4.500 por hora a todos sus ingenieros de procesos recién egresados. Un
empleado tı́picamente trabaja entre 20 y 65 horas por semana. La gerencia de informática le pide
Como se puede observar, las dos lı́neas que definen esta función tienen una indentación a la derecha, desarrollar un programa que calcule el sueldo de un empleado a partir del número de horas trabajadas.”
quedando ambas subordinadas a la función misma.
En la situación anterior, la última frase es la que indica cuál es el problema que queremos resolver:
escribir un programa que determine un valor en función de otro. Más especı́ficamente, el programa
2.1.3 Alcance de una variable recibe como entrada un valor, la cantidad de horas trabajadas, y produce otro, el sueldo de un
La definición de una variable dentro de una función tiene un alcance local. El significado de esta frase empleado en pesos. La primera oración implica cómo se debe calcular el resultado, pero no lo especifica
se explicará a continuación a través de tres ejemplos. explı́citamente. Ahora bien, en este ejemplo particular, esta operación no requiere mayor esfuerzo: si
un empleado trabaja una cantidad h de horas, su sueldo será: 4.500 · h.
Imaginemos que declaramos una variable a. Si una función realiza alguna operación que requiere
de esta variable, el valor utilizado será aquel que contiene la variable. Por ejemplo: Ahora que tenemos una expresión para modelar el problema, simplemente creamos una función en
Python para calcular los valores:
>>> a = 100
>>> def sumaValorA(x): def sueldo(h):
... return x + a return 4500 * h
>>> sumaValorA(1)
101 En este caso, la función se llama sueldo, recibe un parámetro h representando a la cantidad de
horas trabajadas, y devuelve 4500 * h, que corresponde al dinero que gana un empleado de la empresa
Sin embargo, una variable puede ser redefinida dentro de una función. En este caso, cada vez que al haber trabajado h horas.
se deba evaluar una expresión dentro de la función que necesite de esta variable, el valor a considerar
será aquel definido dentro de la función misma. Además, la redefinición de una variable se hace de
manera local, por lo que no afectará al valor de la variable definida fuera de la función. Esto se puede 2.3 Un Poco Más Sobre Errores
observar en el siguiente ejemplo:
En la clase anterior hablamos acerca de los distintos tipos de error que se pueden producir al generar
programas de computación. En particular hablamos de los errores de sintaxis, de nombre y lógicos.
>>> a = 100
En esta parte de la clase se discutirán dos nuevos tipos de error: los errores en tiempo de ejecución y
>>> def sumaValorA(x):
los errores de indentación.
... a = 200
... return x + a
>>> sumaValorA(1) 2.3.1 Errores de ejecución
201 Existen maneras de cometer errores que el intérprete de Python no notará hasta que la expresión escrita
>>> a sea evaluada. Un claro ejemplo es la división por cero. Para el intérprete de Python la expresión 1 / 0
100 representa la división entre dos números cualquiera, pero que al evaluarla se generará el error descrito
a continuación:
Por otra parte, si el argumento de una función tiene el mismo nombre que una variable definida
fuera de ésta, la función evaluará sus instrucciones con el valor del argumento, pues es la variable que >>> 1 / 0
está dentro de su alcance: Traceback (most recent call last):
File "<stdin>", line 1, in <module>
>>> a = 100 ZeroDivisionError: integer division or modulo by zero
>>> def sumaValorA(a):
... return 1 + a En Python este tipo de error se llama ZeroDivisionError, la que indica claramente la fuente de
>>> sumaValorA(5) la falla.
6
APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
2.3. UN POCO MÁS SOBRE ERRORES 12

Otra manera de obtener este tipo de error es invocando una función con un número equivocado de
argumentos. Por ejemplo, si utilizamos la función areaCirculo con dos argumentos en vez de uno,
recibiremos un mensaje de error que lo indica:

>>> areaCirculo(5,3)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: areaCirculo() takes exactly 1 argument (2 given)
Capı́tulo 3

Receta de Diseño1
2.3.2 Errores de indentación
Como vimos anteriormente, la indentación de un conjunto de instrucciones en Python tiene una
connotación semántica, no sólo sintáctica, puesto que indica la subordinación de una instrucción.
Ası́, es posible generar errores cuando una instrucción no está indentada de manera correcta.

Imaginemos que queremos definir una función que calcule el área de un cuadrado dado el largo de En el capı́tulo anterior vimos que el desarrollo de una función requiere varios pasos. Necesitamos
uno de sus lados. Una implementación de esta función podrı́a ser de la siguiente manera: saber qué es lo relevante en el enunciado del problema y qué podemos ignorar. Además, necesitamos
saber qué es lo que la función recibirá como parámetros, y cómo relaciona estos parámetros con
>>> def areaCuadrado(lado): la salida esperada. Además, debemos saber, o averiguar, si Python provee operaciones básicas para
... return lado * lado manejar la información que necesitamos trabajar en la función. Si no, deberı́amos desarrollar funciones
auxiliares que implementen dichas operaciones. Finalmente, una vez que tengamos desarrollada la
Sin embargo, al intentar evaluar la expresión anterior, se obtiene un mensaje de error que indica función, necesitamos verificar si efectivamente realiza el cálculo esperado (para el cual efectivamente
que se espera una indentación de la instrucción para poder definir la función de manera correcta: implementamos la función). Esto puede evidenciar errores de sintaxis, errores de ejecución, o incluso
errores de diseño.
>>> def areaCuadrado(lado):
... return lado * lado Para trabajar apropiadamente, lo mejor es seguir una receta de diseño, esto es, una descripción
File "<stdin>", line 2 paso a paso de qué es lo que tenemos que hacer y en qué orden. Basándonos en lo que hemos visto
return lado * lado hasta ahora, el desarrollo de un programa requiere al menos las siguientes cuatro actividades:
^
IndentationError: expected an indented block
3.1 Entender el Propósito de la Función
El objetivo de diseñar una función es el crear un mecanismo que consume y produce información.
Luego, deberı́amos empezar cada función dándole un nombre significativo y especificando qué tipo de
información consume y qué tipo de información produce. A esto lo llamamos contrato. Por ejemplo,
ası́ escribirı́amos el contrato de la función areaAnillo como sigue:

# areaAnillo: num num -> float

El contrato consiste en dos partes: la primera, a la izquierda de los dos puntos especifica el nombre
de la función; la segunda, a la derecha de los dos puntos, especifica qué tipo de datos consume y qué
es lo que produce. Los tipos de valores de entrada se separan de los de salida por una flecha. En el
caso de nuestro ejemplo el tipo de datos que consume es de tipo numérico, es decir, puede ser de tipo
entero (int) o real (float), por lo que lo representamos con la palabra num. Mientras que el valor que
produce solo puede ser de tipo real, por lo que se representa con la palabra float.

Una vez que tenemos especificado el contrato, podemos agregar el encabezado. Éste reformula el
nombre de la función y le da a cada argumento un nombre distintivo. Estos nombres son variables y
se denominan los parámetros de la función. Miremos con más detalle el contrato y el encabezado de
1 Traducido al español y adaptado de: M. Felleisen et al.: How to Design Programs, MIT Press. Disponible en:

www.htdp.org

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN 13


3.2. DAR EJEMPLOS DE USO DE LA FUNCIÓN 14 3.4. PROBAR LA FUNCIÓN 15

la función areaAnillo: Notemos que podemos formular el cuerpo de la función únicamente si entendemos cómo la función
calcula el valor de salida a partir del conjunto de valores de entrada. Ası́, si la relación entre las
# areaAnillo: num num -> float entradas y la salida están dadas por una fórmula matemática, basta con traducir esta expresión a
def areaAnillo(exterior, interior): Python. Si por el contrario nos enfrentamos a un problema verbal, debemos construir previamente la
... secuencia de pasos necesaria para formular la expresión.

Aquı́ especificamos que los parámetros (en orden) que recibe la función se llaman exterior e En nuestro ejemplo, para resolver el problema basta con reutilizar la función areaCirculo definida
interior. previamente. Ası́, la traducción en Python de este proceso serı́a:

Finalmente, usando el contrato y los parámetros, debemos formular un propósito para la función, def areaAnillo(exterior, interior):
esto es, un comentario breve sobre qué es lo que la función calcula. Para la mayorı́a de nuestras return areaCirculo(exterior) - areaCirculo(interior)
funciones, basta con escribir una o dos lı́neas; en la medida que vayamos desarrollando funciones y
programas cada vez más grandes, podremos llegar a necesitar agregar más información para explicar el
propósito de una función. Ası́, hasta el momento llevamos lo siguiente en la especificación de nuestra 3.4 Probar la Función
función:
Después de completar la definición de la función, debemos probarla. El proceso de probar una función
se llama testeo, y cada prueba se conoce como test.
# areaAnillo: num num -> float
# calcula el área de un anillo de radio exterior y cuyo agujero es de radio interior
En cualquier función que desarrollemos, nos debemos asegurar que al menos calcula efectivamente
el valor esperado para los ejemplos definidos en el encabezado. Para facilitar el testeo, podemos hacer
def areaAnillo(exterior, interior):
uso del comando assert de Python para definir un caso de uso y compararlo con el valor esperado.
...
Ası́, por ejemplo, si queremos probar que un valor calculado de la función es igual a uno que
3.2 Dar Ejemplos de Uso de la Función calculamos manualmente, podemos proceder como sigue:

Para tener un mejor entendimiento sobre qué es lo que debe calcular la función, evaluamos ejemplos assert areaAnillo(5, 3) == 50.24
para valores de entrada significativos y determinamos manualmente cuál debe ser la salida esperada.
Por ejemplo, la función areaAnillo debe generar el valor 50.24 para las entradas 5 y 3, pues es la En este caso, le indicamos a Python que evalúe la aplicación de la función areaAnillo con los
diferencia de área del cı́rculo exterior con el área del cı́rculo interior. Ası́, la especificación de nuestra parámetros 5 y 3, y verifique si el resultado obtenido es efectivamente 50.24. Si ese es el caso, la
función queda de la forma: función se dice que pasa el test. En caso contrario, Python lanza un error y es entonces indicio que
debemos verificar con detalle nuestra función. Por ejemplo:
# areaAnillo: num num -> float
# calcula el área de un anillo de radio exterior y agujero de radio interior >>> assert areaAnillo(5, 3) == 50.24
# ejemplo: areaAnillo(5, 3) debe producir 50.24 >>> assert areaAnillo(5, 3) == 0
def areaAnillo(exterior, interior): Traceback (most recent call last):
... File "<stdin>", line 1, in <module>
AssertionError
El hacer ejemplos ANTES DE ESCRIBIR EL CUERPO DE LA FUNCIÓN ayuda de muchas
maneras. Primero, es la única manera segura de descubrir errores lógicos. Si usáramos la función una Es importante recalcar que las pruebas que realizamos no pueden probar que una función produce
vez implementada para generar estos ejemplos, estarı́amos tentados a confiar en la función porque es salidas correctas para TODAS las entradas posibles. Esto se debe a que hay un número infinito de
mucho más fácil evaluar la función que predecir qué es lo que efectivamente hace. Segundo, los ejemplos combinaciones de valores de entrada para pasar como parámetros. Sin embargo, el testeo es una
nos fuerzan a pensar a través del proceso computacional, el que, para casos más complejos que veremos estrategia muy potente para verificar errores de sintaxis o de diseño en la función.
más adelante, es crı́tico para el desarrollo del cuerpo de la función. Finalmente, los ejemplos ilustran
la prosa informal del propósito de la función. Ası́, futuros lectores del código, tales como profesores, Para los casos en los que la función no pasa un test, debemos poner especial atención a los ejemplos
colegas, o incluso clientes, podrán entender cuál es el concepto que está detrás del código. que especificamos en el encabezado. En efecto, es posible que los ejemplos estén incorrectos, que la
función tenga algún tipo de error, o incluso que tanto los ejemplos como la función tengan errores.
En cualquier caso, deberı́amos volver a revisar la definición de la función siguiendo los cuatro pasos
3.3 Especificar el Cuerpo de la Función anteriores.
Finalmente, debemos definir cuál es el cuerpo de la función. Esto es, debemos reemplazar los puntos
suspensivos (...) de nuestra definición anterior por un conjunto de instrucciones que conformarán el
cómo se procesarán los parámetros de entrada para producir la salida.

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
3.4. PROBAR LA FUNCIÓN 16

Finalmente, la definición completa de nuestra función siguiendo la receta de diseño es como sigue:

# areaAnillo: num num -> float


# calcula el área de un anillo de radio exterior y agujero de radio interior
# ejemplo: areaAnillo(5, 3) debe producir 50.24
def areaAnillo(exterior, interior):
return areaCirculo(exterior) - areaCirculo(interior) Capı́tulo 4
# Tests
assert areaAnillo(5, 3) == 50.24
Módulos y Programas1
Notemos que todas las funciones que se definan deben seguir la receta de diseño, incluso las funciones
auxiliares (como es el caso de la función areaCirculo en el ejemplo anterior).
En general, un programa consta no sólo de una, sino de muchas definiciones de funciones. Por ejemplo,
si retomamos el ejemplo del anillo que vimos en el capı́tulo 2, tenemos dos funciones: una para calcular
el área de un cı́rculo (areaCirculo) y una para calcular el área del anillo propiamente tal (areaAnillo).
En otras palabras, dado que la función areaAnillo retorna el valor que queremos en nuestro programa,
decimos que es la función principal. De igual manera, dado que la función areaCirculo apoya a la
función principal, decimos que es una función auxiliar.

El uso de funciones auxiliares hace que el diseño de programas sea más manejable, y deja finalmente
al código más limpio y entendible de leer. Por ejemplo, consideremos las siguientes dos versiones para
la función areaAnillo:

def areaAnillo(interior, exterior):


return areaCirculo(exterior) - areaCirculo(interior)

def areaAnillo(interior, exterior):


return 3.14 * exterior ** 2 - 3.14 * interior ** 2

La primera definición está basada en una composición de funciones auxiliares. El diseñarla de esta
manera nos ayudó a descomponer el problema en subproblemas más pequeños, pero más abordables.
De hecho, el sólo leer la definición de la función (sin siquiera saber cómo están definidas las funciones
auxiliares) nos da a entender que para calcular el área del anillo basta con restar el área de un cı́rculo
externo con el área del cı́rculo en su interior. Por el contrario, la definición de la segunda versión de
nuestra función obliga al lector el reconstruir la idea de que las subexpresiones en efecto calculan el
área de un cı́rculo. Peor aún, estamos escribiendo dos veces la misma expresión (!)

Para un programa pequeño como el que hemos visto en el ejemplo, las diferencias entre ambos estilos
de diseño de funciones son menores, aun cuando bastante significativas. Sin embargo, para programas
o funciones más grandes, el usar funciones auxiliares no se vuelve una opción, sino una necesidad. Esto
es, cada vez que se nos pida escribir un programa, debemos considerar el descomponerlo en funciones,
y éstas a su vez descomponerlas en funciones auxiliares hasta que cada una de ellas resuelva UNO Y
SÓLO UN SUBPROBLEMA particular.
1 Parte de este texto fue traducido al español y adaptado de: M. Felleisen et al.: How to Design Programs, MIT Press.

Disponible en: www.htdp.org

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN 17


4.1. DESCOMPONIENDO UN PROGRAMA EN FUNCIONES 18 4.1. DESCOMPONIENDO UN PROGRAMA EN FUNCIONES 19

4.1 Descomponiendo un programa en funciones # gastos: int -> int


# calcular los gastos totales, dado precioEntrada
Consideremos el siguiente problema: def gastos(precioEntrada):
...
“Una importante cadena de cines de Santiago tiene completa libertad en fijar los precios de las
entradas. Claramente, mientras más cara sea la entrada, menos personas estarán dispuestas a pagar # espectadores: int -> int
por ellas. En un reciente estudio de mercado, se determinó que hay una relación entre el precio al que # calcular el número de espectadores, dado precioEntrada
se venden las entradas y la cantidad de espectadores promedio: a un precio de 5.000 por entrada, 120 def espectadores(precioEntrada):
personas van a ver la pelı́cula; al reducir 500 en el precio de la entrada, los espectadores aumentan ...
en 15. Desafortunadamente, mientras más personas ocupan la sala para ver la pelı́cula, más se debe
gastar en limpieza y mantenimiento general. Para reproducir una pelı́cula, el cine gasta 180.000. Esto nos permite enunciar la primera regla en el diseño de programas:
Asimismo, se gastan en promedio 40 por espectador por conceptos de limpieza y mantenimiento. El
gerente del cine le encarga determinar cuál es la relación exacta entre las ganancias y el precio de las
entradas para poder decidir a qué precio se debe vender cada entrada para maximizar las ganancias Antes de escribir cualquier lı́nea de código siga la receta de diseño para cada función:
totales.” formule el contrato, encabezado y propósito de la función, plantee ejemplos de uso
relevantes y formule casos de prueba para verificar que su función se comportará
Si leemos el problema, está clara cuál es la tarea que nos piden. Sin embargo, no resulta del todo correctamente.
evidente el cómo hacerlo. Lo único de lo que podemos estar seguros es que varias cantidades dependen
entre sı́. Una vez escritas las formulaciones básicas de las funciones y al haber calculado a mano una serie
de ejemplos de cálculo, podemos reemplazar los puntos suspensivos ... por expresiones de Python.
Cuando nos vemos enfrentados a estas situaciones, lo mejor es identificar las dependencias y ver En efecto, la función ganancias calcula su resultado como la diferencia entre los resultados arrojados
las relaciones una por una: por las funciones ingresos y gastos, tal como lo sugiere el enunciado del problema y el análisis de
las dependencias que hicimos anteriormente. El cálculo de cada una de estas funciones depende del
Las ganancias corresponden a la diferencia entre los ingresos y los gastos. precio de las entradas (precioEntrada), que es lo que indicamos como parámetro de las funciones.
Para calcular los ingresos, primero calculamos el número de espectadores para precioEntrada y lo
Los ingresos se generan exclusivamente a través de la venta de entradas. Corresponde al producto multiplicamos por precioEntrada. De igual manera, para calcular los gastos sumamos el costo fijo
del valor de la entrada por el número de espectadores. al costo variable, que corresponde al producto entre el número de espectadores y 40. Finalmente, el
cálculo del número de espectadores también se sigue del enunciado del problema: podemos suponer
Los gastos están formados por dos ı́temes: un gasto fijo ( 180.000) y un gasto variable que
una relación lineal entre el número de espectadores y el valor de la entrada. Ası́, 120 espectadores están
depende del número de espectadores.
dispuestos a pagar 5.000, mientras que cada 500 que se rebajen del precio, vendrán 15 espectadores
Finalmente, el enunciado del problema también especifica cómo el número de espectadores más.
depende del precio de las entradas.
La definición de las funciones es como sigue:
Definamos, pues, una función por cada una de estas dependencias; después de todo, las funciones
precisamente calculan cómo distintos valores dependen de otros. Siguiendo la receta de diseño que
presentamos en el capı́tulo anterior, comenzaremos definiendo los contratos, encabezados y propósitos def ganancias(precioEntrada):
para cada una de las funciones: return ingresos(precioEntrada) - gastos(precioEntrada)
# ganancias: int -> int def ingresos(precioEntrada):
# calcular las ganancias como la diferencia entre los ingresos y los gastos return espectadores(precioEntrada) * precioEntrada
# dado precioEntrada
def ganancias(precioEntrada): def gastos(precioEntrada):
... return 180000 + espectadores(precioEntrada) * 40
Notemos que las ganancias totales dependen del precio de las entradas, dado que tanto los ingresos def espectadores(precioEntrada):
como los gastos dependen a su vez del precio de las entradas. return 120 + (5000 - precioEntrada) * 15 / 500
# ingresos: int -> int
# calcular el ingreso total, dado precioEntrada
def ingresos(precioEntrada): Si bien es cierto que podrı́amos haber escrito directamente la expresión para calcular el número
... de espectadores en todas las funciones, esto es altamente desventajoso en el caso de querer modificar
una parte en la definición de la función. De igual manera, el código resultante serı́a completamente
ilegible. Ası́, formulamos la siguiente regla que debemos seguir junto con la receta de diseño:
APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
4.2. MÓDULOS 20 4.2. MÓDULOS 21

# ejemplo: perimetro(2, 3, 2) devuelve 7


Diseñe funciones auxiliares para cada dependencia entre cantidades mencionadas en la def perimetro(a,b,c):
especificación de un problema y por cada dependencia descubierta al elaborar ejemplos return a + b + c
de casos de uso. Siga la receta de diseño para cada una de ellas. # Test
assert perimetro(2, 3, 2) == 7
De igual manera, en ocasiones podemos encontrarnos con valores que se repiten varias veces en
una misma función o programa. Claramente, si queremos modificar su valor, no nos gustarı́a tener Dado que esta función pertenece a lo que se esperarı́a fueran las funcionalidades disponibles de un
que modificarlo en cada una de las lı́neas en que aparece. Luego, lo recomendable es que sigamos una triángulo, crearemos un módulo que la almacene, cuyo nombre será triangulo. Ası́, abriremos un
definición de variable, en la que asociamos un identificador con un valor (de la misma manera que archivo con nombre triangulo.py y copiaremos nuestra función dentro de él.
a una variable le asociamos el resultado de una expresión). Por ejemplo, podemos asociarle el valor
3.14 a una variable de nombre PI para referirnos al valor de ⇡ en todas las lı́neas que necesitemos en Luego, solo nos queda definir la función de área. Sabemos que el área de un triángulo puede
nuestro programa. Ası́: calcularse en función de su semiperı́metro, representado por p, que no es más que la mitad del perı́metro
de un triángulo. La relación entre área y semiperı́metro de un triángulo de lados a, b y c está dada
PI = 3.14 por la siguiente fórmula:
p
Luego, cada vez que nos refiramos a PI, el intérprete reemplazará el valor por 3.14. A = p ⇤ (p a) ⇤ (p b) ⇤ (p c)

El usar nombres para las constantes hace más entendible el código para identificar dónde se Para traducir esta fórmula en una función ejecutable necesitamos la función raı́z cuadrada, que
reemplazan distintos valores. De igual manera, el programa se vuelve más mantenible en el caso está incluida en el módulo math de Python. Para importar un módulo externo debemos incluir la
de necesitar modificar el valor de la constante: sólo lo cambiamos en la lı́nea en que hacemos la siguiente lı́nea en nuestro módulo triángulo: import math, que literalmente significa importar un
definición, y este cambio se propaga hacia abajo cada vez que se llama al identificador. En caso módulo externo para ser usado en un programa. Para utilizar una función de un módulo, la notación a
contrario, deberı́amos modificar a mano cada una de las lı́neas en que escribimos directamente el valor. usar es modulo.funcion(...). Luego, si la función raı́z cuadrada del módulo math de Python se llama
sqrt y toma un parámetro, podemos definir la función área de un triángulo de la siguiente manera:
Formulemos esta tercera regla:
# area: num num num -> float
# calcula el área de un triangulo de lados a,b, y c
Dé nombres relevantes a las constantes que utilizará frecuentemente en su programa, y # ejemplo: area(2, 3, 2) devuelve 1.98...
utilice estos nombres en lugar de hacer referencia directa a su valor. def area(a, b, c):
semi = perimetro(a, b, c) / 2.0
area = math.sqrt(semi * (semi - a) * (semi - b) * (semi - c))
4.2 Módulos return area
# Tests
La programación modular es una técnica de diseño que separa las funciones de un programa en
assert area(3,4,5) == 6
módulos, los cuales definen una finalidad única y contienen todo lo necesario, código fuente y variables,
para cumplirla. Conceptualmente, un módulo representa una separación de intereses, mejorando la
Finalmente, nuestro módulo triangulo quedarı́a de la siguiente manera:
mantenibilidad de un software ya que se fuerzan lı́mites lógicos entre sus componentes. Ası́, dada una
segmentación clara de las funcionalidades de un programa, es más fácil la búsqueda e identificación de
errores. import math

Hasta el momento, solo hemos escrito programas en el intérprete de Python, por lo que no podemos # perimetro: num num num -> num
reutilizar el código que hemos generado hasta el momento. Para guardar código en Python, lo debemos # calcula el perimetro de un triangulo de lados a, b, y c
hacer en archivos con extensión .py. Ası́, basta con abrir un editor de texto (como por ejemplo el bloc # ejemplo: perimetro(2, 3, 2) devuelve 7
de notas), copiar las funciones que deseamos almacenar y guardar el archivo con un nombre adecuado def perimetro(a,b,c):
y extensión .py. Es importante destacar que existen muchas herramientas que destacan las palabras return a + b + c
claves de Python con diferentes colores, haciendo más claro el proceso de escribir código. Una de ellas # Test
es el IDLE de Python, cuyas instrucciones de instalación se pueden encontrar en los anexos de este assert perimetro(2, 3, 2) == 7
apunte.
# area: num num num -> float
Imaginemos ahora que queremos calcular el perı́metro y el área de un triángulo dado el largo de # calcula el area de un triangulo de lados a,b, y c
sus lados. Primero debemos definir la función perimetro que recibe tres parámetros: # ejemplo: area(3,4,5) devuelve 6...
def area(a, b, c):
# perimetro: num num num -> num semi = perimetro(a, b, c) / 2.0
# calcula el perimetro de un triangulo de lados a, b, y c
APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
4.3. PROGRAMAS INTERACTIVOS 22 4.3. PROGRAMAS INTERACTIVOS 23

>>> numero = raw input(‘Ingrese un numero’)


area = math.sqrt(semi * (semi - a) * (semi - b) * (semi - c)) Ingrese un numero10
return area >>> numero
# Test ‘10’
assert area(3,4,5) == 6 >>> doble = numero * 2
>>> doble
‘1010’

4.3 Programas Interactivos Para que la interacción entre el computador y el humano no sea solamente en una dirección,
también es posible que el programa entregue información al usuario. Ası́, un programa puede desplegar
Muchas veces se requiere crear programas que poseen algún tipo de interacción con el usuario. Por información en la consola de Python usando la función print. Esta función se utiliza escribiendo su
ejemplo, el usuario podrı́a entregar el valor de los lados de un triángulo para calcular su perı́metro o palabra clave, seguida del texto o número a imprimir, como se ve en el siguiente ejemplo:
su área. En esta sección cubriremos dos conceptos básicos de cómo interactuar con un programa de
software: pedir datos al usuario e imprimir mensajes en pantalla. print ‘Hola, mundo!’
Hola, mundo!
Para pedir datos al usuario, Python provee dos funciones: input y raw input. La primera de ellas,
input, recibe como parámetro un mensaje de tipo texto para el usuario y recupera el dato ingresado. Cuando queremos mostrar más de un texto o número en una misma lı́nea, por ejemplo dos frases
Esto lo podemos ver en el siguiente ejemplo, en el cual se le pide al usuario ingresar un número: seguidas, podemos unirlas por comas. Notemos que esto es equivalente a crear un elemento de tipo
texto generado con el operador +. Para ver como funciona, preguntemos el nombre y el apellido al
>>> input(‘Ingrese un numero ’) usuario, y luego mostrémoslo en pantalla.
Ingrese un numero 4
4 >>>nombre = input(‘Cual es su nombre?’)
Cual es su nombre?‘Enrique’
Los valores ingresados por el usuario pueden ser guardados en variables. Con la función input, el >>>apellido = input(‘Cual es su apellido?’)
tipo de la variable será el más adecuado a lo que ingrese el usuario. Es decir, si el usuario entrega un Cual es su apellido?‘Jorquera’
número, la variable será de tipo numérico, y si el usuario entrega una palabra o frase, la variable será >>> print ‘Su nombre es’, nombre, apellido
de tipo texto. Veamos el ingreso de número: Su nombre es Enrique Jorquera

>>> numero = input(‘Ingrese un numero ’) Volvamos al ejemplo del inicio, en donde calculamos el perı́metro y área de un triángulo. Ya que
Ingrese un numero 10 sabemos cómo preguntar información al usuario, serı́a interesante construir un programa que pregunte
>>> numero los lados de un triángulo al usuario y utilice nuestro módulo para calcular los valores de su perı́metro
10 y área.
>>> doble = numero * 2
>>> doble Para realizar este programa, debemos realizar tres pasos:
20
1. Importar el módulo creado;
El ingreso de texto es similar: 2. preguntar por los valores necesarios, en este caso los lados del triángulo;

>>> nombre = input(‘Cual es su nombre?’) 3. utilizar el módulo triangulo para calcular el área y perı́metro.
Cual es su nombre?‘Enrique’
>>> nombre
‘Enrique’ Ası́, primero que nada, debemos importar el módulo que creamos con la palabra clave import:

Es importante notar que al ingresar valores de tipo texto, estos deben estar entre comillas para import triangulo
ser indentificados como tal por el intérprete. Cuando el usuario intenta ingresar texto sin comillas, el
intérprete mostrará un error en pantalla. Luego, debemos preguntar por el largo de cada lado del triángulo y almacenarlos en variables cuyos
nombres sean representativos, como se muestra a continuación:
La otra función para ingresar datos disponible en Python, raw input, tiene un comportamiento
similar, con la excepción de que todo valor ingresado se almacenará con tipo texto. Esto se ve en el print ‘Calcular el area y perimetro de un triangulo’
siguiente código: l1 = input(‘Ingrese el largo del primer lado’)
l2 = input(‘Ingrese el largo del segundo lado’)

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
4.3. PROGRAMAS INTERACTIVOS 24

l3 = input(‘Ingrese el largo del tercer lado’)

Y finalmente utilizamos nuestro módulo para calcular al área y perı́metro del triángulo dado:

print ‘El perimetro del triangulo es’, triangulo.perimetro(l1, l2, l3)


print ‘El area del triangulo es’, triangulo.area(l1, l2, l3)

El programa resultante se puede ver a continuación:


Capı́tulo 5

import triangulo Expresiones y Funciones


print ‘Calcular el area y primetro de un triangulo’
l1 = input(‘Ingrese el largo del primer lado’)
Condicionales1
l2 = input(‘Ingrese el largo del segundo lado’)
l3 = input(‘Ingrese el largo del tercer lado’)

print ‘El perimetro del triangulo es’, triangulo.perimetro(l1, l2, l3) En general, los programas deben trabajar con distintos datos en distintas situaciones. Por ejemplo, un
print ‘El area del triangulo es’, triangulo.area(l1, l2, l3) videojuego puede tener que determinar cuál es la velocidad de un objeto en un rango determinado, o
bien cuál es su posición en pantalla. Para un programa de control de maquinaria, una condición puede
describir en qué casos una válvula se debe abrir. Para manejar condiciones en nuestros programas,
necesitamos una manera de saber si esta condición será verdadera o falsa. Ası́, necesitamos una
Ahora que tenemos listo nuestro programa, podemos guardarlo en un archivo .py y ejecutarlo cada
nueva clase de valores, los que, por convención, llamamos valores booleanos (o valores de verdad). En
vez que necesitemos calcular el área y perı́metro de un triángulo cualquiera (suponiendo que los valores
este capı́tulo veremos los valores de tipo booleano, expresiones que se evalúan a valores booleanos, y
entregados corresponden a un triángulo válido).
expresiones que calculan valores dependiendo del valor de verdad de una evaluación.

5.1 Valores Booleanos


Consideremos el siguiente problema:

“Genera S.A. le paga 4.500 por hora a todos sus ingenieros de procesos recién egresados. Un empleado
tı́picamente trabaja entre 20 y 65 horas por semana. La gerencia de informática le pide desarrollar un
programa que calcule el sueldo de un empleado a partir del número de horas trabajadas si este valor
está dentro del rango apropiado.”

Las palabras en cursiva resaltan qué es lo nuevo respecto al problema que presentamos en el capı́tulo
de Funciones. Esta nueva restricción implica que el programa debe manipular al valor de entrada de
una manera si tiene una forma especı́fica, y de otra manera si no. En otras palabras, de la misma
manera que las personas toman decisiones a partir de ciertas condiciones, los programas deben ser
capaces de operar de manera condicional.

Las condiciones no deberı́an ser nada nuevo para nosotros. En matemática, hablamos de
proposiciones verdaderas y falsas, las que efectivamente describen condiciones. Por ejemplo, un número
puede ser igual a, menor que, o mayor que otro número. Ası́, si x e y son números, podemos plantear
las siguientes tres proposiciones acerca de x e y:

1. x = y: “x es igual a y”;

2. x < y: “x es estrictamente menor que y”;


1 Partede este texto fue traducido al español y adaptado de: M. Felleisen et al.: How to Design Programs, MIT Press.
Disponible en: www.htdp.org

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN 25


5.1. VALORES BOOLEANOS 26 5.2. FUNCIONES SOBRE BOOLEANOS 27

3. x > y: “x es estrictamente mayor que y”. Para expresar condiciones compuestas en Python usaremos tres conectores lógicos: and (conjunción
lógica: “y”), or (disyunción lógica: “o”) y not (negación: “no”). Por ejemplo, supongamos que
Para cualquier par de números (reales), una y sólo una de estas tres proposiciones es verdadera. queremos combinar las proposiciones atómicas x == y y y < z, de tal manera que la proposición
Por ejemplo, si x = 4 y y = 5, entonces la segunda proposición es verdadera y las otras son falsas. Si compuesta sea verdadera cuando ambas condiciones sean verdaderas. En Python escribirı́amos:
x = 5 y y = 4, entonces la tercera es verdadera y las otras son falsas. En general, una proposición es
verdadera para ciertos valores de variables y falsa para otros. x == y and y < z

Además de determinar si una proposición atómica es verdadera o falsa en algún caso, a veces para expresar esta relación. De igual manera, si queremos formular una proposición compuesta que
resulta importante determinar si la combinación de distintas proposiciones resulta verdadera o falsa. sea verdadera cuando (al menos) una de las proposiciones sea verdadera, escribimos:
Consideremos las tres proposiciones anteriores, las que podemos combinar, por ejemplo, de distintas
maneras: x == y or y < z.
1. x = y y x < y y x > y; Finalmente, si escribimos algo como:
2. x = y o x < y o x > y;
not x == y
3. x = y o x < y.
lo que estamos indicando es que deseamos que la negación de la proposición sea verdadera.
La primera proposición compuesta es siempre falsa, pues dado cualquier par de números (reales)
para x e y, dos de las tres proposiciones atómicas son falsas. La segunda proposición compuesta es, Las condiciones compuestas, al igual que las condiciones atómicas, se evalúan a True o False.
sin embargo, siempre verdadera para cualquier par de números (reales) x e y. Finalmente, la tercera Consideremos por ejemplo la siguiente condición compuesta: 5 == 5 and 5 < 6. Es fácil notar que
proposición compuesta es verdadera para ciertos valores y falsa para otros. Por ejemplo, es verdadera está formada por dos proposiciones atómicas: 5 == 5 y 5 < 6. Ambas se evalúan a True, y luego, la
para x = 4 y y = 4, y para x = 4 y y = 5, mientras que es falsa si x = 5 y y = 3. evaluación de la compuesta se evalúa a: True and True, que naturalmente da como resultado True
de acuerdo a las reglas de la lógica proposicional. Las reglas de evaluación para or y not siguen el
Al igual que en matemática, Python provee comandos especı́ficos para representar el valor de verdad mismo patrón.
de proposiciones atómicas, para representar estas proposiciones, para combinarlas y para evaluarlas.
Ası́, el valor lógico verdadero es True, y el valor falso se representa por False. Si una proposición En las siguientes secciones veremos por qué es necesario formular condiciones para programar y
relaciona dos números, esto lo podemos representar usando operadores relacionales, tales como: ==, < explicaremos cómo hacerlo.
y >.

Traduciendo en Python las tres proposiciones matemáticas que definimos inicialmente, tendrı́amos 5.2 Funciones sobre Booleanos
lo siguiente:
Consideremos la siguiente función sencilla para verificar una condición sobre un número:
1. x == y: “x es igual a y”;
# esIgualA5: num -> bool
2. x < y: “x es estrictamente menor que y”; # determinar si n es igual a 5
def esIgualA5(n):
3. x > y: “x es estrictamente mayor que y”.
return n == 5
Además de los operadores anteriores, Python provee como operadores relacionales: <= (menor o
igual a), >= (mayor o igual a), y != (distinto de). Esta función produce True si y sólo si su argumento es igual a 5. Su contrato contiene un nuevo
elemento: la palabra bool. Al igual que int, float y str, la palabra bool representa una clase
Una expresión de Python que compara números tiene un resultado, al igual que cualquier otra de valores booleanos que está definida en Python. Sin embargo, a diferencia de los valores de tipo
expresión de Python. El resultado, sin embargo, es True o False, y no un número. Ası́, cuando una numérico y texto, los booleanos sólo pueden ser True o False.
proposición atómica entre dos números es verdadera, en Python se evalúa a True. Por ejemplo:
Consideremos este ejemplo un poco más sofisticado:
>>> 4 < 5
True # estaEntre5y6: num -> bool
# determinar si n está entre 5 y 6 (sin incluirlos)
De igual manera, una proposición falsa se evalúa a False: def estaEntre5y6(n):
return 5 < n and n < 6
>>> 4 == 5
False Es claro ver que esta función consume un número (entero o real) y produce True si el número está
entre 5 y 6, sin incluirlos. Una forma de entender el funcionamiento de esta función es describiendo el

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
5.3. CONDICIONES 28 5.3. CONDICIONES 29

intervalo que definen las condiciones en la recta numérica, tal como lo muestra la siguiente figura. if x == y:
print ’Son iguales!’

Una expresión condicional puede estar compuesta de más de una pregunta asociada a una respuesta.
En el ejemplo anterior, podrı́amos además decir cuál de las dos variables representa al número mayor.
Ası́, las expresiones condicionales también pueden ser de la forma:

De igual manera, si queremos operar con condiciones más complejas sobre números, un primer paso
si pregunta entonces respuesta,
puede ser determinar los rangos de definición en la recta numérica, y luego definir una función sobre
sino pregunta entonces respuesta
los valores que se pueden tomar en el (los) intervalo(s). Por ejemplo, la siguiente función determina si
...
un número está entre 5 o 6, o bien es mayor que 10:
sino pregunta entonces respuesta
# estaEntre5y6MayorQue10: num -> bool En Python, para modelar este tipo de expresiones podemos utilizar las instrucciones elif y else,
# determinar si n está entre 5 y 6 (sin incluirlos), o bien es mayor o igual que 10 como se muestra a continuación:
def estaEntre5y6MayorQue10(n):
return estaEntre5y6(n) or n >= 10
if pregunta: if pregunta:
Y la figura que representa las porciones de la recta numérica donde la función se evalúa a True es: respuesta respuesta
elif pregunta: elif pregunta:
respuesta O respuesta
... ...
elif pregunta: else:
respuesta respuesta
Esto es, cualquier número entre 5 y 6 sin incluirlos, o bien, cualquier número mayor o igual que 10.
En este caso, desarrollamos una condición compuesta componiendo los distintos trozos que definen al
intervalo donde la función se debe evaluar a verdadero. Al igual que en las expresiones condicionales, los tres puntos indican que las expresiones if pueden
tener más de una condición. Las expresiones condicionales, como ya hemos visto, se componen de dos
expresiones pregunta y una respuesta. La pregunta es una expresión condicional que al ser evaluada
5.3 Condiciones siempre debe entregar un valor booleano, y la respuesta es una expresión que sólo será evaluada si es
que la condición asociada a esta se cumple.
Imaginemos que queremos crear un programa que juegue al cachipún con el usuario, pero que siempre
le gane, independiente de lo que éste le entregue. esto significa que debemos diseñar e implementar Tomemos el ejemplo anterior, en donde comparamos las variables de tipo numérico x e y, y
un programa que, dada una jugada del usuario, entregue la jugada que le gana dadas las reglas mostremos al usuario cuál de los dos es mayor o, en su defecto, si son iguales. Ası́, tenemos tres
del cachipún. Para esto, en las dos secciones siguientes veremos las expresiones condicionales y las casos posibles: (i) ambas variables representan a números del mismo valor; (ii) x tiene un valor mayor
instrucciones que provee Python para crear funciones con este tipo de expresiones. a y; (iii) y tiene un valor mayor a x. La traducción de estas tres condiciones en el lenguaje Python
está dado como sigue:
Las expresiones condicionales se caracterizan por ser del tipo:

si pregunta entonces respuesta if x == y: if x == y:


En particular para Python, la traducción de estas expresiones está dada de la siguiente manera: print ’Son iguales!’ print ’Son iguales!’
elif x > y: elif x > y:
print ’x es mayor que y’ print ’x es mayor que y’
elif y > x: else:
if pregunta: print ’y es mayor que x’ print ’y es mayor que x’
respuesta

Al ser ejecutada, se verifica si el resultado de la evaluación de la pregunta es verdadero o falso. En Cuando se evalúa una expresión condicional completa, esto se hace en orden, evaluando cada
Python, esto quiere decir si el valor evaluado es igual a True o False. Por ejemplo, imaginemos que pregunta, o condición, una por una. Si una pregunta se evalúa como verdadero, entonces la respuesta
tenemos dos variables de tipo numérico y queremos saber si son iguales. Si las variables se llaman x e asociada a esa pregunta se evaluará y será el resultado de la expresión condicional completa. Si no
y, podemos mostrar en pantalla al usuario si es que esta condición es verdadera: es ası́, entonces se continuará con la evaluación de la siguiente pregunta y ası́ sucesivamente hasta
que alguna de las condiciones se cumpla. Esto quiere decir que para el ejemplo anterior, primero se
APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
5.3. CONDICIONES 30 5.4. BLOQUES DE CÓDIGO CONDICIONALES EN PYTHON 31

evaluará la primera pregunta (x == y) y si esta se cumple, se mostrará en consola el mensaje ’Son Finalmente, escribimos un test para probar que nuestar función se comporta de manera adecuada.
iguales!’. Si es que no se cumple, entonces seguirá con la siguiente instrucción elif y evaluará su
pregunta asociada, x > y, imprimiendo en pantalla si es que esta condición se cumple. Si no, evaluará assert jaliscoCachipun(’tijera’) == ’piedra’
la última pregunta e imprimirá el mensaje.
La definición complesta de nuestra función esta dada como sigue:
Aunque las expresiones del ejemplo anterior tienen una sintaxis algo diferente, ambas son
equivalentes. Podemos notar que la expresión de la derecha está formada solamente con instrucciones
if y elif, lo que significa que se evalúan las tres condiciones posibles de nuestro ejemplo de manera
# jaliscoCachipun: str -> str
explı́cita. Mientras que la expresión de la derecha utiliza la instrucción else, la cual indica que su
# entrega la jugada ganadora del cachipun dada una entrada valida
respuesta será evaluada solo si ninguna de las preguntas anteriores se evalúa como verdadera.
# ejemplo: jaliscoCachipun(’tijera’) debe producir ’piedra’
def jaliscoCachipun(jugada):
Volvamos a nuestro ejemplo del cachipún en el cual el usuario siempre pierde. Para diseñar un
if jugada == ’piedra’:
programa que determine la jugada ganadora dada una entrada del usuario, debemos identificar las tres
return ’papel’
situaciones posibles, resumidas a continuación:
elif jugada == ’papel’:
Si el usuario entrega piedra, el programa debe entregar papel return ’tijera’
elif jugada == ’tijera’:
Si el usuario entrega papel, el programa debe entregar tijera return ’piedra’

Si el usuario entrega tijera, el programa debe entregar piedra # test:


assert jaliscoCachipun(’tijera’) == ’piedra’
Luego, el programa completo consta de tres partes principales: (i) pedir al usuario la jugada a
ingresar; (ii) identificar la jugada que le ganará a la ingresada por el jugador humano; y por último
(iii) mostrarla en pantalla. La segunda parte estará definida en una función que, dada una entrada, Ahora que nuestra función está completa, podemos usarla para jugar con el usuario:
entregue como resultado la jugada ganadora. Ası́, siguiendo la receta de diseño, debemos, primero que
todo, escribir su contrato y formular su propósito: print ’Juego del Jalisco cachipun’
# jaliscoCachipun: str -> str jugada = input(’Ingrese una jugada (piedra, papel o tijera)’)
# entrega la jugada ganadora del cachipun dada una entrada valida jugadaGanadora = jaliscoCachipun(jugada)
def jaliscoCachipun(jugada): print (’Yo te gano con ’ + jugadaGanadora)
...

Luego, debemos agregar un ejemplo de la función:


5.4 Bloques de Código Condicionales en Python
Dado que la respuesta de una expresión condicional puede estar compuesta por más de una lı́nea
# jaliscoCachipun: str -> str de código es necesario indicar a qué pregunta pertenecen. Para esto, todas las lı́neas de código que
# entrega la jugada ganadora del cachipun dada una entrada valida pertenezcan a la respuesta de una expresión condicional deben estar indentadas un nivel más que
# ejemplo: jaliscoCachipun(’tijera’) debe producir ’piedra’ la instrucción condicional a la que pertenecen. En otras palabras, todas las lı́neas que tengan una
def jaliscoCachipun(jugada): indentación más que la cláusula if están subordinadas a esta, y se dice que forman un bloque de
... código. Un bloque de código sólo será evaluado si es que condición asociada se cumple.

A continuación debemos especificar el cuerpo: En el ejemplo que vimos anteriormente, si no agregamos la indentación correspondiente se producirá
un error al intentar ejecutar el programa:
# jaliscoCachipun: str -> str
# entrega la jugada ganadora del cachipun dada una entrada valida
# ejemplo: jaliscoCachipun(’tijera’) debe producir ’piedra’ if x == y:
def jaliscoCachipun(jugada): print ’Son iguales!’
if jugada == ’piedra’: elif x > y:
return ’papel’ print ’x es mayor que y’
elif jugada == ’papel’: elif y > x:
return ’tijera’ print ’y es mayor que x’
elif jugada == ’tijera’:
return ’piedra’

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
5.5. DISEÑANDO FUNCIONES CONDICIONALES 32 5.5. DISEÑANDO FUNCIONES CONDICIONALES 33

5.5 Diseñando Funciones Condicionales función:

Tal como vimos anteriormente, La clave para diseñar funciones que requieran expresiones condicionales if (...):
es reconocer que el enunciado del problema genera casos e identificar cuáles son. Para enfatizar ...
la importancia de esta idea, introduciremos y discutiremos la receta de diseño para las funciones elif (...):
condicionales. La nueva receta introduce un nuevo paso, llamado análisis de los datos, la cual requiere ...
que un programador entienda las diferentes situaciones que se discuten en el enunciado del problema. elif (...):
También modifica los pasos de Ejemplo y Cuerpo de la receta de diseño explicada en los capı́tulos ...
anteriores.
Luego formulamos las condiciones para describir cada una de las situaciones. Las condiciones
son proposiciones sobre los parámetros de la función, expresados con operadores relacionales o con
5.5.1 Análisis de los datos y definición funciones hechas por nosotros mismos.

Luego de determinar que en el enunciado de un problema debemos lidiar con diferentes situaciones, es Las lı́neas de nuestro ejemplo se completa para traducirse en las siguientes tres condiciones:
necesario identificar cada una de ellas.
1. (1  hora) y (hora  12)
Para funciones numéricas, una buena estrategia es dibujar una recta numérica e identificar los
2. (12 < hora) y (hora  21)
intervalos correspondientes a la situación particular a estudiar. Imaginemos que queremos implementar
un programa que retorne el saludo correspondiente a la hora del dı́a. Ası́, si son más de las 1 de la 3. (21 < hora)
mañana y menos de las 12 de la tarde, el programa responderá ‘Buenos dı́as!’; si menos de las 21 horas,
el mensaje será ‘Buenas tardes!’; y si es más de las 21, entonces el programa deseará las buenas noches. Agregando estas condiciones a la función, tenemos una mejor aproximación de la definición final:
Consideremos el contrato de esta función:
def saludo(hora)
# saludo: int -> str if (1 <= hora) and (hora <= 12):
# Determinar el saludo adecuado a la hora del dı́a 1 <= h <= 24 ...
def saludo(hora): elif (12 < hora) and (hora <= 21):
...
Esta función recibe como entrada números enteros que están dentro del rago descrito por el contrato, elif (21 < hora):
y genera respuestas para tres diferentes situaciones, indicados por los intervalos de la siguiente figura: ...

En este punto, el programador debe asegurarse que las condiciones escogidas distinguen las
diferentes entradas posibles de manera correcta. En particular, que cada dato posible esté dentro
de una posible situación o intervalo. Esto quiere decir que cuando una pregunta o condición son
evaluadas como True, todas las condiciones precedentes deben ser evaluadas como False.

5.5.4 El cuerpo de la función: la respuesta de cada condición


Finalmente, debemos determinar qué debe producir la función por cada una de las cláusulas if. Más
concretamente, debemos considerar cada expresión if por separado, asumiendo que la condición se
5.5.2 Dar ejemplos de uso la función cumple.
Los ejemplos a escoger para este paso en la receta de diseño deben considerar las diferentes situaciones En nuestro ejemplo, los resultados son especificados directamente del enunciado del problema.
posibles. Como mı́nimo, debemos desarrollar un ejemplo por cada situación. Si describimos cada Estos son ’Buenos dias!’, ’Buenas tardes!’, y ’Buenas noches!’. En ejemplos más complejos,
situación como un intervalo numérico, los ejemplos también deben incluir todos los casos de borde. debe ser el programador quien determina la expresión la respuesta de cada condición, puesto que no
siempre están descritas de manera tan explı́cita. Estas se pueden contruir siguiendo los pasos de la
Para nuestra función saludo, deberı́amos usar 1, 12, y 21 como casos de borde. Además, receta de diseño que hemos aprendido hasta ahora.
deberı́amos escoger números como 8, 16, y 22 para probar el comportamiento al interior de cada
uno de los tres intervalos.
5.5.5 Simplificando las condiciones
5.5.3 El cuerpo de la función: el diseño de las condiciones Cuando la definición de una función está completa y probada, un programador querrá verificar si es
que las condiciones pueden ser simplificadas. En nuestro ejemplo, sabemos que la hora es siempre
El cuerpo de la función debe estar compuesta de una instrucción if que tiene tantas clásulas como mayor o igual a uno, por lo que la primera condición podrı́a ser formulada como:
situaciones diferentes. Este requerimiento sugiere de inmediato el siguiente cuerpo para nuestra
APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
5.5. DISEÑANDO FUNCIONES CONDICIONALES 34 5.5. DISEÑANDO FUNCIONES CONDICIONALES 35

hora <= 12 Serı́a correcto el ejemplo? En realidad, no lo es. Si revisan los intervalos en la figura, se van a dar
cuenta que el programa devuelve un saludo equivocado para la 12, entre otros problemas. Eso muestra
Más aún, sabemos que las expresiones if son evaluadas secuencialmente. Esto es, cuando la segunda la importancia de agregar tests ANTES de escribir codigo!
condición es evaluada, la primera ya debe haber producido False. Por lo tanto sabemos que en la
segunda condicional la cantidad no es menor o igual a 12, lo que implica que su componente izquierda
es innecesaria. La definición completa y simplificada de la función saludo se describe como sigue:

# saludo: int -> str


# Determinar el saludo adecuado a la hora del dı́a 1 <= h <= 24
# ejemplo saludo(11) debe devolver ’Buenos dias!’
def saludo(hora):
if (hora <= 12):
return ’Buenos dias!’
elif (hora <= 21):
return ’Buenas tardes!’
elif (21 < hora):
return ’Buenas noches!’
# test:
assert saludo(11) == ’Buenos dias!’

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
6.1. POTENCIAS, FACTORIALES Y SUCESIONES 37

# potencia: num int -> num


# calcula la potencia de base elevado a exp
# ejemplo: potencia(2, 4) devuelve 16
def potencia(base, exp):
if exp == 0:
# caso base
Capı́tulo 6 return 1
else:
# caso recursivo
return base * potencia(base, exp - 1)
Recursión # Test
assert potencia(2, 4) == 16
assert potencia(-1, 5) == -1
assert potencia(3, 0) == 1

Muchas veces nos tocará enfrentarnos con definiciones que dependen de sı́ mismas. En particular, en Para entender cómo definir una función recursiva será clave la etapa de entender el propósito de
programación se habla de funciones y estructuras recursivas cuando su definición depende de la misma la función. Como vimos, una función cumple el objetivo de consumir y producir información. Si en
definición de éstas. En este capı́tulo veremos un par de ejemplos de funciones recursivas. el proceso de consumir información se llega al mismo problema inicial (usualmente con una entrada
o un parámetro más pequeño), entonces una solución recursiva puede ser correcta. En el ejemplo de
la potencia, vimos que, por definición, debemos multiplicar la base por otro valor, que resulta ser la
6.1 Potencias, factoriales y sucesiones misma potencia con un exponente más pequeño, en cuyo caso conviene utilizar una solución recursiva.
Por ejemplo, para calcular la potencia de un número con exponente entero, ab , podemos usar esta
definición: De manera similar podemos definir el factorial:
⇢ ⇢
1 si b = 0 1 si n = 0
ab = n! =
a · ab 1 si b > 0 n · (n 1)! si n > 0

Como vemos, cuando el exponente es mayor que 0, para calcular la potencia necesitamos la misma # factorial: int -> int
definición con un exponente menor. La evaluación una potencia termina en el caso en que el exponente # calcula el factorial de n
es 0. Por ejemplo, si queremos calcular 24 , basta con aplicar la definición: # ejemplo: factorial(10) devuelve 3628800
def factorial(n):
if n == 0:
24 = 2 · 24 1 # caso base
return 1
= 2 · 23 else:
= 2 · (2 · 22 ) # caso recursivo
= 2 · (2 · (2 · 21 )) return n * factorial(n - 1)
# Test
= 2 · (2 · (2 · (2 · 20 ))) assert factorial(0) == 1
= 2 · (2 · (2 · (2 · 1))) assert factorial(5) == 120
= 16 assert factorial(10) == 3628800

Una función que se define en términos de sı́ misma es llamada función recursiva. Otro ejemplo clásico de recursión es la generación de los números de Fibonacci. Los números de
Fibonacci forman una sucesión de números que parten de la siguiente forma:
4
Observe que si quitamos la primera parte de la definición de potencia, al calcular 2 , su evaluación
nunca llegará a término. Esta parte es necesaria para dar término a la evaluación de una función 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, . . .
recursiva, a la cual llamaremos caso base.
Se puede apreciar que cada número de la sucesión es igual a la suma de los dos números anteriores.
La segunda parte de la definición, a la cual llamaremos caso recursivo, es la que hace uso de su
Por supuesto, los dos primeros números de Fibonacci (0 y 1) son parte de la definición (caso base) de
propia definición para continuar la evaluación hasta llegar al caso base.
la sucesión, dado que no hay dos números anteriores para formarlos. El n-ésimo número de Fibonacci
se calcula sumando los dos números anteriores de la sucesión, por lo que la recursión es clara en este
Veamos cómo queda la función potencia escrita en Python:
caso. Formalmente, cada número de Fibonacci se puede calcular siguiendo esta definición:

36 APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN


6.2. TORRES DE HANOI 38 6.2. TORRES DE HANOI 39

⇢ Supongamos que ya tenemos una función hanoi(n) que nos dice cuántos movimientos hay que
n si 0  n  1 realizar para mover n discos de una vara a otra. Esa función es la que queremos definir, pero al
Fn =
Fn 1 + Fn 2 si n > 1 mismo tiempo la necesitamos para resolver el problema(!). En el ejemplo de la figura necesitamos 15
Y la implementación en Python: movimientos para resolver el puzzle.
# fibonacci: int -> int
# calcula el n-esimo numero de la sucesion de fibonacci En resumen, debemos considerar los siguientes movimientos:
# ejemplo: fibonacci(7) devuelve 13
Para mover el disco más grande de una vara a otra, necesitamos mover los n 1 discos anteriores
def fibonacci(n):
a otra vara, lo cual nos toma hanoi(n-1) movimientos.
if n < 2:
# caso base Luego, debemos mover el disco más grande de su vara a la desocupada, esto nos toma 1
return n movimiento.
else:
# caso recursivo A continuación, debemos volver a mover los n 1 discos restantes para que queden encima del
return fibonacci(n - 1) + fibonacci(n - 2) disco grande que acabamos de mover. Esto nuevamente nos toma hanoi(n-1) movimientos.
# Test
En total, necesitamos 2⇥ hanoi(n-1) +1 movimientos para n discos.
assert fibonacci(0) == 0
assert fibonacci(1) == 1 ¿Cuál es el caso base? Si tenemos 1 disco, sólo debemos moverlo de su vara a la otra para completar
assert fibonacci(7) == 13 el juego.

6.2 Torres de Hanoi


Un ejemplo de recursión más complicado es el problema de las Torres de Hanoi.

Las Torres de Hanoi es el nombre de un puzzle matemático que consiste en mover todos los discos
de una vara a otra, bajo ciertas restricciones. El juego consta de una plataforma con tres varas y n
discos puestos en orden decreciente de tamaño en una de ellas. El objetivo del juego es mover todos
los discos de una vara a la otra, de forma que al final se mantenga el mismo orden.

Las reglas del juego son las siguientes:


1. Sólo 1 disco puede ser movido a la vez.
2. No puede haber un disco más grande encima de uno más pequeño.
Figura 6.1: Mover los n 1 primeros discos, recursivamente hacia la segunda vara.
3. Un movimiento consiste en mover un disco en la cima de una pila de discos hacia otra pila de
discos puestos en otra vara.
Nos interesa saber cuántos movimientos son necesarios para resolver el juego.

Solución

La clave para resolver el puzzle no está en determinar cuáles son los movimientos a realizar, sino en
que el juego puede ser descompuesto en instancias más pequeñas. En el caso de las Torres de Hanoi,
el problema está en mover n discos. Por lo tanto, veamos una forma de resolver el problema de forma
de tener que resolver el juego con n 1 discos, y volvamos a aplicar el procedimiento hasta mover
todos los discos.

El objetivo del juego es mover la pila completa de una vara a la otra. Por lo que, inicialmente, lo
único a lo que podemos apuntar a lograr es a trasladar el disco más grande de su vara a otra, y no nos
queda otra opción que mover todos los discos restantes de su vara a otra.
Figura 6.2: Mover el último disco hacia la tercera vara.

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
6.3. COPO DE NIEVE DE KOCH 40 6.3. COPO DE NIEVE DE KOCH 41

Figura 6.4: Copo de nieve de Koch.

Figura 6.3: Volver a mover los primeros n 1 discos, recursivamente hacia la tercera vara.

Ahora que entendimos el propósito y la solución del juego, podemos escribirla en Python:

# hanoi: int -> int


# calcula la cantidad de movimientos necesarios para resolver
# las Torres de Hanoi con n discos y 3 varas
# ejemplo: hanoi(4) devuelve 15
def hanoi(n):
if n == 1:
# caso base
return 1 Figura 6.5: 4 primeras iteraciones de la generación del fractal. Fuente: Wikipedia.
else:
# caso recursivo
return 2 * hanoi(n - 1) + 1 sin él, la figura se irá generando indefinidamente.
# Test
assert hanoi(1) == 1 Para programar nuestro copo de nieve en Python, utilizaremos el módulo Turtle que viene incluido
assert hanoi(4) == 15 en el lenguaje. El módulo Turtle provee varias funciones que dibujan en la pantalla. Conceptualmente,
assert hanoi(5) == 31 se trata de una tortuga robótica que se mueve en la pantalla marcando una lı́nea a su paso. Algunas
de las funciones provistas son:
Se puede apreciar que la solución de Hanoi sigue un patrón especial. De hecho, la solución a la
ecuación de recurrencia h(n) = 2 · h(n 1) + 1 es h(n) = 2n 1, por lo que si hubiéramos llegado a turtle.forward(size): Se mueve size pixeles en su dirección.
ese resultado, podrı́amos utilizar la función potencia para resolver el problema. turtle.left(angle), turtle.right(angle): La tortuga gira a la izquierda o a la derecha,
respectivamente, dependiendo de su sentido, en angle grados.
6.3 Copo de nieve de Koch turtle.speed(speed): Se establece la velocidad de la torturga. El parámetro speed = 0 indica
que se mueve a la máxima velocidad.
Otro ejemplo interesante de recursión es el copo de nieve de Koch. El copo de nieve de Koch es un
fractal cuya forma es similar a un copo de nieve. En la Figura 6.4 se puede apreciar un ejemplo. turtle.done(): Se le indica que se han terminado las instrucciones para la tortuga.

El objetivo es describir el contorno de la figura hasta cierto nivel (puesto que el perı́metro de la Con estas funciones podemos indicarle cómo dibujar un fractal. Sin importar dónde comencemos,
figura final es infinito). Para esto, es necesario describir un poco más en detalle la figura. debemos dibujar un triángulo equilátero y al avanzar 1/3 de su lado, se dibuja un fractal nuevamente.
La Figura 6.6 muestra cómo deberı́a quedar nuestra implementación.
El fractal se genera a partir de un triángulo equilátero de lado s. A s/3 de distancia de un vértice Analicemos el proceso de dibujar el copo de nieve. Observe que el copo de nieve se trata de dibujar
se genera otro trı́angulo equilátero, de lado s/3. A distancia (s/3)/3 del vértice del último trı́angulo, un triángulo equilátero, por lo que podemos dividir el problema en dibujar sólo un lado (puesto que
se vuelve a generar otro más, y ası́ sucesivamente. En la Figura 6.5 se pueden apreciar 4 iteraciones los otros dos son iguales, salvo por el ángulo de donde viene). Supongamos que tenemos una función
del proceso. snowflake que dibuja los tres lados. Cada lado debe ser dibujado usando la curva de Koch.
¿Cómo podemos describir su contorno de manera recursiva? No es difı́cil observar que al generar
la figura, al estar parados en algún triángulo de alguna iteración, a 1/3 del lado de distancia de un La función que dibuja cada lado la llamaremos koch , que representará la curva de Koch. Esta
vértice comenzamos a generar ¡la misma figura! El caso base lo debemos definir nosotros, puesto que función debe dibujar cada lado del trı́angulo actual. Esta función deberı́a recibir el lado del triángulo
APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
6.3. COPO DE NIEVE DE KOCH 42 6.3. COPO DE NIEVE DE KOCH 43

Figura 6.7: Primer intento del copo de nieve, juntando las tres curvas de Koch de la primera
implementación.

Para solucionar este problema, modifiquemos nuestro algoritmo para que sólo dibuje una lı́nea recta
cuando llegamos al lı́mite del tamaño:

1. Si min size size , avanzar size en la dirección actual.


2. Si no:

Dibujar la misma curva de Koch de lado 1/3 ⇥ size .


Girar a la izquierda 60 grados.
Dibujar la misma curva de Koch de lado 1/3 ⇥ size .
Girar a la derecha 120 grados.
Figura 6.6: El resultado de dibujar el copo de nieve con Turtle Dibujar la misma curva de Koch de lado 1/3 ⇥ size .
Girar a la izquierda 60 grados.
inicial y el caso base, es decir, el tamaño mı́nimo a partir del cual no debe continuar la recursión. Dibujar la misma curva de Koch de lado 1/3 ⇥ size .
Llamemos a estos parámetros size y min size , respectivamente.
Una implementación posible es la siguiente: Al graficar esta implementación, resultará en lo que se puede ver en la Figura 6.8.

1. Avanzar 1/3 ⇥ size en la dirección actual.


2. Girar a la izquierda 60 grados.
3. Dibujar la misma curva de Koch de lado 1/3 ⇥ size .
4. Girar a la derecha 120 grados.
5. Dibujar la misma curva de Koch de lado 1/3 ⇥ size .
Figura 6.8: La curva de Koch para un lado del triángulo.
6. Girar a la izquierda 60 grados.
La implementación en Python final:
7. Avanzar 1/3 ⇥ size en la dirección actual.

Sin embargo, tiene un error. Si dibujamos esta curva tres veces y creamos el triángulo con import turtle
snowflake , resultará en lo que se pude apreciar en la Figura 6.7. Al separar nuestra función en
dos, una que dibuja un lado y la otra que usa la primera para dibujar los tres lados, hemos perdido # koch: int int ->
información. En particular, los vértices de los triángulos que se forman indirectamente al juntar las # dibuja la curva de koch de largo size y largo minimo min size
tres curvas (que son iguales al primero, al ser equiláteros del mismo tamaño) no generan sub-triángulos # ejemplo: koch(320, 1)
y no se forma la figura del copo de nieve. Para esto debemos generar más sub-triángulos incluso en def koch(size, min size):
esos vértices. if (size <= min size):

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
6.4. RECETA DE DISEÑO PARA LA RECURSIÓN 44 6.4. RECETA DE DISEÑO PARA LA RECURSIÓN 45

6. Aplicar el problema a uno de los ejemplos


# caso base
turtle.forward(size) (a) Tomar un ejemplo: 2 potencia 3 = 8
else: (b) Determinar los parametros y el resultado
# caso recursivo Los parametros son 2 y 3, el resultado 8
koch(size / 3, min size)
(c) Determinar cuales de los parametros son piezas del problema
turtle.left(60)
2 es la base, y 3 es la potencia
koch(size / 3, min size)
turtle.right(120) (d) Cual es la repuesta para la llamada recursiva?
koch(size / 3, min size) Para reducir el problema, tenemos que restar uno a la potencia, entonces la
turtle.left(60) llamada recursiva es: 2 potencia 2 = 4
koch(size / 3, min size) (e) Determinar como se combinan los elementos para determinar la respuesta final
dado que 2 potencia 2 = 4, multiplicamos este resultado por la base (2), y tenemos el
# snowflake: int int -> resultado final, 8
# dibuja el copo de nieve de Koch de un triangulo de lado size
# y lado minimo min size 7. Ocupar la receta de diseño normal, tomando en cuenta que el patron de base es una funcion
# ejemplo: snowflake(320, 1) condicional, una rama siendo el caso de base, la otra siendo el caso recursivo.
def snowflake(size, min size):
koch(size, min size)
turtle.right(120)
koch(size, min size)
turtle.right(120)
koch(size, min size)

# ejemplo de uso
turtle.speed(0)
snowflake(320, 1)
turtle.done()

6.4 Receta de diseño para la recursión


Cuando escribimos funciones recursivas, es necesario seguir una versión distinta de la receta de diseño.
En efecto, antes de escribir el codigo, tenemos que seguir varios pasos bien definidos.

1. Escribir varios ejemplos de uso de la funcion (incluyendo parametros y resultado).


2 potencia 4 = 16
2 potencia 0 = 1
2 potencia 3 = 8

2. Decidir cual de los argumentos va a tener una descomposición recursiva.


El argumento con el procedimiento recursivo es la potencia
3. Entender cual es el caso de base para este argumento.
Cuando la potencia es igual a 0, el resultado es uno
4. Entender cual es el paso recursivo, como se descompone el problema
Uno resta una a la potencia, y multiplica
5. Nombrar cada pieza del problema
Tenemos una “base”, y una “potencia”

APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN
7.1. AFIRMACIONES O ASSERTIONS 47

de usar en los tests. En el caso de la potencia, la definición cambia de acuerdo al valor del exponente,
por lo que el caso de borde serı́a un buen test para nuestra función:

assert potencia(10000000, 0) == 1

Si tenemos una función, sea o no recursiva, con casos de borde o extremos dentro del dominio
Capı́tulo 7 de datos, debemos testearla en esos casos. En general, debemos probar nuestra función en casos
representativos. Por ejemplo, en potencia, el primer y último test son redundantes. El segundo es
relevante ya que usamos otro tipo de datos para la base. Y el último también ya que usamos un caso
de borde.
Testeo y depuración de programas
7.1 Afirmaciones o Assertions
Hasta el momento hemos visto usar la sentencia assert (afirmación) de Python para probar nuestras
En este capı́tulo veremos formas y técnicas de testo y depuración de programas, basándonos en el funciones. La sintaxis especı́fica es la siguiente:
diseño por contrato visto en el capı́tulo de Receta de Diseño.
assert <condicion>
Tal como vimos en aquel capı́tulo, es necesario probar que la función que definamos cumpla con el
contrato estipulado. Estas pruebas deben asegurar con suficiente certeza de que la función cumple su Como observación, assert no es una expresión. No puede ser evaluada y asignada a una variable,
objetivo de acuerdo a los parámetros ingresados. En este punto el contrato es muy importante, ya que sino que puede ser vista como una sentencia o una palabra clave de Python que realiza una acción. Por
especifica los tipos de datos y sus dominios que serán considerados dentro de la función. No es factible otra parte, <condicion> sı́ corresponde a una expresión, por lo que se puede usar cualquier expresión
probar todos los posibles parámetros de una función, pero el contrato disminuye considerablemente que se evalúe a un valor de tipo Boolean en el lugar de <condicion>. Por ejemplo:
estas opciones. De los casos restantes, debemos escoger sólo los casos más representativos.
assert True
Por ejemplo, consideremos la función maximo, que tiene el siguiente contrato: assert 10 < 12
assert a == b and (c < d or a < b)
# maximo: num num -> num
# devuelve el mayor de ambos números, a y b Cuando la condición se evalúa a True, la afirmación no hace nada y el programa puede continuar.
# ejemplo: maximo(2, 4) devuelve 4 Cuando la condición evalúa a False (es decir, si no se cumple), la afirmación arroja un error y el
def maximo(a, b): programa termina:
...
>>> assert False
El contrato establece que los parámetros de la función deben ser numéricos, por lo que el siguiente Traceback (most recent call last):
no serı́a un buen test: File "<stdin>", line 1, in <module>
AssertionError
assert maximo(’hola’, 5) == ’hola’

En cambio, este serı́a un buen test para la función: Esto es útil para probar el buen funcionamiento de nuestras funciones. Claramente, el
comportamiento de las afirmaciones sugiere que Ud. debe escribirlas antes de escribir el código de su
assert maximo(10, 20) == 20 función, con valores que Ud. haya calculado antes. Recuerde que el código es una representación de
su solución, no la solución.
Consideremos la función potencia definida en el capı́tulo de Recursión. Esta función acepta como
parámetros un número como base, y un entero como exponente. Podrı́amos probar distintos casos de El uso de distintos operadores lógicos para assert nos ayuda a escribir mejores tests. Observe que
esta forma: el uso de más operadores no es una caracterı́stica especial de la afirmación, sino que corresponde a que
la condición que se pasa es una expresión, y en ella se puede usar cualquier operador booleano.
assert potencia(2, 4) == 16
assert potencia(1.5, 3) == 3.375 En particular, podemos utilizar operadores no sólo de igualdad, sino también de comparación (< o
assert potencia(10, 3) == 1000 >, etc). Por ejemplo, suponga que tenemos una función que calcula un número al azar entre 1 y 10.
¿Cómo hacemos un test para eso, si cada evaluación de la función resultará en un valor distinto? En
Sin embargo, tal como definimos potencia, tanto en código como matemáticamente, hay casos este caso, la observación clave está en que no nos importa qué valor tome la función, sino el rango de
llamados de borde, es decir, extremos dentro del dominio de datos que acepta, que serı́an relevantes valores. Por ejemplo:

46 APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN


7.2. TESTEAR CON NÚMEROS REALES 48 7.2. TESTEAR CON NÚMEROS REALES 49

import random dy = (y1 - y0)


# escogeAlAzar: -> int return (dx ** 2 + dy ** 2) ** 0.5
# Devuelve un número al azar entre 1 y 10
def escogeAlAzar(): Consideremos las siguientes expresiones:
# random.random() retorna un número al azar entre 0 y 1
return int(10 * random.random()) + 1 >>> d1 = distancia(0.1, 0.2, 0.2, 0.1)
# Test 0.14142135623730953
assert escogeAlAzar() <= 10 >>> d2 = distancia(1, 2, 2, 1)
assert escogeAlAzar() >= 1 1.4142135623730951 >>> 10 * d1 == d2
False
Si nuestra función retorna de un tipo booleano, no es necesario indicar la igualdad. Si tenemos una
función esPar que retorna True si su argumento es un entero par, y False si no, puede ser probada Conceptualmente, la última expresión debı́a ser verdadera, pero fue evaluada a False por errores
de la siguiente forma: de precisión. Para esto usamos un valor de tolerancia ✏ para evitar estos problemas:

assert esPar(4) >>> epsilon = 0.00001


assert esPar(5) == False >>> abs(10 * d1 - d2) < epsilon
True
Dado que esPar evalúa a un booleano, no es necesario indicar la igualdad. Si retorna False, es
necesario indicarla, puesto que en caso contrario la afirmación arrojará error al no cumplirse. El valor de epsilon dependerá de cuánta precisión necesitemos para nuestro programa. La función
abs devuelve la valor absoluta de su argumento: abs(1) == 1, abs(-1) == 1, etc. Es necesario porque
sin esto, una valor muy grande de d2 cumplirı́a con la asserción. Ahora podemos definir nuestros tests:
7.2 Testear con números reales
# Tests
En Python, y en la gran mayorı́a de los lenguajes de programación, los números reales son representados
assert distancia(0, 0, 4, 0) == 3.0
en el computador utilizando aritmética de punto flotante. Los números reales son infinitos, pero una
assert abs(distancia(0, 1, 1, 0) - 1.4142) < 0.0001
máquina es finita y por lo tanto es necesario representar sólo un subconjunto finito de los números
reales en un computador.

Estos números están sujetos a errores de precisión. Es decir, algunas operaciones no serán exactas
debido a que no existe una representación para cada número posible. Puede intentar este ejemplo en
el intérprete:

>>> 0.1 + 0.2


0.30000000000000004

Los errores de precisión también pueden propagarse. Por ejemplo, considere dos cantidades, a0 y
a1 , que en su representación en un computador poseen errores e0 y e1 respectivamente (como 0.1+0.2
en Python). Si multiplicamos estos valores, el error se amplifica:

(a0 + e0 ) · (a1 + e1 ) = a0 a1 + a0 e1 + a1 e0
Con esto en mente, ¿cómo podemos estar seguros de que una función que manipule números de
punto flotante está correcta con respecto a nuestras pruebas? Para esto utilizamos una tolerancia, o
epsilon ✏, comparar dentro de un rango de valores.

Implementemos la función distancia euclidiana que calcula la distancia entre dos puntos dados por
sus coordenadas, x0 , y0 , x1 , y1 .

# distancia: num num num num -> num


# calcula la distancia euclidiana entre (x0, y0) a (x1, y1)
# ejemplo: distancia(1, 0, 4, 0) devuelve 3.0
def distancia(x0, y0, x1, y1):
dx = (x1 - x0)
APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN APUNTE DE USO INTERNO PROHIBIDA SU DISTRIBUCIÓN

También podría gustarte