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