Issue 14814: Ensure ordering semantics across all 3 entity types in ipaddress are consistent and well-defined
diff --git a/Lib/ipaddress.py b/Lib/ipaddress.py
index b1e07fc..2019009 100644
--- a/Lib/ipaddress.py
+++ b/Lib/ipaddress.py
@@ -12,7 +12,7 @@
 
 
 import struct
-
+import functools
 
 IPV4LENGTH = 32
 IPV6LENGTH = 128
@@ -405,7 +405,38 @@
     return NotImplemented
 
 
-class _IPAddressBase:
+class _TotalOrderingMixin:
+    # Helper that derives the other comparison operations from
+    # __lt__ and __eq__
+    def __eq__(self, other):
+        raise NotImplementedError
+    def __ne__(self, other):
+        equal = self.__eq__(other)
+        if equal is NotImplemented:
+            return NotImplemented
+        return not equal
+    def __lt__(self, other):
+        raise NotImplementedError
+    def __le__(self, other):
+        less = self.__lt__(other)
+        if less is NotImplemented or not less:
+            return self.__eq__(other)
+        return less
+    def __gt__(self, other):
+        less = self.__lt__(other)
+        if less is NotImplemented:
+            return NotImplemented
+        equal = self.__eq__(other)
+        if equal is NotImplemented:
+            return NotImplemented
+        return not (less or equal)
+    def __ge__(self, other):
+        less = self.__lt__(other)
+        if less is NotImplemented:
+            return NotImplemented
+        return not less
+
+class _IPAddressBase(_TotalOrderingMixin):
 
     """The mother class."""
 
@@ -465,7 +496,6 @@
             prefixlen = self._prefixlen
         return self._string_from_ip_int(self._ip_int_from_prefix(prefixlen))
 
-
 class _BaseAddress(_IPAddressBase):
 
     """A generic IP object.
@@ -493,24 +523,6 @@
         except AttributeError:
             return NotImplemented
 
-    def __ne__(self, other):
-        eq = self.__eq__(other)
-        if eq is NotImplemented:
-            return NotImplemented
-        return not eq
-
-    def __le__(self, other):
-        gt = self.__gt__(other)
-        if gt is NotImplemented:
-            return NotImplemented
-        return not gt
-
-    def __ge__(self, other):
-        lt = self.__lt__(other)
-        if lt is NotImplemented:
-            return NotImplemented
-        return not lt
-
     def __lt__(self, other):
         if self._version != other._version:
             raise TypeError('%s and %s are not of the same version' % (
@@ -522,17 +534,6 @@
             return self._ip < other._ip
         return False
 
-    def __gt__(self, other):
-        if self._version != other._version:
-            raise TypeError('%s and %s are not of the same version' % (
-                             self, other))
-        if not isinstance(other, _BaseAddress):
-            raise TypeError('%s and %s are not of the same type' % (
-                             self, other))
-        if self._ip != other._ip:
-            return self._ip > other._ip
-        return False
-
     # Shorthand for Integer addition and subtraction. This is not
     # meant to ever support addition/subtraction of addresses.
     def __add__(self, other):
@@ -625,31 +626,6 @@
             return self.netmask < other.netmask
         return False
 
-    def __gt__(self, other):
-        if self._version != other._version:
-            raise TypeError('%s and %s are not of the same version' % (
-                             self, other))
-        if not isinstance(other, _BaseNetwork):
-            raise TypeError('%s and %s are not of the same type' % (
-                             self, other))
-        if self.network_address != other.network_address:
-            return self.network_address > other.network_address
-        if self.netmask != other.netmask:
-            return self.netmask > other.netmask
-        return False
-
-    def __le__(self, other):
-        gt = self.__gt__(other)
-        if gt is NotImplemented:
-            return NotImplemented
-        return not gt
-
-    def __ge__(self, other):
-        lt = self.__lt__(other)
-        if lt is NotImplemented:
-            return NotImplemented
-        return not lt
-
     def __eq__(self, other):
         try:
             return (self._version == other._version and
@@ -658,12 +634,6 @@
         except AttributeError:
             return NotImplemented
 
-    def __ne__(self, other):
-        eq = self.__eq__(other)
-        if eq is NotImplemented:
-            return NotImplemented
-        return not eq
-
     def __hash__(self):
         return hash(int(self.network_address) ^ int(self.netmask))
 
@@ -1292,11 +1262,27 @@
                           self.network.prefixlen)
 
     def __eq__(self, other):
+        address_equal = IPv4Address.__eq__(self, other)
+        if not address_equal or address_equal is NotImplemented:
+            return address_equal
         try:
-            return (IPv4Address.__eq__(self, other) and
-                    self.network == other.network)
+            return self.network == other.network
         except AttributeError:
+            # An interface with an associated network is NOT the
+            # same as an unassociated address. That's why the hash
+            # takes the extra info into account.
+            return False
+
+    def __lt__(self, other):
+        address_less = IPv4Address.__lt__(self, other)
+        if address_less is NotImplemented:
             return NotImplemented
+        try:
+            return self.network < other.network
+        except AttributeError:
+            # We *do* allow addresses and interfaces to be sorted. The
+            # unassociated address is considered less than all interfaces.
+            return False
 
     def __hash__(self):
         return self._ip ^ self._prefixlen ^ int(self.network.network_address)
@@ -1928,11 +1914,27 @@
                           self.network.prefixlen)
 
     def __eq__(self, other):
+        address_equal = IPv6Address.__eq__(self, other)
+        if not address_equal or address_equal is NotImplemented:
+            return address_equal
         try:
-            return (IPv6Address.__eq__(self, other) and
-                    self.network == other.network)
+            return self.network == other.network
         except AttributeError:
+            # An interface with an associated network is NOT the
+            # same as an unassociated address. That's why the hash
+            # takes the extra info into account.
+            return False
+
+    def __lt__(self, other):
+        address_less = IPv6Address.__lt__(self, other)
+        if address_less is NotImplemented:
             return NotImplemented
+        try:
+            return self.network < other.network
+        except AttributeError:
+            # We *do* allow addresses and interfaces to be sorted. The
+            # unassociated address is considered less than all interfaces.
+            return False
 
     def __hash__(self):
         return self._ip ^ self._prefixlen ^ int(self.network.network_address)