Chapter 14
Clases y métodos
14.1 Características de la orientación a objetos
Python es un lenguaje de programación orientado a objetos, lo que
significa que porporciona 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:
\begin{itemize
\item 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.
\item 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.
\end{itemize}
Por ejemplo, la clase {\tt Hora} definida en el Capítulo~\ref{hora}
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 {\tt Punto}
y {\tt Rectángulo} se corresponden con los conceptos matemáticos de un
punto y un rectángulo.
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 más concisa y expresa con más precisión
a la estructura del programa.
Por ejemplo, en el programa {\tt 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 {\tt Hora} como parámetro.
Esta observación es la que motiva los {\bf métodos}. Ya hemos visto varios
métodos, como {\tt keys} y {\tt values}, que se invocan sobre diccionarios.
Cada método está asociado con una clase y está pensado para invocarse sobre
instancias de esa clase.
\index{método}
\index{función}
\index{instancia!objeto}
\index{instancia de un objeto}
Los métodos son como las funciones, con dos diferencias:
\begin{itemize}
\item Los métodos se definen dentro de una definición de clase para
explicitar la relación entre la clase y el métido.
\item La sintaxis para invocar un método es diferente de la de la
llamada a una función.
\end{itemize}
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.
\section{{\tt imprimeHora}}
\label{imprimeHora}
\index{imprimir!objeto}
En el Capítulo~\ref{hora}, definimos una clase llamada {\tt Hora} y
escribimos una fución llamada {\tt imprimeHora}, que debería ser parecida
a esto:
\beforeverb
\begin{verbatim}
class Hora:
pass
def imprimeHora(hora):
print str(hora.horas) + ":" +
str(hora.minutos) + ":" +
str(hora.segundos)
\end{verbatim}
\afterverb
%
Para llamar a esta función, pasábamos un objeto {\tt Hora} como parámetro:
\beforeverb
\begin{verbatim}
>>> horaActual = Hora()
>>> horaActual.horas = 9
>>> horaActual.minutos = 14
>>> horaActual.segundos = 30
>>> impriemHora(horaActual)
\end{verbatim}
\afterverb
%
Para convertir {\tt imprimeHora} en un método, 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 cómo cambia el sangrado.
\beforeverb
\begin{verbatim}
class Hora:
def imprimeHora(hora):
print str(hora.horas) + ":" +
str(hora.minutos) + ":" +
str(hora.segundos)
\end{verbatim}
\afterverb
%
Ahora podemos invocar {\tt imprimeHora} usando la notación de punto.
\index{notación de punto}
\beforeverb
\begin{verbatim}
>>> horaActual.imprimeHora()
\end{verbatim}
\afterverb
%
Como es habitual, el objeto sobre el que se invoca el método aparece
delante del punto y el nombre del método aparece tras el punto.
El objeto sobre el que se invoca el método se asigna al primer parámetro,
así que en este caso {\tt horaActual} se asigna al parámetro {\tt hora}.
Por convenio, el primer parámetro de un método se llama {\tt 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, {\tt imprimeHora(horaActual)},
sugiere que la función es el agente activo. Dice algo como "¡Oye {\tt
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 {\tt horaActual.imprimeHora()}
dice "¡Oye {\tt horaActual}! ¡Imprímete!"
Este cambio de perspectiva puede ser más 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 más versátiles, y hace
más fácil mantener y reutilizar código.
\section{Otro ejemplo}
Vamos a convertir {\tt incremento} (de la Sección~\ref{incremento})
en un método. Para ahorrar espacio, dejaremos a un lado los métodos
ya definidos, pero usted debería mantenerlos en su versión:
\adjustpage{-1}
\beforeverb
\begin{verbatim}
class Hora:
#aquí van las definiciones anteriores de métodos...
def incremento(self, segundos):
self.segundos = segundos + self.segundos
while self.segundos >= 60:
self.segundos = self.segundos - 60
self.minutos = self.minutos + 1
while self.minutos >= 60:
self.minutos = self.minutos - 60
self.horas = self.horas + 1
\end{verbatim}
\afterverb
%
La transformación es puramente mecánica; hemos llevado la definición
del método al interior de la definición de la clase y hemos cambiado
el nombre del primer parámetro.
Ahora podemos invocar {\tt incremento} como un método.
\beforeverb
\begin{verbatim}
horaActual.incremento(500)
\end{verbatim}
\afterverb
%
De nuevo, el objeto sobre el que invocamos el método se asigna al primer
parámetro, {\tt self}. El segundo parámetro, {\tt segundos} toma el valor
de {\tt 500}.
\begin{quote}
{\em Como ejercicio, convierta {\tt convertirASegundos}
(de la Sección~\ref{convertir}) en un método de la clase
{\tt Hora}.}
\end{quote}
\section{Un ejemplo más complicado}
La función {\tt despues} es ligeramente más complicada porque opera sobre
dos objetos {\tt Hora}, no sólo sobre uno. Sólo podemos convertir uno de
los parámetros en {\tt self}; el otro se queda como está:
\beforeverb
\begin{verbatim}
class Hora:
#aquí van las definiciones anteriores de métodos...
def despues(self, hora2):
if self.horas > hora2.horas:
return 1
if self.horas < hora2.horas:
return 0
if self.minutos > hora2.minutos:
return 1
if self.minutos < hora2.minutos:
return 0
if self.segundos > hora2.segundos:
return 1
return 0
\end{verbatim}
\afterverb
%
Invocamos este método sobre un objeto y pasamos el otro como argumento:
\beforeverb
\begin{verbatim}
if horaHecho.despues(horaActual):
print "El pan estará hecho después de empezar."
\end{verbatim}
\afterverb
%
Casi puede leer la invocación como una mezcla de inglés y español: "Si
la hora-hecho es depués de la hora-actual, entonces..."
\section{Argumentos opcionales}
Hemos visto funciones internas que toman un número variable de argumentos.
Por ejemplo, {\tt 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 {\tt encontrar} para que haga lo mismo que {\tt string.find}.
Esta es la versión original de la Sección~\ref{encontrar}:
\beforeverb
\begin{verbatim}
def encontrar(str, ch):
indice = 0
while indice < len(str):
if str[indice] == ch:
return indice
indice = indice + 1
return -1
\end{verbatim}
\afterverb
%
Esta es la versión aumentada y mejorada:
\beforeverb
\begin{verbatim}
def encontrar(str, ch, comienzo=0):
indice = comienzo
while indice < len(str):
if str[indice] == ch:
return indice
indice = indice + 1
return -1
\end{verbatim}
\afterverb
%
El tercer parámetro, {\tt comienzo}, es opcional porque se proporciona
un valor por omisión, {\tt 0}. Si invocamos {\tt encontrar} sólo con
dos argumentos, utilizamos el valor por omisión y comenzamos por el
principio de la cadena:
\beforeverb
\begin{verbatim}
>>> encontrar("arriba", "r")
1
\end{verbatim}
\afterverb
%
Si le damos un tercer parámetro, {\bf anula} el predefinido:
\beforeverb
\begin{verbatim}
>>> encontrar("arriba", "r", 2)
2
>>> encontrar("arriba", "r", 3)
-1
\end{verbatim}
\afterverb
%
\begin{quote}
{\em Como ejercicio, añada un cuarto parámetro, {\tt fin}, que especifique
dónde dejar de buscar.
Cuidado: Este ejercicio tiene truco. El valor por omisión de {\tt fin} debería
ser {\tt len(str)}, pero eso no funciona. Los valores por omisión se evalúan
al definir la función, no al llamarla. Cuando se define {\tt encontrar},
{\tt str} aún no existe, así que no puede averiguar su longitud.}
\end{quote}
\section{El método de inicialización}
\index{método de inicialización}
\index{método!inicialización}
El {\bf método de inicialización} es un método especial que se invoca al crear
un objeto. El nombre de este método es {\tt \_\_init\_\_} (dos guiones bajos,
seguidos de {\tt init} y dos guiones bajos más). Un método de inicialización
para la clase {\tt Hora} es así:
\beforeverb
\begin{verbatim}
class Hora:
def __init__(self, horas=0, minutos=0, segundos=0):
self.horas = horas
self.minutos = minutos
self.segundos = segundos
\end{verbatim}
\afterverb
%
No hay conflicto entre el atributo {\tt self.horas} y el parámetro {\tt hours}.
la notación de punto especifica a qué variable nos referimos.
\index{notación de punto}
Cuando invocamos el constructor {\tt Hora}, los argumentos que damos se pasan a
{\tt init}:
\beforeverb
\begin{verbatim}
>>> horaActual = Hora(9, 14, 30)
>>> horaActual.imprimeHora()
>>> 9:14:30
\end{verbatim}
\afterverb
%
Como los parámetros son opcionales, podemos omitirlos:
\beforeverb
\begin{verbatim}
>>> horaActual = Hora()
>>> horaActual.imprimeHora()
>>> 0:0:0
\end{verbatim}
\afterverb
%
O dar sólo el primer parámetro:
\beforeverb
\begin{verbatim}
>>> horaActual = Hora (9)
>>> horaActual.imprimeHora()
>>> 9:0:0
\end{verbatim}
\afterverb
%
O los dos primeros parámetros:
\beforeverb
\begin{verbatim}
>>> horaActual = Hora (9, 14)
>>> horaActual.imprimeHora()
>>> 9:14:0
\end{verbatim}
\afterverb
%
Finalmente, podemos dar un subconjunto de los parámetros nombrándolos
explicitamente:
\beforeverb
\begin{verbatim}
>>> horaActual = Hora(segundos = 30, horas = 9)
>>> horaActual.imprimeHora()
>>> 9:0:30
\end{verbatim}
\afterverb
%
\section{Revisión de los Puntos}
\index{clase Punto}
\index{clase!Punto}
Vamos a reescribir la clase {\tt Punto} de la Sección~\ref{punto}
con un estilo más orientado a objetos:
\beforeverb
\begin{verbatim}
class Punto:
def __init__(self, x=0, y=0):
self.x = x
self.y = y
def __str__(self):
return '(' + str(self.x) + ', ' + str(self.y) + ')'
\end{verbatim}
\afterverb
%
El método 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 método, {\tt \_\_str\_\_}, devuelve una representación en forma
de cadena de un objeto {\tt Punto}. Si una clase ofrece un método llamado
{\tt \_\_str\_\_}, se impone al comportamiento por defecto de la función
interna {\tt str} de Python.
\beforeverb
\begin{verbatim}
>>> p = Punto(3, 4)
>>> str(p)
'(3, 4)'
\end{verbatim}
\afterverb
%
Imprimir un objeto {\tt Punto} invoca implícitamente a {\tt \_\_str\_\_} sobre
el objeto, así que definir {\tt \_\_str\_\_} también cambia el comportamiento de
{\tt print}:
\beforeverb
\begin{verbatim}
>>> p = Punto(3, 4)
>>> print p
(3, 4)
\end{verbatim}
\afterverb
%
Cuando escribimos una nueva clase, casi siempre empezamos escribiendo {\tt
\_\_init\_\_}, que facilita el instanciar objetos, y {\tt \_\_str\_\_}, que casi
siempre es útil para la depuración.
\section{Sobrecarga de operadores}
\label{sobrecarga de operadores}
\index{sobrecarga de operadores}
\index{operador!sobrecarga}
\index{producto interior}
\index{multiplicación escalar}
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
{\bf sobrecarga de operadores}. Es especialmente útil cuando definimos nuevos tipos
matemáticos.
Por ejemplo, para suplantar al operador de suma {\tt +} necesitamos proporcionar un
método llamado {\tt \_\_add\_\_}:
\beforeverb
\begin{verbatim}
class Punto:
# aquí van los métodos que ya habíamos definido...
def __add__(self, otro):
return Punto(self.x + otro.x, self.y + otro.y)
\end{verbatim}
\afterverb
%
Como es habitual, el primer parámetro es el objeto sobre el que se invoca el método.
El segundo parámetro se llama convenientemente {\tt otro} para distinguirlo del
mismo ({\tt self}). Para sumar dos {\tt Punto}s, creamos y devolvemos un nuevo
{\tt Punto} que contiene la suma de las coordenadas $x$ y la suma de las coordenadas
$y$.
Ahora, cuando apliquemos el operador {\tt +} a objetos {\tt Punto}, Python invocará
a {\tt \_\_add\_\_}:
\beforeverb
\begin{verbatim}
>>> p1 = Punto(3, 4)
>>> p2 = Punto(5, 7)
>>> p3 = p1 + p2
>>> print p3
(8, 11)
\end{verbatim}
\afterverb
%
La expresión {\tt p1 + p2} equivale a {\tt p1.\_\_add\_\_(p2)}, pero es obviamente
más elegante.
\begin{quote}
{\em Como ejercicio, añada un método {\tt \_\_sub\_\_(self, otro)} que sobrecargue
el operador resta y pruébelo.}
\end{quote}
Hay varias formas de sobrecargar el comportamiento del operador multiplicación:
definiendo un método llamado {\tt \_\_mul\_\_}, o {\tt \_\_rmul\_\_}, o ambos.
Si el operando a la izquierda de {\tt *} es un {\tt Punto}, Python invoca a
{\tt \_\_mul\_\_}, lo que presupone que el otro operando es también un {\tt Punto}.
Calcula el {\bf producto interno} de dos puntos, definido según las reglas del
álgebra lineal:
\beforeverb
\begin{verbatim}
def __mul__(self, otro):
return self.x * otro.x + self.y * otro.y
\end{verbatim}
\afterverb
%
Si el operando a la izquierda de {\tt *} es un tipo primitivo y el operando de la
derecha es un {\tt Punto}, Python invca a {\tt \_\_rmul\_\_}, lo que realiza una
{\bf multiplicación escalar}:
\beforeverb
\begin{verbatim}
def __rmul__(self, otro):
return Punto(otro * self.x, otro * self.y)
\end{verbatim}
\afterverb
%
El resultado es un nuevo {\tt Punto} cuyas coordenadas son múltiplos de las coordenadas
originales. Si {\tt otro} es un tipo que no se puede multiplicar por un número en coma
flotante, entonces {\tt \_\_rmul\_\_} causará un error.
Este ejemplo muestra ambos tipos de multiplicación:
\beforeverb
\begin{verbatim}
>>> p1 = Punto(3, 4)
>>> p2 = Punto(5, 7)
>>> print p1 * p2
43
>>> print 2 * p2
(10, 14)
\end{verbatim}
\afterverb
%
¿Qué ocurre si intentamos evaluar {\tt p2 * 2}? Como el primer parámetro
es un {\tt Point}, Python invoca a {\tt \_\_mul\_\_} con {\tt 2} como el
segundo parámetro. Dentro de {\tt \_\_mul\_\_}, el programa intenta acceder
a la coordenada {\tt x} de {\tt otro}, pero no lo consigue porque un entero
no tiene atributos:
\beforeverb
\begin{verbatim}
>>> print p2 * 2
AttributeError: 'int' object has no attribute 'x'
\end{verbatim}
\afterverb
%
Desgraciadamente, el mensaje de error es un poco opaco. Este ejemplo muestra
algunas de las difucultades de la programación orientada a objetos. A veces es
difícil averiguar simplemente qué código se está ejecutando.
Para ver un ejemplo más completo de sobrecarga de operadores, vaya al
Apéndice~\ref{sobrecarga}.
\section{Polimorfismo}
\index{polimorfismo}
La mayoría de los métodos que hemos escrito funcionan sólo para un tipo
específico. Cuando usted crea un nuevo objeto, escribe métodos que operan
sobre ese tipo.
Pero hay ciertas operaciones que querría aplicar a muchos tipos, como
las operaciones aritméticas de las secciones anteriores. Si muchos tipos
admiten el mismo conjunto de operaciones, puede escribir funciones que
trabajen sobre cualquiera de esos tipos.
Por ejemplo, la operación {\tt multisuma} (común en álgebra lineal) toma
tres parámetros; multiplica los dos primeros y luego suma el tercero. Podemos
escribirla en Python así:
\beforeverb
\begin{verbatim}
def multisuma (x, y, z):
return x * y + z
\end{verbatim}
\afterverb
%
Este método trabajará con cualquier valor de {\tt x} e {\tt y}
que se pueda multiplicar y con cualquier valor de {\tt z} que se
pueda sumar al producto.
Podemos invocarlo con valores numéricos:
\beforeverb
\begin{verbatim}
>>> multisuma (3, 2, 1)
7
\end{verbatim}
\afterverb
%
O con {\tt Punto}s:
\beforeverb
\begin{verbatim}
>>> p1 = Punto(3, 4)
>>> p2 = Punto(5, 7)
>>> print multisuma (2, p1, p2)
(11, 15)
>>> print multisuma (p1, p2, 1)
44
\end{verbatim}
\afterverb
%
En el primer caso, el {\tt Punto} se multiplica por un escalar
y luego se suma a otro {\tt Punto}. En el segundo caso, el
producto interior produce un valor numérico, así que el tercer
parámetro también debe ser un valor numérico.
Una función como ésta que puede tomar parámetros con diferentes
tipos se llama {\bf polimórfica}.
Como un ejemplo más, observe el método {\tt delDerechoYDelReves},
que imprime dos veces una lista, hacia adelante y hacia atrás:
\beforeverb
\begin{verbatim}
def delDerechoYDelReves(derecho):
import copy
reves = copy.copy(derecho)
reves.reverse()
print str(derecho) + str(reves)
\end{verbatim}
\afterverb
%
Como el método {\tt reverse} es un modificador, hacemos una copia
de la lista antes de darle la vuelta. Así, este método no modifica
la lista que recibe como parámetro.
He aquí un ejemplo que aplica {\tt delDerechoYDelReves} a una lista:
\beforeverb
\begin{verbatim}
>>> miLista = [1, 2, 3, 4]
>>> delDerechoYDelReves(miLista)
[1, 2, 3, 4][4, 3, 2, 1]
\end{verbatim}
\afterverb
%
Por supuesto, pretendíamos aplicar esta función a listas, así que
no es sorprendente que funcione. Lo sorprendente es que pudiéramos
usarla con un {\tt Punto}.
Para determinar si una función se puede aplicar a un nuevo tipo,
aplicamos la regla fundamental del polimorfismo:
\begin{quote}
{\bf Si todas las operaciones realizadas dentro de la función se
pueden aplicar al tipo, la función se puede aplicar al tipo.}
\end{quote}
Las operaciones del método incluyen {\tt copy}, {\tt reverse} y
{\tt print}.
{\tt copy} trabaja sobre cualquier objeto, y ya hemos escrito un
método {\tt \_\_str\_\_} para los {\tt Punto}s, así que todo lo que
necesitamos es un método {\tt reverse} en la clase {\tt Punto}:
\beforeverb
\begin{verbatim}
def reverse(self):
self.x , self.y = self.y, self.x
\end{verbatim}
\afterverb
%
Ahora podemos pasar {\tt Punto}s a {\tt delDerechoYDelReves}:
\beforeverb
\begin{verbatim}
>>> p = Punto(3, 4)
>>> delDerechoYDelReves(p)
(3, 4)(4, 3)
\end{verbatim}
\afterverb
%
El mejor tipo de polimorfismo es el que no se busca, cuando usted descubre
que una función que había escrito se puede aplicar a un tipo para el
que nunca la había planeado.
\section{Glosario}
\begin{description}
\item[lenguaje orientado a objetos:] Un lenduaje que ofrece características,
como clases definidas por el usuario y herencia, que facilitan la programación
orientada a objetos.
\item[programación orientada a objetos:] Un estilo de programación en el que
los datos y las operaciones que los manipulan están organizadas en clases y
métodos.
\item[método:] Una función definida dentro de una definición de clase y que
se invoca sobre instancias de esa clase.
\item[imponer:] Reemplazar una opción por omisión. Los ejemplos incluyen el
reemplazo de un parámetro por omisión con un argumento particular y el reemplazo
de un método por omisión proporcionando un nuevo método con el mismo nombre.
\item[método de inicialización:] Un método especial que se invoca automáticamente
al crear un nuevo objeto y que inicializa los atributos del objeto.
\item[sobrecarga de operadores:] Ampliar los operadores internos ({\tt +}, {\tt -},
{\tt *}, {\tt >}, {\tt <}, etc.) de modo que trabajen con tipos definidos por el
usuario.
\item[producto interno:] Una operación definida en álgebra lineal que multiplica dos
{\tt Punto}s y entrega un valor numérico.
\item[multiplicación escalar:] Una operación definida en álgebra lineal que multiplica
cada una de las coordenadas de un {\tt Punto} por un valor numérico.
\item[polimórfica:] Una función que puede operar sobra más de un tipo. Si todas las
operaciones realizadas dentro de una función se pueden aplicar a un tipo, la función
se puede aplicar a ese tipo.
\index{lenguaje de programación orientado a objetos}
\index{método}
\index{método de inicialización}
\index{imponer}
\index{sobrecarga}
\index{sobrecarga de operadores}
\index{producto interior}
\index{multiplicación escalar}
\index{polimórfica}
\end{description}