| """Tests for binary operators on subtypes of built-in types.""" |
| |
| import unittest |
| from test import support |
| from operator import eq, ne, lt, gt, le, ge |
| from abc import ABCMeta |
| |
| def gcd(a, b): |
| """Greatest common divisor using Euclid's algorithm.""" |
| while a: |
| a, b = b%a, a |
| return b |
| |
| def isint(x): |
| """Test whether an object is an instance of int.""" |
| return isinstance(x, int) |
| |
| def isnum(x): |
| """Test whether an object is an instance of a built-in numeric type.""" |
| for T in int, float, complex: |
| if isinstance(x, T): |
| return 1 |
| return 0 |
| |
| def isRat(x): |
| """Test wheter an object is an instance of the Rat class.""" |
| return isinstance(x, Rat) |
| |
| class Rat(object): |
| |
| """Rational number implemented as a normalized pair of ints.""" |
| |
| __slots__ = ['_Rat__num', '_Rat__den'] |
| |
| def __init__(self, num=0, den=1): |
| """Constructor: Rat([num[, den]]). |
| |
| The arguments must be ints, and default to (0, 1).""" |
| if not isint(num): |
| raise TypeError("Rat numerator must be int (%r)" % num) |
| if not isint(den): |
| raise TypeError("Rat denominator must be int (%r)" % den) |
| # But the zero is always on |
| if den == 0: |
| raise ZeroDivisionError("zero denominator") |
| g = gcd(den, num) |
| self.__num = int(num//g) |
| self.__den = int(den//g) |
| |
| def _get_num(self): |
| """Accessor function for read-only 'num' attribute of Rat.""" |
| return self.__num |
| num = property(_get_num, None) |
| |
| def _get_den(self): |
| """Accessor function for read-only 'den' attribute of Rat.""" |
| return self.__den |
| den = property(_get_den, None) |
| |
| def __repr__(self): |
| """Convert a Rat to a string resembling a Rat constructor call.""" |
| return "Rat(%d, %d)" % (self.__num, self.__den) |
| |
| def __str__(self): |
| """Convert a Rat to a string resembling a decimal numeric value.""" |
| return str(float(self)) |
| |
| def __float__(self): |
| """Convert a Rat to a float.""" |
| return self.__num*1.0/self.__den |
| |
| def __int__(self): |
| """Convert a Rat to an int; self.den must be 1.""" |
| if self.__den == 1: |
| try: |
| return int(self.__num) |
| except OverflowError: |
| raise OverflowError("%s too large to convert to int" % |
| repr(self)) |
| raise ValueError("can't convert %s to int" % repr(self)) |
| |
| def __add__(self, other): |
| """Add two Rats, or a Rat and a number.""" |
| if isint(other): |
| other = Rat(other) |
| if isRat(other): |
| return Rat(self.__num*other.__den + other.__num*self.__den, |
| self.__den*other.__den) |
| if isnum(other): |
| return float(self) + other |
| return NotImplemented |
| |
| __radd__ = __add__ |
| |
| def __sub__(self, other): |
| """Subtract two Rats, or a Rat and a number.""" |
| if isint(other): |
| other = Rat(other) |
| if isRat(other): |
| return Rat(self.__num*other.__den - other.__num*self.__den, |
| self.__den*other.__den) |
| if isnum(other): |
| return float(self) - other |
| return NotImplemented |
| |
| def __rsub__(self, other): |
| """Subtract two Rats, or a Rat and a number (reversed args).""" |
| if isint(other): |
| other = Rat(other) |
| if isRat(other): |
| return Rat(other.__num*self.__den - self.__num*other.__den, |
| self.__den*other.__den) |
| if isnum(other): |
| return other - float(self) |
| return NotImplemented |
| |
| def __mul__(self, other): |
| """Multiply two Rats, or a Rat and a number.""" |
| if isRat(other): |
| return Rat(self.__num*other.__num, self.__den*other.__den) |
| if isint(other): |
| return Rat(self.__num*other, self.__den) |
| if isnum(other): |
| return float(self)*other |
| return NotImplemented |
| |
| __rmul__ = __mul__ |
| |
| def __truediv__(self, other): |
| """Divide two Rats, or a Rat and a number.""" |
| if isRat(other): |
| return Rat(self.__num*other.__den, self.__den*other.__num) |
| if isint(other): |
| return Rat(self.__num, self.__den*other) |
| if isnum(other): |
| return float(self) / other |
| return NotImplemented |
| |
| def __rtruediv__(self, other): |
| """Divide two Rats, or a Rat and a number (reversed args).""" |
| if isRat(other): |
| return Rat(other.__num*self.__den, other.__den*self.__num) |
| if isint(other): |
| return Rat(other*self.__den, self.__num) |
| if isnum(other): |
| return other / float(self) |
| return NotImplemented |
| |
| def __floordiv__(self, other): |
| """Divide two Rats, returning the floored result.""" |
| if isint(other): |
| other = Rat(other) |
| elif not isRat(other): |
| return NotImplemented |
| x = self/other |
| return x.__num // x.__den |
| |
| def __rfloordiv__(self, other): |
| """Divide two Rats, returning the floored result (reversed args).""" |
| x = other/self |
| return x.__num // x.__den |
| |
| def __divmod__(self, other): |
| """Divide two Rats, returning quotient and remainder.""" |
| if isint(other): |
| other = Rat(other) |
| elif not isRat(other): |
| return NotImplemented |
| x = self//other |
| return (x, self - other * x) |
| |
| def __rdivmod__(self, other): |
| """Divide two Rats, returning quotient and remainder (reversed args).""" |
| if isint(other): |
| other = Rat(other) |
| elif not isRat(other): |
| return NotImplemented |
| return divmod(other, self) |
| |
| def __mod__(self, other): |
| """Take one Rat modulo another.""" |
| return divmod(self, other)[1] |
| |
| def __rmod__(self, other): |
| """Take one Rat modulo another (reversed args).""" |
| return divmod(other, self)[1] |
| |
| def __eq__(self, other): |
| """Compare two Rats for equality.""" |
| if isint(other): |
| return self.__den == 1 and self.__num == other |
| if isRat(other): |
| return self.__num == other.__num and self.__den == other.__den |
| if isnum(other): |
| return float(self) == other |
| return NotImplemented |
| |
| class RatTestCase(unittest.TestCase): |
| """Unit tests for Rat class and its support utilities.""" |
| |
| def test_gcd(self): |
| self.assertEqual(gcd(10, 12), 2) |
| self.assertEqual(gcd(10, 15), 5) |
| self.assertEqual(gcd(10, 11), 1) |
| self.assertEqual(gcd(100, 15), 5) |
| self.assertEqual(gcd(-10, 2), -2) |
| self.assertEqual(gcd(10, -2), 2) |
| self.assertEqual(gcd(-10, -2), -2) |
| for i in range(1, 20): |
| for j in range(1, 20): |
| self.assertTrue(gcd(i, j) > 0) |
| self.assertTrue(gcd(-i, j) < 0) |
| self.assertTrue(gcd(i, -j) > 0) |
| self.assertTrue(gcd(-i, -j) < 0) |
| |
| def test_constructor(self): |
| a = Rat(10, 15) |
| self.assertEqual(a.num, 2) |
| self.assertEqual(a.den, 3) |
| a = Rat(10, -15) |
| self.assertEqual(a.num, -2) |
| self.assertEqual(a.den, 3) |
| a = Rat(-10, 15) |
| self.assertEqual(a.num, -2) |
| self.assertEqual(a.den, 3) |
| a = Rat(-10, -15) |
| self.assertEqual(a.num, 2) |
| self.assertEqual(a.den, 3) |
| a = Rat(7) |
| self.assertEqual(a.num, 7) |
| self.assertEqual(a.den, 1) |
| try: |
| a = Rat(1, 0) |
| except ZeroDivisionError: |
| pass |
| else: |
| self.fail("Rat(1, 0) didn't raise ZeroDivisionError") |
| for bad in "0", 0.0, 0j, (), [], {}, None, Rat, unittest: |
| try: |
| a = Rat(bad) |
| except TypeError: |
| pass |
| else: |
| self.fail("Rat(%r) didn't raise TypeError" % bad) |
| try: |
| a = Rat(1, bad) |
| except TypeError: |
| pass |
| else: |
| self.fail("Rat(1, %r) didn't raise TypeError" % bad) |
| |
| def test_add(self): |
| self.assertEqual(Rat(2, 3) + Rat(1, 3), 1) |
| self.assertEqual(Rat(2, 3) + 1, Rat(5, 3)) |
| self.assertEqual(1 + Rat(2, 3), Rat(5, 3)) |
| self.assertEqual(1.0 + Rat(1, 2), 1.5) |
| self.assertEqual(Rat(1, 2) + 1.0, 1.5) |
| |
| def test_sub(self): |
| self.assertEqual(Rat(7, 2) - Rat(7, 5), Rat(21, 10)) |
| self.assertEqual(Rat(7, 5) - 1, Rat(2, 5)) |
| self.assertEqual(1 - Rat(3, 5), Rat(2, 5)) |
| self.assertEqual(Rat(3, 2) - 1.0, 0.5) |
| self.assertEqual(1.0 - Rat(1, 2), 0.5) |
| |
| def test_mul(self): |
| self.assertEqual(Rat(2, 3) * Rat(5, 7), Rat(10, 21)) |
| self.assertEqual(Rat(10, 3) * 3, 10) |
| self.assertEqual(3 * Rat(10, 3), 10) |
| self.assertEqual(Rat(10, 5) * 0.5, 1.0) |
| self.assertEqual(0.5 * Rat(10, 5), 1.0) |
| |
| def test_div(self): |
| self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) |
| self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) |
| self.assertEqual(2 / Rat(5), Rat(2, 5)) |
| self.assertEqual(3.0 * Rat(1, 2), 1.5) |
| self.assertEqual(Rat(1, 2) * 3.0, 1.5) |
| |
| def test_floordiv(self): |
| self.assertEqual(Rat(10) // Rat(4), 2) |
| self.assertEqual(Rat(10, 3) // Rat(4, 3), 2) |
| self.assertEqual(Rat(10) // 4, 2) |
| self.assertEqual(10 // Rat(4), 2) |
| |
| def test_eq(self): |
| self.assertEqual(Rat(10), Rat(20, 2)) |
| self.assertEqual(Rat(10), 10) |
| self.assertEqual(10, Rat(10)) |
| self.assertEqual(Rat(10), 10.0) |
| self.assertEqual(10.0, Rat(10)) |
| |
| def test_true_div(self): |
| self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) |
| self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) |
| self.assertEqual(2 / Rat(5), Rat(2, 5)) |
| self.assertEqual(3.0 * Rat(1, 2), 1.5) |
| self.assertEqual(Rat(1, 2) * 3.0, 1.5) |
| self.assertEqual(eval('1/2'), 0.5) |
| |
| # XXX Ran out of steam; TO DO: divmod, div, future division |
| |
| |
| class OperationLogger: |
| """Base class for classes with operation logging.""" |
| def __init__(self, logger): |
| self.logger = logger |
| def log_operation(self, *args): |
| self.logger(*args) |
| |
| def op_sequence(op, *classes): |
| """Return the sequence of operations that results from applying |
| the operation `op` to instances of the given classes.""" |
| log = [] |
| instances = [] |
| for c in classes: |
| instances.append(c(log.append)) |
| |
| try: |
| op(*instances) |
| except TypeError: |
| pass |
| return log |
| |
| class A(OperationLogger): |
| def __eq__(self, other): |
| self.log_operation('A.__eq__') |
| return NotImplemented |
| def __le__(self, other): |
| self.log_operation('A.__le__') |
| return NotImplemented |
| def __ge__(self, other): |
| self.log_operation('A.__ge__') |
| return NotImplemented |
| |
| class B(OperationLogger, metaclass=ABCMeta): |
| def __eq__(self, other): |
| self.log_operation('B.__eq__') |
| return NotImplemented |
| def __le__(self, other): |
| self.log_operation('B.__le__') |
| return NotImplemented |
| def __ge__(self, other): |
| self.log_operation('B.__ge__') |
| return NotImplemented |
| |
| class C(B): |
| def __eq__(self, other): |
| self.log_operation('C.__eq__') |
| return NotImplemented |
| def __le__(self, other): |
| self.log_operation('C.__le__') |
| return NotImplemented |
| def __ge__(self, other): |
| self.log_operation('C.__ge__') |
| return NotImplemented |
| |
| class V(OperationLogger): |
| """Virtual subclass of B""" |
| def __eq__(self, other): |
| self.log_operation('V.__eq__') |
| return NotImplemented |
| def __le__(self, other): |
| self.log_operation('V.__le__') |
| return NotImplemented |
| def __ge__(self, other): |
| self.log_operation('V.__ge__') |
| return NotImplemented |
| B.register(V) |
| |
| |
| class OperationOrderTests(unittest.TestCase): |
| def test_comparison_orders(self): |
| self.assertEqual(op_sequence(eq, A, A), ['A.__eq__', 'A.__eq__']) |
| self.assertEqual(op_sequence(eq, A, B), ['A.__eq__', 'B.__eq__']) |
| self.assertEqual(op_sequence(eq, B, A), ['B.__eq__', 'A.__eq__']) |
| # C is a subclass of B, so C.__eq__ is called first |
| self.assertEqual(op_sequence(eq, B, C), ['C.__eq__', 'B.__eq__']) |
| self.assertEqual(op_sequence(eq, C, B), ['C.__eq__', 'B.__eq__']) |
| |
| self.assertEqual(op_sequence(le, A, A), ['A.__le__', 'A.__ge__']) |
| self.assertEqual(op_sequence(le, A, B), ['A.__le__', 'B.__ge__']) |
| self.assertEqual(op_sequence(le, B, A), ['B.__le__', 'A.__ge__']) |
| self.assertEqual(op_sequence(le, B, C), ['C.__ge__', 'B.__le__']) |
| self.assertEqual(op_sequence(le, C, B), ['C.__le__', 'B.__ge__']) |
| |
| self.assertTrue(issubclass(V, B)) |
| self.assertEqual(op_sequence(eq, B, V), ['B.__eq__', 'V.__eq__']) |
| self.assertEqual(op_sequence(le, B, V), ['B.__le__', 'V.__ge__']) |
| |
| |
| if __name__ == "__main__": |
| unittest.main() |