Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 0603d30

Browse files
committed
Issue #23132: Mitigate regression in speed and clarity in functools.total_ordering.
1 parent 212994e commit 0603d30

2 files changed

Lines changed: 80 additions & 64 deletions

File tree

Lib/functools.py

Lines changed: 78 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -89,91 +89,106 @@ def wraps(wrapped,
8989
### total_ordering class decorator
9090
################################################################################
9191

92-
# The correct way to indicate that a comparison operation doesn't
93-
# recognise the other type is to return NotImplemented and let the
94-
# interpreter handle raising TypeError if both operands return
95-
# NotImplemented from their respective comparison methods
96-
#
97-
# This makes the implementation of total_ordering more complicated, since
98-
# we need to be careful not to trigger infinite recursion when two
99-
# different types that both use this decorator encounter each other.
100-
#
101-
# For example, if a type implements __lt__, it's natural to define
102-
# __gt__ as something like:
103-
#
104-
# lambda self, other: not self < other and not self == other
105-
#
106-
# However, using the operator syntax like that ends up invoking the full
107-
# type checking machinery again and means we can end up bouncing back and
108-
# forth between the two operands until we run out of stack space.
109-
#
110-
# The solution is to define helper functions that invoke the appropriate
111-
# magic methods directly, ensuring we only try each operand once, and
112-
# return NotImplemented immediately if it is returned from the
113-
# underlying user provided method. Using this scheme, the __gt__ derived
114-
# from a user provided __lt__ becomes:
115-
#
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)
92+
# The total ordering functions all invoke the root magic method directly
93+
# rather than using the corresponding operator. This avoids possible
94+
# infinite recursion that could occur when the operator dispatch logic
95+
# detects a NotImplemented result and then calls a reflected method.
96+
97+
def _gt_from_lt(self, other):
98+
'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).'
99+
op_result = self.__lt__(other)
100+
if op_result is NotImplemented:
101+
return NotImplemented
102+
return not op_result and self != other
103+
104+
def _le_from_lt(self, other):
105+
'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).'
106+
op_result = self.__lt__(other)
107+
return op_result or self == other
108+
109+
def _ge_from_lt(self, other):
110+
'Return a >= b. Computed by @total_ordering from (not a < b).'
111+
op_result = self.__lt__(other)
124112
if op_result is NotImplemented:
125113
return NotImplemented
126114
return not op_result
127115

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)
116+
def _ge_from_le(self, other):
117+
'Return a >= b. Computed by @total_ordering from (not a <= b) or (a == b).'
118+
op_result = self.__le__(other)
132119
if op_result is NotImplemented:
133120
return NotImplemented
134-
return op_result or self == other
121+
return not op_result or self == other
122+
123+
def _lt_from_le(self, other):
124+
'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).'
125+
op_result = self.__le__(other)
126+
if op_result is NotImplemented:
127+
return NotImplemented
128+
return op_result and self != other
129+
130+
def _gt_from_le(self, other):
131+
'Return a > b. Computed by @total_ordering from (not a <= b).'
132+
op_result = self.__le__(other)
133+
if op_result is NotImplemented:
134+
return NotImplemented
135+
return not op_result
135136

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)
137+
def _lt_from_gt(self, other):
138+
'Return a < b. Computed by @total_ordering from (not a > b) and (a != b).'
139+
op_result = self.__gt__(other)
142140
if op_result is NotImplemented:
143141
return NotImplemented
144142
return not op_result and self != other
145143

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)
144+
def _ge_from_gt(self, other):
145+
'Return a >= b. Computed by @total_ordering from (a > b) or (a == b).'
146+
op_result = self.__gt__(other)
147+
return op_result or self == other
148+
149+
def _le_from_gt(self, other):
150+
'Return a <= b. Computed by @total_ordering from (not a > b).'
151+
op_result = self.__gt__(other)
152+
if op_result is NotImplemented:
153+
return NotImplemented
154+
return not op_result
155+
156+
def _le_from_ge(self, other):
157+
'Return a <= b. Computed by @total_ordering from (not a >= b) or (a == b).'
158+
op_result = self.__ge__(other)
150159
if op_result is NotImplemented:
151160
return NotImplemented
152161
return not op_result or self == other
153162

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)
163+
def _gt_from_ge(self, other):
164+
'Return a > b. Computed by @total_ordering from (a >= b) and (a != b).'
165+
op_result = self.__ge__(other)
158166
if op_result is NotImplemented:
159167
return NotImplemented
160168
return op_result and self != other
161169

170+
def _lt_from_ge(self, other):
171+
'Return a < b. Computed by @total_ordering from (not a >= b).'
172+
op_result = self.__ge__(other)
173+
if op_result is NotImplemented:
174+
return NotImplemented
175+
return not op_result
176+
162177
def total_ordering(cls):
163178
"""Class decorator that fills in missing ordering methods"""
164179
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))]
180+
'__lt__': [('__gt__', _gt_from_lt),
181+
('__le__', _le_from_lt),
182+
('__ge__', _ge_from_lt)],
183+
'__le__': [('__ge__', _ge_from_le),
184+
('__lt__', _lt_from_le),
185+
('__gt__', _gt_from_le)],
186+
'__gt__': [('__lt__', _lt_from_gt),
187+
('__ge__', _ge_from_gt),
188+
('__le__', _le_from_gt)],
189+
'__ge__': [('__le__', _le_from_ge),
190+
('__gt__', _gt_from_ge),
191+
('__lt__', _lt_from_ge)]
177192
}
178193
# Find user-defined comparisons (not those inherited from object).
179194
roots = [op for op in convert if getattr(cls, op, None) is not getattr(object, op, None)]
@@ -183,7 +198,6 @@ def total_ordering(cls):
183198
for opname, opfunc in convert[root]:
184199
if opname not in roots:
185200
opfunc.__name__ = opname
186-
opfunc.__doc__ = getattr(int, opname).__doc__
187201
setattr(cls, opname, opfunc)
188202
return cls
189203

Misc/NEWS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,8 @@ Library
4747
- Issue #23111: In the ftplib, make ssl.PROTOCOL_SSLv23 the default protocol
4848
version.
4949

50+
- Issue #23132: Mitigate regression in speed and clarity in functools.total_ordering.
51+
5052
- Issue #22585: On OpenBSD 5.6 and newer, os.urandom() now calls getentropy(),
5153
instead of reading /dev/urandom, to get pseudo-random bytes.
5254

0 commit comments

Comments
 (0)