1"""Tests for binary operators on subtypes of built-in types.""" 2 3import unittest 4from operator import eq, le, ne 5from abc import ABCMeta 6 7def gcd(a, b): 8 """Greatest common divisor using Euclid's algorithm.""" 9 while a: 10 a, b = b%a, a 11 return b 12 13def isint(x): 14 """Test whether an object is an instance of int.""" 15 return isinstance(x, int) 16 17def isnum(x): 18 """Test whether an object is an instance of a built-in numeric type.""" 19 for T in int, float, complex: 20 if isinstance(x, T): 21 return 1 22 return 0 23 24def isRat(x): 25 """Test whether an object is an instance of the Rat class.""" 26 return isinstance(x, Rat) 27 28class Rat(object): 29 30 """Rational number implemented as a normalized pair of ints.""" 31 32 __slots__ = ['_Rat__num', '_Rat__den'] 33 34 def __init__(self, num=0, den=1): 35 """Constructor: Rat([num[, den]]). 36 37 The arguments must be ints, and default to (0, 1).""" 38 if not isint(num): 39 raise TypeError("Rat numerator must be int (%r)" % num) 40 if not isint(den): 41 raise TypeError("Rat denominator must be int (%r)" % den) 42 # But the zero is always on 43 if den == 0: 44 raise ZeroDivisionError("zero denominator") 45 g = gcd(den, num) 46 self.__num = int(num//g) 47 self.__den = int(den//g) 48 49 def _get_num(self): 50 """Accessor function for read-only 'num' attribute of Rat.""" 51 return self.__num 52 num = property(_get_num, None) 53 54 def _get_den(self): 55 """Accessor function for read-only 'den' attribute of Rat.""" 56 return self.__den 57 den = property(_get_den, None) 58 59 def __repr__(self): 60 """Convert a Rat to a 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: 77 raise OverflowError("%s too large to convert to int" % 78 repr(self)) 79 raise ValueError("can't convert %s to int" % repr(self)) 80 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 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 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): 172 """Divide two Rats, returning quotient and remainder (reversed args).""" 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 197class RatTestCase(unittest.TestCase): 198 """Unit tests for Rat class and its support utilities.""" 199 200 def test_gcd(self): 201 self.assertEqual(gcd(10, 12), 2) 202 self.assertEqual(gcd(10, 15), 5) 203 self.assertEqual(gcd(10, 11), 1) 204 self.assertEqual(gcd(100, 15), 5) 205 self.assertEqual(gcd(-10, 2), -2) 206 self.assertEqual(gcd(10, -2), 2) 207 self.assertEqual(gcd(-10, -2), -2) 208 for i in range(1, 20): 209 for j in range(1, 20): 210 self.assertTrue(gcd(i, j) > 0) 211 self.assertTrue(gcd(-i, j) < 0) 212 self.assertTrue(gcd(i, -j) > 0) 213 self.assertTrue(gcd(-i, -j) < 0) 214 215 def test_constructor(self): 216 a = Rat(10, 15) 217 self.assertEqual(a.num, 2) 218 self.assertEqual(a.den, 3) 219 a = Rat(10, -15) 220 self.assertEqual(a.num, -2) 221 self.assertEqual(a.den, 3) 222 a = Rat(-10, 15) 223 self.assertEqual(a.num, -2) 224 self.assertEqual(a.den, 3) 225 a = Rat(-10, -15) 226 self.assertEqual(a.num, 2) 227 self.assertEqual(a.den, 3) 228 a = Rat(7) 229 self.assertEqual(a.num, 7) 230 self.assertEqual(a.den, 1) 231 try: 232 a = Rat(1, 0) 233 except ZeroDivisionError: 234 pass 235 else: 236 self.fail("Rat(1, 0) didn't raise ZeroDivisionError") 237 for bad in "0", 0.0, 0j, (), [], {}, None, Rat, unittest: 238 try: 239 a = Rat(bad) 240 except TypeError: 241 pass 242 else: 243 self.fail("Rat(%r) didn't raise TypeError" % bad) 244 try: 245 a = Rat(1, bad) 246 except TypeError: 247 pass 248 else: 249 self.fail("Rat(1, %r) didn't raise TypeError" % bad) 250 251 def test_add(self): 252 self.assertEqual(Rat(2, 3) + Rat(1, 3), 1) 253 self.assertEqual(Rat(2, 3) + 1, Rat(5, 3)) 254 self.assertEqual(1 + Rat(2, 3), Rat(5, 3)) 255 self.assertEqual(1.0 + Rat(1, 2), 1.5) 256 self.assertEqual(Rat(1, 2) + 1.0, 1.5) 257 258 def test_sub(self): 259 self.assertEqual(Rat(7, 2) - Rat(7, 5), Rat(21, 10)) 260 self.assertEqual(Rat(7, 5) - 1, Rat(2, 5)) 261 self.assertEqual(1 - Rat(3, 5), Rat(2, 5)) 262 self.assertEqual(Rat(3, 2) - 1.0, 0.5) 263 self.assertEqual(1.0 - Rat(1, 2), 0.5) 264 265 def test_mul(self): 266 self.assertEqual(Rat(2, 3) * Rat(5, 7), Rat(10, 21)) 267 self.assertEqual(Rat(10, 3) * 3, 10) 268 self.assertEqual(3 * Rat(10, 3), 10) 269 self.assertEqual(Rat(10, 5) * 0.5, 1.0) 270 self.assertEqual(0.5 * Rat(10, 5), 1.0) 271 272 def test_div(self): 273 self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) 274 self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) 275 self.assertEqual(2 / Rat(5), Rat(2, 5)) 276 self.assertEqual(3.0 * Rat(1, 2), 1.5) 277 self.assertEqual(Rat(1, 2) * 3.0, 1.5) 278 279 def test_floordiv(self): 280 self.assertEqual(Rat(10) // Rat(4), 2) 281 self.assertEqual(Rat(10, 3) // Rat(4, 3), 2) 282 self.assertEqual(Rat(10) // 4, 2) 283 self.assertEqual(10 // Rat(4), 2) 284 285 def test_eq(self): 286 self.assertEqual(Rat(10), Rat(20, 2)) 287 self.assertEqual(Rat(10), 10) 288 self.assertEqual(10, Rat(10)) 289 self.assertEqual(Rat(10), 10.0) 290 self.assertEqual(10.0, Rat(10)) 291 292 def test_true_div(self): 293 self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3)) 294 self.assertEqual(Rat(10, 3) / 3, Rat(10, 9)) 295 self.assertEqual(2 / Rat(5), Rat(2, 5)) 296 self.assertEqual(3.0 * Rat(1, 2), 1.5) 297 self.assertEqual(Rat(1, 2) * 3.0, 1.5) 298 self.assertEqual(eval('1/2'), 0.5) 299 300 # XXX Ran out of steam; TO DO: divmod, div, future division 301 302 303class OperationLogger: 304 """Base class for classes with operation logging.""" 305 def __init__(self, logger): 306 self.logger = logger 307 def log_operation(self, *args): 308 self.logger(*args) 309 310def op_sequence(op, *classes): 311 """Return the sequence of operations that results from applying 312 the operation `op` to instances of the given classes.""" 313 log = [] 314 instances = [] 315 for c in classes: 316 instances.append(c(log.append)) 317 318 try: 319 op(*instances) 320 except TypeError: 321 pass 322 return log 323 324class A(OperationLogger): 325 def __eq__(self, other): 326 self.log_operation('A.__eq__') 327 return NotImplemented 328 def __le__(self, other): 329 self.log_operation('A.__le__') 330 return NotImplemented 331 def __ge__(self, other): 332 self.log_operation('A.__ge__') 333 return NotImplemented 334 335class B(OperationLogger, metaclass=ABCMeta): 336 def __eq__(self, other): 337 self.log_operation('B.__eq__') 338 return NotImplemented 339 def __le__(self, other): 340 self.log_operation('B.__le__') 341 return NotImplemented 342 def __ge__(self, other): 343 self.log_operation('B.__ge__') 344 return NotImplemented 345 346class C(B): 347 def __eq__(self, other): 348 self.log_operation('C.__eq__') 349 return NotImplemented 350 def __le__(self, other): 351 self.log_operation('C.__le__') 352 return NotImplemented 353 def __ge__(self, other): 354 self.log_operation('C.__ge__') 355 return NotImplemented 356 357class V(OperationLogger): 358 """Virtual subclass of B""" 359 def __eq__(self, other): 360 self.log_operation('V.__eq__') 361 return NotImplemented 362 def __le__(self, other): 363 self.log_operation('V.__le__') 364 return NotImplemented 365 def __ge__(self, other): 366 self.log_operation('V.__ge__') 367 return NotImplemented 368B.register(V) 369 370 371class OperationOrderTests(unittest.TestCase): 372 def test_comparison_orders(self): 373 self.assertEqual(op_sequence(eq, A, A), ['A.__eq__', 'A.__eq__']) 374 self.assertEqual(op_sequence(eq, A, B), ['A.__eq__', 'B.__eq__']) 375 self.assertEqual(op_sequence(eq, B, A), ['B.__eq__', 'A.__eq__']) 376 # C is a subclass of B, so C.__eq__ is called first 377 self.assertEqual(op_sequence(eq, B, C), ['C.__eq__', 'B.__eq__']) 378 self.assertEqual(op_sequence(eq, C, B), ['C.__eq__', 'B.__eq__']) 379 380 self.assertEqual(op_sequence(le, A, A), ['A.__le__', 'A.__ge__']) 381 self.assertEqual(op_sequence(le, A, B), ['A.__le__', 'B.__ge__']) 382 self.assertEqual(op_sequence(le, B, A), ['B.__le__', 'A.__ge__']) 383 self.assertEqual(op_sequence(le, B, C), ['C.__ge__', 'B.__le__']) 384 self.assertEqual(op_sequence(le, C, B), ['C.__le__', 'B.__ge__']) 385 386 self.assertTrue(issubclass(V, B)) 387 self.assertEqual(op_sequence(eq, B, V), ['B.__eq__', 'V.__eq__']) 388 self.assertEqual(op_sequence(le, B, V), ['B.__le__', 'V.__ge__']) 389 390class SupEq(object): 391 """Class that can test equality""" 392 def __eq__(self, other): 393 return True 394 395class S(SupEq): 396 """Subclass of SupEq that should fail""" 397 __eq__ = None 398 399class F(object): 400 """Independent class that should fall back""" 401 402class X(object): 403 """Independent class that should fail""" 404 __eq__ = None 405 406class SN(SupEq): 407 """Subclass of SupEq that can test equality, but not non-equality""" 408 __ne__ = None 409 410class XN: 411 """Independent class that can test equality, but not non-equality""" 412 def __eq__(self, other): 413 return True 414 __ne__ = None 415 416class FallbackBlockingTests(unittest.TestCase): 417 """Unit tests for None method blocking""" 418 419 def test_fallback_rmethod_blocking(self): 420 e, f, s, x = SupEq(), F(), S(), X() 421 self.assertEqual(e, e) 422 self.assertEqual(e, f) 423 self.assertEqual(f, e) 424 # left operand is checked first 425 self.assertEqual(e, x) 426 self.assertRaises(TypeError, eq, x, e) 427 # S is a subclass, so it's always checked first 428 self.assertRaises(TypeError, eq, e, s) 429 self.assertRaises(TypeError, eq, s, e) 430 431 def test_fallback_ne_blocking(self): 432 e, sn, xn = SupEq(), SN(), XN() 433 self.assertFalse(e != e) 434 self.assertRaises(TypeError, ne, e, sn) 435 self.assertRaises(TypeError, ne, sn, e) 436 self.assertFalse(e != xn) 437 self.assertRaises(TypeError, ne, xn, e) 438 439if __name__ == "__main__": 440 unittest.main() 441