reSTify PEP 207 (#371)

This commit is contained in:
Huang Huang 2017-09-13 07:26:10 +08:00 committed by Guido van Rossum
parent 3035a38e55
commit 27d218f1bb
1 changed files with 351 additions and 331 deletions

View File

@ -5,459 +5,479 @@ Last-Modified: $Date$
Author: guido@python.org (Guido van Rossum), DavidA@ActiveState.com (David Ascher) Author: guido@python.org (Guido van Rossum), DavidA@ActiveState.com (David Ascher)
Status: Final Status: Final
Type: Standards Track Type: Standards Track
Content-Type: text/x-rst
Created: Created:
Python-Version: 2.1 Python-Version: 2.1
Post-History: Post-History:
Abstract Abstract
========
This PEP proposes several new features for comparisons: This PEP proposes several new features for comparisons:
- Allow separately overloading of <, >, <=, >=, ==, !=, both in - Allow separately overloading of <, >, <=, >=, ==, !=, both in
classes and in C extensions. classes and in C extensions.
- Allow any of those overloaded operators to return something else - Allow any of those overloaded operators to return something else
besides a Boolean result. besides a Boolean result.
Motivation Motivation
==========
The main motivation comes from NumPy, whose users agree that A<B The main motivation comes from NumPy, whose users agree that A<B
should return an array of elementwise comparison outcomes; they should return an array of elementwise comparison outcomes; they
currently have to spell this as less(A,B) because A<B can only currently have to spell this as less(A,B) because A<B can only
return a Boolean result or raise an exception. return a Boolean result or raise an exception.
An additional motivation is that frequently, types don't have a An additional motivation is that frequently, types don't have a
natural ordering, but still need to be compared for equality. natural ordering, but still need to be compared for equality.
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.
Also, for some object types an equality test can be implemented Also, for some object types an equality test can be implemented
much more efficiently than an ordering test; for example, lists much more efficiently than an ordering test; for example, lists
and dictionaries that differ in length are unequal, but the and dictionaries that differ in length are unequal, but the
ordering requires inspecting some (potentially all) items. ordering requires inspecting some (potentially all) items.
Previous Work Previous Work
=============
Rich Comparisons have been proposed before; in particular by David Rich Comparisons have been proposed before; in particular by David
Ascher, after experience with Numerical Python: Ascher, after experience with Numerical Python:
http://starship.python.net/crew/da/proposals/richcmp.html http://starship.python.net/crew/da/proposals/richcmp.html
It is also included below as an Appendix. Most of the material in It is also included below as an Appendix. Most of the material in
this PEP is derived from David's proposal. this PEP is derived from David's proposal.
Concerns Concerns
========
1 Backwards compatibility, both at the Python level (classes using 1. Backwards compatibility, both at the Python level (classes using
__cmp__ need not be changed) and at the C level (extensions ``__cmp__`` need not be changed) and at the C level (extensions
defining tp_compare need not be changed, code using defining ``tp_comparea`` need not be changed, code using
PyObject_Compare() must work even if the compared objects use ``PyObject_Compare()`` must work even if the compared objects use
the new rich comparison scheme). the new rich comparison scheme).
2 When A<B returns a matrix of elementwise comparisons, an easy 2. When A<B returns a matrix of elementwise comparisons, an easy
mistake to make is to use this expression in a Boolean context. mistake to make is to use this expression in a Boolean context.
Without special precautions, it would always be true. This use Without special precautions, it would always be true. This use
should raise an exception instead. should raise an exception instead.
3 If a class overrides x==y but nothing else, should x!=y be 3. If a class overrides x==y but nothing else, should x!=y be
computed as not(x==y), or fail? What about the similar computed as not(x==y), or fail? What about the similar
relationship between < and >=, or between > and <=? relationship between < and >=, or between > and <=?
4 Similarly, should we allow x<y to be calculated from y>x? And 4. Similarly, should we allow x<y to be calculated from y>x? And
x<=y from not(x>y)? And x==y from y==x, or x!=y from y!=x? x<=y from not(x>y)? And x==y from y==x, or x!=y from y!=x?
5 When comparison operators return elementwise comparisons, what 5. When comparison operators return elementwise comparisons, what
to do about shortcut operators like A<B<C, ``A<B and C<D'', to do about shortcut operators like A<B<C, ``A<B and C<D``,
``A<B or C<D''? ``A<B or C<D``?
6 What to do about min() and max(), the 'in' and 'not in' 6. What to do about ``min()`` and ``max()``, the 'in' and 'not in'
operators, list.sort(), dictionary key comparison, and other operators, ``list.sort()``, dictionary key comparison, and other
uses of comparisons by built-in operations? uses of comparisons by built-in operations?
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_richcompare(), and a an object defines ``tp_compare()`` but not ``tp_richcompare()``, and a
rich comparison is requested, the outcome of tp_compare() is rich comparison is requested, the outcome of ``tp_compare()`` is
used in 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. When a Full forward compatibility can be achieved as follows. When a
classic comparison is requested on an object that implements classic comparison is requested on an object that implements
tp_richcompare(), up to three comparisons are used: first == is ``tp_richcompare()``, up to three comparisons are used: first == is
tried, and if it returns true, 0 is returned; next, < is tried 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 and if it returns true, -1 is returned; next, > is tried and if
it returns true, +1 is returned. If any operator tried returns it returns true, +1 is returned. If any operator tried returns
a non-Boolean value (see below), the exception raised by a non-Boolean value (see below), the exception raised by
conversion to Boolean is passed through. If none of the conversion to Boolean is passed through. If none of the
operators tried returns true, the classic comparison fallbacks operators tried returns true, the classic comparison fallbacks
are tried next. are tried next.
(I thought long and hard about the order in which the three (I thought long and hard about the order in which the three
comparisons should be tried. At one point I had a convincing comparisons should be tried. At one point I had a convincing
argument for doing it in this order, based on the behavior of argument for doing it in this order, based on the behavior of
comparisons for cyclical data structures. But since that code comparisons for cyclical data structures. But since that code
has changed again, I'm not so sure that it makes a difference has changed again, I'm not so sure that it makes a difference
any more.) 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.
Such a type is considered a non-Boolean. Such a type is considered a non-Boolean.
3 The == and != operators are not assumed to be each other's 3. The == and != operators are not assumed to be each other's
complement (e.g. IEEE 754 floating point numbers do not satisfy complement (e.g. IEEE 754 floating point numbers do not satisfy
this). It is up to the type to implement this if desired. this). It is up to the type to implement this if desired.
Similar for < and >=, or > and <=; there are lots of examples Similar for < and >=, or > and <=; there are lots of examples
where these assumptions aren't true (e.g. tabnanny). where these assumptions aren't true (e.g. tabnanny).
4 The reflexivity rules *are* assumed by Python. Thus, the 4. The reflexivity rules **are** assumed by Python. Thus, the
interpreter may swap y>x with x<y, y>=x with x<=y, and may swap interpreter may swap y>x with x<y, y>=x with x<=y, and may swap
the arguments of x==y and x!=y. (Note: Python currently assumes the arguments of x==y and x!=y. (Note: Python currently assumes
that x==x is always true and x!=x is never true; this should not that x==x is always true and x!=x is never true; this should not
be assumed.) be assumed.)
5 In the current proposal, when A<B returns an array of 5. In the current proposal, when A<B returns an array of
elementwise comparisons, this outcome is considered non-Boolean, elementwise comparisons, this outcome is considered non-Boolean,
and its interpretation as Boolean by the shortcut operators and its interpretation as Boolean by the shortcut operators
raises an exception. David Ascher's proposal tries to deal raises an exception. David Ascher's proposal tries to deal
with this; I don't think this is worth the additional complexity with this; I don't think this is worth the additional complexity
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)&(B<C). (A<B)&(B<C).
6 The min() and list.sort() operations will only use the 6. The ``min()`` and ``list.sort()`` operations will only use the
< operator; max() will only use the > operator. The 'in' and < operator; max() will only use the > operator. The 'in' and
'not in' operators and dictionary lookup will only use the == 'not in' operators and dictionary lookup will only use the ==
operator. operator.
Implementation Proposal Implementation Proposal
=======================
This closely follows David Ascher's proposal. This closely follows David Ascher's proposal.
C API C API
-----
- New functions: - New functions::
PyObject *PyObject_RichCompare(PyObject *, PyObject *, int) PyObject *PyObject_RichCompare(PyObject *, PyObject *, int)
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
Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT or Py_GE. Py_LT, Py_LE, Py_EQ, Py_NE, Py_GT or Py_GE.
::
int PyObject_RichCompareBool(PyObject *, PyObject *, int) int PyObject_RichCompareBool(PyObject *, PyObject *, int)
This performs the requested rich comparison, returning a This performs the requested rich comparison, returning a
Boolean: -1 for exception, 0 for false, 1 for true. The 3rd 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 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 Py_GE. Note that when ``PyObject_RichCompare()`` returns a
non-Boolean object, PyObject_RichCompareBool() will raise an non-Boolean object, ``PyObject_RichCompareBool()`` will raise an
exception. exception.
- New typedef: - New typedef::
typedef PyObject *(*richcmpfunc) (PyObject *, PyObject *, int); typedef PyObject *(*richcmpfunc) (PyObject *, PyObject *, int);
- New slot in type object, replacing spare tp_xxx7: - New slot in type object, replacing spare tp_xxx7::
richcmpfunc tp_richcompare; richcmpfunc tp_richcompare;
This should be a function with the same signature as This should be a function with the same signature as
PyObject_RichCompare(), and performing the same comparison. ``PyObject_RichCompare()``, and performing the same comparison.
At least one of the arguments is of the type whose At least one of the arguments is of the type whose
tp_richcompare slot is being used, but the other may have a tp_richcompare slot is being used, but the other may have a
different type. If the function cannot compare the particular different type. If the function cannot compare the particular
combination of objects, it should return a new reference to combination of objects, it should return a new reference to
Py_NotImplemented. ``Py_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).
Changes to the interpreter Changes to the interpreter
--------------------------
- 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() or changed to call ``PyObject_RichCompare()`` or
PyObject_RichCompareBool() instead; if the C code needs to know ``PyObject_RichCompareBool()`` instead; if the C code needs to know
the outcome of the comparison, PyObject_IsTrue() is called on the outcome of the comparison, ``PyObject_IsTrue()`` is called on
the result (which may raise an exception). the result (which may raise an exception).
- Most 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; I've converted lists, tuples, complex numbers, and optional; I've converted lists, tuples, complex numbers, and
arrays so far, and am not sure whether I will convert others.) arrays so far, and am not sure whether I will convert others.)
Classes Classes
-------
- Classes can define new special methods __lt__, __le__, __eq__, - Classes can define new special methods ``__lt__``, ``__le__``, ``__eq__``,
__ne__,__gt__, __ge__ to override the corresponding operators. ``__ne__``, ``__gt__``, ``__ge__`` to override the corresponding operators.
(I.e., <, <=, ==, !=, >, >=. You gotta love the Fortran (I.e., <, <=, ==, !=, >, >=. You gotta love the Fortran
heritage.) If a class defines __cmp__ as well, it is only used heritage.) If a class defines ``__cmp__`` as well, it is only used
when __lt__ etc. have been tried and return NotImplemented. when ``__lt__`` etc. have been tried and return ``NotImplemented``.
Copyright Copyright
=========
This document has been placed in the public domain. This document has been placed in the public domain.
Appendix Appendix
========
Here is most of David Ascher's original proposal (version 0.2.1, Here is most of David Ascher's original proposal (version 0.2.1,
dated Wed Jul 22 16:49:28 1998; I've left the Contents, History dated Wed Jul 22 16:49:28 1998; I've left the Contents, History
and Patches sections out). It addresses almost all concerns and Patches sections out). It addresses almost all concerns
above. above.
Abstract Abstract
========
A new mechanism allowing comparisons of Python objects to return A new mechanism allowing comparisons of Python objects to return
values other than -1, 0, or 1 (or raise exceptions) is values other than -1, 0, or 1 (or raise exceptions) is
proposed. This mechanism is entirely backwards compatible, and can proposed. This mechanism is entirely backwards compatible, and can
be controlled at the level of the C PyObject type or of the Python be controlled at the level of the C ``PyObject`` type or of the Python
class definition. There are three cooperating parts to the class definition. There are three cooperating parts to the
proposed mechanism: proposed mechanism:
- the use of the last slot in the type object structure to store a - the use of the last slot in the type object structure to store a
pointer to a rich comparison function pointer to a rich comparison function
- the addition of special methods for classes - the addition of special methods for classes
- the addition of an optional argument to the builtin cmp() - the addition of an optional argument to the builtin ``cmp()``
function. function.
Motivation Motivation
==========
The current comparison protocol for Python objects assumes that The current comparison protocol for Python objects assumes that
any two Python objects can be compared (as of Python 1.5, object any two Python objects can be compared (as of Python 1.5, object
comparisons can raise exceptions), and that the return value for comparisons can raise exceptions), and that the return value for
any comparison should be -1, 0 or 1. -1 indicates that the first any comparison should be -1, 0 or 1. -1 indicates that the first
argument to the comparison function is less than the right one, +1 argument to the comparison function is less than the right one, +1
indicating the contrapositive, and 0 indicating that the two indicating the contrapositive, and 0 indicating that the two
objects are equal. While this mechanism allows the establishment objects are equal. While this mechanism allows the establishment
of an order relationship (e.g. for use by the sort() method of list of an order relationship (e.g. for use by the ``sort()`` method of list
objects), it has proven to be limited in the context of Numeric objects), it has proven to be limited in the context of Numeric
Python (NumPy). Python (NumPy).
Specifically, NumPy allows the creation of multidimensional Specifically, NumPy allows the creation of multidimensional
arrays, which support most of the numeric operators. Thus: arrays, which support most of the numeric operators. Thus::
x = array((1,2,3,4)) y = array((2,2,4,4)) x = array((1,2,3,4)) y = array((2,2,4,4))
are two NumPy arrays. While they can be added elementwise,: are two NumPy arrays. While they can be added elementwise,::
z = x + y # z == array((3,4,7,8)) z = x + y # z == array((3,4,7,8))
they cannot be compared in the current framework - the released they cannot be compared in the current framework - the released
version of NumPy compares the pointers, (thus yielding junk version of NumPy compares the pointers, (thus yielding junk
information) which was the only solution before the recent information) which was the only solution before the recent
addition of the ability (in 1.5) to raise exceptions in comparison addition of the ability (in 1.5) to raise exceptions in comparison
functions. functions.
Even with the ability to raise exceptions, the current protocol Even with the ability to raise exceptions, the current protocol
makes array comparisons useless. To deal with this fact, NumPy makes array comparisons useless. To deal with this fact, NumPy
includes several functions which perform the comparisons: less(), includes several functions which perform the comparisons: ``less()``,
less_equal(), greater(), greater_equal(), equal(), ``less_equal()``, ``greater()``, ``greater_equal()``, ``equal()``,
not_equal(). These functions return arrays with the same shape as ``not_equal()``. These functions return arrays with the same shape as
their arguments (modulo broadcasting), filled with 0's and 1's their arguments (modulo broadcasting), filled with 0's and 1's
depending on whether the comparison is true or not for each depending on whether the comparison is true or not for each
element pair. Thus, for example, using the arrays x and y defined element pair. Thus, for example, using the arrays x and y defined
above: above::
less(x,y) less(x,y)
would be an array containing the numbers (1,0,0,0). would be an array containing the numbers (1,0,0,0).
The current proposal is to modify the Python object interface to The current proposal is to modify the Python object interface to
allow the NumPy package to make it so that x < y returns the same allow the NumPy package to make it so that x < y returns the same
thing as less(x,y). The exact return value is up to the NumPy thing as less(x,y). The exact return value is up to the NumPy
package -- what this proposal really asks for is changing the package -- what this proposal really asks for is changing the
Python core so that extension objects have the ability to return Python core so that extension objects have the ability to return
something other than -1, 0, 1, should their authors choose to do something other than -1, 0, 1, should their authors choose to do
so. so.
Current State of Affairs Current State of Affairs
========================
The current protocol is, at the C level, that each object type The current protocol is, at the C level, that each object type
defines a tp_compare slot, which is a pointer to a function which defines a ``tp_compare`` slot, which is a pointer to a function which
takes two PyObject* references and returns -1, 0, or 1. This takes two ``PyObject*`` references and returns -1, 0, or 1. This
function is called by the PyObject_Compare() function defined in function is called by the ``PyObject_Compare()`` function defined in
the C API. PyObject_Compare() is also called by the builtin the C API. ``PyObject_Compare()`` is also called by the builtin
function cmp() which takes two arguments. function ``cmp()`` which takes two arguments.
Proposed Mechanism Proposed Mechanism
------------------
1. Changes to the C structure for type objects 1. Changes to the C structure for type objects
The last available slot in the PyTypeObject, reserved up to now The last available slot in the ``PyTypeObject``, reserved up to now
for future expansion, is used to optionally store a pointer to a for future expansion, is used to optionally store a pointer to a
new comparison function, of type richcmpfunc defined by: new comparison function, of type richcmpfunc defined by::
typedef PyObject *(*richcmpfunc) typedef PyObject *(*richcmpfunc)
Py_PROTO((PyObject *, PyObject *, int)); Py_PROTO((PyObject *, PyObject *, int));
This function takes three arguments. The first two are the objects This function takes three arguments. The first two are the objects
to be compared, and the third is an integer corresponding to an to be compared, and the third is an integer corresponding to an
opcode (one of LT, LE, EQ, NE, GT, GE). If this slot is left NULL, opcode (one of LT, LE, EQ, NE, GT, GE). If this slot is left NULL,
then rich comparison for that object type is not supported (except then rich comparison for that object type is not supported (except
for class instances whose class provide the special methods for class instances whose class provide the special methods
described below). described below).
The above opcodes need to be added to the published Python/C API The above opcodes need to be added to the published Python/C API
(probably under the names Py_LT, Py_LE, etc.) (probably under the names Py_LT, Py_LE, etc.)
2. Additions of special methods for classes 2. Additions of special methods for classes
Classes wishing to support the rich comparison mechanisms must add Classes wishing to support the rich comparison mechanisms must add
one or more of the following new special methods: one or more of the following new special methods::
def __lt__(self, other): def __lt__(self, other):
... ...
def __le__(self, other): def __le__(self, other):
... ...
def __gt__(self, other): def __gt__(self, other):
... ...
def __ge__(self, other): def __ge__(self, other):
... ...
def __eq__(self, other): def __eq__(self, other):
... ...
def __ne__(self, other): def __ne__(self, other):
... ...
Each of these is called when the class instance is the on the Each of these is called when the class instance is the on the
left-hand-side of the corresponding operators (<, <=, >, >=, ==, left-hand-side of the corresponding operators (<, <=, >, >=, ==,
and != or <>). The argument other is set to the object on the and != or <>). The argument other is set to the object on the
right side of the operator. The return value of these methods is right side of the operator. The return value of these methods is
up to the class implementor (after all, that's the entire point of up to the class implementor (after all, that's the entire point of
the proposal). the proposal).
If the object on the left side of the operator does not define an If the object on the left side of the operator does not define an
appropriate rich comparison operator (either at the C level or appropriate rich comparison operator (either at the C level or
with one of the special methods, then the comparison is reversed, with one of the special methods, then the comparison is reversed,
and the right hand operator is called with the opposite operator, and the right hand operator is called with the opposite operator,
and the two objects are swapped. This assumes that a < b and b > a and the two objects are swapped. This assumes that a < b and b > a
are equivalent, as are a <= b and b >= a, and that == and != are are equivalent, as are a <= b and b >= a, and that == and != are
commutative (e.g. a == b if and only if b == a). commutative (e.g. a == b if and only if b == a).
For example, if obj1 is an object which supports the rich For example, if obj1 is an object which supports the rich
comparison protocol and x and y are objects which do not support comparison protocol and x and y are objects which do not support
the rich comparison protocol, then obj1 < x will call the __lt__ the rich comparison protocol, then obj1 < x will call the ``__lt__``
method of obj1 with x as the second argument. x < obj1 will call method of obj1 with x as the second argument. x < obj1 will call
obj1's __gt__ method with x as a second argument, and x < y will obj1's ``__gt__`` method with x as a second argument, and x < y will
just use the existing (non-rich) comparison mechanism. just use the existing (non-rich) comparison mechanism.
The above mechanism is such that classes can get away with not The above mechanism is such that classes can get away with not
implementing either __lt__ and __le__ or __gt__ and implementing either ``__lt__`` and ``__le__`` or ``__gt__`` and
__ge__. Further smarts could have been added to the comparison ``__ge__``. Further smarts could have been added to the comparison
mechanism, but this limited set of allowed "swaps" was chosen mechanism, but this limited set of allowed "swaps" was chosen
because it doesn't require the infrastructure to do any processing because it doesn't require the infrastructure to do any processing
(negation) of return values. The choice of six special methods was (negation) of return values. The choice of six special methods was
made over a single (e.g. __richcmp__) method to allow the made over a single (e.g. ``__richcmp__``) method to allow the
dispatching on the opcode to be performed at the level of the C dispatching on the opcode to be performed at the level of the C
implementation rather than the user-defined method. implementation rather than the user-defined method.
3. Addition of an optional argument to the builtin cmp() 3. Addition of an optional argument to the builtin ``cmp()``
The builtin cmp() is still used for simple comparisons. For rich The builtin ``cmp()`` is still used for simple comparisons. For rich
comparisons, it is called with a third argument, one of "<", "<=", comparisons, it is called with a third argument, one of "<", "<=",
">", ">=", "==", "!=", "<>" (the last two have the same ">", ">=", "==", "!=", "<>" (the last two have the same
meaning). When called with one of these strings as the third meaning). When called with one of these strings as the third
argument, cmp() can return any Python object. Otherwise, it can argument, ``cmp()`` can return any Python object. Otherwise, it can
only return -1, 0 or 1 as before. only return -1, 0 or 1 as before.
Chained Comparisons Chained Comparisons
-------------------
Problem Problem
'''''''
It would be nice to allow objects for which the comparison returns It would be nice to allow objects for which the comparison returns
something other than -1, 0, or 1 to be used in chained something other than -1, 0, or 1 to be used in chained
comparisons, such as: comparisons, such as::
x < y < z x < y < z
Currently, this is interpreted by Python as: Currently, this is interpreted by Python as::
temp1 = x < y temp1 = x < y
if temp1: if temp1:
return y < z return y < z
else:
return temp1
Note that this requires testing the truth value of the result of
comparisons, with potential "shortcutting" of the right-side
comparison testings. In other words, the truth-value of the result
of the result of the comparison determines the result of a chained
operation. This is problematic in the case of arrays, since if x,
y and z are three arrays, then the user expects::
x < y < z
to be an array of 0's and 1's where 1's are in the locations
corresponding to the elements of y which are between the
corresponding elements in x and z. In other words, the right-hand
side must be evaluated regardless of the result of x < y, which is
incompatible with the mechanism currently in use by the parser.
Solution
''''''''
Guido mentioned that one possible way out would be to change the
code generated by chained comparisons to allow arrays to be
chained-compared intelligently. What follows is a mixture of his
idea and my suggestions. The code generated for x < y < z would be
equivalent to::
temp1 = x < y
if temp1:
temp2 = y < z
return boolean_combine(temp1, temp2)
else:
return temp1
where boolean_combine is a new function which does something like
the following::
def boolean_combine(a, b):
if hasattr(a, '__boolean_and__') or \
hasattr(b, '__boolean_and__'):
try:
return a.__boolean_and__(b)
except:
return b.__boolean_and__(a)
else: # standard behavior
if a:
return b
else: else:
return temp1 return 0
Note that this requires testing the truth value of the result of where the ``__boolean_and__`` special method is implemented for
comparisons, with potential "shortcutting" of the right-side C-level types by another value of the third argument to the
comparison testings. In other words, the truth-value of the result richcmp function. This method would perform a boolean comparison
of the result of the comparison determines the result of a chained of the arrays (currently implemented in the umath module as the
operation. This is problematic in the case of arrays, since if x, logical_and ufunc).
y and z are three arrays, then the user expects:
x < y < z Thus, objects returned by rich comparisons should always test
true, but should define another special method which creates
boolean combinations of them and their argument.
to be an array of 0's and 1's where 1's are in the locations This solution has the advantage of allowing chained comparisons to
corresponding to the elements of y which are between the work for arrays, but the disadvantage that it requires comparison
corresponding elements in x and z. In other words, the right-hand arrays to always return true (in an ideal world, I'd have them
side must be evaluated regardless of the result of x < y, which is always raise an exception on truth testing, since the meaning of
incompatible with the mechanism currently in use by the parser. testing "if a>b:" is massively ambiguous.
Solution The inlining already present which deals with integer comparisons
would still apply, resulting in no performance cost for the most
common cases.
Guido mentioned that one possible way out would be to change the ..
code generated by chained comparisons to allow arrays to be Local Variables:
chained-compared intelligently. What follows is a mixture of his mode: indented-text
idea and my suggestions. The code generated for x < y < z would be indent-tabs-mode: nil
equivalent to: End:
temp1 = x < y
if temp1:
temp2 = y < z
return boolean_combine(temp1, temp2)
else:
return temp1
where boolean_combine is a new function which does something like
the following:
def boolean_combine(a, b):
if hasattr(a, '__boolean_and__') or \
hasattr(b, '__boolean_and__'):
try:
return a.__boolean_and__(b)
except:
return b.__boolean_and__(a)
else: # standard behavior
if a:
return b
else:
return 0
where the __boolean_and__ special method is implemented for
C-level types by another value of the third argument to the
richcmp function. This method would perform a boolean comparison
of the arrays (currently implemented in the umath module as the
logical_and ufunc).
Thus, objects returned by rich comparisons should always test
true, but should define another special method which creates
boolean combinations of them and their argument.
This solution has the advantage of allowing chained comparisons to
work for arrays, but the disadvantage that it requires comparison
arrays to always return true (in an ideal world, I'd have them
always raise an exception on truth testing, since the meaning of
testing "if a>b:" is massively ambiguous.
The inlining already present which deals with integer comparisons
would still apply, resulting in no performance cost for the most
common cases.
Local Variables:
mode: indented-text
indent-tabs-mode: nil
End: