• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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