]> granicus.if.org Git - python/commitdiff
Issue #23132: Improve performance and introspection support of comparison
authorSerhiy Storchaka <storchaka@gmail.com>
Thu, 1 Jan 2015 13:23:12 +0000 (15:23 +0200)
committerSerhiy Storchaka <storchaka@gmail.com>
Thu, 1 Jan 2015 13:23:12 +0000 (15:23 +0200)
methods created by functool.total_ordering.

Lib/functools.py
Lib/test/test_functools.py
Misc/NEWS

index c1ca85063255ca8f8e46e22a40b69763127a7b4d..db8cc8239f27224734982e45abdc1041536df4b7 100644 (file)
@@ -113,76 +113,85 @@ def wraps(wrapped,
 # underlying user provided method. Using this scheme, the __gt__ derived
 # from a user provided __lt__ becomes:
 #
-#    lambda self, other: _not_op_and_not_eq(self.__lt__, self, other))
-
-def _not_op(op, other):
-    # "not a < b" handles "a >= b"
-    # "not a <= b" handles "a > b"
-    # "not a >= b" handles "a < b"
-    # "not a > b" handles "a <= b"
-    op_result = op(other)
+#    'def __gt__(self, other):' + _not_op_and_not_eq % '__lt__'
+
+# "not a < b" handles "a >= b"
+# "not a <= b" handles "a > b"
+# "not a >= b" handles "a < b"
+# "not a > b" handles "a <= b"
+_not_op = '''
+    op_result = self.%s(other)
     if op_result is NotImplemented:
         return NotImplemented
     return not op_result
+'''
 
-def _op_or_eq(op, self, other):
-    # "a < b or a == b" handles "a <= b"
-    # "a > b or a == b" handles "a >= b"
-    op_result = op(other)
+# "a > b or a == b" handles "a >= b"
+# "a < b or a == b" handles "a <= b"
+_op_or_eq = '''
+    op_result = self.%s(other)
     if op_result is NotImplemented:
         return NotImplemented
     return op_result or self == other
-
-def _not_op_and_not_eq(op, self, other):
-    # "not (a < b or a == b)" handles "a > b"
-    # "not a < b and a != b" is equivalent
-    # "not (a > b or a == b)" handles "a < b"
-    # "not a > b and a != b" is equivalent
-    op_result = op(other)
+'''
+
+# "not (a < b or a == b)" handles "a > b"
+# "not a < b and a != b" is equivalent
+# "not (a > b or a == b)" handles "a < b"
+# "not a > b and a != b" is equivalent
+_not_op_and_not_eq = '''
+    op_result = self.%s(other)
     if op_result is NotImplemented:
         return NotImplemented
     return not op_result and self != other
+'''
 
-def _not_op_or_eq(op, self, other):
-    # "not a <= b or a == b" handles "a >= b"
-    # "not a >= b or a == b" handles "a <= b"
-    op_result = op(other)
+# "not a <= b or a == b" handles "a >= b"
+# "not a >= b or a == b" handles "a <= b"
+_not_op_or_eq = '''
+    op_result = self.%s(other)
     if op_result is NotImplemented:
         return NotImplemented
     return not op_result or self == other
+'''
 
-def _op_and_not_eq(op, self, other):
-    # "a <= b and not a == b" handles "a < b"
-    # "a >= b and not a == b" handles "a > b"
-    op_result = op(other)
+# "a <= b and not a == b" handles "a < b"
+# "a >= b and not a == b" handles "a > b"
+_op_and_not_eq = '''
+    op_result = self.%s(other)
     if op_result is NotImplemented:
         return NotImplemented
     return op_result and self != other
+'''
 
 def total_ordering(cls):
     """Class decorator that fills in missing ordering methods"""
     convert = {
-        '__lt__': [('__gt__', lambda self, other: _not_op_and_not_eq(self.__lt__, self, other)),
-                   ('__le__', lambda self, other: _op_or_eq(self.__lt__, self, other)),
-                   ('__ge__', lambda self, other: _not_op(self.__lt__, other))],
-        '__le__': [('__ge__', lambda self, other: _not_op_or_eq(self.__le__, self, other)),
-                   ('__lt__', lambda self, other: _op_and_not_eq(self.__le__, self, other)),
-                   ('__gt__', lambda self, other: _not_op(self.__le__, other))],
-        '__gt__': [('__lt__', lambda self, other: _not_op_and_not_eq(self.__gt__, self, other)),
-                   ('__ge__', lambda self, other: _op_or_eq(self.__gt__, self, other)),
-                   ('__le__', lambda self, other: _not_op(self.__gt__, other))],
-        '__ge__': [('__le__', lambda self, other: _not_op_or_eq(self.__ge__, self, other)),
-                   ('__gt__', lambda self, other: _op_and_not_eq(self.__ge__, self, other)),
-                   ('__lt__', lambda self, other: _not_op(self.__ge__, other))]
+        '__lt__': {'__gt__': _not_op_and_not_eq,
+                   '__le__': _op_or_eq,
+                   '__ge__': _not_op},
+        '__le__': {'__ge__': _not_op_or_eq,
+                   '__lt__': _op_and_not_eq,
+                   '__gt__': _not_op},
+        '__gt__': {'__lt__': _not_op_and_not_eq,
+                   '__ge__': _op_or_eq,
+                   '__le__': _not_op},
+        '__ge__': {'__le__': _not_op_or_eq,
+                   '__gt__': _op_and_not_eq,
+                   '__lt__': _not_op}
     }
     # Find user-defined comparisons (not those inherited from object).
     roots = [op for op in convert if getattr(cls, op, None) is not getattr(object, op, None)]
     if not roots:
         raise ValueError('must define at least one ordering operation: < > <= >=')
     root = max(roots)       # prefer __lt__ to __le__ to __gt__ to __ge__
-    for opname, opfunc in convert[root]:
+    for opname, opfunc in convert[root].items():
         if opname not in roots:
-            opfunc.__name__ = opname
+            namespace = {}
+            exec('def %s(self, other):%s' % (opname, opfunc % root), namespace)
+            opfunc = namespace[opname]
+            opfunc.__qualname__ = '%s.%s' % (cls.__qualname__, opname)
+            opfunc.__module__ = cls.__module__
             opfunc.__doc__ = getattr(int, opname).__doc__
             setattr(cls, opname, opfunc)
     return cls
index 10120530b81ec3e0cd2f0ee58c4e0e7f9d5ad4a0..fbb43e43e65813b86c41524be26ee4f28e869f6b 100644 (file)
@@ -880,6 +880,24 @@ class TestTotalOrdering(unittest.TestCase):
             with self.assertRaises(TypeError):
                 a <= b
 
+    def test_pickle(self):
+        for proto in range(4, pickle.HIGHEST_PROTOCOL + 1):
+            for name in '__lt__', '__gt__', '__le__', '__ge__':
+                with self.subTest(method=name, proto=proto):
+                    method = getattr(Orderable_LT, name)
+                    method_copy = pickle.loads(pickle.dumps(method, proto))
+                    self.assertIs(method_copy, method)
+
+@functools.total_ordering
+class Orderable_LT:
+    def __init__(self, value):
+        self.value = value
+    def __lt__(self, other):
+        return self.value < other.value
+    def __eq__(self, other):
+        return self.value == other.value
+
+
 class TestLRU(unittest.TestCase):
 
     def test_lru(self):
index dfcf72a25c9459d1ed0f3684a236a6dfb397a16a..43666420fc5ba0c821a896e81d4fc22af0e4517f 100644 (file)
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -196,6 +196,9 @@ Core and Builtins
 Library
 -------
 
+- Issue #23132: Improve performance and introspection support of comparison
+  methods created by functool.total_ordering.
+
 - Issue #19776: Add a expanduser() method on Path objects.
 
 - Issue #23112: Fix SimpleHTTPServer to correctly carry the query string and