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