Chapter 15
\newcommand{\eningles[1]{{\foreignlanguage{english}{#1}}}
\newcommand{\revisar}[1]{\textbf{***}\marginpar{\raggedright\tiny\textsc{#1}}}
\chapter{Conjuntos de objetos}
\section{Composición}
\index{composición}
\index{estructura anidada}
Hasta ahora, ya ha visto varios ejemplos de composición. Uno de los
primeros ejemplos fue el uso de la llamada a un método como parte de
una expresión. Otro ejemplo es la estructura anidada de las
sentencias; se puede escribir una sentencia {\tt if} dentro de un
bucle {\tt while}, dentro de otra sentencia {\tt if}, y así
sucesivamente.
Una vez visto este patrón, y sabiendo acerca de listas y objetos, no
le debería sorprender que pueda crear listas de objetos. También
puede crear objetos que contengan listas (en forma de atributos);
puede crear listas que contengan listas; objetos que contengan
objetos, y así indefinidamente.
En este capítulo y el siguiente, exploraremos algunos ejemplos de
estas combinaciones, y usaremos objetos {\tt Carta} como ejemplo.
\section{Objetos {\tt Carta}}
\index{Carta}
\index{clase!Carta}
Si no está usted familiarizado con los naipes de juego comunes, puede
ser un buen momento para que consiga un mazo, si no este capítulo puede
que no tenga mucho sentido. Hay cincuenta y dos naipes en una baraja inglesa,
cada uno de los cuales pertenece a un palo y tiene un valor; hay
cuatro palos diferentes y trece valores. Los palos son Picas,
Corazones, Diamantes, y Tréboles (en el orden descendente según el
\eningles{bridge}). Los valores son As, 2, 3, 4, 5,
6, 7, 8, 9, 10, Sota, Reina, y Rey. Dependiendo del tipo de juego que
se juegue, el valor del As puede ser mayor al Rey o inferior al 2.
\index{valor}
\index{palo}
Si queremos definir un nuevo objeto para representar un naipe,
es obvio qué atributos debería tener: {\tt valor} y {\tt palo}.
Lo que no es tan obvio es el tipo que se debe dar a los atributos.
Una posibilidad es usar cadenas de caracteres que contengan palabras
como \verb+"Picas"+ para los palos y \verb+"Reina"+ para los valores.
Un problema de esta implementación es que no será fácil comparar
naipes para ver cuál tiene mayor valor o palo.
\index{codificar}
\index{cifrar}
\index{corresponder}
Una alternativa es usar números enteros para {\bf codificar} los
valores y palos. Con el término ``codificar'' no queremos significar lo
que algunas personas pueden pensar, acerca de cifrar o traducir a un
código secreto. Lo que un programador entiende por ``codificar'' es
``definir una correspondencia entre una secuencia de números y los elementos
que se desea representar''. Por ejemplo:
\beforefig
\begin{tabular}{l c l}
Picas & $\mapsto$ & 3 \\
Corazones & $\mapsto$ & 2 \\
Diamantes & $\mapsto$ & 1 \\
Tréboles & $\mapsto$ & 0
\end{tabular}
\afterfig
Esta correspondencia tiene una característica obvia: los palos corresponden a
números enteros en orden, o sea que podemos comparar los palos al
comparar los números. La asociación de los valores es bastante obvia;
cada uno de los valores numéricos se asocia con el entero
correspondiente, y para las figuras:
\beforefig
\begin{tabular}{l c l}
Sota & $\mapsto$ & 11 \\
Reina & $\mapsto$ & 12 \\
Rey & $\mapsto$ & 13 \\
\end{tabular}
\afterfig
Estamos usando una notación matemática para estas asociaciones por una
razón: no son parte del programa Python. Son parte del diseño del
programa, pero nunca aparecen explícitamente en el código fuente. La
definición de clase para el tipo {\tt Carta} se parecerá a:
\beforeverb
\begin{verbatim}
class Carta:
def __init__(self, palo=0, valor=0):
self.palo = palo
self.valor = valor
\end{verbatim}
\afterverb
%
Como acostumbramos, proporcionaremos un método de inicialización que
toma un parámetro opcional para cada atributo.
\index{constructor}
Para crear un objeto que representa el 3 de Tréboles, usaremos la
instrucción:
\beforeverb
\begin{verbatim}
tresDeTreboles = Carta(0, 3)
\end{verbatim}
\afterverb
%
El primer argumento, {\tt 0}, representa el palo de Tréboles.
\section{Atributos de clase y el método {\tt \_\_str\_\_}}
\index{atributo de clase}
\index{atributo!clase}
Para poder imprimir los objetos {\tt Carta} de una manera fácil de
leer para las personas, vamos a establecer una correspondencia entre
los códigos enteros y las palabras. Una manera natural de hacer esto
es con listas de cadenas de caracteres. Asignaremos estas listas
dentro de {\bf atributos de clase} al principio de la definición de clase:
\beforeverb
\begin{verbatim}
class Carta:
listaDePalos = ["Tréboles", "Diamantes", "Corazones",
"Picas"]
listaDeValores = ["nada", "As", "2", "3", "4", "5", "6", "7",
"8", "9", "10", "Sota", "Reina", "Rey"]
# se omite el método init
def __str__(self):
return (self.listaDeValores[self.valor] + " de " +
self.listaDePalos[self.palo])
\end{verbatim}
\afterverb
%
Un atributo de clase se define fuera de cualquier método, y puede
accederse desde cualquiera de los métodos de la clase.
Dentro de {\tt \_\_str\_\_}, podemos usar {\tt listaDePalos} y {\tt
listaDeValores} para asociar los valores numéricos de {\tt palo} y
{\tt valor} con cadenas de caracteres. Por ejemplo, la expresión
\verb+self.listaDePalos[self.palo]+ significa ``usa el atributo {\tt
palo} del objeto {\tt self} como un índice dentro del atributo de
clase denominado {\tt listaDePalos}, y selecciona la cadena
apropiada''.
El motivo del {\tt ``nada''} en el primer elemento de
{\tt listaDeValores} es para relleno del elemento de
posición cero en la lista, que nunca se usará. Los únicos valores
lícitos para el valor van de 1 a 13. No es obligatorio que
desperdiciemos este primer elemento. Podríamos haber comenzado en 0
como es usual, pero es menos confuso si el 2 se codifica como 2, el 3
como 3, y así sucesivamente.
Con los métodos que tenemos hasta ahora, podemos crear e imprimir
naipes:
\beforeverb
\begin{verbatim}
>>> carta1 = Carta(1, 11)
>>> print carta1
Sota de Diamantes
\end{verbatim}
\afterverb
%
Los atributos de clase como {\tt listaDePalos} son compartidos por
todos los objetos de tipo {\tt Carta}. La ventaja de esto es que
podemos usar cualquier objeto {\tt Carta} para acceder a los atributos
de clase:
\beforeverb
\begin{verbatim}
>>> carta2 = Carta(1, 3)
>>> print carta2
3 de Diamantes
>>> print carta2.listaDePalos[1]
Diamantes
\end{verbatim}
\afterverb
%
La desventaja es que si modificamos un atributo de clase, afectaremos
a cada instancia de la clase. Por ejemplo, si decidimos que ``Sota de
Diamantes'' en realidad debería llamarse ``Sota de Ballenas Bailarinas'', podríamos
hacer lo siguiente:
\index{instancia!objeto}
\index{instancia de objeto}
\beforeverb
\begin{verbatim}
>>> carta1.listaDePalos[1] = "Ballenas Bailarinas"
>>> print carta1
Sota de Ballenas Bailarinas
\end{verbatim}
\afterverb
%
El problema es que \emph{todos} los Diamantes se transformarán en
Ballenas Bailarinas:
\beforeverb
\begin{verbatim}
>>> print carta2
3 de Ballenas Bailarinas
\end{verbatim}
\afterverb
%
En general no es una buena idea modificar los atributos de clase.
\section{Comparación de naipes}
\label{comparecard}
\index{operador!condicional}
\index{operador condicional}
Para los tipos primitivos, existen operadores condicionales ({\tt <{}
}, {\tt >{}}, {\tt ==}, etc.) que comparan valores y determinan
cuando uno es mayor, menor, o igual a otro. Para los tipos definidos
por el usuario, podemos sustituir el comportamiento de los operadores
internos si proporcionamos un método llamado {\tt \_\_cmp\_\_}. Por
convención, {\tt \_\_cmp\_\_} toma dos parámetros, {\tt self} y {\tt
otro}, y retorna 1 si el primer objeto es el mayor, -1 si el
segundo objeto es el mayor, y 0 si ambos son iguales.
\index{sustituir}
\index{sobrecarga de operadores}
\index{orden}
\index{orden completo}
\index{orden parcial}
Algunos tipos están completamente ordenados, lo que significa que se
pueden comparar dos elementos cualesquiera y decir cuál es el mayor.
Por ejemplo, los números enteros y los números en coma flotante
tienen un orden completo. Algunos conjuntos no tienen orden, o sea,
que no existe ninguna manera significativa de decir que un elemento es
mayor a otro. Por ejemplo, las frutas no tienen orden, lo que explica
por qué no se pueden comparar peras con manzanas.
El conjunto de los naipes tiene un orden parcial, lo que
significa que algunas veces se pueden comparar los naipes, y otras
veces no. Por ejemplo, usted sabe que el 3 de Tréboles es mayor que el 2
de Tréboles y el 3 de Diamantes es mayor que el 3 de Tréboles. Pero,
¿cuál es mejor?, ¿el 3 de Tréboles o el 2 de Diamantes?. Uno tiene
mayor valor, pero el otro tiene mayor palo.
\index{comparable}
A los fines de hacer que los naipes sean comparables, se debe decidir
qué es más importante: valor o palo. Para no mentir, la selección
es arbitraria. Como algo hay que elegir, diremos que el palo es más
importante, porque un mazo nuevo viene ordenado con todos los Tréboles
primero, luego con todos los Diamantes, y así sucesivamente.
Con esa decisión tomada, podemos escribir {\tt \_\_cmp\_\_}:
\beforeverb
\begin{verbatim}
def __cmp__(self, otro):
# controlar el palo
if self.palo > otro.palo: return 1
if self.palo < otro.palo: return -1
# si son del mismo palo, controlar el valor
if self.valor > otro.valor: return 1
if self.valor < otro.valor: return -1
# los valores son iguales, es un empate
return 0
\end{verbatim}
\afterverb
%
En este ordenamiento, los Ases son menores que los doses.
\begin{quote}
{\em Como ejercicio, modifique {\tt \_\_cmp\_\_} de tal manera que
los Ases tengan mayor valor que los Reyes.}
\end{quote}
\section{Mazos de naipes}
\index{lista!de objetos}
\index{objetos!lista de}
\index{mazo}
Ahora que ya tenemos los objetos para representar las {\tt Carta}s,
el próximo paso lógico es definir una clase para
representar un {\tt Mazo}. Por supuesto, un mazo está compuesto de
naipes, así que cada objeto {\tt Mazo} contendrá una lista de naipes
como atributo.
\index{método de inicialización}
\index{método!inicialización}
A continuación se muestra una definición para la clase {\tt Mazo}. El
método de inicialización crea el atributo {\tt cartas} y genera el
conjunto estándar de cincuenta y dos naipes.
\index{composición}
\index{bucle!anidado}
\beforeverb
\begin{verbatim}
class Mazo:
def __init__(self):
self.cartas = []
for palo in range(4):
for valor in range(1, 14):
self.cartas.append(Carta(palo, valor))
\end{verbatim}
\afterverb
%
La forma más fácil de poblar el mazo es mediante un bucle anidado. El
bucle exterior enumera los palos desde 0 hasta 3. El bucle interior
enumera los valores desde 1 hasta 13. Como el bucle exterior itera cuatro
veces, y el interior itera trece veces, la cantidad total de veces que
se ejecuta el cuerpo interior es cincuenta y dos (trece por cuatro).
Cada iteración crea una nueva instancia de {\tt Carta} con el palo y
valor actual, y agrega dicho naipe a la lista de {\tt cartas}.
El método {\tt append} funciona sobre listas pero no sobre tuplas, por
supuesto.
\index{método append}
\index{métodos de lista}
\index{método!lista}
\adjustpage{1}
\section{Impresión del mazo de naipes}
\label{muestraMazo}
\index{impresión!objeto mazo}
Como es usual, cuando definimos un nuevo tipo de objeto queremos un
método que imprima el contenido del objeto. Para imprimir un {\tt
Mazo}, recorremos la lista e imprimimos cada {\tt Carta}:
\beforeverb
\begin{verbatim}
class Mazo:
...
def muestraMazo(self):
for carta in self.cartas:
print carta
\end{verbatim}
\afterverb
%
Desde ahora en adelante, los puntos suspensivos ({\tt ...}) indicarán que hemos
omitido los otros métodos en la clase.
En lugar de escribir un método {\tt muestraMazo}, podríamos escribir
un método {\tt \_\_str\_\_} para la clase {\tt Mazo}. La ventaja de
{\tt \_\_str\_\_} está en que es más flexible. En lugar de imprimir
directamente el contenido del objeto, {\tt \_\_str\_\_} genera una
representación en forma de cadena de caracteres que las otras partes
del programa pueden manipular antes de imprimir o almacenar para un
uso posterior.
Se presenta ahora una versión de {\tt \_\_str\_\_} que retorna una
representación como cadena de caracteres de un {\tt Mazo}. Para darle
un toque especial, acomoda los naipes en una cascada, de tal manera
que cada naipe está sangrado un espacio más que el precedente.
\beforeverb
\begin{verbatim}
class Mazo:
...
def __str__(self):
s = ""
for i in range(len(self.cartas)):
s = s + " "*i + str(self.cartas[i]) + "\n"
return s
\end{verbatim}
\afterverb
%
Este ejemplo demuestra varias características. Primero, en lugar de
recorrer {\tt self.cartas} y asignar cada naipe a una variable, usamos
{\tt i} como variable de bucle e índice de la lista de naipes.
Segundo, utilizamos el operador de multiplicación de cadenas de
caracteres para sangrar cada naipe un espacio más que el anterior.
La expresión {\tt " "*i} prooprciona una cantidad de espacios igual al
valor actual de {\tt i}.
Tercero, en lugar de usar la instrucción {\tt print} para imprimir los
naipes, utilizamos la función {\tt str}. El pasar un objeto como
argumento a {\tt str} es equivalente a invocar el método {\tt
\_\_str\_\_} sobre dicho objeto.
\index{acumulador}
Finalmente, usamos la variable {\tt s} como {\bf acumulador}.
Inicialmente, {\tt s} es una cadena de caracteres vacía. En cada pasada
a través del bucle, se genera una nueva cadena de caracteres que se
concatena con el viejo valor de {\tt s} para obtener el nuevo valor.
Cuando el bucle termina, {\tt s} contiene la representación completa
en formato de cadena de caracteres del {\tt Mazo}, la cual se ve como
a continuación se presenta:
\adjustpage{-2}
\beforeverb
\begin{verbatim}
>>> mazo = Mazo()
>>> print mazo
As de Tréboles
2 de Tréboles
3 de Tréboles
4 de Tréboles
5 de Tréboles
6 de Tréboles
7 de Tréboles
8 de Tréboles
9 de Tréboles
10 de Tréboles
Sota de Tréboles
Reina de Tréboles
Rey de Tréboles
As of Diamantes
\end{verbatim}
\afterverb
%
Y así sucesivamente. Aún cuando los resultados aparecen en 52
renglones, se trata de sólo una única larga cadena de caracteres que
contiene los saltos de línea.
\section{Barajar el mazo}
\index{barajar}
Si un mazo está perfectamente barajado, cualquier naipe tiene
la misma probabilidad de aparecer en cualquier posición del mazo, y
cualquier lugar en el mazo tiene la misma probabilidad de contener
cualquier naipe.
\index{aleatorio}
\index{random}
\index{randrange}
Para mezclar el mazo, utilizaremos la función {\tt randrange} del
módulo {\tt random}. Esta función toma dos enteros como argumentos
{\tt a} y {\tt b}, y elige un número entero en forma aleatoria en el
rango {\tt a <= x < b}. Como el límite superior es estrictamente
menor a {\tt b}, podemos usar la longitud de la lista como el segundo
argumento y de esa
manera tendremos garantizado un índice legal dentro de la lista. Por
ejemplo, esta expresión selecciona el índice de un naipe al azar
dentro del mazo:
\beforeverb
\begin{verbatim}
random.randrange(0, len(self.cartas))
\end{verbatim}
\afterverb
%
Una manera sencilla de mezclar el mazo es recorrer los naipes e
intercambiar cada una con otra elegida al azar. Es posible que el
naipe se intercambie consigo mismo, pero no es un problema. De hecho,
si eliminamos esa posibilidad, el orden de los naipes no será
completamente al azar:
\adjustpage{-2}
\beforeverb
\begin{verbatim}
class Mazo:
...
def mezclar(self):
import random
nCartas = len(self.cartas)
for i in range(nCartas):
j = random.randrange(i, nCartas)
self.cartas[i], self.cartas[j] =\
self.cartas[j], self.cartas[i]
\end{verbatim}
\afterverb
%
En lugar de presuponer que hay cincuenta y dos naipes en el mazo,
obtenemos la longitud real de la lista y la almacenamos en {\tt
nCartas}.
\index{intercambio}
\index{asignación de tuplas}
\index{asignación!tupla}
Para cada naipe del mazo, seleccionamos un naipe al azar entre
aquellos que no han sido intercambiados aún. Luego intercambiamos el
naipe actual ({\tt i}) con el naipe seleccionado ({\tt j}). Para
intercambiar los naipes usaremos la asignación de tuplas, como se
describe en la Sección~\ref{tuple assignment}:
\beforeverb
\begin{verbatim}
self.cartas[i], self.cartas[j] = self.cartas[j], self.cartas[i]
\end{verbatim}
\afterverb
%
\begin{quote}
{\em Como ejercicio, reescriba esta línea de código sin usar una
asignación de secuencias.}
\end{quote}
\section{Eliminación y reparto de los naipes}
\index{eliminación de naipes}
Otro método que podría ser útil para la clase {\tt Mazo} es {\tt
eliminaCarta}, que toma un naipe como parámetro, lo elimina, y
retorna verdadero (1) si el naipe estaba en el mazo, y falso (0) si no
estaba:
\beforeverb
\begin{verbatim}
class Mazo:
...
def eliminaCarta(self, carta):
if carta in self.cartas:
self.cartas.remove(carta)
return 1
else:
return 0
\end{verbatim}
\afterverb
%
El operador {\tt in} retorna verdadero si el primer operando está en
el segundo, el cual debe ser una lista o tupla. Si el primer operando
es un objeto, Python usa el método {\tt \_\_cmp\_\_} del objeto para
determinar la igualdad entre los elementos de la lista. Como el {\tt
\_\_cmp\_\_} en la clase {\tt Carta} verifica la igualdad en
profundidad, el método {\tt eliminaCarta} también verifica igualdad en
profundidad.
\index{operador in}
\index{operador!in}
Para repartir los naipes, queremos eliminar y devolver el naipe que
ocupa la posición superior en el mazo. El método {\tt pop} de las
listas proporciona una manera conveniente de realizar esto:
\beforeverb
\begin{verbatim}
class Mazo:
...
def darCarta(self):
return self.cartas.pop()
\end{verbatim}
\afterverb
%
En realidad, {\tt pop} elimina el \emph{último} naipe en la lista, así
que en efecto estamos repartiendo desde el extremo inferior del mazo.
\index{función booleana}
\index{función!booleana}
Otra operación más que es muy probable necesitemos es la función
booleana {\tt estaVacio}, la cual devuelve verdadero si el mazo no
contiene ningún naipe:
\beforeverb
\begin{verbatim}
class Deck:
...
def estaVacio(self):
return (len(self.cartas) == 0)
\end{verbatim}
\afterverb
\section{Glosario}
\begin{description}
\item[codificar:] Representar un conjunto de valores uilizando otro
conjunto de valores, entre los cuales se construye una correspondencia.
\item[atributo de clase:] Una variable que se define dentro de la
definición de una clase pero fuera de cualquiera de sus métodos.
Los atributos de clase son accesibles desde cualquier método de la
clase y están compartidos por todas las instancias de la misma.
\item[acumulador:] Una variable que se usa en un bucle para acumular
una serie de valores, por ejemplo concatenándolos dentro de una
cadena de caracteres o adicionándolos a una suma.
\index{codificar}
\index{atributo de clase}
\index{atributo!clase}
\index{acumulador}
\end{description}
%\selectlanguage{english}
%%% Local Variables:
%%% mode: latex
%%% TeX-master: "top"
%%% End: