Issue #23132: Mitigate regression in speed and clarity in functools.total_ordering.
This commit is contained in:
parent
212994e4e2
commit
0603d3049e
154
Lib/functools.py
154
Lib/functools.py
@ -89,91 +89,106 @@ def wraps(wrapped,
|
|||||||
### total_ordering class decorator
|
### total_ordering class decorator
|
||||||
################################################################################
|
################################################################################
|
||||||
|
|
||||||
# The correct way to indicate that a comparison operation doesn't
|
# The total ordering functions all invoke the root magic method directly
|
||||||
# recognise the other type is to return NotImplemented and let the
|
# rather than using the corresponding operator. This avoids possible
|
||||||
# interpreter handle raising TypeError if both operands return
|
# infinite recursion that could occur when the operator dispatch logic
|
||||||
# NotImplemented from their respective comparison methods
|
# detects a NotImplemented result and then calls a reflected method.
|
||||||
#
|
|
||||||
# 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))
|
|
||||||
|
|
||||||
def _not_op(op, other):
|
def _gt_from_lt(self, other):
|
||||||
# "not a < b" handles "a >= b"
|
'Return a > b. Computed by @total_ordering from (not a < b) and (a != b).'
|
||||||
# "not a <= b" handles "a > b"
|
op_result = self.__lt__(other)
|
||||||
# "not a >= b" handles "a < b"
|
|
||||||
# "not a > b" handles "a <= b"
|
|
||||||
op_result = op(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)
|
|
||||||
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)
|
|
||||||
if op_result is NotImplemented:
|
if op_result is NotImplemented:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return not op_result and self != other
|
return not op_result and self != other
|
||||||
|
|
||||||
def _not_op_or_eq(op, self, other):
|
def _le_from_lt(self, other):
|
||||||
# "not a <= b or a == b" handles "a >= b"
|
'Return a <= b. Computed by @total_ordering from (a < b) or (a == b).'
|
||||||
# "not a >= b or a == b" handles "a <= b"
|
op_result = self.__lt__(other)
|
||||||
op_result = op(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 _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:
|
if op_result is NotImplemented:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return not op_result or self == other
|
return not op_result or self == other
|
||||||
|
|
||||||
def _op_and_not_eq(op, self, other):
|
def _lt_from_le(self, other):
|
||||||
# "a <= b and not a == b" handles "a < b"
|
'Return a < b. Computed by @total_ordering from (a <= b) and (a != b).'
|
||||||
# "a >= b and not a == b" handles "a > b"
|
op_result = self.__le__(other)
|
||||||
op_result = op(other)
|
|
||||||
if op_result is NotImplemented:
|
if op_result is NotImplemented:
|
||||||
return NotImplemented
|
return NotImplemented
|
||||||
return op_result and self != other
|
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 _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 _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):
|
def total_ordering(cls):
|
||||||
"""Class decorator that fills in missing ordering methods"""
|
"""Class decorator that fills in missing ordering methods"""
|
||||||
convert = {
|
convert = {
|
||||||
'__lt__': [('__gt__', lambda self, other: _not_op_and_not_eq(self.__lt__, self, other)),
|
'__lt__': [('__gt__', _gt_from_lt),
|
||||||
('__le__', lambda self, other: _op_or_eq(self.__lt__, self, other)),
|
('__le__', _le_from_lt),
|
||||||
('__ge__', lambda self, other: _not_op(self.__lt__, other))],
|
('__ge__', _ge_from_lt)],
|
||||||
'__le__': [('__ge__', lambda self, other: _not_op_or_eq(self.__le__, self, other)),
|
'__le__': [('__ge__', _ge_from_le),
|
||||||
('__lt__', lambda self, other: _op_and_not_eq(self.__le__, self, other)),
|
('__lt__', _lt_from_le),
|
||||||
('__gt__', lambda self, other: _not_op(self.__le__, other))],
|
('__gt__', _gt_from_le)],
|
||||||
'__gt__': [('__lt__', lambda self, other: _not_op_and_not_eq(self.__gt__, self, other)),
|
'__gt__': [('__lt__', _lt_from_gt),
|
||||||
('__ge__', lambda self, other: _op_or_eq(self.__gt__, self, other)),
|
('__ge__', _ge_from_gt),
|
||||||
('__le__', lambda self, other: _not_op(self.__gt__, other))],
|
('__le__', _le_from_gt)],
|
||||||
'__ge__': [('__le__', lambda self, other: _not_op_or_eq(self.__ge__, self, other)),
|
'__ge__': [('__le__', _le_from_ge),
|
||||||
('__gt__', lambda self, other: _op_and_not_eq(self.__ge__, self, other)),
|
('__gt__', _gt_from_ge),
|
||||||
('__lt__', lambda self, other: _not_op(self.__ge__, other))]
|
('__lt__', _lt_from_ge)]
|
||||||
}
|
}
|
||||||
# Find user-defined comparisons (not those inherited from object).
|
# 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)]
|
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):
|
|||||||
for opname, opfunc in convert[root]:
|
for opname, opfunc in convert[root]:
|
||||||
if opname not in roots:
|
if opname not in roots:
|
||||||
opfunc.__name__ = opname
|
opfunc.__name__ = opname
|
||||||
opfunc.__doc__ = getattr(int, opname).__doc__
|
|
||||||
setattr(cls, opname, opfunc)
|
setattr(cls, opname, opfunc)
|
||||||
return cls
|
return cls
|
||||||
|
|
||||||
|
@ -47,6 +47,8 @@ Library
|
|||||||
- Issue #23111: In the ftplib, make ssl.PROTOCOL_SSLv23 the default protocol
|
- Issue #23111: In the ftplib, make ssl.PROTOCOL_SSLv23 the default protocol
|
||||||
version.
|
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(),
|
- Issue #22585: On OpenBSD 5.6 and newer, os.urandom() now calls getentropy(),
|
||||||
instead of reading /dev/urandom, to get pseudo-random bytes.
|
instead of reading /dev/urandom, to get pseudo-random bytes.
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user