Cesar Systems

Herramientas Informaticas

12.5. Rectángulos

Digamos que queremos una clase que represente un rectangulo. La pregunta es, ¿que información tenemos que proporcionar para definir un rectangulo?

Para simplificar las cosas, supongamos que el rectangulo esta orientado vertical u horizontalmente, nunca en diagonal.

Tenemos varias posibilidades: podemos señalar el centro del rectangulo (dos coordenadas) y su tamaño (anchura y altura); o podemos señalar una de las esquinas y el tamaño; o podemos señalar dos esquinas opuestas. Un modo convencional es señalar la esquina superior izquierda del rectangulo y el tamaño.

De nuevo, definiremos una nueva clase:

   1: class Rectangulo: # Prohibidos los acentos fuera de las cadenas!

   2: pass

Y la instanciaremos:

   1: caja = Rectangulo()

   2: caja.anchura = 100.0

   3: caja.altura = 200.0

Este código crea un nuevo objeto Rectangulo con dos atributos en coma flotante. ¡Para señalar la esquina superior izquierda podemos incrustar un objeto dentro de otro!

   1: caja.esquina = Punto()

   2: caja.esquina.x = 0.0;

   3: caja.esquina.y = 0.0;

El operador punto compone. La expresión caja.esquina.x significa “ve al objeto al que se re¯ere caja y selecciona el atributo llamado esquina; entonces ve a ese objeto y selecciona el atributo llamado x”.

La figura muestra el estado de este objeto:

Sin título

12.6. Instancias como valores de retorno

Las funciones pueden devolver instancias. Por ejemplo, encuentraCentro acepta un Rectangulo como argumento y devuelve un Punto que contiene las coordenadas del centro del Rectangulo:

   1: def encuentraCentro(caja):

   2:     p = Punto()

   3:     p.x = caja.esquina.x + caja.anchura/2.0

   4:     p.y = caja.esquina.y + caja.altura/2.0

   5:     return p

Para llamar a esta función, pase caja como argumento y asigne el resultado a una variable:

   1: >>> centro = encuentraCentro(caja)

   2: >>> imprimePunto(centro)

   3: (50.0, 100.0)

12.7. Los objetos son mudables

Podemos cambiar el estado de un objeto efectuando una asignación sobre uno de sus atributos. Por ejemplo, para cambiar el tamaño de un rectangulo sin cambiar su posición, podemos cambiar los valores de anchura y altura:

   1: caja.anchura = caja.anchura + 50

   2: caja.altura = caja.altura + 100

Podemos encapsular este código en un metodo y generalizarlo para agrandar el rectangulo en cualquier cantidad:

   1: def agrandaRect(caja, danchura, daltura) :

   2:     caja.anchura = caja.anchura + danchura

   3:     caja.altura = caja.altura + daltura

Las variables danchura y daltura indican cuanto debe agrandarse el rectangulo en cada dirección. Invocar este método tiene el efecto de modificar el Rectangulo que se pasa como argumento.
Por ejemplo, podemos crear un nuevo Rectangulo llamado bob y pasárselo a agrandaRect:

   1: >>> bob = Rectangulo()

   2: >>> bob.anchura = 100.0

   3: >>> bob.altura = 200.0

   4: >>> bob.esquina = Punto()

   5: >>> bob.esquina.x = 0.0;

   6: >>> bob.esquina.y = 0.0;

   7: >>> agrandaRect(bob, 50, 100)

Mientras agrandaRect se esta ejecutando, el parámetro caja es un alias de bob.

Cualquier cambio que haga a caja afectara también a bob.

A modo de ejercicio, escriba una función llamada mueveRect que
tome un Rectangulo y dos parámetros llamados dx y dy. Tiene que
cambiar la posición del rectangulo añadiendo dx a la coordenada x
de esquina y añadiendo dy a la coordenada y de esquina.

 

 

12.8. Copiado

El uso de alias puede hacer que un programa sea difícil de leer, porque los cambios hechos en un lugar pueden tener efectos inesperados en otro lugar. Es difícil estar al tanto de todas las variables a las que puede apuntar un objeto
dado.

Copiar un objeto es, muchas veces, una alternativa a la creación de un alias. El modulo copy contiene una función llamada copy que puede duplicar cualquier
objeto:

   1: >>> import copy

   2: >>> p1 = Punto()

   3: >>> p1.x = 3

   4: >>> p1.y = 4

   5: >>> p2 = copy.copy(p1)

   6: >>> p1 == p2

   7: 0

   8: >>> mismoPunto(p1, p2)

   9: 1

Una vez que hemos importado el modulo copy, podemos usar el metodo copy para hacer un nuevo Punto. p1 y p2 no son el mismo punto, pero contienen los mismos datos.

Para copiar un objeto simple como un Punto, que no contiene objetos incrustados, copy es suficiente. Esto se llama copiado superficial.

Para algo como un Rectangulo, que contiene una referencia a un Punto, copy no lo hace del todo bien. Copia la referencia al objeto Punto, de modo que tanto el Rectangulo viejo como el nuevo apuntan a un único Punto.

Si creamos una caja, b1, de la forma habitual y entonces hacemos una copia, b2, usando copy, el diagrama de estados resultante se ve así:

Sin título

Es casi seguro que esto no es lo que queremos. En este caso, la invocación de agrandaRect sobre uno de los Rectángulos no afectaría al otro, ¡pero la invocación de mueveRect sobre cualquiera afectaría a ambos! Este comportamiento es confuso y propicia los errores.

Afortunadamente, el modulo copy contiene un metodo llamado deepcopy que copia no solo el objeto sino también cualesquiera objetos incrustados. No le sorprenderá saber que esta operación se llama copia profunda (deep copy).

   1: >>> b2 = copy.deepcopy(b1)

Ahora b1 y b2 son objetos totalmente independientes.

Podemos usar deepcopy para reescribir agrandaRect de modo que en lugar de modificar un Rectangulo existente, cree un nuevo Rectangulo que tiene la misma localización que el viejo pero nuevas dimensiones:

   1: def agrandaRect(caja, danchura, daltura) :

   2:     import copy

   3:     nuevaCaja = copy.deepcopy(caja)

   4:     nuevaCaja.anchura = nuevaCaja.anchura + danchura

   5:     nuevaCaja.altura = nuevaCaja.altura + daltura

   6:     return nuevaCaja

Como ejercicio, rescriba mueveRect de modo que cree y devuelva un nuevo Rectangulo en lugar de modificar el viejo.

12.9. Glosario

clase: Un tipo compuesto definido por el usuario. También se puede pensar en una clase como una plantilla para los objetos que son instancias de la misma.

instanciar: Crear una instancia de una clase.

instancia: Un objeto que pertenece a una clase.

objeto: Un tipo de dato compuesto que suele usarse para representar una cosa o concepto del mundo real.

constructor: Un metodo usado para crear nuevos objetos.

atributo: Uno de los elementos de datos con nombre que constituyen una instancia.

igualdad superficial: Igualdad de referencias, o dos referencias que apuntan al mismo objeto.

igualdad profunda: Igualdad de valores, o dos referencias que apuntan a objetos que tienen el mismo valor.

copia superficial: Copiar el contenido de un objeto, incluyendo cualquier referencia a objetos incrustados; implementada por la función copy del modulo copy.

copia profunda: Copiar el contenido de un objeto así como cualesquiera objetos incrustados, y los incrustados en estos, y así sucesivamente; implementada por la función deepcopy del modulo copy.

Capítulo 13

Clases y funciones

13.1. Hora

Como otro ejemplo de un tipo definido por el usuario, definiremos una clase llamada Hora que registra la hora del día. La definición de la clase es como sigue:

   1: class Hora:

   2: pass

Podemos crear un nuevo objeto Hora y asignar atributos para contener las horas, minutos y segundos:

   1: hora = Hora()

   2: hora.horas = 11

   3: hora.minutos = 59

   4: hora.segundos = 30

El diagrama de estado del objeto Hora es así:

Sin título

A modo de ejercicio, escriba una función imprimeHora que acepte un objeto Hora como argumento y lo imprima en el formato horas:minutos:segundos.

Como un segundo ejercicio, escriba una función booleana después que tome dos objetos Hora, t1 y t2, como argumentos y devuelva verdadero (1) si t1 sigue cronológicamente a t2 y falso (0) en caso contrario.

13.2. Funciones puras

En las próximas secciones, escribiremos dos versiones de una función llamada sumaHora que calcule la suma de dos Horas. Mostraran dos tipos de funciones: funciones puras y modificadores.

Este es un esbozo de sumaHora:

   1: def sumaHora(t1, t2):

   2:     suma = Hora()

   3:     suma.horas = t1.horas + t2.horas

   4:     suma.minutos = t1.minutos + t2.minutos

   5:     suma.segundos = t1.segundos + t2.segundos

   6:     return suma

La función crea un nuevo objeto Hora, inicializa sus atributos y devuelve una referencia al nuevo objeto. A esto se le llama función pura porque no modifica ninguno de los objetos que se le pasan y no tiene efectos laterales, como mostrar un valor o tomar una entrada del usuario.

Aquí tiene un ejemplo de como usar esta función. Crearemos dos objetos Hora: horaActual, que contiene la hora actual, y horaPan, que contiene la cantidad de tiempo que necesita un panadero para hacer pan. Luego usaremos sumaHora
para averiguar cuando estará hecho el pan. Si aun no ha terminado de escribir imprimeHora, eche un vistazo a la Sección 14.2 antes de probar esto:

   1: >>> horaActual = Hora()

   2: >>> horaActual.horas = 9

   3: >>> horaActual.minutos = 14

   4: >>> horaActual.segundos = 30

   5: >>> horaPan = Hora()

   6: >>> horaPan.horas = 3

   7: >>> horaPan.minutos = 35

   8: >>> horaPan.segundos = 0

   9: >>> horaHecho = sumaHora(horaActual, horaPan)

  10: >>> imprimeHora(horaHecho)

La salida de este programa es 12:49:30, lo que es correcto. Por otra parte, hay casos en los que el resultado no es correcto. ¿Puede imaginar uno?

El problema es que esta función no trata los casos en los que el numero de segundos o minutos suma mas que sesenta.

Cuando ocurre eso, debemos “llevar” los segundos sobrantes a la columna de los minutos o los minutos extras a la columna de las horas.

He aquí una versión corregida de la función:

 

   1: def sumaHora(t1, t2):

   2:     suma = Hora()

   3:     suma.horas = t1.horas + t2.horas

   4:     suma.minutos = t1.minutos + t2.minutos

   5:     suma.segundos = t1.segundos + t2.segundos

   6:         if suma.segundos >= 60:

   7:         suma.segundos = suma.segundos - 60

   8:         suma.minutos = suma.minutos + 1

   9:         if suma.minutos >= 60:

  10:         suma.minutos = suma.minutos - 60

  11:         suma.horas = suma.horas + 1

  12:     return suma

Aunque esta función es correcta, empieza a ser grande. Mas adelante sugeriremos una aproximación alternativa que nos dará un código mas corto.

 

 

13.3. Modificadores

Hay veces en las que es útil que una función modifique uno o mas de los objetos que recibe como parámetros.

Normalmente, el llamante conserva una referencia a los objetos que pasa, así que cualquier cambio que la función haga será visible para el llamante. Las funciones que trabajan así se llaman modificadores.

incremento, que añade un numero dado de segundos a un objeto Hora, se escribiría de forma natural como un modificador. Un esbozo rápido de la función podría ser este:

   1: def incremento(hora, segundos):

   2:     hora.segundos = hora.segundos + segundos

   3:     if hora.segundos >= 60:

   4:         hora.segundos = hora.segundos - 60

   5:         hora.minutos = hora.minutos + 1

   6:     if hora.minutos >= 60:

   7:         hora.minutos = hora.minutos - 60

   8:         hora.horas = hroa.horas + 1

La primera l³nea realiza la operación básica, las restantes tratan con los casos especiales que vimos antes.

¿Es correcta esta función?

¿Que ocurre si el parámetro segundos es mucho mayor que sesenta? En tal caso, no es suficiente con acarrear una vez; debemos seguir haciéndolo hasta que segundos sea menor que sesenta. Una solución es sustituir las sentencias if por sentencias while:

   1: def incremento(hora, segundos):

   2:     hora.segundos = hora.segundos + segundos

   3:     while hora.segundos >= 60:

   4:         hora.segundos = hora.segundos - 60

   5:         hora.minutos = hora.minutos + 1

   6:     while hora.minutos >= 60:

   7:         hora.minutos = hora.minutos - 60

   8:         hora.horas = hora.horas + 1

Ahora esta función es correcta, pero no es la solución mas eficiente.

Como ejercicio, reescriba esta función de modo que no contenga tantos bucles.

Como un segundo ejercicio, reescriba incremento como una funcion pura, y escriba una función que llame a ambas versiones.

Página 119 de 143

Creado con WordPress & Tema de Anders Norén