Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 1 | """Tests for binary operators on subtypes of built-in types.""" |
| 2 | |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 3 | import unittest |
Benjamin Peterson | ee8712c | 2008-05-20 21:35:26 +0000 | [diff] [blame] | 4 | from test import support |
Mark Dickinson | 6f1d049 | 2009-11-15 13:58:49 +0000 | [diff] [blame] | 5 | from operator import eq, ne, lt, gt, le, ge |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 6 | |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 7 | def gcd(a, b): |
| 8 | """Greatest common divisor using Euclid's algorithm.""" |
| 9 | while a: |
| 10 | a, b = b%a, a |
| 11 | return b |
| 12 | |
| 13 | def isint(x): |
Mark Dickinson | 5c2db37 | 2009-12-05 20:28:34 +0000 | [diff] [blame] | 14 | """Test whether an object is an instance of int.""" |
| 15 | return isinstance(x, int) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 16 | |
| 17 | def isnum(x): |
| 18 | """Test whether an object is an instance of a built-in numeric type.""" |
Guido van Rossum | e2a383d | 2007-01-15 16:59:06 +0000 | [diff] [blame] | 19 | for T in int, int, float, complex: |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 20 | if isinstance(x, T): |
| 21 | return 1 |
| 22 | return 0 |
| 23 | |
| 24 | def isRat(x): |
| 25 | """Test wheter an object is an instance of the Rat class.""" |
| 26 | return isinstance(x, Rat) |
| 27 | |
| 28 | class Rat(object): |
| 29 | |
Mark Dickinson | 5c2db37 | 2009-12-05 20:28:34 +0000 | [diff] [blame] | 30 | """Rational number implemented as a normalized pair of ints.""" |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 31 | |
| 32 | __slots__ = ['_Rat__num', '_Rat__den'] |
| 33 | |
Guido van Rossum | e2a383d | 2007-01-15 16:59:06 +0000 | [diff] [blame] | 34 | def __init__(self, num=0, den=1): |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 35 | """Constructor: Rat([num[, den]]). |
| 36 | |
Mark Dickinson | 5c2db37 | 2009-12-05 20:28:34 +0000 | [diff] [blame] | 37 | The arguments must be ints, and default to (0, 1).""" |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 38 | if not isint(num): |
Mark Dickinson | 5c2db37 | 2009-12-05 20:28:34 +0000 | [diff] [blame] | 39 | raise TypeError("Rat numerator must be int (%r)" % num) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 40 | if not isint(den): |
Mark Dickinson | 5c2db37 | 2009-12-05 20:28:34 +0000 | [diff] [blame] | 41 | raise TypeError("Rat denominator must be int (%r)" % den) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 42 | # But the zero is always on |
| 43 | if den == 0: |
Collin Winter | 3add4d7 | 2007-08-29 23:37:32 +0000 | [diff] [blame] | 44 | raise ZeroDivisionError("zero denominator") |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 45 | g = gcd(den, num) |
Guido van Rossum | e2a383d | 2007-01-15 16:59:06 +0000 | [diff] [blame] | 46 | self.__num = int(num//g) |
| 47 | self.__den = int(den//g) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 48 | |
| 49 | def _get_num(self): |
| 50 | """Accessor function for read-only 'num' attribute of Rat.""" |
| 51 | return self.__num |
Guido van Rossum | 8bce4ac | 2001-09-06 21:56:42 +0000 | [diff] [blame] | 52 | num = property(_get_num, None) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 53 | |
| 54 | def _get_den(self): |
| 55 | """Accessor function for read-only 'den' attribute of Rat.""" |
| 56 | return self.__den |
Guido van Rossum | 8bce4ac | 2001-09-06 21:56:42 +0000 | [diff] [blame] | 57 | den = property(_get_den, None) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 58 | |
| 59 | def __repr__(self): |
| 60 | """Convert a Rat to an string resembling a Rat constructor call.""" |
| 61 | return "Rat(%d, %d)" % (self.__num, self.__den) |
| 62 | |
| 63 | def __str__(self): |
| 64 | """Convert a Rat to a string resembling a decimal numeric value.""" |
| 65 | return str(float(self)) |
| 66 | |
| 67 | def __float__(self): |
| 68 | """Convert a Rat to a float.""" |
| 69 | return self.__num*1.0/self.__den |
| 70 | |
| 71 | def __int__(self): |
| 72 | """Convert a Rat to an int; self.den must be 1.""" |
| 73 | if self.__den == 1: |
| 74 | try: |
| 75 | return int(self.__num) |
| 76 | except OverflowError: |
Collin Winter | 3add4d7 | 2007-08-29 23:37:32 +0000 | [diff] [blame] | 77 | raise OverflowError("%s too large to convert to int" % |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 78 | repr(self)) |
Collin Winter | 3add4d7 | 2007-08-29 23:37:32 +0000 | [diff] [blame] | 79 | raise ValueError("can't convert %s to int" % repr(self)) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 80 | |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 81 | def __add__(self, other): |
| 82 | """Add two Rats, or a Rat and a number.""" |
| 83 | if isint(other): |
| 84 | other = Rat(other) |
| 85 | if isRat(other): |
| 86 | return Rat(self.__num*other.__den + other.__num*self.__den, |
| 87 | self.__den*other.__den) |
| 88 | if isnum(other): |
| 89 | return float(self) + other |
| 90 | return NotImplemented |
| 91 | |
| 92 | __radd__ = __add__ |
| 93 | |
| 94 | def __sub__(self, other): |
| 95 | """Subtract two Rats, or a Rat and a number.""" |
| 96 | if isint(other): |
| 97 | other = Rat(other) |
| 98 | if isRat(other): |
| 99 | return Rat(self.__num*other.__den - other.__num*self.__den, |
| 100 | self.__den*other.__den) |
| 101 | if isnum(other): |
| 102 | return float(self) - other |
| 103 | return NotImplemented |
| 104 | |
| 105 | def __rsub__(self, other): |
| 106 | """Subtract two Rats, or a Rat and a number (reversed args).""" |
| 107 | if isint(other): |
| 108 | other = Rat(other) |
| 109 | if isRat(other): |
| 110 | return Rat(other.__num*self.__den - self.__num*other.__den, |
| 111 | self.__den*other.__den) |
| 112 | if isnum(other): |
| 113 | return other - float(self) |
| 114 | return NotImplemented |
| 115 | |
| 116 | def __mul__(self, other): |
| 117 | """Multiply two Rats, or a Rat and a number.""" |
| 118 | if isRat(other): |
| 119 | return Rat(self.__num*other.__num, self.__den*other.__den) |
| 120 | if isint(other): |
| 121 | return Rat(self.__num*other, self.__den) |
| 122 | if isnum(other): |
| 123 | return float(self)*other |
| 124 | return NotImplemented |
| 125 | |
| 126 | __rmul__ = __mul__ |
| 127 | |
| 128 | def __truediv__(self, other): |
| 129 | """Divide two Rats, or a Rat and a number.""" |
| 130 | if isRat(other): |
| 131 | return Rat(self.__num*other.__den, self.__den*other.__num) |
| 132 | if isint(other): |
| 133 | return Rat(self.__num, self.__den*other) |
| 134 | if isnum(other): |
| 135 | return float(self) / other |
| 136 | return NotImplemented |
| 137 | |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 138 | def __rtruediv__(self, other): |
| 139 | """Divide two Rats, or a Rat and a number (reversed args).""" |
| 140 | if isRat(other): |
| 141 | return Rat(other.__num*self.__den, other.__den*self.__num) |
| 142 | if isint(other): |
| 143 | return Rat(other*self.__den, self.__num) |
| 144 | if isnum(other): |
| 145 | return other / float(self) |
| 146 | return NotImplemented |
| 147 | |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 148 | def __floordiv__(self, other): |
| 149 | """Divide two Rats, returning the floored result.""" |
| 150 | if isint(other): |
| 151 | other = Rat(other) |
| 152 | elif not isRat(other): |
| 153 | return NotImplemented |
| 154 | x = self/other |
| 155 | return x.__num // x.__den |
| 156 | |
| 157 | def __rfloordiv__(self, other): |
| 158 | """Divide two Rats, returning the floored result (reversed args).""" |
| 159 | x = other/self |
| 160 | return x.__num // x.__den |
| 161 | |
| 162 | def __divmod__(self, other): |
| 163 | """Divide two Rats, returning quotient and remainder.""" |
| 164 | if isint(other): |
| 165 | other = Rat(other) |
| 166 | elif not isRat(other): |
| 167 | return NotImplemented |
| 168 | x = self//other |
| 169 | return (x, self - other * x) |
| 170 | |
| 171 | def __rdivmod__(self, other): |
Brett Cannon | 53e9a8b | 2005-04-30 05:50:19 +0000 | [diff] [blame] | 172 | """Divide two Rats, returning quotient and remainder (reversed args).""" |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 173 | if isint(other): |
| 174 | other = Rat(other) |
| 175 | elif not isRat(other): |
| 176 | return NotImplemented |
| 177 | return divmod(other, self) |
| 178 | |
| 179 | def __mod__(self, other): |
| 180 | """Take one Rat modulo another.""" |
| 181 | return divmod(self, other)[1] |
| 182 | |
| 183 | def __rmod__(self, other): |
| 184 | """Take one Rat modulo another (reversed args).""" |
| 185 | return divmod(other, self)[1] |
| 186 | |
| 187 | def __eq__(self, other): |
| 188 | """Compare two Rats for equality.""" |
| 189 | if isint(other): |
| 190 | return self.__den == 1 and self.__num == other |
| 191 | if isRat(other): |
| 192 | return self.__num == other.__num and self.__den == other.__den |
| 193 | if isnum(other): |
| 194 | return float(self) == other |
| 195 | return NotImplemented |
| 196 | |
| 197 | def __ne__(self, other): |
| 198 | """Compare two Rats for inequality.""" |
| 199 | return not self == other |
| 200 | |
| 201 | class RatTestCase(unittest.TestCase): |
| 202 | """Unit tests for Rat class and its support utilities.""" |
| 203 | |
| 204 | def test_gcd(self): |
| 205 | self.assertEqual(gcd(10, 12), 2) |
| 206 | self.assertEqual(gcd(10, 15), 5) |
| 207 | self.assertEqual(gcd(10, 11), 1) |
| 208 | self.assertEqual(gcd(100, 15), 5) |
| 209 | self.assertEqual(gcd(-10, 2), -2) |
| 210 | self.assertEqual(gcd(10, -2), 2) |
| 211 | self.assertEqual(gcd(-10, -2), -2) |
| 212 | for i in range(1, 20): |
| 213 | for j in range(1, 20): |
Benjamin Peterson | c9c0f20 | 2009-06-30 23:06:06 +0000 | [diff] [blame] | 214 | self.assertTrue(gcd(i, j) > 0) |
| 215 | self.assertTrue(gcd(-i, j) < 0) |
| 216 | self.assertTrue(gcd(i, -j) > 0) |
| 217 | self.assertTrue(gcd(-i, -j) < 0) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 218 | |
| 219 | def test_constructor(self): |
| 220 | a = Rat(10, 15) |
| 221 | self.assertEqual(a.num, 2) |
| 222 | self.assertEqual(a.den, 3) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 223 | a = Rat(10, -15) |
| 224 | self.assertEqual(a.num, -2) |
| 225 | self.assertEqual(a.den, 3) |
| 226 | a = Rat(-10, 15) |
| 227 | self.assertEqual(a.num, -2) |
| 228 | self.assertEqual(a.den, 3) |
| 229 | a = Rat(-10, -15) |
| 230 | self.assertEqual(a.num, 2) |
| 231 | self.assertEqual(a.den, 3) |
| 232 | a = Rat(7) |
| 233 | self.assertEqual(a.num, 7) |
| 234 | self.assertEqual(a.den, 1) |
| 235 | try: |
| 236 | a = Rat(1, 0) |
| 237 | except ZeroDivisionError: |
| 238 | pass |
| 239 | else: |
| 240 | self.fail("Rat(1, 0) didn't raise ZeroDivisionError") |
| 241 | for bad in "0", 0.0, 0j, (), [], {}, None, Rat, unittest: |
| 242 | try: |
| 243 | a = Rat(bad) |
| 244 | except TypeError: |
| 245 | pass |
| 246 | else: |
| 247 | self.fail("Rat(%r) didn't raise TypeError" % bad) |
| 248 | try: |
| 249 | a = Rat(1, bad) |
| 250 | except TypeError: |
| 251 | pass |
| 252 | else: |
| 253 | self.fail("Rat(1, %r) didn't raise TypeError" % bad) |
| 254 | |
| 255 | def test_add(self): |
| 256 | self.assertEqual(Rat(2, 3) + Rat(1, 3), 1) |
| 257 | self.assertEqual(Rat(2, 3) + 1, Rat(5, 3)) |
| 258 | self.assertEqual(1 + Rat(2, 3), Rat(5, 3)) |
| 259 | self.assertEqual(1.0 + Rat(1, 2), 1.5) |
| 260 | self.assertEqual(Rat(1, 2) + 1.0, 1.5) |
| 261 | |
| 262 | def test_sub(self): |
| 263 | self.assertEqual(Rat(7, 2) - Rat(7, 5), Rat(21, 10)) |
| 264 | self.assertEqual(Rat(7, 5) - 1, Rat(2, 5)) |
| 265 | self.assertEqual(1 - Rat(3, 5), Rat(2, 5)) |
| 266 | self.assertEqual(Rat(3, 2) - 1.0, 0.5) |
| 267 | self.assertEqual(1.0 - Rat(1, 2), 0.5) |
| 268 | |
| 269 | def test_mul(self): |
| 270 | self.assertEqual(Rat(2, 3) * Rat(5, 7), Rat(10, 21)) |
| 271 | self.assertEqual(Rat(10, 3) * 3, 10) |
| 272 | self.assertEqual(3 * Rat(10, 3), 10) |
| 273 | self.assertEqual(Rat(10, 5) * 0.5, 1.0) |
| 274 | self.assertEqual(0.5 * Rat(10, 5), 1.0) |
| 275 | |
| 276 | def test_div(self): |
| 277 | self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) |
| 278 | self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) |
| 279 | self.assertEqual(2 / Rat(5), Rat(2, 5)) |
| 280 | self.assertEqual(3.0 * Rat(1, 2), 1.5) |
| 281 | self.assertEqual(Rat(1, 2) * 3.0, 1.5) |
| 282 | |
| 283 | def test_floordiv(self): |
| 284 | self.assertEqual(Rat(10) // Rat(4), 2) |
| 285 | self.assertEqual(Rat(10, 3) // Rat(4, 3), 2) |
| 286 | self.assertEqual(Rat(10) // 4, 2) |
| 287 | self.assertEqual(10 // Rat(4), 2) |
| 288 | |
| 289 | def test_eq(self): |
| 290 | self.assertEqual(Rat(10), Rat(20, 2)) |
| 291 | self.assertEqual(Rat(10), 10) |
| 292 | self.assertEqual(10, Rat(10)) |
| 293 | self.assertEqual(Rat(10), 10.0) |
| 294 | self.assertEqual(10.0, Rat(10)) |
| 295 | |
Benjamin Peterson | 42806ba | 2008-06-28 23:34:00 +0000 | [diff] [blame] | 296 | def test_true_div(self): |
| 297 | self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) |
| 298 | self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) |
| 299 | self.assertEqual(2 / Rat(5), Rat(2, 5)) |
| 300 | self.assertEqual(3.0 * Rat(1, 2), 1.5) |
| 301 | self.assertEqual(Rat(1, 2) * 3.0, 1.5) |
| 302 | self.assertEqual(eval('1/2'), 0.5) |
Guido van Rossum | 64deef2 | 2001-08-08 22:27:20 +0000 | [diff] [blame] | 303 | |
| 304 | # XXX Ran out of steam; TO DO: divmod, div, future division |
| 305 | |
Fred Drake | 2e2be37 | 2001-09-20 21:33:42 +0000 | [diff] [blame] | 306 | |
Mark Dickinson | 6f1d049 | 2009-11-15 13:58:49 +0000 | [diff] [blame] | 307 | class OperationLogger: |
| 308 | """Base class for classes with operation logging.""" |
| 309 | def __init__(self, logger): |
| 310 | self.logger = logger |
| 311 | def log_operation(self, *args): |
| 312 | self.logger(*args) |
| 313 | |
| 314 | def op_sequence(op, *classes): |
| 315 | """Return the sequence of operations that results from applying |
| 316 | the operation `op` to instances of the given classes.""" |
| 317 | log = [] |
| 318 | instances = [] |
| 319 | for c in classes: |
| 320 | instances.append(c(log.append)) |
| 321 | |
| 322 | try: |
| 323 | op(*instances) |
| 324 | except TypeError: |
| 325 | pass |
| 326 | return log |
| 327 | |
| 328 | class A(OperationLogger): |
| 329 | def __eq__(self, other): |
| 330 | self.log_operation('A.__eq__') |
| 331 | return NotImplemented |
| 332 | def __le__(self, other): |
| 333 | self.log_operation('A.__le__') |
| 334 | return NotImplemented |
| 335 | def __ge__(self, other): |
| 336 | self.log_operation('A.__ge__') |
| 337 | return NotImplemented |
| 338 | |
| 339 | class B(OperationLogger): |
| 340 | def __eq__(self, other): |
| 341 | self.log_operation('B.__eq__') |
| 342 | return NotImplemented |
| 343 | def __le__(self, other): |
| 344 | self.log_operation('B.__le__') |
| 345 | return NotImplemented |
| 346 | def __ge__(self, other): |
| 347 | self.log_operation('B.__ge__') |
| 348 | return NotImplemented |
| 349 | |
| 350 | class C(B): |
| 351 | def __eq__(self, other): |
| 352 | self.log_operation('C.__eq__') |
| 353 | return NotImplemented |
| 354 | def __le__(self, other): |
| 355 | self.log_operation('C.__le__') |
| 356 | return NotImplemented |
| 357 | def __ge__(self, other): |
| 358 | self.log_operation('C.__ge__') |
| 359 | return NotImplemented |
| 360 | |
| 361 | class OperationOrderTests(unittest.TestCase): |
| 362 | def test_comparison_orders(self): |
| 363 | self.assertEqual(op_sequence(eq, A, A), ['A.__eq__', 'A.__eq__']) |
| 364 | self.assertEqual(op_sequence(eq, A, B), ['A.__eq__', 'B.__eq__']) |
| 365 | self.assertEqual(op_sequence(eq, B, A), ['B.__eq__', 'A.__eq__']) |
| 366 | # C is a subclass of B, so C.__eq__ is called first |
| 367 | self.assertEqual(op_sequence(eq, B, C), ['C.__eq__', 'B.__eq__']) |
| 368 | self.assertEqual(op_sequence(eq, C, B), ['C.__eq__', 'B.__eq__']) |
| 369 | |
| 370 | self.assertEqual(op_sequence(le, A, A), ['A.__le__', 'A.__ge__']) |
| 371 | self.assertEqual(op_sequence(le, A, B), ['A.__le__', 'B.__ge__']) |
| 372 | self.assertEqual(op_sequence(le, B, A), ['B.__le__', 'A.__ge__']) |
| 373 | self.assertEqual(op_sequence(le, B, C), ['C.__ge__', 'B.__le__']) |
| 374 | self.assertEqual(op_sequence(le, C, B), ['C.__le__', 'B.__ge__']) |
| 375 | |
| 376 | def test_main(): |
| 377 | support.run_unittest(RatTestCase, OperationOrderTests) |
Fred Drake | 2e2be37 | 2001-09-20 21:33:42 +0000 | [diff] [blame] | 378 | |
| 379 | if __name__ == "__main__": |
| 380 | test_main() |