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$
|
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
|
||||||
|
|
Loading…
Reference in New Issue