Siguiente Subir Anterior Aprende a pensar como un informático Índice

Capítulo 6:

Comentarios

Iteración

6.1 Asignación múltiple

Como ya habrás descubierto, es válido hacer más de una asignación a la misma variable. Una nueva asignación hace que una variable existente haga referencia a un valor nuevo y deje de referirse al antiguo.

pedro = 5
print pedro,
pedro = 7
print pedro

La salida de este programa es 5 7, ya que la primera vez que se imprime pedro, su valor es 5 y, la segunda, 7. La coma que aparece al final de la sentencia print suprime la nueva línea después de la salida; por esa razón, las dos salidas aparecen en la misma línea.

Aquí se muestra como es una asignación múltiple en un diagrama de estado:

En una asignación múltiple, es muy importante distinguir una operación de asignación y una sentencia de igualdad. Debido al hecho de que Python utiliza el signo igual (=) para las asignaciones, resulta tentador interpretar una sentencia del tipo a = b como una sentencia de igualdad. Y, de hecho, no lo es.

En primer lugar, la igualdad es conmutativa; la asignación, no. En Matemáticas, por ejemplo, si a = 7, entonces 7 = a. Sin embargo, la sentencia a = 7 es válida en Python, y 7 = a no lo es.

Además, una sentencia de igualdad siempre es verdadera en Matemáticas. Si en este momento a = b, a será siempre igual a b. En Python, una sentencia de asignación puede igualar dos variables, pero no tienen que quedar de ese modo:

a = 5
b = a    # a y b ahora son iguales
a = 3    # a y b ya no son iguales

La tercera línea cambia el valor de a, pero no cambia el valor de b; por lo tanto, ya no son iguales. (En algunos lenguajes de programación, se utiliza un símbolo distinto para la asignación, como <- o :=, para evitar confusiones.)

Aunque la asignación múltiple suele ser útil, deberías utilizarla con cuidado: puede dificultar la lectura y depuración del código si los valores de las variables cambian con frecuencia. Comentarios

6.2 La sentencia while

Los ordenadores se utilizan a menudo para automatizar tareas repetitivas. Repetir tareas idénticas o similares sin cometer errores es algo que los ordenadores hacen bien y, las personas, mal.

Hemos visto dos programas, nLines y countdown, que utilizan la recursión para realizar una repetición, a la que también se llama iteración. Como la iteración es muy habitual, Python proporciona diversas características del lenguaje que la hacen más sencilla. La primera característica que vamos a ver es la sentencia while.

Aquí se muestra como es un countdown con una sentencia while:

def countdown(n):
  while n > 0:
    print n
    n = n-1
  print "¡Despegue!"

Dado que eliminamos la invocación recursiva, esta función no es, por lo tanto, recursiva.

Casi se puede leer la sentencia while como si fuera inglés. Su significado es: "Mientras n sea mayor que 0, sigue mostrando el valor de n y reduciendo después dicho valor de n por 1. Cuando llegue a 0, muestra la palabra ¡Despegue!"

Aquí se muestra el flujo de ejecución para una sentencia while de manera más formal:

  1. Evalúa la condición, dando como resultado 0 o 1.
  2. Si la condición es falsa (0), sale de la sentencia while, y continúa con la ejecución de la siguiente sentencia.
  3. Si la condición es verdadera (1), ejecuta cada una de las sentencias en el cuerpo y, a continuación, vuelve al paso 1.

El cuerpo consta de todas las sentencias que aparecen debajo del encabezamiento con la misma sangría.

Este tipo de flujo se denomina bucle, ya que vuelve al principio en forma de espiral. Observa que si la primera vez que se realiza el bucle la condición es falsa, las sentencias en su interior nunca se ejecutarán.

El cuerpo del bucle debería cambiar el valor de una o más variables, para que así la condición llegue a ser falsa y el bucle termine. Si no, el bucle se repetirá sin cesar, lo cual se denomina bucle infinito. Los informáticos consideran como una fuente inagotable de entretenimiento la observación de que las instrucciones del champú (enjabonar, enjuagar y repetir) son un bucle infinito.

En el caso de countdown, podemos demostrar que el bucle termina porque sabemos que el valor de n es finito, y observamos que dicho valor n se va haciendo más pequeño cada vez que se repite el bucle; así pues, terminaremos por obtener 0 al final. En otros casos, no es tan fácil de predecir:

def sequence(n):
  while n != 1:
    print n,
    if n%2 == 0:        # n es par
      n = n/2
    else:               # n es impar
      n = n*3+1

La condición para este bucle es n != 1 ; por lo tanto, el bucle continuará hasta que n sea 1, lo cual hará que la condición sea falsa.

Cada vez que se ejecuta el bucle, el programa produce el valor de n y, a continuación, comprueba si es par o impar. Si es par, el valor de n se divide entre 2; si es impar, se reemplaza por n*3+1. Por ejemplo, si el valor inicial (el argumento que pasa a secuencia) es 3, la secuencia resultante es 3, 10, 5, 16, 8, 4, 2, 1.

Debido al hecho de que n a veces aumenta y a veces disminuye, no existe ninguna prueba evidente de que n pueda llegar alguna vez a 1, o bien de que el programa termine. Podemos demostrar la finalización en algunos casos concretos de n; por ejemplo, si el valor inicial es una potencia de dos, el valor de n será par cada vez que se ejecute el bucle hasta que llegue a 1. El ejemplo anterior termina con una sentencia de este estilo, la cual comienza con 16.

Dejando a un lado estos valores concretos, lo interesante sería demostrar si este programa finaliza para todos los valores de n. Hasta ahora, nadie ha podido demostrarlo o refutarlo.

Como ejercicio, vuelve a escribir la función nLines de la Sección 4.9 utilizando la iteración en vez de la recursión.
Comentarios

6.3 Tablas

Una de las utilidades de los bucles consiste en generar datos tabulares. Antes de que se pudiera acceder fácilmente a los ordenadores, la gente tenía que calcular a mano logaritmos, senos, cosenos, y otras funciones matemáticas. Para facilitar estas labores, los libros de matemáticas contenían extensas tablas que enumeraban los valores de dichas funciones. La elaboración de las tablas era lenta y aburrida y, además, solían estar llenas de errores.

Cuando llegaron los ordenadores, una de las reacciones iniciales fue la euforia. Se pensaba que su uso no produciría errores a la hora de generar tablas. Esto resultó ser cierto en la mayoría de los casos, pero fue una impresión precipitada. Poco tiempo después, los ordenadores y calculadoras se extendieron de tal modo que las tablas quedaron desfasadas.

Bueno, casi. Los ordenadores utilizan tablas de valores en algunas operaciones para obtener respuestas aproximadas y, a continuación, realizan cómputos con el fin de mejorar la aproximación. En algunos casos, han aparecido errores en las tablas subyacentes; el caso más conocido es el de la tabla que utilizaba el Intel Pentium para hacer la división de coma flotante.

Aunque una tabla de logaritmos ya no es tan útil como lo fue en su momento, aún sigue siendo un buen ejemplo de iteración. El siguiente programa produce una secuencia de valores en la columna izquierda y, sus logaritmos, aparecen en la columna derecha:

x = 1.0
while x < 10.0:
  print x, '\t', math.log(x)
  x = x + 1.0

La cadena '\t' representa un carácter de tabulación.

A medida que se muestran en pantalla las cadenas y caracteres, un marcador invisible denominado cursor examina la posición que ocupará el siguiente carácter. Después de una sentencia print, el cursor se coloca normalmente al principio de la siguiente línea.

El carácter de tabulación desplaza el cursor a la derecha hasta que llega a uno de los puntos de tabulación. Los tabuladores son útiles para elaborar columnas de texto alineadas, como en el caso de la salida del programa anterior:

1.0     0.0
2.0     0.69314718056
3.0     1.09861228867
4.0     1.38629436112
5.0     1.60943791243
6.0     1.79175946923
7.0     1.94591014906
8.0     2.07944154168
9.0     2.19722457734

Si estos valores parecen extraños, recuerda que la función log utiliza la base e. Puesto que las potencias de dos son sumamente importantes en Informática, a menudo queremos encontrar logaritmos de base 2. Para conseguirlo, podemos utilizar la siguiente fórmula:

log2 x =
loge x

loge 2

Al cambiar la sentencia de salida a

   print x, '\t',  math.log(x)/math.log(2.0)

se obtiene

1.0     0.0
2.0     1.0
3.0     1.58496250072
4.0     2.0
5.0     2.32192809489
6.0     2.58496250072
7.0     2.80735492206
8.0     3.0
9.0     3.16992500144

Podemos observar que 1, 2, 4 y 8 son potencias de dos, ya que sus logaritmos de base 2 son números enteros. Si quisiéramos encontrar los logaritmos de otras potencias de dos, podríamos modificar el programa del siguiente modo:

x = 1.0
while x < 100.0:
  print x, '\t', math.log(x)/math.log(2.0)
  x = x * 2.0

Ahora, en lugar de sumar algo a x cada vez que se ejecute el bucle, el cual da como resultado una secuencia aritmética, multiplicamos x por algo, y así se obtendrá una secuencia geométrica. El resultado es:

1.0     0.0
2.0     1.0
4.0     2.0
8.0     3.0
16.0    4.0
32.0    5.0
64.0    6.0

Debido a los caracteres de tabulación entre las columnas, la posición de la segunda columna no depende del número de dígitos de la primera columna.

Tal vez las tablas de logaritmos ya no sean de utilidad, pero para los informáticos sí es pertinente conocer las potencias de dos.

Como ejercicio, modifica este programa para que produzca las potencias de dos hasta 65,536 (es decir,216). Imprímelo y memorízalo.

El carácter de barra invertida en '\t' indica el comienzo de una secuencia de escape. Las secuencias de escape se utilizan para representar caracteres invisibles, como tabulaciones y nuevas líneas. La secuencia \n representa una nueva línea.

Una secuencia de escape puede aparecer en cualquier parte de la cadena; en el ejemplo, la secuencia de escape de la tabulación es lo único que hay en la cadena.

¿Cómo crees que se representa una barra invertida en una cadena?

Como ejercicio, escribe una cadena simple que

produzca
        esta
               salida.

Comentarios

6.4 Tablas bidimensionales

Una tabla bidimensional es una tabla en la que puedes leer el valor en la intersección de una fila y una columna. Una tabla de multiplicar constituye un buen ejemplo. Pongamos que quieres imprimir una tabla de multiplicar para los valores que van del 1 al 6.

Una buena manera de empezar es escribir un bucle que imprima todos los múltiplos de 2 en una línea.

i = 1
while i <= 6:
  print 2*i, '   ',
  i = i + 1
print

La primera línea inicializa una variable denominada i, que actúa como variable de bucle o contador. A medida que el bucle se ejecuta, el valor de i aumenta del 1 al 6. Cuando i llega a 7, el bucle finaliza. Cada vez que se ejecuta el bucle, muestra el valor de 2*i seguido de tres espacios.

Una vez más, la coma de la sentencia print suprime la nueva línea. Después de que el bucle se termine, la segunda sentencia print comienza una nueva línea.

La salida del programa es

2      4      6      8      10     12

Hasta ahora, todo bien. El siguiente paso es encapsular y generalizar. Comentarios

6.5 Encapsulación y generalización

La encapsulación es el proceso de envolver un trozo de código en una función, permitiéndote así sacar partido a todas las utilidades que tienen las funciones. Ya has visto dos ejemplos de encapsulación: printParity en la Sección 4.5 y isDivisible en la Sección 5.4.

La generalización supone tomar algo específico (como la impresión de los múltiplos de 2) y hacerlo más general (como la impresión de los múltiplos de cualquier número entero).

Esta función encapsula el bucle anterior y lo generaliza para imprimir múltiplos de n:

def printMultiples(n):
  i = 1
  while i <= 6:
    print n*i, '\t',
    i = i + 1
  print

Para encapsular, lo único que hicimos fue añadir la primera línea, la cual declara el nombre de la función y la lista de parámetros. Para generalizar, sólo tuvimos que reemplazar el valor 2 por el parámetro n.

Si a esta función la denominamos argumento 2, obtenemos la misma salida que antes. Con el argumento 3, el resultado es

3      6      9      12     15     18

Con el argumento 4, el resultado es

4      8      12     16     20     24

A estas alturas es probable que deduzcas cómo imprimir una tabla de multiplicar : dándole a printMultiples el nombre de distintos argumentos de manera reiterada. De hecho, podemos utilizar otro bucle:

i = 1
while i <= 6:
  printMultiples(i)
  i = i + 1

Observa la similitud de este bucle con el que está dentro de printMultiples. Lo único que hicimos fue reemplazar la sentencia print por un invocador de función.

La salida de este programa es una tabla de multiplicar:

1      2      3      4      5      6
2      4      6      8      10     12
3      6      9      12     15     18
4      8      12     16     20     24
5      10     15     20     25     30
6      12     18     24     30     36

Comentarios

6.6 Más sobre la encapsulación

Con el fin de demostrar una vez más la encapsulación, tomaremos el código que aparece al final de la sección Sección 6.5 y lo envolveremos en una función:

def printMultTable():
  i = 1
  while i <= 6:
    printMultiples(i)
    i = i + 1

Este proceso es un plan de desarrollo habitual. Desarrollamos el código al escribir las líneas de código fuera de cualquier función, o bien al teclearlas para el intérprete. Cuando el código entra en funcionamiento, lo extraemos y lo envolvemos en una función.

Este plan de desarrollo es especialmente útil al empezar a escribir si no sabes cómo dividir el programa en funciones, ya que te permite diseñar a medida que vas avanzando. Comentarios

6.7 Variables locales

Puede que te preguntes cómo podemos hacer uso de una misma variable, i, tanto en printMultiples como en printMultTable. ¿Acaso no causa problemas el hecho de que una de las funciones cambie el valor de la variable?

La respuesta es negativa, ya que la i que aparece en printMultiples y la i de printMultTable no son la misma variable.

Las variables que se crean dentro de una definición de función son locales; por lo tanto, no se puede acceder a una variable local desde fuera de su función. Esto supone que puedes tener múltiples variables que compartan el mismo nombre siempre que no estén en la misma función.

El diagrama de pila para este programa muestra que las dos variables denominadas i no son la misma. Hacen referencia a distintos valores, y el cambio de una no afecta a la otra.

El valor de i en printMultTable va del 1 al 6. En el diagrama, resulta ser el 3. La próxima vez que se ejecute el bucle, será 4. Y cada vez que se ejecute el bucle, printMultTable le dará a printMultiples el valor actual de i como argumento. Dicho valor se asigna al parámetro n.

Dentro de printMultiples, el valor de i va del 1 al 6. En el diagrama, resulta ser el 2. El cambio de esta variable no produce ningún efecto en el valor que tiene i en printMultTable.

Es habitual y perfectamente válido tener distintas variables locales que compartan el mismo nombre; en especial, nombres como i y j se utilizan con frecuencia como variables de bucle. Si evitas su uso en una función sólo por el hecho de que ya los utilizaste en otra parte, es probable que hagas que el programa sea más difícil de leer. Comentarios

6.8 Más sobre generalización

Para poner otro ejemplo de generalización, imagínate que quisieras un programa capaz de imprimir una tabla de multiplicar de cualquier tamaño, y no sólo la de 6 filas y 6 columnas. Podrías añadir un parámetro a printMultTable:

def printMultTable(alto):
  i = 1
  while i <= alto:
    printMultiples(i)
    i = i + 1

Hemos reemplazado el valor 6 por el parámetro alto. Si a printMultTable lo denominamos argumento 7, muestra lo siguiente:

1      2      3      4      5      6
2      4      6      8      10     12
3      6      9      12     15     18
4      8      12     16     20     24
5      10     15     20     25     30
6      12     18     24     30     36
7      14     21     28     35     42

Esto estaría bien si no fuera porque es probable que queramos que nuestra tabla sea cuadrada, y que tenga el mismo número de filas y columnas. Para conseguirlo, añadimos otro parámetro a printMultiples con el fin de especificar el número de columnas que debería tener la tabla.

Para ser repetitivos, a este parámetro lo denominaremos alto, demostrando así que distintas funciones pueden tener parámetros con el mismo nombre, al igual que las variables locales. Aquí tenemos el programa completo:

def printMultiples(n, alto):
  i = 1
  while i <= alto:
    print n*i, '\t',
    i = i + 1
  print

def
printMultTable(alto):
  i = 1
  while i <= alto:
    printMultiples(i, alto)
    i = i + 1

Observa que, cuando añadimos un nuevo parámetro, tuvimos que cambiar la primera línea de la función (el encabezado de función), y también la parte donde se denomina a la función en printMultTable.

Como era de esperar, este programa genera una tabla cuadrada de 6 filas y 6 columnas:

1      2      3      4      5      6      7
2      4      6      8      10     12     14
3      6      9      12     15     18     21
4      8      12     16     20     24     28
5      10     15     20     25     30     35
6      12     18     24     30     36     42
7      14     21     28     35     42     49

Cuando generalizas una función de manera apropiada, es habitual que tu programa tenga unas capacidades que no preveías. Por ejemplo, es probable que observes que, debido a que ab = ba, todas las entradas de la tabla aparecen dos veces. Podrías ahorrar tinta al imprimir sólo la mitad de la tabla. Para conseguirlo, sólo tienes que cambiar una línea de printMultTable. Cambia

    printMultiples(i, alto)

por

    printMultiples(i, i)

y obtendrás

1
2      4
3      6      9
4      8      12     16
5      10     15     20     25
6      12     18     24     30     36
7      14     21     28     35     42     49

Como ejercicio, sigue la ejecución de esta versión de printMultTable y deduce cómo funciona.
Comentarios

6.9 Funciones

Ya hemos mencionado varias veces "todas las utilidades que tienen las funciones" A estas alturas, es probable que te preguntes cuáles son exactamente esas utilidades. Aquí tenemos algunas:

Comentarios

6.10 Glosario

asignación múltiple
Hacer más de una asignación a la misma variable durante la ejecución de un programa.
iteración
Ejecución repetida de un conjunto de sentencias mediante el uso de un bucle o de una invocación de función recursiva.
bucle
Una sentencia o grupo de sentencias que se ejecutan de manera reiterada hasta que se cumple una condición de finalización.
bucle infinito
Una bucle en el que nunca se cumple la condición de finalización.
cuerpo
Las sentencias en el interior de un bucle.
variable de bucle
Una variable que se utiliza como parte de la condición de finalización de un bucle.
tabulación
Un carácter especial que hace que el cursor se desplace hasta el siguiente punto de tabulación de la línea en que se encuentra.
nueva línea
Un carácter especial que hace que el cursor se desplace hasta el principio de la siguiente línea .
cursor
Un marcador invisible que examina la posición en la que se imprimirá el siguiente carácter.
secuencia de escape
Un carácter de escape (\), seguido de uno o más caracteres imprimibles, que se utiliza para designar un carácter no imprimible.
encapsular
Dividir un programa largo y complejo en componentes (como las funciones) y aislar dichos componentes entre sí; por ejemplo, mediante el uso de variables locales.
generalizar
Reemplazar algo que sea de una especificidad innecesaria (como un valor constante) por algo de una generalidad apropiada (como una variable o parámetro). La generalización hace que el código sea más versátil, con más posibilidades de volver a utilizarse, e incluso más fácil de escribir.
plan de desarrollo
Un proceso que desarrolla un programa. En este capítulo, hemos demostrado un estilo basado en el desarrollo del código para realizar cosas simples y específicas y, seguidamente, encapsular y generalizar.


Siguiente Subir Anterior Aprende a pensar como un informático Índice