blob: 299af09c4983dfdb83d35bc99fda5c5f95f35151 [file] [log] [blame]
Guido van Rossum64deef22001-08-08 22:27:20 +00001"""Tests for binary operators on subtypes of built-in types."""
2
Guido van Rossum64deef22001-08-08 22:27:20 +00003import unittest
Guido van Rossum97c1adf2016-08-18 09:22:23 -07004from operator import eq, le, ne
Robert Collinsd84b29f2015-08-07 10:22:54 +12005from abc import ABCMeta
Guido van Rossum64deef22001-08-08 22:27:20 +00006
Guido van Rossum64deef22001-08-08 22:27:20 +00007def 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):
Mark Dickinson5c2db372009-12-05 20:28:34 +000014 """Test whether an object is an instance of int."""
15 return isinstance(x, int)
Guido van Rossum64deef22001-08-08 22:27:20 +000016
17def isnum(x):
18 """Test whether an object is an instance of a built-in numeric type."""
Florent Xicluna02ea12b22010-07-28 16:39:41 +000019 for T in int, float, complex:
Guido van Rossum64deef22001-08-08 22:27:20 +000020 if isinstance(x, T):
21 return 1
22 return 0
23
24def isRat(x):
Ville Skyttä49b27342017-08-03 09:00:59 +030025 """Test whether an object is an instance of the Rat class."""
Guido van Rossum64deef22001-08-08 22:27:20 +000026 return isinstance(x, Rat)
27
28class Rat(object):
29
Mark Dickinson5c2db372009-12-05 20:28:34 +000030 """Rational number implemented as a normalized pair of ints."""
Guido van Rossum64deef22001-08-08 22:27:20 +000031
32 __slots__ = ['_Rat__num', '_Rat__den']
33
Guido van Rossume2a383d2007-01-15 16:59:06 +000034 def __init__(self, num=0, den=1):
Guido van Rossum64deef22001-08-08 22:27:20 +000035 """Constructor: Rat([num[, den]]).
36
Mark Dickinson5c2db372009-12-05 20:28:34 +000037 The arguments must be ints, and default to (0, 1)."""
Guido van Rossum64deef22001-08-08 22:27:20 +000038 if not isint(num):
Mark Dickinson5c2db372009-12-05 20:28:34 +000039 raise TypeError("Rat numerator must be int (%r)" % num)
Guido van Rossum64deef22001-08-08 22:27:20 +000040 if not isint(den):
Mark Dickinson5c2db372009-12-05 20:28:34 +000041 raise TypeError("Rat denominator must be int (%r)" % den)
Guido van Rossum64deef22001-08-08 22:27:20 +000042 # But the zero is always on
43 if den == 0:
Collin Winter3add4d72007-08-29 23:37:32 +000044 raise ZeroDivisionError("zero denominator")
Guido van Rossum64deef22001-08-08 22:27:20 +000045 g = gcd(den, num)
Guido van Rossume2a383d2007-01-15 16:59:06 +000046 self.__num = int(num//g)
47 self.__den = int(den//g)
Guido van Rossum64deef22001-08-08 22:27:20 +000048
49 def _get_num(self):
50 """Accessor function for read-only 'num' attribute of Rat."""
51 return self.__num
Guido van Rossum8bce4ac2001-09-06 21:56:42 +000052 num = property(_get_num, None)
Guido van Rossum64deef22001-08-08 22:27:20 +000053
54 def _get_den(self):
55 """Accessor function for read-only 'den' attribute of Rat."""
56 return self.__den
Guido van Rossum8bce4ac2001-09-06 21:56:42 +000057 den = property(_get_den, None)
Guido van Rossum64deef22001-08-08 22:27:20 +000058
59 def __repr__(self):
Serhiy Storchaka6a7b3a72016-04-17 08:32:47 +030060 """Convert a Rat to a string resembling a Rat constructor call."""
Guido van Rossum64deef22001-08-08 22:27:20 +000061 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 Winter3add4d72007-08-29 23:37:32 +000077 raise OverflowError("%s too large to convert to int" %
Guido van Rossum64deef22001-08-08 22:27:20 +000078 repr(self))
Collin Winter3add4d72007-08-29 23:37:32 +000079 raise ValueError("can't convert %s to int" % repr(self))
Guido van Rossum64deef22001-08-08 22:27:20 +000080
Guido van Rossum64deef22001-08-08 22:27:20 +000081 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 Rossum64deef22001-08-08 22:27:20 +0000138 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 Rossum64deef22001-08-08 22:27:20 +0000148 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 Cannon53e9a8b2005-04-30 05:50:19 +0000172 """Divide two Rats, returning quotient and remainder (reversed args)."""
Guido van Rossum64deef22001-08-08 22:27:20 +0000173 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
Guido van Rossum64deef22001-08-08 22:27:20 +0000197class 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):
Benjamin Petersonc9c0f202009-06-30 23:06:06 +0000210 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)
Guido van Rossum64deef22001-08-08 22:27:20 +0000214
215 def test_constructor(self):
216 a = Rat(10, 15)
217 self.assertEqual(a.num, 2)
218 self.assertEqual(a.den, 3)
Guido van Rossum64deef22001-08-08 22:27:20 +0000219 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
Benjamin Peterson42806ba2008-06-28 23:34:00 +0000292 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)
Guido van Rossum64deef22001-08-08 22:27:20 +0000299
300 # XXX Ran out of steam; TO DO: divmod, div, future division
301
Fred Drake2e2be372001-09-20 21:33:42 +0000302
Mark Dickinson6f1d0492009-11-15 13:58:49 +0000303class 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
Robert Collinsd84b29f2015-08-07 10:22:54 +1200335class B(OperationLogger, metaclass=ABCMeta):
Mark Dickinson6f1d0492009-11-15 13:58:49 +0000336 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
Robert Collinsd84b29f2015-08-07 10:22:54 +1200357class 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
Mark Dickinson6f1d0492009-11-15 13:58:49 +0000371class 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
Robert Collinsd84b29f2015-08-07 10:22:54 +1200386 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
Guido van Rossum97c1adf2016-08-18 09:22:23 -0700390class 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)
Robert Collinsd84b29f2015-08-07 10:22:54 +1200438
Fred Drake2e2be372001-09-20 21:33:42 +0000439if __name__ == "__main__":
Zachary Ware38c707e2015-04-13 15:00:43 -0500440 unittest.main()