@@ -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+
162177def 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
0 commit comments