[Python-checkins] cpython (3.4): Issue #23132: Mitigate regression in speed and clarity in

raymond.hettinger python-checkins at python.org
Tue Jan 6 07:00:17 CET 2015


https://hg.python.org/cpython/rev/09b0da38ce8d
changeset:   94037:09b0da38ce8d
branch:      3.4
parent:      94035:990ce80d8283
user:        Raymond Hettinger <python at rcn.com>
date:        Mon Jan 05 21:52:10 2015 -0800
summary:
  Issue #23132: Mitigate regression in speed and clarity in functools.total_ordering.

files:
  Lib/functools.py |  140 +++++++++++++++++++---------------
  Misc/NEWS        |    2 +
  2 files changed, 79 insertions(+), 63 deletions(-)


diff --git a/Lib/functools.py b/Lib/functools.py
--- a/Lib/functools.py
+++ b/Lib/functools.py
@@ -89,91 +89,106 @@
 ### total_ordering class decorator
 ################################################################################
 
-# The correct way to indicate that a comparison operation doesn't
-# recognise the other type is to return NotImplemented and let the
-# interpreter handle raising TypeError if both operands return
-# NotImplemented from their respective comparison methods
-#
-# This makes the implementation of total_ordering more complicated, since
-# we need to be careful not to trigger infinite recursion when two
-# different types that both use this decorator encounter each other.
-#
-# For example, if a type implements __lt__, it's natural to define
-# __gt__ as something like:
-#
-#    lambda self, other: not self < other and not self == other
-#
-# However, using the operator syntax like that ends up invoking the full
-# type checking machinery again and means we can end up bouncing back and
-# forth between the two operands until we run out of stack space.
-#
-# The solution is to define helper functions that invoke the appropriate
-# magic methods directly, ensuring we only try each operand once, and
-# return NotImplemented immediately if it is returned from the
-# 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))
+# The total ordering functions all invoke the root magic method directly
+# rather than using the corresponding operator.  This avoids possible
+# infinite recursion that could occur when the operator dispatch logic
+# detects a NotImplemented result and then calls a reflected method.
 
-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_from_lt(self, other):
+    'Return a > b.  Computed by @total_ordering from (not a < b) and (a != b).'
+    op_result = self.__lt__(other)
+    if op_result is NotImplemented:
+        return NotImplemented
+    return not op_result and self != other
+
+def _le_from_lt(self, other):
+    'Return a <= b.  Computed by @total_ordering from (a < b) or (a == b).'
+    op_result = self.__lt__(other)
+    return op_result or self == other
+
+def _ge_from_lt(self, other):
+    'Return a >= b.  Computed by @total_ordering from (not a < b).'
+    op_result = self.__lt__(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)
+def _ge_from_le(self, other):
+    'Return a >= b.  Computed by @total_ordering from (not a <= b) or (a == b).'
+    op_result = self.__le__(other)
     if op_result is NotImplemented:
         return NotImplemented
-    return op_result or self == other
+    return not 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)
+def _lt_from_le(self, other):
+    'Return a < b.  Computed by @total_ordering from (a <= b) and (a != b).'
+    op_result = self.__le__(other)
+    if op_result is NotImplemented:
+        return NotImplemented
+    return op_result and self != other
+
+def _gt_from_le(self, other):
+    'Return a > b.  Computed by @total_ordering from (not a <= b).'
+    op_result = self.__le__(other)
+    if op_result is NotImplemented:
+        return NotImplemented
+    return not op_result
+
+def _lt_from_gt(self, other):
+    'Return a < b.  Computed by @total_ordering from (not a > b) and (a != b).'
+    op_result = self.__gt__(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)
+def _ge_from_gt(self, other):
+    'Return a >= b.  Computed by @total_ordering from (a > b) or (a == b).'
+    op_result = self.__gt__(other)
+    return op_result or self == other
+
+def _le_from_gt(self, other):
+    'Return a <= b.  Computed by @total_ordering from (not a > b).'
+    op_result = self.__gt__(other)
+    if op_result is NotImplemented:
+        return NotImplemented
+    return not op_result
+
+def _le_from_ge(self, other):
+    'Return a <= b.  Computed by @total_ordering from (not a >= b) or (a == b).'
+    op_result = self.__ge__(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)
+def _gt_from_ge(self, other):
+    'Return a > b.  Computed by @total_ordering from (a >= b) and (a != b).'
+    op_result = self.__ge__(other)
     if op_result is NotImplemented:
         return NotImplemented
     return op_result and self != other
 
+def _lt_from_ge(self, other):
+    'Return a < b.  Computed by @total_ordering from (not a >= b).'
+    op_result = self.__ge__(other)
+    if op_result is NotImplemented:
+        return NotImplemented
+    return not op_result
+
 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__', _gt_from_lt),
+                   ('__le__', _le_from_lt),
+                   ('__ge__', _ge_from_lt)],
+        '__le__': [('__ge__', _ge_from_le),
+                   ('__lt__', _lt_from_le),
+                   ('__gt__', _gt_from_le)],
+        '__gt__': [('__lt__', _lt_from_gt),
+                   ('__ge__', _ge_from_gt),
+                   ('__le__', _le_from_gt)],
+        '__ge__': [('__le__', _le_from_ge),
+                   ('__gt__', _gt_from_ge),
+                   ('__lt__', _lt_from_ge)]
     }
     # 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)]
@@ -183,7 +198,6 @@
     for opname, opfunc in convert[root]:
         if opname not in roots:
             opfunc.__name__ = opname
-            opfunc.__doc__ = getattr(int, opname).__doc__
             setattr(cls, opname, opfunc)
     return cls
 
diff --git a/Misc/NEWS b/Misc/NEWS
--- a/Misc/NEWS
+++ b/Misc/NEWS
@@ -47,6 +47,8 @@
 - Issue #23111: In the ftplib, make ssl.PROTOCOL_SSLv23 the default protocol
   version.
 
+- Issue #23132: Mitigate regression in speed and clarity in functools.total_ordering.
+
 - Issue #22585: On OpenBSD 5.6 and newer, os.urandom() now calls getentropy(),
   instead of reading /dev/urandom, to get pseudo-random bytes.
 

-- 
Repository URL: https://hg.python.org/cpython


More information about the Python-checkins mailing list