Next Up Previous Hi Index

Appendix B

Creando un nuevo tipo de datos

Los lenguajes de programaci\'on 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 Fraction que funcione de manera muy similar a los tipos num\'ericos nativos, enteros, enteros largos y flotantes.

Las fracciones, tambi\'en conocidas como n\'umeros racionales, son valores que pueden ser expresados como la proporci\'on entre dos n\'umeros enteros, tal como 5/6. Al n\'umero superior se se le llama numerador y al inferior se se le llama denominador.

Comenzamos definiendo la clase Fraction con un m\'etodo de inicializaci\'on que reciba el numerador y el demonimador como enteros:

class Fraction:
  def __init__(self, numerator, denominator=1):
    self.numerator = numerator
    self.denominator = denominator

El denominador es opcional. Una fracci\'on con un s\'olo par\'ametro representa un n\'umero entero. Si el numerador es n, construimos la fracci\'on n/1.

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

class Fraction:
  ...
  def __str__(self):
    return "%d/%d" % (self.numerator, self.denominator)

Para probar lo que tenemos hasta ahora, lo ponemos en un fichero llamado Fraction.py y lo importamos desde el int\'erprete de Python. Entonces creamos un objeto fracci\'on y lo imprimimos.

>>> from Fraction import fraction
>>> spam = Fraction(5,6)
>>> print "The fraction is", spam
The fraction is 5/6

Como siemplre, la funci\'on print invoca impl\'icitamente al m\'etodo __str__

Multiplicaci\'on de fracciones

Nos gustar\'ia poder aplicar las operaciones normales de suma, resta, multiplicaci\'on y divisi\'on a las fracciones. Para ello, podemos sobrecargar los operadores matem\'aticos para los objetos de clase Fraction.

Comenzaremos con la multiplicaci\'on porque es la m\'as f\'acil de implementar. Para multiplicar dos fraciones, creamos una nueva fracci\'on 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\'etodo que sobrecarga el operador *:

class Fraction:
  ...
  def __mul__(self, object):
    return Fraction(self.numerator*object.numerator,
                    self.denominator*object.denominator)

Podemos probar este m\'etodo computando el producto de dos fracciones:

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

Funciona, pero \textexclamdown podemos hacerlo mejor! Podemos ampliar el m\'etodo para manejar la multiplicaci\'on por un entero. Usamos la funci\'on type para ver si other es un entero y convertirlo en una fracci\'on en tal caso.

class Fraction:
  ...
  def __mul__(self, other):
    if type(other) == type(5):
      other = Fraction(other)
    return Fraction(self.numerator   * other.numerator,
                    self.denominator * other.denominator)

Ahora funciona la multiplicaci\'on para fracciones y enterpos, pero s\'olo si la fracci\'on es el operando de la izquierda.

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

Para evaluar el operador binario de la multiplicaci\'on, Python comprueba primero el operando de la izquierda para ver si provee un m\'etodo __mul__ que soporte el tipo del segundo operando. En este caso, el operador nativo de multiplicaci\'on del entero no soporta fracciones.

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

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

class Fraction:
  ...
  __rmul__ = __mul__

Esta asignaci\'on hace que el m\'etodo __rmul__ sea el mismo que __mul__. Ahora si evaluamos 4  * Fraction(5,6), Python llamar\'a al m\'etodo __rmul__ del objeto Fraction y le pasar\'a 4 como par\'ametro:

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

Dado que __rmul__ es lo mismo que __mul__, y __mul__ puede manejar un par\'ametro entero, ya est\'a hecho.

Suma de fracciones

La suma es m\'as complicada que la multiplicaci\'on, pero todav\'ia no tan mala. La suma de a/b y c/d es la fracci\'on (a*d+c*b)/b*d.

Usando como modelo el c\'odigo de la multiplicaci\'on, podemos escribir __add__ y __radd__:

class Fraction:
  ...
  def __add__(self, other):
    if type(other) == type(5):
      other = Fraction(other)
    return Fraction(self.numerator   * other.denominator +
                    self.denominator * other.numerator,
                    self.denominator * other.denominator)

  __radd__ = __add__

Podemos probar estos m\'etodos con Fractions y enteros.

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

Los dos primeros ejemplos llaman a __add__; el \'ultimo llama a __radd__.

Algoritmo Eucl\'ideo

En el ejemplo anterior, computamos la suma de 5/6 + 5/6 y obtuvimos 60/36. Esto es correcto, pero no es la mejor forma de representar la respuesta. Para reducir la fracci\'on a su expresi\'on m\'as simple, hemos de dividir el numerador y el denominador por el m\'aximo com\'un divisor (MCD) de ambos, que es 12. El resultado ser\'ia 5/3.

En general, siempre que creamos un nuevo objeto Fraction, deber\'iamos reducirlo dividiendo el numerador y el denominador por el MCD de ambos. Si la fracci\'on ya est\'a reducida, el MCD es 1.

Euclides de Alejandr\'ia (aprox. 325--265 AC) prensent\'o un algoritmo para encontrar el MCD de dos n\'umeros entermos m y n:

Si n divide a m, 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\'on recursiva puede expresarse concisamente como una funci\'on:

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

En la primera l\'inea de la funci\'on, usamos el operador de m\'odulo para comprobar la divisibilidad. En la \'ultima l\'inea, lo usamos para calcular el resto de la divisi\'on.

Dado que todas las operaciones que hemos escrito creaban un nuevo objeto Fraction para devolver el resultado, podemos reducir todos los resultados modificando el m\'etodo de inicializaci\'on.

class Fraction:
  def __init__(self, numerator, denominator=1):
    g = gcd (numerator, denominator)
    self.numerator   =   numerator / g
    self.denominator = denominator / g

Ahora siempre que creemos un objeto Fraction, \'este es reducido a su expresi\'on m\'as simple:

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

Una caracter\'istica estupenda de gcd es que si la fracci\'on es negativa, el signo menos ser\'a siempre movido al numerador.

Comparando fracciones

Supongamos que tenemos dos objetos Fraction, a y b, y evaluamos a == b. La implemetaci\'on por defecto de == comprueba la igualdad superficial, por lo que s\'olo devuelve true si a y b son el mismo objeto.

Queremos m\'as bien devolver true si a y b tienen el mismo valor     eso es, igualdad en profundidad.

Hemos de ense
'unar a las fracciones como compararse entre s\'i. Como vimos en la Secci\'on 15.4, podemos sobrecargar todos los operadores de comparaci\'on de una vez proporcionando un m\'etodo __cmp__.

Por convenio, el m\'etodo __cmp__ devuelve un n\'umero negativo si self es menor que other, zero si son lo mismo, y un n\'umero positivo si self es mayor que other.

La forma m\'as simple de comparar dos fracciones es la multipicaci\'on cruzada. Si a/b > c/d, entonces ad > bc. Con esto en mente, aqu\'i est\'a el c\'odigo para __cmp__:

class Fraction:
  ...
  def __cmp__(self, other):
    diff = (self.numerator  * other.denominator -
            other.numerator * self.denominator)
    return diff

Si self es mayor que other, entonces diff ser\'a positivo. Si other is mayor, entonces diff ser\'a ngativo. Si son iguales, diff es cero.

M\'as adelante

Por supuesto, a\'un no hemos terminado. Todav\'ia hemos de implementar la resta sobrecargando __sub__ y la divisi\'on sobrecargando __div__.

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

Luego, hemos de proveer los m\'etodos __rsub__ y __rdiv__. Desafortunadamente, no podemos usar el mismo truco que usamos para la suma y la multiplicaci\'on, porque la resta y la divisi\'on no son conmutativas. No podemos igualar __rsub__y __rdiv__ a __sub__ y __div__respectivamente. En estas operaciones, el orden de los operandos tiene importancia.

Para manejar la negaci\'on unitaria, que es el uso del signo menos con un \'unico operando, sobrecargamos el m\'etodo __neg__.

Podemos computar potencias sobrecargando __pow__, pero la implementaci\'on tiene truco. Si el exponente no es un n\'mero entero entonces no ser\'a posigle representar el resultado como una Fraction. Por ejemplo, Fraction(2)  ** Fraction(1,2) es la raiz cuadrada de 2, que es un n\'umero irracional (no se puede representar como una fracci\'on. Por lo tanto, no es f\'acil escribir la versi\'on m\'as general de {\tt \_\_pow\_\_}. \index{irrational} Existe otra extensi\'on a la clase {\tt Fraction} que cabr\'ia considerar. Hasta ahora, hemos asumido que en numerador y el denominador son enteros. Podr\'iamos considerar la posibilidad de pertimirles que sean enteros largos. \begin{quote} {\em Como ejercicio, termine la implementaci\'on de la clase {\tt Fraction} de forma que pueda manejar resta, divisi\'on, exponenciaci\'on y enteros largos como numerador y denominador.} \end{quote} \section{Glossary} \begin{description} \item[m\'aximo com\'un divisor (MCD):] El mayor entero positivo que divide al numerador y al denominador de una fracci\'on sin que quede un resto. \item[reducir:] Cambiar la fracci\'on a su forma equivalente con un MCD igual a 1. \item[negaci\'on unitaria:] Operaci\'on que computa una inversi\'on aditiva, normalmente denotada con un signo menos delante. Se denomina ``unitaria'' en contraste con la operaci\'on binaria menos, que es la resta. \index{greatest common divisor} \index{reduce} \index{unitaria negation} \end{description}


Next Up Previous Hi Index