Skip to content

Commit 697a526

Browse files
Issue #23132: Improve performance and introspection support of comparison
methods created by functool.total_ordering.
1 parent a2d8622 commit 697a526

3 files changed

Lines changed: 71 additions & 41 deletions

File tree

Lib/functools.py

Lines changed: 50 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -113,76 +113,85 @@ def wraps(wrapped,
113113
# underlying user provided method. Using this scheme, the __gt__ derived
114114
# from a user provided __lt__ becomes:
115115
#
116-
# lambda self, other: _not_op_and_not_eq(self.__lt__, self, other))
117-
118-
def _not_op(op, other):
119-
# "not a < b" handles "a >= b"
120-
# "not a <= b" handles "a > b"
121-
# "not a >= b" handles "a < b"
122-
# "not a > b" handles "a <= b"
123-
op_result = op(other)
116+
# 'def __gt__(self, other):' + _not_op_and_not_eq % '__lt__'
117+
118+
# "not a < b" handles "a >= b"
119+
# "not a <= b" handles "a > b"
120+
# "not a >= b" handles "a < b"
121+
# "not a > b" handles "a <= b"
122+
_not_op = '''
123+
op_result = self.%s(other)
124124
if op_result is NotImplemented:
125125
return NotImplemented
126126
return not op_result
127+
'''
127128

128-
def _op_or_eq(op, self, other):
129-
# "a < b or a == b" handles "a <= b"
130-
# "a > b or a == b" handles "a >= b"
131-
op_result = op(other)
129+
# "a > b or a == b" handles "a >= b"
130+
# "a < b or a == b" handles "a <= b"
131+
_op_or_eq = '''
132+
op_result = self.%s(other)
132133
if op_result is NotImplemented:
133134
return NotImplemented
134135
return op_result or self == other
135-
136-
def _not_op_and_not_eq(op, self, other):
137-
# "not (a < b or a == b)" handles "a > b"
138-
# "not a < b and a != b" is equivalent
139-
# "not (a > b or a == b)" handles "a < b"
140-
# "not a > b and a != b" is equivalent
141-
op_result = op(other)
136+
'''
137+
138+
# "not (a < b or a == b)" handles "a > b"
139+
# "not a < b and a != b" is equivalent
140+
# "not (a > b or a == b)" handles "a < b"
141+
# "not a > b and a != b" is equivalent
142+
_not_op_and_not_eq = '''
143+
op_result = self.%s(other)
142144
if op_result is NotImplemented:
143145
return NotImplemented
144146
return not op_result and self != other
147+
'''
145148

146-
def _not_op_or_eq(op, self, other):
147-
# "not a <= b or a == b" handles "a >= b"
148-
# "not a >= b or a == b" handles "a <= b"
149-
op_result = op(other)
149+
# "not a <= b or a == b" handles "a >= b"
150+
# "not a >= b or a == b" handles "a <= b"
151+
_not_op_or_eq = '''
152+
op_result = self.%s(other)
150153
if op_result is NotImplemented:
151154
return NotImplemented
152155
return not op_result or self == other
156+
'''
153157

154-
def _op_and_not_eq(op, self, other):
155-
# "a <= b and not a == b" handles "a < b"
156-
# "a >= b and not a == b" handles "a > b"
157-
op_result = op(other)
158+
# "a <= b and not a == b" handles "a < b"
159+
# "a >= b and not a == b" handles "a > b"
160+
_op_and_not_eq = '''
161+
op_result = self.%s(other)
158162
if op_result is NotImplemented:
159163
return NotImplemented
160164
return op_result and self != other
165+
'''
161166

162167
def total_ordering(cls):
163168
"""Class decorator that fills in missing ordering methods"""
164169
convert = {
165-
'__lt__': [('__gt__', lambda self, other: _not_op_and_not_eq(self.__lt__, self, other)),
166-
('__le__', lambda self, other: _op_or_eq(self.__lt__, self, other)),
167-
('__ge__', lambda self, other: _not_op(self.__lt__, other))],
168-
'__le__': [('__ge__', lambda self, other: _not_op_or_eq(self.__le__, self, other)),
169-
('__lt__', lambda self, other: _op_and_not_eq(self.__le__, self, other)),
170-
('__gt__', lambda self, other: _not_op(self.__le__, other))],
171-
'__gt__': [('__lt__', lambda self, other: _not_op_and_not_eq(self.__gt__, self, other)),
172-
('__ge__', lambda self, other: _op_or_eq(self.__gt__, self, other)),
173-
('__le__', lambda self, other: _not_op(self.__gt__, other))],
174-
'__ge__': [('__le__', lambda self, other: _not_op_or_eq(self.__ge__, self, other)),
175-
('__gt__', lambda self, other: _op_and_not_eq(self.__ge__, self, other)),
176-
('__lt__', lambda self, other: _not_op(self.__ge__, other))]
170+
'__lt__': {'__gt__': _not_op_and_not_eq,
171+
'__le__': _op_or_eq,
172+
'__ge__': _not_op},
173+
'__le__': {'__ge__': _not_op_or_eq,
174+
'__lt__': _op_and_not_eq,
175+
'__gt__': _not_op},
176+
'__gt__': {'__lt__': _not_op_and_not_eq,
177+
'__ge__': _op_or_eq,
178+
'__le__': _not_op},
179+
'__ge__': {'__le__': _not_op_or_eq,
180+
'__gt__': _op_and_not_eq,
181+
'__lt__': _not_op}
177182
}
178183
# Find user-defined comparisons (not those inherited from object).
179184
roots = [op for op in convert if getattr(cls, op, None) is not getattr(object, op, None)]
180185
if not roots:
181186
raise ValueError('must define at least one ordering operation: < > <= >=')
182187
root = max(roots) # prefer __lt__ to __le__ to __gt__ to __ge__
183-
for opname, opfunc in convert[root]:
188+
for opname, opfunc in convert[root].items():
184189
if opname not in roots:
185-
opfunc.__name__ = opname
190+
namespace = {}
191+
exec('def %s(self, other):%s' % (opname, opfunc % root), namespace)
192+
opfunc = namespace[opname]
193+
opfunc.__qualname__ = '%s.%s' % (cls.__qualname__, opname)
194+
opfunc.__module__ = cls.__module__
186195
opfunc.__doc__ = getattr(int, opname).__doc__
187196
setattr(cls, opname, opfunc)
188197
return cls

Lib/test/test_functools.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,24 @@ def __lt__(self, other):
880880
with self.assertRaises(TypeError):
881881
a <= b
882882

883+
def test_pickle(self):
884+
for proto in range(4, pickle.HIGHEST_PROTOCOL + 1):
885+
for name in '__lt__', '__gt__', '__le__', '__ge__':
886+
with self.subTest(method=name, proto=proto):
887+
method = getattr(Orderable_LT, name)
888+
method_copy = pickle.loads(pickle.dumps(method, proto))
889+
self.assertIs(method_copy, method)
890+
891+
@functools.total_ordering
892+
class Orderable_LT:
893+
def __init__(self, value):
894+
self.value = value
895+
def __lt__(self, other):
896+
return self.value < other.value
897+
def __eq__(self, other):
898+
return self.value == other.value
899+
900+
883901
class TestLRU(unittest.TestCase):
884902

885903
def test_lru(self):

Misc/NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,9 @@ Core and Builtins
196196
Library
197197
-------
198198

199+
- Issue #23132: Improve performance and introspection support of comparison
200+
methods created by functool.total_ordering.
201+
199202
- Issue #19776: Add a expanduser() method on Path objects.
200203

201204
- Issue #23112: Fix SimpleHTTPServer to correctly carry the query string and

0 commit comments

Comments
 (0)