mirror of
				https://github.com/python/cpython.git
				synced 2025-11-03 23:21:29 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			380 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			380 lines
		
	
	
	
		
			12 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""Tests for binary operators on subtypes of built-in types."""
 | 
						|
 | 
						|
import unittest
 | 
						|
from test import support
 | 
						|
from operator import eq, ne, lt, gt, le, ge
 | 
						|
 | 
						|
def gcd(a, b):
 | 
						|
    """Greatest common divisor using Euclid's algorithm."""
 | 
						|
    while a:
 | 
						|
        a, b = b%a, a
 | 
						|
    return b
 | 
						|
 | 
						|
def isint(x):
 | 
						|
    """Test whether an object is an instance of int."""
 | 
						|
    return isinstance(x, int)
 | 
						|
 | 
						|
def isnum(x):
 | 
						|
    """Test whether an object is an instance of a built-in numeric type."""
 | 
						|
    for T in int, float, complex:
 | 
						|
        if isinstance(x, T):
 | 
						|
            return 1
 | 
						|
    return 0
 | 
						|
 | 
						|
def isRat(x):
 | 
						|
    """Test wheter an object is an instance of the Rat class."""
 | 
						|
    return isinstance(x, Rat)
 | 
						|
 | 
						|
class Rat(object):
 | 
						|
 | 
						|
    """Rational number implemented as a normalized pair of ints."""
 | 
						|
 | 
						|
    __slots__ = ['_Rat__num', '_Rat__den']
 | 
						|
 | 
						|
    def __init__(self, num=0, den=1):
 | 
						|
        """Constructor: Rat([num[, den]]).
 | 
						|
 | 
						|
        The arguments must be ints, and default to (0, 1)."""
 | 
						|
        if not isint(num):
 | 
						|
            raise TypeError("Rat numerator must be int (%r)" % num)
 | 
						|
        if not isint(den):
 | 
						|
            raise TypeError("Rat denominator must be int (%r)" % den)
 | 
						|
        # But the zero is always on
 | 
						|
        if den == 0:
 | 
						|
            raise ZeroDivisionError("zero denominator")
 | 
						|
        g = gcd(den, num)
 | 
						|
        self.__num = int(num//g)
 | 
						|
        self.__den = int(den//g)
 | 
						|
 | 
						|
    def _get_num(self):
 | 
						|
        """Accessor function for read-only 'num' attribute of Rat."""
 | 
						|
        return self.__num
 | 
						|
    num = property(_get_num, None)
 | 
						|
 | 
						|
    def _get_den(self):
 | 
						|
        """Accessor function for read-only 'den' attribute of Rat."""
 | 
						|
        return self.__den
 | 
						|
    den = property(_get_den, None)
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        """Convert a Rat to an string resembling a Rat constructor call."""
 | 
						|
        return "Rat(%d, %d)" % (self.__num, self.__den)
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        """Convert a Rat to a string resembling a decimal numeric value."""
 | 
						|
        return str(float(self))
 | 
						|
 | 
						|
    def __float__(self):
 | 
						|
        """Convert a Rat to a float."""
 | 
						|
        return self.__num*1.0/self.__den
 | 
						|
 | 
						|
    def __int__(self):
 | 
						|
        """Convert a Rat to an int; self.den must be 1."""
 | 
						|
        if self.__den == 1:
 | 
						|
            try:
 | 
						|
                return int(self.__num)
 | 
						|
            except OverflowError:
 | 
						|
                raise OverflowError("%s too large to convert to int" %
 | 
						|
                                      repr(self))
 | 
						|
        raise ValueError("can't convert %s to int" % repr(self))
 | 
						|
 | 
						|
    def __add__(self, other):
 | 
						|
        """Add two Rats, or a Rat and a number."""
 | 
						|
        if isint(other):
 | 
						|
            other = Rat(other)
 | 
						|
        if isRat(other):
 | 
						|
            return Rat(self.__num*other.__den + other.__num*self.__den,
 | 
						|
                       self.__den*other.__den)
 | 
						|
        if isnum(other):
 | 
						|
            return float(self) + other
 | 
						|
        return NotImplemented
 | 
						|
 | 
						|
    __radd__ = __add__
 | 
						|
 | 
						|
    def __sub__(self, other):
 | 
						|
        """Subtract two Rats, or a Rat and a number."""
 | 
						|
        if isint(other):
 | 
						|
            other = Rat(other)
 | 
						|
        if isRat(other):
 | 
						|
            return Rat(self.__num*other.__den - other.__num*self.__den,
 | 
						|
                       self.__den*other.__den)
 | 
						|
        if isnum(other):
 | 
						|
            return float(self) - other
 | 
						|
        return NotImplemented
 | 
						|
 | 
						|
    def __rsub__(self, other):
 | 
						|
        """Subtract two Rats, or a Rat and a number (reversed args)."""
 | 
						|
        if isint(other):
 | 
						|
            other = Rat(other)
 | 
						|
        if isRat(other):
 | 
						|
            return Rat(other.__num*self.__den - self.__num*other.__den,
 | 
						|
                       self.__den*other.__den)
 | 
						|
        if isnum(other):
 | 
						|
            return other - float(self)
 | 
						|
        return NotImplemented
 | 
						|
 | 
						|
    def __mul__(self, other):
 | 
						|
        """Multiply two Rats, or a Rat and a number."""
 | 
						|
        if isRat(other):
 | 
						|
            return Rat(self.__num*other.__num, self.__den*other.__den)
 | 
						|
        if isint(other):
 | 
						|
            return Rat(self.__num*other, self.__den)
 | 
						|
        if isnum(other):
 | 
						|
            return float(self)*other
 | 
						|
        return NotImplemented
 | 
						|
 | 
						|
    __rmul__ = __mul__
 | 
						|
 | 
						|
    def __truediv__(self, other):
 | 
						|
        """Divide two Rats, or a Rat and a number."""
 | 
						|
        if isRat(other):
 | 
						|
            return Rat(self.__num*other.__den, self.__den*other.__num)
 | 
						|
        if isint(other):
 | 
						|
            return Rat(self.__num, self.__den*other)
 | 
						|
        if isnum(other):
 | 
						|
            return float(self) / other
 | 
						|
        return NotImplemented
 | 
						|
 | 
						|
    def __rtruediv__(self, other):
 | 
						|
        """Divide two Rats, or a Rat and a number (reversed args)."""
 | 
						|
        if isRat(other):
 | 
						|
            return Rat(other.__num*self.__den, other.__den*self.__num)
 | 
						|
        if isint(other):
 | 
						|
            return Rat(other*self.__den, self.__num)
 | 
						|
        if isnum(other):
 | 
						|
            return other / float(self)
 | 
						|
        return NotImplemented
 | 
						|
 | 
						|
    def __floordiv__(self, other):
 | 
						|
        """Divide two Rats, returning the floored result."""
 | 
						|
        if isint(other):
 | 
						|
            other = Rat(other)
 | 
						|
        elif not isRat(other):
 | 
						|
            return NotImplemented
 | 
						|
        x = self/other
 | 
						|
        return x.__num // x.__den
 | 
						|
 | 
						|
    def __rfloordiv__(self, other):
 | 
						|
        """Divide two Rats, returning the floored result (reversed args)."""
 | 
						|
        x = other/self
 | 
						|
        return x.__num // x.__den
 | 
						|
 | 
						|
    def __divmod__(self, other):
 | 
						|
        """Divide two Rats, returning quotient and remainder."""
 | 
						|
        if isint(other):
 | 
						|
            other = Rat(other)
 | 
						|
        elif not isRat(other):
 | 
						|
            return NotImplemented
 | 
						|
        x = self//other
 | 
						|
        return (x, self - other * x)
 | 
						|
 | 
						|
    def __rdivmod__(self, other):
 | 
						|
        """Divide two Rats, returning quotient and remainder (reversed args)."""
 | 
						|
        if isint(other):
 | 
						|
            other = Rat(other)
 | 
						|
        elif not isRat(other):
 | 
						|
            return NotImplemented
 | 
						|
        return divmod(other, self)
 | 
						|
 | 
						|
    def __mod__(self, other):
 | 
						|
        """Take one Rat modulo another."""
 | 
						|
        return divmod(self, other)[1]
 | 
						|
 | 
						|
    def __rmod__(self, other):
 | 
						|
        """Take one Rat modulo another (reversed args)."""
 | 
						|
        return divmod(other, self)[1]
 | 
						|
 | 
						|
    def __eq__(self, other):
 | 
						|
        """Compare two Rats for equality."""
 | 
						|
        if isint(other):
 | 
						|
            return self.__den == 1 and self.__num == other
 | 
						|
        if isRat(other):
 | 
						|
            return self.__num == other.__num and self.__den == other.__den
 | 
						|
        if isnum(other):
 | 
						|
            return float(self) == other
 | 
						|
        return NotImplemented
 | 
						|
 | 
						|
    def __ne__(self, other):
 | 
						|
        """Compare two Rats for inequality."""
 | 
						|
        return not self == other
 | 
						|
 | 
						|
class RatTestCase(unittest.TestCase):
 | 
						|
    """Unit tests for Rat class and its support utilities."""
 | 
						|
 | 
						|
    def test_gcd(self):
 | 
						|
        self.assertEqual(gcd(10, 12), 2)
 | 
						|
        self.assertEqual(gcd(10, 15), 5)
 | 
						|
        self.assertEqual(gcd(10, 11), 1)
 | 
						|
        self.assertEqual(gcd(100, 15), 5)
 | 
						|
        self.assertEqual(gcd(-10, 2), -2)
 | 
						|
        self.assertEqual(gcd(10, -2), 2)
 | 
						|
        self.assertEqual(gcd(-10, -2), -2)
 | 
						|
        for i in range(1, 20):
 | 
						|
            for j in range(1, 20):
 | 
						|
                self.assertTrue(gcd(i, j) > 0)
 | 
						|
                self.assertTrue(gcd(-i, j) < 0)
 | 
						|
                self.assertTrue(gcd(i, -j) > 0)
 | 
						|
                self.assertTrue(gcd(-i, -j) < 0)
 | 
						|
 | 
						|
    def test_constructor(self):
 | 
						|
        a = Rat(10, 15)
 | 
						|
        self.assertEqual(a.num, 2)
 | 
						|
        self.assertEqual(a.den, 3)
 | 
						|
        a = Rat(10, -15)
 | 
						|
        self.assertEqual(a.num, -2)
 | 
						|
        self.assertEqual(a.den, 3)
 | 
						|
        a = Rat(-10, 15)
 | 
						|
        self.assertEqual(a.num, -2)
 | 
						|
        self.assertEqual(a.den, 3)
 | 
						|
        a = Rat(-10, -15)
 | 
						|
        self.assertEqual(a.num, 2)
 | 
						|
        self.assertEqual(a.den, 3)
 | 
						|
        a = Rat(7)
 | 
						|
        self.assertEqual(a.num, 7)
 | 
						|
        self.assertEqual(a.den, 1)
 | 
						|
        try:
 | 
						|
            a = Rat(1, 0)
 | 
						|
        except ZeroDivisionError:
 | 
						|
            pass
 | 
						|
        else:
 | 
						|
            self.fail("Rat(1, 0) didn't raise ZeroDivisionError")
 | 
						|
        for bad in "0", 0.0, 0j, (), [], {}, None, Rat, unittest:
 | 
						|
            try:
 | 
						|
                a = Rat(bad)
 | 
						|
            except TypeError:
 | 
						|
                pass
 | 
						|
            else:
 | 
						|
                self.fail("Rat(%r) didn't raise TypeError" % bad)
 | 
						|
            try:
 | 
						|
                a = Rat(1, bad)
 | 
						|
            except TypeError:
 | 
						|
                pass
 | 
						|
            else:
 | 
						|
                self.fail("Rat(1, %r) didn't raise TypeError" % bad)
 | 
						|
 | 
						|
    def test_add(self):
 | 
						|
        self.assertEqual(Rat(2, 3) + Rat(1, 3), 1)
 | 
						|
        self.assertEqual(Rat(2, 3) + 1, Rat(5, 3))
 | 
						|
        self.assertEqual(1 + Rat(2, 3), Rat(5, 3))
 | 
						|
        self.assertEqual(1.0 + Rat(1, 2), 1.5)
 | 
						|
        self.assertEqual(Rat(1, 2) + 1.0, 1.5)
 | 
						|
 | 
						|
    def test_sub(self):
 | 
						|
        self.assertEqual(Rat(7, 2) - Rat(7, 5), Rat(21, 10))
 | 
						|
        self.assertEqual(Rat(7, 5) - 1, Rat(2, 5))
 | 
						|
        self.assertEqual(1 - Rat(3, 5), Rat(2, 5))
 | 
						|
        self.assertEqual(Rat(3, 2) - 1.0, 0.5)
 | 
						|
        self.assertEqual(1.0 - Rat(1, 2), 0.5)
 | 
						|
 | 
						|
    def test_mul(self):
 | 
						|
        self.assertEqual(Rat(2, 3) * Rat(5, 7), Rat(10, 21))
 | 
						|
        self.assertEqual(Rat(10, 3) * 3, 10)
 | 
						|
        self.assertEqual(3 * Rat(10, 3), 10)
 | 
						|
        self.assertEqual(Rat(10, 5) * 0.5, 1.0)
 | 
						|
        self.assertEqual(0.5 * Rat(10, 5), 1.0)
 | 
						|
 | 
						|
    def test_div(self):
 | 
						|
        self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3))
 | 
						|
        self.assertEqual(Rat(10, 3) / 3, Rat(10, 9))
 | 
						|
        self.assertEqual(2 / Rat(5), Rat(2, 5))
 | 
						|
        self.assertEqual(3.0 * Rat(1, 2), 1.5)
 | 
						|
        self.assertEqual(Rat(1, 2) * 3.0, 1.5)
 | 
						|
 | 
						|
    def test_floordiv(self):
 | 
						|
        self.assertEqual(Rat(10) // Rat(4), 2)
 | 
						|
        self.assertEqual(Rat(10, 3) // Rat(4, 3), 2)
 | 
						|
        self.assertEqual(Rat(10) // 4, 2)
 | 
						|
        self.assertEqual(10 // Rat(4), 2)
 | 
						|
 | 
						|
    def test_eq(self):
 | 
						|
        self.assertEqual(Rat(10), Rat(20, 2))
 | 
						|
        self.assertEqual(Rat(10), 10)
 | 
						|
        self.assertEqual(10, Rat(10))
 | 
						|
        self.assertEqual(Rat(10), 10.0)
 | 
						|
        self.assertEqual(10.0, Rat(10))
 | 
						|
 | 
						|
    def test_true_div(self):
 | 
						|
        self.assertEqual(Rat(10, 3) / Rat(5, 7), Rat(14, 3))
 | 
						|
        self.assertEqual(Rat(10, 3) / 3, Rat(10, 9))
 | 
						|
        self.assertEqual(2 / Rat(5), Rat(2, 5))
 | 
						|
        self.assertEqual(3.0 * Rat(1, 2), 1.5)
 | 
						|
        self.assertEqual(Rat(1, 2) * 3.0, 1.5)
 | 
						|
        self.assertEqual(eval('1/2'), 0.5)
 | 
						|
 | 
						|
    # XXX Ran out of steam; TO DO: divmod, div, future division
 | 
						|
 | 
						|
 | 
						|
class OperationLogger:
 | 
						|
    """Base class for classes with operation logging."""
 | 
						|
    def __init__(self, logger):
 | 
						|
        self.logger = logger
 | 
						|
    def log_operation(self, *args):
 | 
						|
        self.logger(*args)
 | 
						|
 | 
						|
def op_sequence(op, *classes):
 | 
						|
    """Return the sequence of operations that results from applying
 | 
						|
    the operation `op` to instances of the given classes."""
 | 
						|
    log = []
 | 
						|
    instances = []
 | 
						|
    for c in classes:
 | 
						|
        instances.append(c(log.append))
 | 
						|
 | 
						|
    try:
 | 
						|
        op(*instances)
 | 
						|
    except TypeError:
 | 
						|
        pass
 | 
						|
    return log
 | 
						|
 | 
						|
class A(OperationLogger):
 | 
						|
    def __eq__(self, other):
 | 
						|
        self.log_operation('A.__eq__')
 | 
						|
        return NotImplemented
 | 
						|
    def __le__(self, other):
 | 
						|
        self.log_operation('A.__le__')
 | 
						|
        return NotImplemented
 | 
						|
    def __ge__(self, other):
 | 
						|
        self.log_operation('A.__ge__')
 | 
						|
        return NotImplemented
 | 
						|
 | 
						|
class B(OperationLogger):
 | 
						|
    def __eq__(self, other):
 | 
						|
        self.log_operation('B.__eq__')
 | 
						|
        return NotImplemented
 | 
						|
    def __le__(self, other):
 | 
						|
        self.log_operation('B.__le__')
 | 
						|
        return NotImplemented
 | 
						|
    def __ge__(self, other):
 | 
						|
        self.log_operation('B.__ge__')
 | 
						|
        return NotImplemented
 | 
						|
 | 
						|
class C(B):
 | 
						|
    def __eq__(self, other):
 | 
						|
        self.log_operation('C.__eq__')
 | 
						|
        return NotImplemented
 | 
						|
    def __le__(self, other):
 | 
						|
        self.log_operation('C.__le__')
 | 
						|
        return NotImplemented
 | 
						|
    def __ge__(self, other):
 | 
						|
        self.log_operation('C.__ge__')
 | 
						|
        return NotImplemented
 | 
						|
 | 
						|
class OperationOrderTests(unittest.TestCase):
 | 
						|
    def test_comparison_orders(self):
 | 
						|
        self.assertEqual(op_sequence(eq, A, A), ['A.__eq__', 'A.__eq__'])
 | 
						|
        self.assertEqual(op_sequence(eq, A, B), ['A.__eq__', 'B.__eq__'])
 | 
						|
        self.assertEqual(op_sequence(eq, B, A), ['B.__eq__', 'A.__eq__'])
 | 
						|
        # C is a subclass of B, so C.__eq__ is called first
 | 
						|
        self.assertEqual(op_sequence(eq, B, C), ['C.__eq__', 'B.__eq__'])
 | 
						|
        self.assertEqual(op_sequence(eq, C, B), ['C.__eq__', 'B.__eq__'])
 | 
						|
 | 
						|
        self.assertEqual(op_sequence(le, A, A), ['A.__le__', 'A.__ge__'])
 | 
						|
        self.assertEqual(op_sequence(le, A, B), ['A.__le__', 'B.__ge__'])
 | 
						|
        self.assertEqual(op_sequence(le, B, A), ['B.__le__', 'A.__ge__'])
 | 
						|
        self.assertEqual(op_sequence(le, B, C), ['C.__ge__', 'B.__le__'])
 | 
						|
        self.assertEqual(op_sequence(le, C, B), ['C.__le__', 'B.__ge__'])
 | 
						|
 | 
						|
def test_main():
 | 
						|
    support.run_unittest(RatTestCase, OperationOrderTests)
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    test_main()
 |