Herramientas Informaticas

Mes: agosto 2012 Página 14 de 22

13.8. Glosario

función pura: Una función que no modifica ninguno de los objetos que recibe como parámetros. La mayor³a de las funciones puras son rentables.

modificador: Una función que modifica uno o mas de los objetos que recibe como parámetros. La mayor³a de los modificadores no entregan resultado.

estilo funcional de programación: Un estilo de programación en el que la mayor³a de las funciones son puras.

desarrollo de prototipos: Una forma de desarrollar programas empezando con un prototipo y probándolo y mejorándolo gradualmente.

desarrollo planificado: Una forma de desarrollar programas que implica una comprensión de alto nivel del problema y mas planificación que desarrollo incremental o desarrollo de prototipos.

algoritmo: Un conjunto de instrucciones para solucionar una clase de problemas por medio de un proceso mecánico sin intervención de inteligencia.

Clases y métodos

14.1. Características de la orientación a objeto

Python es un lenguaje de programación orientado a objetos, lo que significa que proporciona características que apoyan la programación orientada a objetos.

No es fácil definir la programación orientada a objetos, pero ya hemos visto
algunas de sus características:

Los programas se hacen a base de definiciones de objetos y definiciones de funciones, y la mayor parte de la computación se expresa en términos de operaciones sobre objetos.

Cada definición de un objeto se corresponde con un objeto o concepto del mundo real, y las funciones que operan en ese objeto se corresponden con las formas en que interactúan los objetos del mundo real.

Por ejemplo, la clase Hora definida en el Capítulo 13 se corresponde con la forma en la que la gente registra la hora del día, y las funciones que definimos se corresponden con el tipo de cosas que la gente hace con las horas. De forma similar, las clases Punto y Rectangulo se corresponden con los conceptos matemáticos de un punto y un rectangulo.

Hasta ahora, no nos hemos aprovechado de las características que Python nos ofrece para dar soporte a la programación orientada a objetos. Hablando estrictamente, estas características no son necesarias. En su mayor³a, proporcionan una sintaxis alternativa para cosas que ya hemos hecho, pero en muchos casos,la alternativa es mas concisa y expresa con mas precisión a la estructura del programa.

Por ejemplo, en el programa Hora no hay una conexión obvia entre la definición de la clase y las definiciones de las funciones que siguen. Observando bien, se hace patente que todas esas funciones toman al menos un objeto Hora como
parámetro.

Esta observación es la que motiva los métodos. Ya hemos visto varios métodos, como keys y values, que se invocan sobre diccionarios. Cada metodo esta asociado con una clase y esta pensado para invocarse sobre instancias de esa clase.

  • Los métodos son como las funciones, con dos diferencias:
  • Los métodos se definen dentro de una definición de clase para explicitar la relación entre la clase y el metodo.

La sintaxis para invocar un metodo es diferente de la de una llamada a una función.

En las próximas secciones tomaremos las funciones de los capítulos anteriores y las transformaremos en métodos. Esta transformación es puramente mecánica; puede hacerla simplemente siguiendo una secuencia de pasos. Si se acostumbra
a convertir de una forma a la otra será capaz de elegir la mejor forma de hacer lo que quiere.

14.2. imprimeHora

En el Capítulo 13, definimos una clase llamada Hora y escribimos una función llamada imprimeHora, que debería ser parecida a esto:

   1: class Hora:

   2: pass

   3: def imprimeHora(hora):

   4: print str(hora.horas) + ":" +

   5: str(hora.minutos) + ":" +

   6: str(hora.segundos)

Para llamar a esta función, pasábamos un objeto Hora como parámetro:

   1: >>> horaActual = Hora()

   2: >>> horaActual.horas = 9

   3: >>> horaActual.minutos = 14

   4: >>> horaActual.segundos = 30

   5: >>> impriemHora(horaActual)

Para convertir imprimeHora en un metodo, todo lo que necesitamos hacer es mover la definición de la función al interior de la definición de la clase. Fíjese en como cambia el sangrado.

 

   1: class Hora:

   2:     def imprimeHora(hora):

   3:         print str(hora.horas) + ":" +

   4:         str(hora.minutos) + ":" +

   5:         str(hora.segundos)

Ahora podemos invocar imprimeHora usando la notación de punto.

   1: >>> horaActual.imprimeHora()

Como es habitual, el objeto sobre el que se invoca el metodo aparece delante del punto y el nombre del metodo aparece tras el punto.

El objeto sobre el que se invoca el metodo se asigna al primer parámetro, así que en este caso horaActual se asigna al parámetro hora.

Por convenio, el primer parámetro de un metodo se llama self. La razón de esto es un tanto rebuscada, pero se basa en una metáfora útil.

La sintaxis para la llamada a una función, imprimeHora(horaActual), sugiere que la función es el agente activo. Dice algo como ¡Oye imprimeHora! Aquí hay un objeto para que lo imprimas”.

En programación orientada a objetos, los objetos son los agentes activos. Una invocación como horaActual.imprimeHora() dice ¡Oye horaActual Imprímete!

Este cambio de perspectiva puede ser mas elegante, pero no es obvio que sea útil. En los ejemplos que hemos visto hasta ahora, puede no serlo. Pero a veces transferir la responsabilidad de las funciones a los objetos hace posible escribir funciones mas versátiles, y hace mas fácil mantener y reutilizar código.

14.3. Otro ejemplo

Vamos a convertir incremento (de la Sección 13.3) en un metodo. Para ahorrar espacio, dejaremos a un lado los métodos ya definidos, pero usted debería mantenerlos en su versión:

   1: class Hora:

   2:     #aquí van las definiciones anteriores de métodos...

   3:     def incremento(self, segundos):

   4:         self.segundos = segundos + self.segundos

   5:         while self.segundos >= 60:

   6:             self.segundos = self.segundos - 60

   7:             self.minutos = self.minutos + 1

   8:         while self.minutos >= 60:

   9:             self.minutos = self.minutos - 60

  10:             self.horas = self.horas + 1

La transformación es puramente mecánica; hemos llevado la definición del metodo al interior de la definición de la clase y hemos cambiado el nombre del primer parámetro.

Ahora podemos invocar incremento como un metodo.

   1: horaActual.incremento(500)

De nuevo, el objeto sobre el que invocamos el metodo se asigna al primer parámetro, self. El segundo parámetro, segundos toma el valor de 500.

Como ejercicio, convierta convertirASegundos (de la Sección 13.5) en un metodo de la clase Hora.

14.4. Un ejemplo más complicado

La función después es ligeramente mas complicada porque opera sobre dos objetos Hora, no solo sobre uno. Solo podemos convertir uno de los parámetros en self; el otro se queda como esta:

   1: class Hora:

   2: #aquí van las definiciones anteriores de métodos...

   3:     def despues(self, hora2):

   4:         if self.horas > hora2.horas:

   5:             return 1

   6:         if self.horas < hora2.horas:

   7:             return 0

   8:         if self.minutos > hora2.minutos:

   9:             return 1

  10:         if self.minutos < hora2.minutos:

  11:             return 0

  12:         if self.segundos > hora2.segundos:

  13:             return 1

  14:         return 0

Invocamos este método sobre un objeto y pasamos el otro como argumento:

   1: if horaHecho.despues(horaActual):

   2: print "El pan estará hecho después de empezar."

 

Casi puede leer la invocación como una mezcla de ingles y español: Si la horahecho es después de la horaactual, entonces…”

 

14.5. Argumentos opcionales

Hemos visto funciones internas que toman un numero variable de argumentos.

Por ejemplo, string.find puede tomar dos, tres o cuatro argumentos.

Es posible escribir funciones definidas por el usuario con listas de argumentos opcionales. Por ejemplo, podemos modernizar nuestra propia versión de encuentra para que haga lo mismo que string.find.

Esta es la versión original de la Sección 7.7:

   1: def encuentra(cad, c):

   2:     indice = 0

   3:         while indice < len(cad):

   4:             if str[indice] == c:

   5:             return indice

   6:         indice = indice + 1

   7:     return –1

 

Esta es la versión aumentada y mejorada:

   1: def encuentra(cad, c, comienzo=0):

   2:     indice = comienzo

   3:     while indice < len(cad):

   4:         if str[indice] == c:

   5:             return indice

   6:         indice = indice + 1

   7:     return –1

 

El tercer parámetro, comienzo, es opcional porque se proporciona un valor por omisión, 0. Si invocamos encuentra solo con dos argumentos, utilizamos el valor por omisión y comenzamos por el principio de la cadena:

   1: >>> encuentra("arriba", "r")

   2: 1

Si le damos un tercer parámetro, anula el predefinido:

   1: >>> encuentra("arriba", "r", 2)

   2: 2

   3: >>> encuentra("arriba", "r", 3)

   4: –1

 

Como ejercicio, a~nada un cuarto parámetro, fin, que especifique donde dejar de buscar.

Cuidado: Este ejercicio tiene truco. El valor por omisión de fin debería ser len(cad), pero eso no funciona. Los valores por omisión se evalúan al definir la función, no al llamarla. Cuando se define encuentra, cad aun no existe, así que no puede averiguar su longitud.

14.6. El método de inicialización

El metodo de inicialización es un metodo especial que se invoca al crear un objeto. El nombre de este metodo es __init__ (dos guiones bajos, seguidos de init y dos guiones bajos mas). Un metodo de inicialización para la clase Hora
es así:

   1: class Hora:

   2:     def __init__(self, horas=0, minutos=0, segundos=0):

   3:         self.horas = horas

   4:         self.minutos = minutos

   5:         self.segundos = segundos

No hay conflicto entre el atributo self.horas y el parámetro horas. la notación de punto especifica a que variable nos referimos.

Cuando invocamos el constructor Hora, los argumentos que damos se pasan a init:

   1: >>> horaActual = Hora(9, 14, 30)

   2: >>> horaActual.imprimeHora()

   3: >>> 9:14:30

Como los parámetros son opcionales, podemos omitirlos:

   1: >>> horaActual = Hora()

   2: >>> horaActual.imprimeHora()

   3: >>> 0:0:0

O dar solo el primer parámetro:

   1: >>> horaActual = Hora (9)

   2: >>> horaActual.imprimeHora()

   3: >>> 9:0:0

O los dos primeros parámetros:

   1: >>> horaActual = Hora (9, 14)

   2: >>> horaActual.imprimeHora()

   3: >>> 9:14:0

Finalmente, podemos dar un subconjunto de los parámetros nombrándolos explícitamente:

   1: >>> horaActual = Hora(segundos = 30, horas = 9)

   2: >>> horaActual.imprimeHora()

   3: >>> 9:0:30

14.7. Revisión de los Puntos

Vamos a reescribir la clase Punto de la Sección 12.1 con un estilo mas orientado a objetos:

   1: class Punto:

   2:     def __init__(self, x=0, y=0):

   3:         self.x = x

   4:         self.y = y

   5:     def __str__(self):

   6:         return '(' + str(self.x) + ', ' + str(self.y) + ')'

 

metodo de inicialización toma los valores de x e y como parámetros opcionales; el valor por omisión de cada parámetro es 0.

El siguiente metodo, __str__ , devuelve una representación en forma de cadena de un objeto Punto. Si una clase ofrece un metodo llamado __str__ , se impone al comportamiento por defecto de la función interna __str__ de Python.

   1: >>> p = Punto(3, 4)

   2: >>> str(p)

   3: '(3, 4)'

Imprimir un objeto Punto invoca implícitamente a __str__ sobre el objeto, así que definir __str__ también cambia el comportamiento de print:

   1: >>> p = Punto(3, 4)

   2: >>> print p

   3: (3, 4)

Cuando escribimos una nueva clase, casi siempre empezamos escribiendo __init__ , que facilita el instanciar objetos, y __str__ , que casi siempre es útil para la depuración.

 

 

14.8. Sobrecarga de operadores

Algunos lenguajes hacen posible cambiar la definición de los operadores internos cuando se aplican a tipos definidos por el usuario. Esta característica se llama sobrecarga de operadores. Es especialmente útil cuando definimos nuevos
tipos matemáticos.

Por ejemplo, para suplantar al operador de suma + necesitamos proporcionar un metodo llamado __add__ :

   1: class Punto:

   2:     # aquí van los métodos que ya habíamos definido...

   3:     def __add__(self, otro):

   4:         return Punto(self.x + otro.x, self.y + otro.y)

Como es habitual, el primer parámetro es el objeto sobre el que se invoca el metodo. El segundo parámetro se llama convenientemente otro para distinguirlo del mismo (self). Para sumar dos Puntos, creamos y devolvemos un nuevo
Punto que contiene la suma de las coordenadas x y la suma de las coordenadas y.

Ahora, cuando apliquemos el operador + a objetos Punto, Python invocara a  __add__ :

   1: >>> p1 = Punto(3, 4)

   2: >>> p2 = Punto(5, 7)

   3: >>> p3 = p1 + p2

   4: >>> print p3

   5: (8, 11)

La expresión p1 + p2 equivale a p1. add (p2), pero es obviamente más elegante.

Como ejercicio, añada un método sub (self, otro) que sobrecargue el operador resta y pruébelo.

   1: def __mul__(self, otro):

   2:     return self.x * otro.x + self.y * otro.y

Si el operando a la izquierda de * es un tipo primitivo y el operando de la derecha es un Punto, Python invoca a __rmul__ , lo que realiza una multiplicación escalar:

   1: def __rmul__(self, otro):

   2:     return Punto(otro * self.x, otro * self.y)

El resultado es un nuevo Punto cuyas coordenadas son múltiplos de las coordenadas originales. Si otro es un tipo que no se puede multiplicar por un numero en coma flotante, entonces rmul causara un error.

Este ejemplo muestra ambos tipos de multiplicación:

   1: >>> p1 = Punto(3, 4)

   2: >>> p2 = Punto(5, 7)

   3: >>> print p1 * p2

   4: 43

   5: >>> print 2 * p2

   6: (10, 14)

Que ocurre si intentamos evaluar p2 * 2? Como el primer parámetro es un Punto, Python invoca a mul con 2 como el segundo parámetro. Dentro de __mul__ , el programa intenta acceder a la coordenada x de otro, pero no lo consigue porque un entero no tiene atributos:

   1: >>> print p2 * 2

   2: AttributeError: 'int' object has no attribute 'x'

Desgraciadamente, el mensaje de error es un poco opaco. Este ejemplo muestra algunas de las dificultades de la programación orientada a objetos. A veces es difícil averiguar simplemente que código se esta ejecutando.

Para ver un ejemplo más completo de sobrecarga de operadores, vaya al Apéndice B.

Página 14 de 22

Creado con WordPress & Tema de Anders Norén