Finalized this PEP. Closed all unresolved issues.
This commit is contained in:
parent
df7922f9d1
commit
d515852375
138
pep-0207.txt
138
pep-0207.txt
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue