Cibernetia > Manuales > Python
Búsqueda personalizada







Capítulo 5 Condiciones y recursión

5.1 Operador de módulo

El operador de módulo trabaja con enteros y devuelve el resto que se obtiene al dividir el primer operando entre el segundo. En Python, el operador de módulo es el signo de porcentaje (%). Tiene la misma sintaxis que otros operadores:

>>> cociente = 7 / 3
>>> print cociente
2
>>> resto = 7 % 3
>>> print resto
1

Así, 7 dividido entre 3 es 2 con resto 1.

El operador módulo resulta ser sorprendentemente útil. Por ejemplo, puedes comprobar si un número es divisible por otro: si x % y es 0, entonces x es divisible por y.

También sirve para extraer el dígito o los dígitos que quedan más a la derecha en un número. Por ejemplo, x%10 devuelve el dígito más a la derecha de x (en base 10). De forma análoga, x%100 devuelve los 2 últimos dígitos.

5.2 Expresiones booleanas

Llamamos booleana a una expresión que puede ser verdadera o falsa. Los siguientes ejemplos utilizan el operador ==, que compara dos operandos y devuelve True si son iguales y False en caso contrario:

>>> 5 == 5
True
>>> 5 == 6
False

Verdadero y Falso son valores especiales que pertenecen al tipo bool; no son cadenas:

>>> Tipo (True)

>>> Tipo (Falso)

El operador == es uno de los operadores de comparación. Hay más:

       x == y  # x es igual a y
       x != y  # x no es igual a y
       x > y   # x es mayor que y
       x < y   # x sea menor y
       x <= y  # x es mayor o igual a y
       x <= y  # x es menor o igual a y

Si bien estas operaciones te pueden resultar familiares, los signos que utiliza Python son diferentes de los símbolos matemáticos. Un error común es utilizar un solo signo igual (=) en lugar de un doble signo igual (==). Recuerde que = es un operador de asignación y == es un operador de comparación. No existen cosas como =< o =>.

5.3 Operadores lógicos

Existen tres operadores lógicos: and, or, y not. La semántica (significado), de estos operadores es similar a su significado en inglés. Por ejemplo, x > 0 and x < 10 es verdad sólo si x es mayor que 0 y menor que 10.

n % 2 == 0 or n % 3 == 0 es cierto si cualquiera de las condiciones es verdadera, es decir, si el número es divisible entre 2 o entre 3.

Por último, el operador not niega una expresión booleana, de modo que not(x > y) es verdadero si x > y es falso, es decir, si x es menor o igual que y.

Estrictamente hablando, los operandos de los operadores lógicos deberían ser expresiones booleanas, pero Python no es demasiado estricto en esto. Cualquier número distinto de cero se interpreta como "verdadero".

>>> 17 y True
True

Esta flexibilidad puede ser útil, pero hay algunas sutilezas a podrían inducir a error. A menudo es mejor evitarlas y usar sólo expresiones booleanas como operandos (a menos que uno tenga muy claro lo que está haciendo).

5.4 ejecuciones condicionales

Con el fin de escribir programas útiles se requeren elementos que permitan verificar ciertas condiciones para que el comportamiento del programa se adecue al cumplimiento o no de tales condiciones. Las estructuras condicionales nos dan esta capacidad. La forma más simple consiste en una declaración de este tipo:

if x> 0:
     print "x es positivo" 

La expresión booleana después de la declaración if es la condición. Si se cumple (es decir, si la expresión es verdadera), se ejecuta la declaración escrita con sangrado. Si no se cumple la condición, no sucede nada.

Las declaraciones if tienen la misma estructura que las definiciones de funciones: una cabecera seguida de una sangría de bloque. Este tipo de declaraciones se llaman declaraciones compuestas.

No hay un límite en el número de declaraciones que pueden aparecer en el cuerpo, pero tiene que haber al menos una. De vez en cuando, es útil disponer de un cuerpo sin declaraciones (por lo general, como una forma de estructurar código que aún no se han escrito). En tal caso, puedes utilizar la declaración pass, que, sencillamente, no hace nada.

if x < 0:
     pass # necesitamos manejar los valores negativos: desarrollaremos el código otro día

5.5 Ejecución alternativa

Una segunda forma de la declaración if es la ejecución alternativa, en la que existen dos posibilidades y la condición determina cuál de las dos posibilidades se ejecuta. La sintaxis es esta:

if x % 2 == 0:
     Impresión 'x es par'
else:
     Impresión 'x es impar'

Si el resto de dividir x entre 2 es 0, sabemos que x es par, y el programa muestra un mensaje que informa de ello. Si la condición es falsa, se ejecuta la segunda serie de declaraciones. Dado que la condición ha de ser forzosamente verdadera o falsa, se ejecutará una de las dos alternativas. Las alternativas son llaman "ramas", porque son ramas del flujo de ejecución.

5.6 Condiciones encadenadas

A veces hay más de dos posibilidades y necesitamos más de dos ramas. Una forma de expresar un algoritmo de este tipo es mediante condiciones encadenadas:

if x < y:
     print "x es menor que y"
elif x > y:
     print "x es mayor que y"
else:
     print "x e y son iguales"
elif es una abreviatura de "si no, si" Una vez más, sólo una de las ramas se ejecutará. No hay límite para el número de declaraciones elif. No tiene por qué haber una cláusula else, pero en caso de haber una, tiene que estar al final.
if choice == 'A':
    functionA()
elif choice == 'B':
    functionB()
elif choice == 'C':
    functionC()

Las condiciones se verifican una a una ordenadamente. Si la primera es falsa, se comprueba la siguiente, y así sucesivamente. Si una de ellos es cierta, se ejecuta hasta el final el código de la rama correspondiente. Si existe más de una condición verdadera, sólo se ejecuta la rama de la primera.

5.7 Condiciones anidadas

Una condición también puede anidarse dentro de otra. Podríamos haber escrito el ejemplo anterior de esta guisa:

if x == y:
    print 'x and y are equal'
else:
    if x < y:
        print 'x is less than y'
    else:
        print 'x is greater than y'

La estructua condicional "exterior" contiene dos ramas. La primera rama contiene una declaración. La segunda rama, en cambio, contiene otra sentencia if con dos cláusulas. Estas dos ramas son a su vez declaraciones, aunque podían haber sido también nuevas condiciones

.

Aunque el sangrado de las declaraciones hace que la estructura sea bastante legible, un anidamiento en cascada puede afectar a la legibilidad del código. En general, siempre que sea posible, es una buena idea evitar los anidamientos muy complejos.

Los operadores lógicos ofrecen una manera de simplificar los anidamientos de condiciones. Por ejemplo, podemos reescribir el siguiente código utilizando una sola condición:

if 0 


La impresión se ejecuta sólo si son ciertas las dos condiciones, de modo que podemos obtener el mismo efecto con el operador:

if 0 

5.8 Recursión

No sólo está permitido que una función llame a otra, sino que incluso puede llamarse a sí misma. Puede que no sea evidente la utilidad de esta característica de Python, pero es una de las trucos más mágicos que un programa puede hacer. Por ejemplo, echa un vistazo a la siguiente función:
def countdown(n):
    if n <= 0:
        print 'patapum'
    else:
        print n
        countdown(n-1)

Si n es 0 o negativo, devuelve la palabra "patapum"; en caso contrario, devuelve n y llama a una función con nombre countdown (es decir, se llama a sí misma) pasando n - 1 como argumento.

¿Qué sucede si llamamos a esta función del siguiente modo?

>>> countdown(3)

La ejecución de countdown comienza con n = 3, y dado que n es mayor que 0, imprime el valor 3, y luego se llama a sí misma...

La ejecución de countdown comienza con n = 2, y dado que n es mayor que 0, imprime el valor 2, y luego se llama a sí misma...

La ejecución de countdown comienza con n = 1, y dado que n es mayor que 0, imprime el valor 1, y luego se llama a sí misma...

La ejecución de la countdown comienza con n = 0, y dado que n no es mayor que 0, imprime la palabra "patapum" y después retorna.

La cuena atrás en que n = 1 retorna.

La cuena atrás en que n = 2 retorna.

La cuena atrás en que n = 3.

Y volvemos a __main__. De modo que la salida final tiene esta pinta:

3
2
1
patapum

Una función que se llama a sí misma es recursiva, y este proceso se llama recursión.

Como ejemplo añadido, podemos escribir una función que imprime una cadena n veces.

def print_n(s, n):
    if n <= 0:
        return
    print s
    print_n(s, n-1)

Si n <= 0 la instrucción return provoca que el program salga de la función. El flujo de ejecución regresa de inmediato al punto desde el cual se llamó a la función, y el resto de líneas de la función no se ejecuta.

El resto de la función es similar a countdown: si n es mayor que 0, muestra s, y a continuación se llama a sí misma para mostrar s un número de veces adicionales igual a n - 1. En consecuencia, el número de líneas de salida es 1 + (n 1), lo cual, como sabrás calcular si conservas en la mollera algunos rudimentos de álgebra, da como resultado n.

En ejemplos sencillos como este es probablemente más sencillo usar un bucle for. Pero veremos más adelante ejemplos que son difíciles de escribir con un bucle y, en cambio, se prestan de forma sencilla a utilizar recursiones, por lo que es bueno comenzar desde ya.

5.9 Diagramas de pila de funciones recursivas

En la sección 3.10 se utilizó un diagrama de pila para representar el estado de un programa durante la llamada a una función. El mismo tipo de diagrama puede ayudar a interpretar una función recursiva.

Cada vez que se llama a una función, Python crea un nuevo marco de la función, que contiene las variables locales y los parámetros de la función. Para una función recursiva, podría haber en la pila más de un marco al mismo tiempo.

Esta figura muestra un diagrama de pila para countdown con n = 3:

Como de costumbre, en la cabecera de la pila está el marco de __main__. Está vacío, ya que no hemos creado ninguna variables en __main__ ni le hemos pasado ningún argumento.

Los cuatro marcos de countdown tienen diferentes valores para el parámetro n. La parte inferior de la pila, donde n = 0, se llama "caso base". No hace ninguna llamada recursiva, de modo que ya no hay marcos.

Dibuje un diagrama de pila para print_n llamado a la función con s = 'Hola' y n = 4.

5.10 Recursión infinita

Si una recursión nunca alcanza un caso base, hará llamadas recursivas indefinidamente y el programa no acabará nunca. Esto se conoce como recursión infinita, y en general no es una buena idea. Aquí tenemos un programa mínimo con una recursión infinita:

def recurse():
    recurse()
En la mayoría de entornos de programación, un programa con recursión infinita en realidad no funcionará siempre. Python informará mediante un mensaje de error cuando se alcance la profundidad máxima de recursión:
  
  File "<stdin>", line 2, in recurse
  File "<stdin>", line 2, in recurse
  File "<stdin>", line 2, in recurse
                  .  
                  .
                  .
  File "<stdin>", line 2, in recurse
RuntimeError: Maximum recursion depth exceeded
Este rastreo es un poco más grande que el que vimos en el capítulo anterior. Cuando el error se produzca, ¡habrá 1000 marcos de recurse en la pila!

5.11 introducción de datos mediante el teclado

Los programas que hemos escrito hasta el momento son un tanto groseros, en el sentido de que no aceptan que el usuario participe en nada. Se limitan a hacer siempre lo mismo.

Python proporciona una función nativa de nombre raw_input que recibe la entrada desde el teclado. Cuando se llama a esta función, el programa se detiene y espera a que el usuario escriba algo. Cuando el usuario pulsa la tecla Return (o Enter), el programa se reanuda y raw_input devuelve una cadena que contiene lo que escribió el usuario.
>>> input = raw_input()
What are you waiting for?
>>> print input
What are you waiting for?

Antes de llamar a raw_input, es una buena idea imprimir una prompt indicando al usuario lo que debe introducir. raw_input toma un prompt como argumento:

>>> name = raw_input('What...is your name?\n')
What...is your name?
Arthur, King of the Britons!
>>> print name
Arthur, King of the Britons!

La secuencia \n al final de la cadena representa un línea nueva, que es un carácter especial que causa una salto de línea. Por eso los datos que introduce el usuario aparecen debajo del símbolo del sistema.

Si es de esperar que el usuario introduzca un entero, puedes tratar de convertir el valor de retorno al tipo int:

>>> prompt = 'What...is the airspeed velocity of an unladen swallow?\n'
>>> speed = raw_input(prompt)
What...is the airspeed velocity of an unladen swallow?
17
>>> int(speed)
17

Pero si el usuario escribe algo más que una cadena de dígitos, obtiene una excepción:

>>> speed = raw_input(prompt)
What...is the airspeed velocity of an unladen swallow?
What do you mean, an African or a European swallow?
>>> int(speed)
ValueError: invalid literal for
int()

Veremos cómo manejar este tipo de error más tarde.

5.12 Depuración

El traceback que muestra Python cuando se produce un error contiene una gran cantidad de información, tanta que puede abrumar, sobre todo cuando hay muchos marcos en la pila. La información más útil es la que se refiere a:

  • qué clase de error se ha producido, y
  • dónde se ha producido.

Normalmente los errores de sintaxis son fácil de encontrar, pero hay algunas gotchas. Los errores relacionados con los espacios en blanco pueden ser complicados, porque los espacios y las tabulaciones son invisibles y estamos acostumbrados a hacer caso omiso de ellos.

>>> x = 5
>>>  y = 6
  File "<stdin>", line 1
    y = 6
    ^
SyntaxError: invalid syntax

En este ejemplo, el problema está en que en la segunda línea hay un sangrado de un espacio, y no debería haberlo. El mensaje de error apunta a y, lo cual es un tanto confuso si no estás familiarizado con este tipo de mensajes.

En general, los mensajes de error indican dónde se descubrió el error, pero el verdadero error podría ser anterior en el código, incluso en una línea previa.

Lo mismo sucede con los errores en tiempo de ejecución. Supón que tratas de calcular un ratio signal to noise expresado en decibelios. La fórmula es SNRdb = 10 log 10 (Psignal / Pnoise). En Python, puede escribir algo como esto:
import math
signal_power = 9
noise_power = 10
ratio = signal_power / noise_power
decibels = 10 * math.log10(ratio)
print decibels

Pero cuando lo ejecutes, recibirás un mensaje de error:

Traceback (most recent call last):
  File "snr.py", line 5, in ?
    decibels = 10 * math.log10(ratio)
OverflowError: math range error

El mensaje de error acusa a la línea 5, pero no hay nada raro en ella. Para encontrar el verdadero error, podría ser útil imprimir el valor ratio, que resulta ser 0. El problema está en la línea 4, ya que dividir dos enteros significa hacer la división entera. La solución pasa por representar las variables signal_power y noise_power mediante números de coma flotante.

Y eso me lleva al Cuarto Teorema de Depuración:

Los mensajes de error informan sobre dónde se ha descubierto el problema, pero no siempre informan sobre dónde se ha originado.

5.13 Glosario

Operador de módulo: el operador, indicado mediante un signo de porcentaje (%), que devuelve el resto que resulta de dividir dos números.

Expresión booleana: una expresión que puede ser verdadera o falsa.

Operador de comparación: uno de los operadores que comparan valores: ==,! =,>, <,> = y <=.

Operador lógico: uno de los operadores que combinan expresiones booleanas: and, or, y not.

Operador condicional: una declaración que controla el flujo de ejecución dependiendo de si se cumplen o no ciertas condiciones.

Condición: la expresión booleana en una sentencia condicional que determina si la subdivisión de código se ejecuta o no.

Compuesta: una declaración que consiste en una cabecera y un cuerpo. La cabecera termina con dos puntos (:). El cuerpo está sangrado en relación con la cabecera.

Cuerpo: la secuencia de declaraciones dentro de una declaración compuesta.

Rama: una de las varias secuencias de declaraciones en una sentencia condicional.

Condicionales encadenados: una sentencia condicional con una serie de otras ramas.

Recursión: el proceso mediante el cual una función se llama a sí misma.

Caso base: en una función recursiva, una rama de la sentencia condicional que no da lugar a una llamada recursiva.

Recursión infinita: una función que se llama a sí misma recursivamente sin llegar jamás al caso base. Al final, una recursión infinita acaba causando un error en tiempo de ejecución.

5.14 Ejercicios

  1. Escribe una función que señala a redes como esta, en cualquier size1:
           + - - - - - - - - + - - +
           | | |
           | | |
           | | |
           | | |
           + - - - - - - - - + - - +
           | | |
           | | |
           | | |
           | | |
           + - - - - - - - - + - - +

    Sugerencia: para imprimir más de un valor en una línea, puedes imprimir una secuencia separada por comas:

    print  '+', '-'

    Si la secuencia termina con una coma, Python sale de la línea inconclusa, por lo que el valor impreso siguiente aparece en la misma línea.

    print '+',
    print '-'

    La salida de estas declaraciones es ' + - '.



Los siguientes ejercicios utilizan TurtleWorld, del capítulo 4:

  1. Leer la siguiente función e intenta averiguar lo que hace. A continuación, ejeútala (ver los ejemplos en el Capítulo 4).
    def draw(t, length, n):
        if n == 0:
            return
        angle = 50
        fd(t, length*n)
        lt(t, angle)
        draw(t, length, n-1)
        rt(t, 2*angle)
        draw(t, length, n-1)
        lt(t, angle)
        bk(t, length*n)
  2. Para dibujar una curva de Koch de longitud x, todo lo que tiene que hacer es:
    1. Dibujar una curva de Koch de longitud x / 3.
    2. Girar a la izquierda 60 grados.
    3. Dibujar una curva de Koch de longitud x / 3.
    4. Girar a la derecha 120 grados.
    5. Dibujar una curva de Koch de longitud x / 3.
    6. Girar a la izquierda 60 grados.
    7. Dibujar una curva de Koch de longitud x / 3.

    La única excepción se produce cuando x es menor que 2. En tal caso, puedes trazar una línea recta con longitud x.

    Escribe una función llamada koch que tome como parámetros una tortuga y una longitud, y que utilice la tortuga para dibujar una curva de Koch de longitud dada. Debería parecerse a esto (basado en un ejercicio de Oualline, Prácticas de programación en C, Third Edition, O'Reilly, 1997):

  3. Escribe una función llamada snowflake que utilice tres curvas de Koch para dibujar el perfil de un copo de nieve.
Manuales | Tesis: Ordenadores, Circuitos integrados...
english
Cibernetia