Finalized this PEP. Closed all unresolved issues.

This commit is contained in:
Guido van Rossum 2001-01-19 22:29:19 +00:00
parent df7922f9d1
commit d515852375
1 changed files with 59 additions and 79 deletions

View File

@ -3,7 +3,7 @@ Title: Rich Comparisions
Version: $Revision$ Version: $Revision$
Author: guido@python.org (Guido van Rossum), DavidA@ActiveState.com (David Ascher) Author: guido@python.org (Guido van Rossum), DavidA@ActiveState.com (David Ascher)
Python-Version: 2.1 Python-Version: 2.1
Status: Incomplete Status: Final
Abstract Abstract
@ -29,8 +29,10 @@ Motivation
Currently such a type *must* implement comparison and thus define Currently such a type *must* implement comparison and thus define
an arbitrary ordering, just so that equality can be tested. an arbitrary ordering, just so that equality can be tested.
More motivation can be found in the proposals listed under Also, for some object types an equality test can be implemented
previous work below. much more efficiently than an ordering test; for example, lists
and dictionaries that differ in length are unequal, but the
ordering requires inspecting some (potentially all) items.
Previous Work Previous Work
@ -76,21 +78,29 @@ Concerns
Proposed Resolutions Proposed Resolutions
1 Full backwards compatibility can be achieved as follows. When 1 Full backwards compatibility can be achieved as follows. When
an object defines tp_compare() but not tp_richcmp(), and a rich an object defines tp_compare() but not tp_richcompare(), and a
comparison is requested, the outcome of tp_compare() is used in rich comparison is requested, the outcome of tp_compare() is
the ovious way. E.g. if "<" is requested, an exception if used in the ovious way. E.g. if "<" is requested, an exception if
tp_compare() raises an exception, the outcome is 1 if tp_compare() raises an exception, the outcome is 1 if
tp_compare() is negative, and 0 if it is zero or positive. Etc. tp_compare() is negative, and 0 if it is zero or positive. Etc.
Full forward compatibility can be achieved as follows. (This is Full forward compatibility can be achieved as follows. When a
a bit arbitrary.) When a classic comparison is requested on an classic comparison is requested on an object that implements
object that only implements tp_richcmp(), up to three tp_richcompare(), up to three comparisons are used: first == is
comparisons are used: first == is tried, and if it returns true, tried, and if it returns true, 0 is returned; next, < is tried
0 is returned; next, < is tried and if it returns true, -1 is and if it returns true, -1 is returned; next, > is tried and if
returned; next, > is tried and if it returns true, +1 is it returns true, +1 is returned. If any operator tried returns
returned. Finally, TypeError("incomparable objects") exception a non-Boolean value (see below), the exception raised by
is raised. If any operator tried returns a non-Boolean value conversion to Boolean is passed through. If none of the
(see below), the exception is passed through. operators tried returns true, the classic comparison fallbacks
are tried next.
(I thought long and hard about the order in which the three
comparisons should be tried. At one point I had a convincing
argument for doing it in this order, based on the behavior of
comparisons for cyclical data structures. But since that code
has changed again, I'm not so sure that it makes a difference
any more.)
2 Any type that returns a collection of Booleans instead of a 2 Any type that returns a collection of Booleans instead of a
single boolean should define nb_nonzero() to raise an exception. single boolean should define nb_nonzero() to raise an exception.
@ -116,9 +126,10 @@ Proposed Resolutions
in the code generator. Instead of A<B<C, you can write in the code generator. Instead of A<B<C, you can write
(A<B)&(C<D). (A<B)&(C<D).
6 The min(), max() and list.sort() operations will only use the 6 The min() and list.sort() operations will only use the
< operator. The 'in' and 'not in' operators and dictionary < operator; max() will only use the > operator. The 'in' and
lookup will only use the == operator. 'not in' operators and dictionary lookup will only use the ==
operator.
Implementation Proposal Implementation Proposal
@ -127,13 +138,22 @@ Implementation Proposal
C API C API
- New function: - New functions:
PyObject *PyObject_RichCompare(PyObject *, PyObject *, enum cmp_op) PyObject *PyObject_RichCompare(PyObject *, PyObject *, enum cmp_op)
This performs the requested rich comparison, returning a Python This performs the requested rich comparison, returning a Python
object or raising an exception. The 3rd argument must be one of object or raising an exception. The 3rd argument must be one of
LT, LE, EQ, NE, GT or GE. Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT or Py_GE.
int PyObject_RichCompareBool(PyObject *, PyObject *, enum cmp_op)
This performs the requested rich comparison, returning a
Boolean: -1 for exception, 0 for false, 1 for true. The 3rd
argument must be one of Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT or
Py_GE. Note that when PyObject_RichCompare() returns a
non-Boolean object, PyObject_RichCompareBool() will raise an
exception.
- New typedef: - New typedef:
@ -143,6 +163,13 @@ Implementation Proposal
richcmpfunc tp_richcompare; richcmpfunc tp_richcompare;
This should be a function with the same signature as
PyObject_RichCompare(), and performing the same comparison.
At least one of the arguments is of the type whose
tp_richcompare slot is being used, but the other may have a
different type. If the function cannot compare the particular
combination of objects, it should return PyExc_NotImplemented.
- PyObject_Compare() is changed to try rich comparisons if they - PyObject_Compare() is changed to try rich comparisons if they
are defined (but only if classic comparisons aren't defined). are defined (but only if classic comparisons aren't defined).
@ -151,70 +178,23 @@ Implementation Proposal
- Whenever PyObject_Compare() is called with the intent of getting - Whenever PyObject_Compare() is called with the intent of getting
the outcome of a particular comparison (e.g. in list.sort(), and the outcome of a particular comparison (e.g. in list.sort(), and
of course for the comparison operators in ceval.c), the code is of course for the comparison operators in ceval.c), the code is
changed to call PyObject_RichCompare() instead; if the C code changed to call PyObject_RichCompare() or
needs to know the outcome of the comparison, PyObject_IsTrue() PyObject_RichCompareBool() instead; if the C code needs to know
is called on the result (which may raise an exception). the outcome of the comparison, PyObject_IsTrue() is called on
the result (which may raise an exception).
- All built-in types that currently define a comparison will be - Most built-in types that currently define a comparison will be
modified to define a rich comparison instead. (This is modified to define a rich comparison instead. (This is
optional!) optional; I've converted lists, tuples, complex numbers, and
arrays so far, and am not sure whether I will convert others.)
Classes Classes
- Classes can define new special methods __lt__, __le__, __gt__, - Classes can define new special methods __lt__, __le__, __eq__,
__ge__, __eq__, __ne__ to override the corresponding operators. __ne__,__gt__, __ge__ to override the corresponding operators.
(You gotta love the Fortran heritage.) If a class overrides (I.e., <, <=, ==, !=, >, >=. You gotta love the Fortran
__cmp__ as well, it is only used by PyObject_Compare(). heritage.) If a class defines __cmp__ as well, it is only used
when __lt__ etc. have been tried and return NotImplemented.
Unresolved Issues
- David Ascher's proposal also introduces cmp(a, b, op) where op
is one of "<", "<=", ">", ">=", "==", "!=", which should return
the same as a <op> b. Is this necessary?
- The rules for mixed comparisons are confusing. I propose to
wait until the new coercion mechanism is in place, and use the
new coercion rules for mixed comparisons. This may occasionally
cause x<y to be replaced by y>x, if x doesn't implement
comparison to y, but y does implement comparison to x.
- With the above design, if a type or class defines both classic
and rich comparisons, classic comparisons override rich
comparisons when PyObject_Compare() is called, but rich
comparisons override when PyObject_RichCompare() is called. Is
this right? Or should rich comparisons always win except when
cmp() is called?
- Should we even bother upgrading the existing types?
- If so, how should comparisons on container types be defined?
Suppose we have a list whose items define rich comparisons. How
should the itemwise comparisons be done? For example:
def __lt__(a, b): # a<b for lists
for i in range(min(len(a), len(b))):
ai, bi = a[i], b[i]
if ai < bi: return 1
if ai == bi: continue
if ai > bi: return 0
raise TypeError, "incomparable item types"
return len(a) < len(b)
This uses the same sequence of comparisons as cmp(), so it may
as well use cmp() instead:
def __lt__(a, b): # a<b for lists
for i in range(min(len(a), len(b))):
c = cmp(a[i], b[i])
if c < 0: return 1
if c == 0: continue
if c > 0: return 0
assert 0 # unreachable
return len(a) < len(b)
And now there's not really a reason to change lists to rich
comparisons.
Copyright Copyright