Next Up Previous Hi Index

Appendix B

Crear un nuevo tipo de datos

Los lenguajes de programaci�n orientados a objetos permiten a los programadores crear nuevos tipos de datos que se comporten de manera muy parecida a los tipos de datos nativos. Exploraremos esta posibilidad construyendo una clase Fraccion que funcione de manera muy similar a los tipos num�ricos nativos, enteros, enteros largos y flotantes.

Las fracciones, tambi�n conocidas como n�meros racionales, son valores que pueden expresrse como la proporci�n entre dos n�meros enteros, tal como 5/6. Al n�mero superior se se le llama numerador y al inferior se se le llama denominador.

Comenzamos definiendo la clase Fraccion con un m�todo de inicializaci�n que nos surta de un numerador y un demonimador enteros:

class Fraccion:
  def __init__(self, numerador, denominador=1):
    self.numerador = numerador
    self.denominador = denominador

El denominador es opcional. Una Fraccion con un s�lo par�metro representa un n�mero entero. Si el numerador es n, construimos la fracci�n n/1.

El siguente paso es escribir un m�todo __str__ para que imprima las fracciones de forma que tenga sentido. La forma natural de hacerlo es "numerador/denominador":

class Fraccion:
  ...
  def __str__(self):
    return "%d/%d" % (self.numerador, self.denominador)

Para probar lo que tenemos hasta ahora, lo ponemos en un fichero llamado Fraccion.py y lo importamos desde el int�rprete de Python. Entonces creamos un objeto fracci�n y lo imprimimos.

>>> from Fraccion import fraccion
>>> mortadela = Fraccion(5,6)
>>> print "La fracci�n es", mortadela
La fracci�n es 5/6

Como siempre, la funci�n print invoca impl�citamente al m�todo __str__.

Multiplicaci�n de fracciones

Nos gustar�a poder aplicar las operaciones normales de suma, resta, multiplicaci�n y divisi�n a las fracciones. Para ello, podemos sobrecargar los operadores matem�ticos para los objetos de clase Fraccion.

Comenzaremos con la multiplicaci�n porque es la m�s f�cil de implementar. Para multiplicar dos fraciones, creamos una nueva fracci�n cuyo numerador es el producto de los numeradores de los operandos y cuyo denominador es el producto de los denominadores de los operandos. __mul__ es el nombre que Python utiliza para el m�todo que sobrecarga al operador *:

class Fraccion:
  ...
  def __mul__(self, otro):
    return Fraccion(self.numerador*otro.numerador,
                    self.denominador*otro.denominador)

Podemos probar este m�todo calculando el producto de dos fracciones:

>>> print Fraccion(5,6) * Fraccion(3,4)
15/24

Funciona, pero �podemos hacerlo mejor! Podemos ampliar el m�todo para manejar la multiplicaci�n por un entero. Usamos la funci�n type para ver si otro es un entero y convertirlo en una fracci�n en tal caso.

class Fraccion:
  ...
  def __mul__(self, otro):
    if type(otro) == type(5):
      otro = Fraccion(otro)
    return Fraccion(self.numerador   * otro.numerador,
                    self.denominador * otro.denominador)

Ahora funciona la multiplicaci�n para fracciones y enteros, pero s�lo si la fracci�n es el operando de la izquierda.

>>> print Fraccion(5,6) * 4
20/6
>>> print 4 * Fraccion(5,6)
TypeError: __mul__ nor __rmul__ defined for these operands

Para evaluar un operador binario como la multiplicaci�n, Python comprueba primero el operando de la izquierda para ver si proporciona un m�todo __mul__ que soporte el tipo del segundo operando. En este caso, el operador nativo de multiplicaci�n del entero no soporta fracciones.

Despu�s, Python comprueba el segundo operando para ver si provee un m�todo __rmul__ que soporte el tipo del primer operando. En este caso, no hemos provisto el m�todo __rmul__, por lo que falla.

Por otra parte, hay una forma sencilla de obtener __rmul__:

class Fraccion:
  ...
  __rmul__ = __mul__

Esta asignaci�n hace que el m�todo __rmul__ sea el mismo que __mul__. Si ahora evaluamos 4 * Fraccion(5,6), Python llamar� al m�todo __rmul__ del objeto Fraccion y le pasar� 4 como par�metro:

>>> print 4 * Fraccion(5,6)
20/6

Dado que __rmul__ es lo mismo que __mul__, y __mul__ puede manejar un par�metro entero, ya est� hecho.

Suma de fracciones

La suma es m�s complicada que la multiplicaci�n, pero a�n es llevadera. La suma de a/b y c/d es la fracci�n (a*d+c*b)/b*d.

Usando como modelo el c�digo de la multiplicaci�n, podemos escribir __add__ y __radd__:

class Fraccion:
  ...
  def __add__(self, otro):
    if type(otro) == type(5):
      otro = Fraccion(otro)
    return Fraccion(self.numerador   * otro.denominador +
                    self.denominador * otro.numerador,
                    self.denominador * otro.denominador)

  __radd__ = __add__

Podemos probar estos m�todos con Fracciones y enteros.

>>> print Fraccion(5,6) + Fraccion(5,6)
60/36
>>> print Fraccion(5,6) + 3
23/6
>>> print 2 + Fraccion(5,6)
17/6

Los dos primeros ejemplos llaman a __add__; el �ltimo llama a __radd__.

Algoritmo de Euclides

En el ejemplo anterior, computamos la suma de 5/6 + 5/6 y obtuvimos 60/36. Es correcto, pero no es la mejor forma de representar la respuesta. Para reducir la fracci�n a su expresi�n m�s simple, hemos de dividir el numerador y el denominador por el m�ximo com�n divisor (MCD) de ambos, que es 12. El resultado ser�a 5/3.

En general, siempre que creamos un nuevo objeto Fraccion, deber�amos reducirlo dividiendo el numerador y el denominador por el MCD de ambos. Si la fracci�n ya est� reducida, el MCD es 1.

Euclides de Alejandr�a (aprox. 325--265 a.~C.) prensent� un algoritmo para encontrar el MCD de dos n�meros entermos m y n:

Si n divide a m sin resto, entonces n es el MCD. De lo contrario, el MCD es el MCD de n y el resto de dividir m entre n.

Esta definici�n recursiva puede expresarse concisamente como una funci�n:

def mcd (m, n):
  if m % n == 0:
    return n
  else:
    return mcd(n, m%n)

En la primera l�nea del cuerpo, usamos el operador de m�dulo para comprobar la divisibilidad. En la �ltima l�nea, lo usamos para calcular el resto de la divisi�n.

Dado que todas las operaciones que hemos escrito creaban un nuevo objeto Fraccion para devolver el resultado, podemos reducir todos los resultados modificando el m�todo de inicializaci�n.

class Fraccion:
  def __init__(self, numerador, denominador=1):
    m = mcd (numerador, denominador)
    self.numerador   =   numerador / m
    self.denominador = denominador / m

Ahora siempre que creemos una Fraccion quedar� reducida a su forma can�nica:

>>> Fraccion(100,-36)
-25/9

Una caracter�stica estupenda de mcd es que si la fracci�n es negativa, el signo menos siempre se trasladar� al numerador.

Comparar fracciones

Supongamos que tenemos dos objetos Fraccion, a y b, y evaluamos a == b. La implemetaci�n por defecto de == comprueba la igualdad superficial, por lo que s�lo devuelve true si a y b son el mismo objeto.

Queremos m�s bien devolver verdadero si a y b tienen el mismo valor     eso es, igualdad en profundidad.

Hemos de ense�ar a las fracciones c�mo compararse entre s�. Como vimos en la Secci�n 15.4, podemos sobrecargar todos los operadores de comparaci�n de una vez proporcionando un m�todo __cmp__.

Por convenio, el m�todo __cmp__ devuelve un n�mero negativo si self es menor que otro, zero si son lo mismo, y un n�mero positivo si self es mayor que otro.

La forma m�s simple de comparar dos fracciones es la multipicaci�n cruzada. Si a/b > c/d, entonces ad > bc. Con esto en mente, aqu� est� el c�digo para __cmp__:

class Fraccion:
  ...
  def __cmp__(self, otro):
    dif = (self.numerador * otro.denominador -
           otro.numerador * self.denominador)
    return dif

Si self es mayor que otro, entonces dif ser� positivo. Si otro is mayor, entonces dif ser� ngativo. Si son iguales, dif es cero.

Forzando la m�quina

Por supuesto, a�n no hemos terminado. Todav�a hemos de implementar la resta sobrecargando __sub__ y la divisi�n sobrecargando __div__.

Una manera de manejar estas operaciones es implementar la negaci�n sobrecargando __neg__ y la inversi�n sobrecargando __invert__. Entonces podemos restar negando el segundo operando y sumando, y podemos dividir invirtiendo el segundo operando y multiplicando.

Luego, hemos de suministrar los m�todos __rsub__ y __rdiv__. Desgraciadamente, no podemos usar el mismo truco que usamos para la suma y la multiplicaci�n, porque la resta y la divisi�n no son conmutativas. No podemos igualar __rsub__y __rdiv__ a __sub__ y __div__. En estas operaciones, el orden de los operandos tiene importancia.

Para manejar la negaci�n unitaria, que es el uso del signo menos con un �nico operando, sobrecargamos el m�todo __neg__.

Podemos computar potencias sobrecargando __pow__, pero la implementaci�n tiene truco. Si el exponente no es un n�mero entero podr�a no ser posible representar el resultado como una Fraccion. Por ejemplo, Fraccion(2) ** Fraccion(1,2) es la raiz cuadrada de 2, que es un n�mero irracional (no se puede representar como una fracci�n). Por lo tanto, no es f�cil escribir la versi�n m�s general de __pow__.

Existe otra extensi�n a la clase Fraccion que cabr�a considerar. Hasta ahora, hemos asumido que el numerador y el denominador son enteros. Podr�amos considerar la posibilidad de pertimirles que sean enteros largos.

Como ejercicio, termine la implementaci�n de la clase Fraccion de forma que pueda manejar resta, divisi�n, exponenciaci�n y enteros largos como numerador y denominador.

Glosario

m�ximo com�n divisor (MCD)
El mayor entero positivo que divide al numerador y al denominador de una fracci�n sin que quede un resto.
reducir
Cambiar la fracci�n a su forma equivalente con un MCD igual a 1.
negaci�n unitaria
Operaci�n que computa el elemento sim�trico aditivo, normalmente denotada con un signo menos delante. Se denomina "unitaria" en contraste con la operaci�n binaria menos, que es la resta.


Next Up Previous Hi Index

" + str + "

Close window

Hosted by www.Geocities.ws

"); } //-->
Hosted by www.Geocities.ws

1