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$
Author: guido@python.org (Guido van Rossum), DavidA@ActiveState.com (David Ascher)
Python-Version: 2.1
Status: Incomplete
Status: Final
Abstract
@ -29,8 +29,10 @@ Motivation
Currently such a type *must* implement comparison and thus define
an arbitrary ordering, just so that equality can be tested.
More motivation can be found in the proposals listed under
previous work below.
Also, for some object types an equality test can be implemented
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
@ -76,21 +78,29 @@ Concerns
Proposed Resolutions
1 Full backwards compatibility can be achieved as follows. When
an object defines tp_compare() but not tp_richcmp(), and a rich
comparison is requested, the outcome of tp_compare() is used in
the ovious way. E.g. if "<" is requested, an exception if
an object defines tp_compare() but not tp_richcompare(), and a
rich comparison is requested, the outcome of tp_compare() is
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() is negative, and 0 if it is zero or positive. Etc.
Full forward compatibility can be achieved as follows. (This is
a bit arbitrary.) When a classic comparison is requested on an
object that only implements tp_richcmp(), up to three
comparisons are used: first == is tried, and if it returns true,
0 is returned; next, < is tried and if it returns true, -1 is
returned; next, > is tried and if it returns true, +1 is
returned. Finally, TypeError("incomparable objects") exception
is raised. If any operator tried returns a non-Boolean value
(see below), the exception is passed through.
Full forward compatibility can be achieved as follows. When a
classic comparison is requested on an object that implements
tp_richcompare(), up to three comparisons are used: first == is
tried, and if it returns true, 0 is returned; next, < is tried
and if it returns true, -1 is returned; next, > is tried and if
it returns true, +1 is returned. If any operator tried returns
a non-Boolean value (see below), the exception raised by
conversion to Boolean is passed through. If none of the
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
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
(A<B)&(C<D).
6 The min(), max() and list.sort() operations will only use the
< operator. The 'in' and 'not in' operators and dictionary
lookup will only use the == operator.
6 The min() and list.sort() operations will only use the
< operator; max() will only use the > operator. The 'in' and
'not in' operators and dictionary lookup will only use the ==
operator.
Implementation Proposal
@ -127,13 +138,22 @@ Implementation Proposal
C API
- New function:
- New functions:
PyObject *PyObject_RichCompare(PyObject *, PyObject *, enum cmp_op)
This performs the requested rich comparison, returning a Python
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:
@ -143,6 +163,13 @@ Implementation Proposal
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
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
the outcome of a particular comparison (e.g. in list.sort(), and
of course for the comparison operators in ceval.c), the code is
changed to call PyObject_RichCompare() instead; if the C code
needs to know the outcome of the comparison, PyObject_IsTrue()
is called on the result (which may raise an exception).
changed to call PyObject_RichCompare() or
PyObject_RichCompareBool() instead; if the C code needs to know
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
optional!)
optional; I've converted lists, tuples, complex numbers, and
arrays so far, and am not sure whether I will convert others.)
Classes
- Classes can define new special methods __lt__, __le__, __gt__,
__ge__, __eq__, __ne__ to override the corresponding operators.
(You gotta love the Fortran heritage.) If a class overrides
__cmp__ as well, it is only used by PyObject_Compare().
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.
- Classes can define new special methods __lt__, __le__, __eq__,
__ne__,__gt__, __ge__ to override the corresponding operators.
(I.e., <, <=, ==, !=, >, >=. You gotta love the Fortran
heritage.) If a class defines __cmp__ as well, it is only used
when __lt__ etc. have been tried and return NotImplemented.
Copyright