CINXE.COM

PEP 207 – Rich Comparisons | peps.python.org

<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="color-scheme" content="light dark"> <title>PEP 207 – Rich Comparisons | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0207/"> <link rel="stylesheet" href="../_static/style.css" type="text/css"> <link rel="stylesheet" href="../_static/mq.css" type="text/css"> <link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light"> <link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark"> <link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss"> <meta property="og:title" content='PEP 207 – Rich Comparisons | peps.python.org'> <meta property="og:description" content="This PEP proposes several new features for comparisons:"> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0207/"> <meta property="og:site_name" content="Python Enhancement Proposals (PEPs)"> <meta property="og:image" content="https://peps.python.org/_static/og-image.png"> <meta property="og:image:alt" content="Python PEPs"> <meta property="og:image:width" content="200"> <meta property="og:image:height" content="200"> <meta name="description" content="This PEP proposes several new features for comparisons:"> <meta name="theme-color" content="#3776ab"> </head> <body> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all"> <title>Following system colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="9"></circle> <path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path> </svg> </symbol> <symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all"> <title>Selected dark colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path> </svg> </symbol> <symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all"> <title>Selected light colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> </symbol> </svg> <script> document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto" </script> <section id="pep-page-section"> <header> <h1>Python Enhancement Proposals</h1> <ul class="breadcrumbs"> <li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> &raquo; </li> <li><a href="../pep-0000/">PEP Index</a> &raquo; </li> <li>PEP 207</li> </ul> <button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())"> <svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg> <span class="visually-hidden">Toggle light / dark / auto colour theme</span> </button> </header> <article> <section id="pep-content"> <h1 class="page-title">PEP 207 – Rich Comparisons</h1> <dl class="rfc2822 field-list simple"> <dt class="field-odd">Author<span class="colon">:</span></dt> <dd class="field-odd">Guido van Rossum &lt;guido&#32;&#97;t&#32;python.org&gt;, David Ascher &lt;DavidA&#32;&#97;t&#32;ActiveState.com&gt;</dd> <dt class="field-even">Status<span class="colon">:</span></dt> <dd class="field-even"><abbr title="Accepted and implementation complete, or no longer active">Final</abbr></dd> <dt class="field-odd">Type<span class="colon">:</span></dt> <dd class="field-odd"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd> <dt class="field-even">Created<span class="colon">:</span></dt> <dd class="field-even">25-Jul-2000</dd> <dt class="field-odd">Python-Version<span class="colon">:</span></dt> <dd class="field-odd">2.1</dd> <dt class="field-even">Post-History<span class="colon">:</span></dt> <dd class="field-even"><p></p></dd> </dl> <hr class="docutils" /> <section id="contents"> <details><summary>Table of Contents</summary><ul class="simple"> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#previous-work">Previous Work</a></li> <li><a class="reference internal" href="#concerns">Concerns</a></li> <li><a class="reference internal" href="#proposed-resolutions">Proposed Resolutions</a></li> <li><a class="reference internal" href="#implementation-proposal">Implementation Proposal</a><ul> <li><a class="reference internal" href="#c-api">C API</a></li> <li><a class="reference internal" href="#changes-to-the-interpreter">Changes to the interpreter</a></li> <li><a class="reference internal" href="#classes">Classes</a></li> </ul> </li> <li><a class="reference internal" href="#copyright">Copyright</a></li> <li><a class="reference internal" href="#appendix">Appendix</a></li> <li><a class="reference internal" href="#id1">Abstract</a></li> <li><a class="reference internal" href="#id2">Motivation</a></li> <li><a class="reference internal" href="#current-state-of-affairs">Current State of Affairs</a><ul> <li><a class="reference internal" href="#proposed-mechanism">Proposed Mechanism</a></li> <li><a class="reference internal" href="#chained-comparisons">Chained Comparisons</a><ul> <li><a class="reference internal" href="#problem">Problem</a></li> <li><a class="reference internal" href="#solution">Solution</a></li> </ul> </li> </ul> </li> </ul> </details></section> <section id="abstract"> <h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2> <p>This PEP proposes several new features for comparisons:</p> <ul class="simple"> <li>Allow separately overloading of &lt;, &gt;, &lt;=, &gt;=, ==, !=, both in classes and in C extensions.</li> <li>Allow any of those overloaded operators to return something else besides a Boolean result.</li> </ul> </section> <section id="motivation"> <h2><a class="toc-backref" href="#motivation" role="doc-backlink">Motivation</a></h2> <p>The main motivation comes from NumPy, whose users agree that A&lt;B should return an array of elementwise comparison outcomes; they currently have to spell this as less(A,B) because A&lt;B can only return a Boolean result or raise an exception.</p> <p>An additional motivation is that frequently, types don’t have a natural ordering, but still need to be compared for equality. Currently such a type <strong>must</strong> implement comparison and thus define an arbitrary ordering, just so that equality can be tested.</p> <p>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.</p> </section> <section id="previous-work"> <h2><a class="toc-backref" href="#previous-work" role="doc-backlink">Previous Work</a></h2> <p>Rich Comparisons have been proposed before; in particular by David Ascher, after experience with Numerical Python:</p> <blockquote> <div><a class="reference external" href="http://starship.python.net/crew/da/proposals/richcmp.html">http://starship.python.net/crew/da/proposals/richcmp.html</a></div></blockquote> <p>It is also included below as an Appendix. Most of the material in this PEP is derived from David’s proposal.</p> </section> <section id="concerns"> <h2><a class="toc-backref" href="#concerns" role="doc-backlink">Concerns</a></h2> <ol class="arabic simple"> <li>Backwards compatibility, both at the Python level (classes using <code class="docutils literal notranslate"><span class="pre">__cmp__</span></code> need not be changed) and at the C level (extensions defining <code class="docutils literal notranslate"><span class="pre">tp_comparea</span></code> need not be changed, code using <code class="docutils literal notranslate"><span class="pre">PyObject_Compare()</span></code> must work even if the compared objects use the new rich comparison scheme).</li> <li>When A&lt;B returns a matrix of elementwise comparisons, an easy mistake to make is to use this expression in a Boolean context. Without special precautions, it would always be true. This use should raise an exception instead.</li> <li>If a class overrides x==y but nothing else, should x!=y be computed as not(x==y), or fail? What about the similar relationship between &lt; and &gt;=, or between &gt; and &lt;=?</li> <li>Similarly, should we allow x&lt;y to be calculated from y&gt;x? And x&lt;=y from not(x&gt;y)? And x==y from y==x, or x!=y from y!=x?</li> <li>When comparison operators return elementwise comparisons, what to do about shortcut operators like A&lt;B&lt;C, <code class="docutils literal notranslate"><span class="pre">A&lt;B</span> <span class="pre">and</span> <span class="pre">C&lt;D</span></code>, <code class="docutils literal notranslate"><span class="pre">A&lt;B</span> <span class="pre">or</span> <span class="pre">C&lt;D</span></code>?</li> <li>What to do about <code class="docutils literal notranslate"><span class="pre">min()</span></code> and <code class="docutils literal notranslate"><span class="pre">max()</span></code>, the ‘in’ and ‘not in’ operators, <code class="docutils literal notranslate"><span class="pre">list.sort()</span></code>, dictionary key comparison, and other uses of comparisons by built-in operations?</li> </ol> </section> <section id="proposed-resolutions"> <h2><a class="toc-backref" href="#proposed-resolutions" role="doc-backlink">Proposed Resolutions</a></h2> <ol class="arabic"> <li>Full backwards compatibility can be achieved as follows. When an object defines <code class="docutils literal notranslate"><span class="pre">tp_compare()</span></code> but not <code class="docutils literal notranslate"><span class="pre">tp_richcompare()</span></code>, and a rich comparison is requested, the outcome of <code class="docutils literal notranslate"><span class="pre">tp_compare()</span></code> is used in the obvious way. E.g. if “&lt;” is requested, an exception if <code class="docutils literal notranslate"><span class="pre">tp_compare()</span></code> raises an exception, the outcome is 1 if <code class="docutils literal notranslate"><span class="pre">tp_compare()</span></code> is negative, and 0 if it is zero or positive. Etc.<p>Full forward compatibility can be achieved as follows. When a classic comparison is requested on an object that implements <code class="docutils literal notranslate"><span class="pre">tp_richcompare()</span></code>, up to three comparisons are used: first == is tried, and if it returns true, 0 is returned; next, &lt; is tried and if it returns true, -1 is returned; next, &gt; 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.</p> <p>(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.)</p> </li> <li>Any type that returns a collection of Booleans instead of a single boolean should define <code class="docutils literal notranslate"><span class="pre">nb_nonzero()</span></code> to raise an exception. Such a type is considered a non-Boolean.</li> <li>The == and != operators are not assumed to be each other’s complement (e.g. IEEE 754 floating point numbers do not satisfy this). It is up to the type to implement this if desired. Similar for &lt; and &gt;=, or &gt; and &lt;=; there are lots of examples where these assumptions aren’t true (e.g. tabnanny).</li> <li>The reflexivity rules <strong>are</strong> assumed by Python. Thus, the interpreter may swap y&gt;x with x&lt;y, y&gt;=x with x&lt;=y, and may swap 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 be assumed.)</li> <li>In the current proposal, when A&lt;B returns an array of elementwise comparisons, this outcome is considered non-Boolean, and its interpretation as Boolean by the shortcut operators raises an exception. David Ascher’s proposal tries to deal with this; I don’t think this is worth the additional complexity in the code generator. Instead of A&lt;B&lt;C, you can write (A&lt;B)&amp;(B&lt;C).</li> <li>The <code class="docutils literal notranslate"><span class="pre">min()</span></code> and <code class="docutils literal notranslate"><span class="pre">list.sort()</span></code> operations will only use the &lt; operator; max() will only use the &gt; operator. The ‘in’ and ‘not in’ operators and dictionary lookup will only use the == operator.</li> </ol> </section> <section id="implementation-proposal"> <h2><a class="toc-backref" href="#implementation-proposal" role="doc-backlink">Implementation Proposal</a></h2> <p>This closely follows David Ascher’s proposal.</p> <section id="c-api"> <h3><a class="toc-backref" href="#c-api" role="doc-backlink">C API</a></h3> <ul> <li>New functions:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">PyObject</span> <span class="o">*</span><span class="n">PyObject_RichCompare</span><span class="p">(</span><span class="n">PyObject</span> <span class="o">*</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> </pre></div> </div> <p>This performs the requested rich comparison, returning a Python 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.</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="nb">int</span> <span class="n">PyObject_RichCompareBool</span><span class="p">(</span><span class="n">PyObject</span> <span class="o">*</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="p">,</span> <span class="nb">int</span><span class="p">)</span> </pre></div> </div> <p>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 <code class="docutils literal notranslate"><span class="pre">PyObject_RichCompare()</span></code> returns a non-Boolean object, <code class="docutils literal notranslate"><span class="pre">PyObject_RichCompareBool()</span></code> will raise an exception.</p> </li> <li>New typedef:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">typedef</span> <span class="n">PyObject</span> <span class="o">*</span><span class="p">(</span><span class="o">*</span><span class="n">richcmpfunc</span><span class="p">)</span> <span class="p">(</span><span class="n">PyObject</span> <span class="o">*</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="p">,</span> <span class="nb">int</span><span class="p">);</span> </pre></div> </div> </li> <li>New slot in type object, replacing spare tp_xxx7:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">richcmpfunc</span> <span class="n">tp_richcompare</span><span class="p">;</span> </pre></div> </div> <p>This should be a function with the same signature as <code class="docutils literal notranslate"><span class="pre">PyObject_RichCompare()</span></code>, 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 a new reference to <code class="docutils literal notranslate"><span class="pre">Py_NotImplemented</span></code>.</p> </li> <li><code class="docutils literal notranslate"><span class="pre">PyObject_Compare()</span></code> is changed to try rich comparisons if they are defined (but only if classic comparisons aren’t defined).</li> </ul> </section> <section id="changes-to-the-interpreter"> <h3><a class="toc-backref" href="#changes-to-the-interpreter" role="doc-backlink">Changes to the interpreter</a></h3> <ul class="simple"> <li>Whenever <code class="docutils literal notranslate"><span class="pre">PyObject_Compare()</span></code> is called with the intent of getting the outcome of a particular comparison (e.g. in <code class="docutils literal notranslate"><span class="pre">list.sort()</span></code>, and of course for the comparison operators in ceval.c), the code is changed to call <code class="docutils literal notranslate"><span class="pre">PyObject_RichCompare()</span></code> or <code class="docutils literal notranslate"><span class="pre">PyObject_RichCompareBool()</span></code> instead; if the C code needs to know the outcome of the comparison, <code class="docutils literal notranslate"><span class="pre">PyObject_IsTrue()</span></code> is called on the result (which may raise an exception).</li> <li>Most built-in types that currently define a comparison will be modified to define a rich comparison instead. (This is optional; I’ve converted lists, tuples, complex numbers, and arrays so far, and am not sure whether I will convert others.)</li> </ul> </section> <section id="classes"> <h3><a class="toc-backref" href="#classes" role="doc-backlink">Classes</a></h3> <ul class="simple"> <li>Classes can define new special methods <code class="docutils literal notranslate"><span class="pre">__lt__</span></code>, <code class="docutils literal notranslate"><span class="pre">__le__</span></code>, <code class="docutils literal notranslate"><span class="pre">__eq__</span></code>, <code class="docutils literal notranslate"><span class="pre">__ne__</span></code>, <code class="docutils literal notranslate"><span class="pre">__gt__</span></code>, <code class="docutils literal notranslate"><span class="pre">__ge__</span></code> to override the corresponding operators. (I.e., &lt;, &lt;=, ==, !=, &gt;, &gt;=. You gotta love the Fortran heritage.) If a class defines <code class="docutils literal notranslate"><span class="pre">__cmp__</span></code> as well, it is only used when <code class="docutils literal notranslate"><span class="pre">__lt__</span></code> etc. have been tried and return <code class="docutils literal notranslate"><span class="pre">NotImplemented</span></code>.</li> </ul> </section> </section> <section id="copyright"> <h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2> <p>This document has been placed in the public domain.</p> </section> <section id="appendix"> <h2><a class="toc-backref" href="#appendix" role="doc-backlink">Appendix</a></h2> <p>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 and Patches sections out). It addresses almost all concerns above.</p> </section> <section id="id1"> <h2><a class="toc-backref" href="#id1" role="doc-backlink">Abstract</a></h2> <p>A new mechanism allowing comparisons of Python objects to return values other than -1, 0, or 1 (or raise exceptions) is proposed. This mechanism is entirely backwards compatible, and can be controlled at the level of the C <code class="docutils literal notranslate"><span class="pre">PyObject</span></code> type or of the Python class definition. There are three cooperating parts to the proposed mechanism:</p> <ul class="simple"> <li>the use of the last slot in the type object structure to store a pointer to a rich comparison function</li> <li>the addition of special methods for classes</li> <li>the addition of an optional argument to the builtin <code class="docutils literal notranslate"><span class="pre">cmp()</span></code> function.</li> </ul> </section> <section id="id2"> <h2><a class="toc-backref" href="#id2" role="doc-backlink">Motivation</a></h2> <p>The current comparison protocol for Python objects assumes that any two Python objects can be compared (as of Python 1.5, object comparisons can raise exceptions), and that the return value for 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 indicating the contrapositive, and 0 indicating that the two objects are equal. While this mechanism allows the establishment of an order relationship (e.g. for use by the <code class="docutils literal notranslate"><span class="pre">sort()</span></code> method of list objects), it has proven to be limited in the context of Numeric Python (NumPy).</p> <p>Specifically, NumPy allows the creation of multidimensional arrays, which support most of the numeric operators. Thus:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">x</span> <span class="o">=</span> <span class="n">array</span><span class="p">((</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">))</span> <span class="n">y</span> <span class="o">=</span> <span class="n">array</span><span class="p">((</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">))</span> </pre></div> </div> <p>are two NumPy arrays. While they can be added elementwise,:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">z</span> <span class="o">=</span> <span class="n">x</span> <span class="o">+</span> <span class="n">y</span> <span class="c1"># z == array((3,4,7,8))</span> </pre></div> </div> <p>they cannot be compared in the current framework - the released version of NumPy compares the pointers, (thus yielding junk information) which was the only solution before the recent addition of the ability (in 1.5) to raise exceptions in comparison functions.</p> <p>Even with the ability to raise exceptions, the current protocol makes array comparisons useless. To deal with this fact, NumPy includes several functions which perform the comparisons: <code class="docutils literal notranslate"><span class="pre">less()</span></code>, <code class="docutils literal notranslate"><span class="pre">less_equal()</span></code>, <code class="docutils literal notranslate"><span class="pre">greater()</span></code>, <code class="docutils literal notranslate"><span class="pre">greater_equal()</span></code>, <code class="docutils literal notranslate"><span class="pre">equal()</span></code>, <code class="docutils literal notranslate"><span class="pre">not_equal()</span></code>. These functions return arrays with the same shape as their arguments (modulo broadcasting), filled with 0’s and 1’s depending on whether the comparison is true or not for each element pair. Thus, for example, using the arrays x and y defined above:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">less</span><span class="p">(</span><span class="n">x</span><span class="p">,</span><span class="n">y</span><span class="p">)</span> </pre></div> </div> <p>would be an array containing the numbers (1,0,0,0).</p> <p>The current proposal is to modify the Python object interface to allow the NumPy package to make it so that x &lt; y returns the same thing as less(x,y). The exact return value is up to the NumPy package – what this proposal really asks for is changing the Python core so that extension objects have the ability to return something other than -1, 0, 1, should their authors choose to do so.</p> </section> <section id="current-state-of-affairs"> <h2><a class="toc-backref" href="#current-state-of-affairs" role="doc-backlink">Current State of Affairs</a></h2> <p>The current protocol is, at the C level, that each object type defines a <code class="docutils literal notranslate"><span class="pre">tp_compare</span></code> slot, which is a pointer to a function which takes two <code class="docutils literal notranslate"><span class="pre">PyObject*</span></code> references and returns -1, 0, or 1. This function is called by the <code class="docutils literal notranslate"><span class="pre">PyObject_Compare()</span></code> function defined in the C API. <code class="docutils literal notranslate"><span class="pre">PyObject_Compare()</span></code> is also called by the builtin function <code class="docutils literal notranslate"><span class="pre">cmp()</span></code> which takes two arguments.</p> <section id="proposed-mechanism"> <h3><a class="toc-backref" href="#proposed-mechanism" role="doc-backlink">Proposed Mechanism</a></h3> <ol class="arabic"> <li>Changes to the C structure for type objects<p>The last available slot in the <code class="docutils literal notranslate"><span class="pre">PyTypeObject</span></code>, reserved up to now for future expansion, is used to optionally store a pointer to a new comparison function, of type richcmpfunc defined by:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">typedef</span> <span class="n">PyObject</span> <span class="o">*</span><span class="p">(</span><span class="o">*</span><span class="n">richcmpfunc</span><span class="p">)</span> <span class="n">Py_PROTO</span><span class="p">((</span><span class="n">PyObject</span> <span class="o">*</span><span class="p">,</span> <span class="n">PyObject</span> <span class="o">*</span><span class="p">,</span> <span class="nb">int</span><span class="p">));</span> </pre></div> </div> <p>This function takes three arguments. The first two are the objects 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, then rich comparison for that object type is not supported (except for class instances whose class provide the special methods described below).</p> <p>The above opcodes need to be added to the published Python/C API (probably under the names Py_LT, Py_LE, etc.)</p> </li> <li>Additions of special methods for classes<p>Classes wishing to support the rich comparison mechanisms must add one or more of the following new special methods:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="fm">__lt__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="o">...</span> <span class="k">def</span> <span class="fm">__le__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="o">...</span> <span class="k">def</span> <span class="fm">__gt__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="o">...</span> <span class="k">def</span> <span class="fm">__ge__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="o">...</span> <span class="k">def</span> <span class="fm">__eq__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="o">...</span> <span class="k">def</span> <span class="fm">__ne__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="n">other</span><span class="p">):</span> <span class="o">...</span> </pre></div> </div> <p>Each of these is called when the class instance is the on the left-hand-side of the corresponding operators (&lt;, &lt;=, &gt;, &gt;=, ==, and != or &lt;&gt;). The argument other is set to the object on the 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 the proposal).</p> <p>If the object on the left side of the operator does not define an appropriate rich comparison operator (either at the C level or with one of the special methods, then the comparison is reversed, and the right hand operator is called with the opposite operator, and the two objects are swapped. This assumes that a &lt; b and b &gt; a are equivalent, as are a &lt;= b and b &gt;= a, and that == and != are commutative (e.g. a == b if and only if b == a).</p> <p>For example, if obj1 is an object which supports the rich comparison protocol and x and y are objects which do not support the rich comparison protocol, then obj1 &lt; x will call the <code class="docutils literal notranslate"><span class="pre">__lt__</span></code> method of obj1 with x as the second argument. x &lt; obj1 will call obj1’s <code class="docutils literal notranslate"><span class="pre">__gt__</span></code> method with x as a second argument, and x &lt; y will just use the existing (non-rich) comparison mechanism.</p> <p>The above mechanism is such that classes can get away with not implementing either <code class="docutils literal notranslate"><span class="pre">__lt__</span></code> and <code class="docutils literal notranslate"><span class="pre">__le__</span></code> or <code class="docutils literal notranslate"><span class="pre">__gt__</span></code> and <code class="docutils literal notranslate"><span class="pre">__ge__</span></code>. Further smarts could have been added to the comparison mechanism, but this limited set of allowed “swaps” was chosen because it doesn’t require the infrastructure to do any processing (negation) of return values. The choice of six special methods was made over a single (e.g. <code class="docutils literal notranslate"><span class="pre">__richcmp__</span></code>) method to allow the dispatching on the opcode to be performed at the level of the C implementation rather than the user-defined method.</p> </li> <li>Addition of an optional argument to the builtin <code class="docutils literal notranslate"><span class="pre">cmp()</span></code><p>The builtin <code class="docutils literal notranslate"><span class="pre">cmp()</span></code> is still used for simple comparisons. For rich comparisons, it is called with a third argument, one of “&lt;”, “&lt;=”, “&gt;”, “&gt;=”, “==”, “!=”, “&lt;&gt;” (the last two have the same meaning). When called with one of these strings as the third argument, <code class="docutils literal notranslate"><span class="pre">cmp()</span></code> can return any Python object. Otherwise, it can only return -1, 0 or 1 as before.</p> </li> </ol> </section> <section id="chained-comparisons"> <h3><a class="toc-backref" href="#chained-comparisons" role="doc-backlink">Chained Comparisons</a></h3> <section id="problem"> <h4><a class="toc-backref" href="#problem" role="doc-backlink">Problem</a></h4> <p>It would be nice to allow objects for which the comparison returns something other than -1, 0, or 1 to be used in chained comparisons, such as:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">x</span> <span class="o">&lt;</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="n">z</span> </pre></div> </div> <p>Currently, this is interpreted by Python as:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">temp1</span> <span class="o">=</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="n">y</span> <span class="k">if</span> <span class="n">temp1</span><span class="p">:</span> <span class="k">return</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="n">z</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="n">temp1</span> </pre></div> </div> <p>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:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">x</span> <span class="o">&lt;</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="n">z</span> </pre></div> </div> <p>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 &lt; y, which is incompatible with the mechanism currently in use by the parser.</p> </section> <section id="solution"> <h4><a class="toc-backref" href="#solution" role="doc-backlink">Solution</a></h4> <p>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 &lt; y &lt; z would be equivalent to:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">temp1</span> <span class="o">=</span> <span class="n">x</span> <span class="o">&lt;</span> <span class="n">y</span> <span class="k">if</span> <span class="n">temp1</span><span class="p">:</span> <span class="n">temp2</span> <span class="o">=</span> <span class="n">y</span> <span class="o">&lt;</span> <span class="n">z</span> <span class="k">return</span> <span class="n">boolean_combine</span><span class="p">(</span><span class="n">temp1</span><span class="p">,</span> <span class="n">temp2</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="n">temp1</span> </pre></div> </div> <p>where boolean_combine is a new function which does something like the following:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span> <span class="nf">boolean_combine</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">b</span><span class="p">):</span> <span class="k">if</span> <span class="nb">hasattr</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="s1">&#39;__boolean_and__&#39;</span><span class="p">)</span> <span class="ow">or</span> \ <span class="nb">hasattr</span><span class="p">(</span><span class="n">b</span><span class="p">,</span> <span class="s1">&#39;__boolean_and__&#39;</span><span class="p">):</span> <span class="k">try</span><span class="p">:</span> <span class="k">return</span> <span class="n">a</span><span class="o">.</span><span class="n">__boolean_and__</span><span class="p">(</span><span class="n">b</span><span class="p">)</span> <span class="k">except</span><span class="p">:</span> <span class="k">return</span> <span class="n">b</span><span class="o">.</span><span class="n">__boolean_and__</span><span class="p">(</span><span class="n">a</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="c1"># standard behavior</span> <span class="k">if</span> <span class="n">a</span><span class="p">:</span> <span class="k">return</span> <span class="n">b</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="mi">0</span> </pre></div> </div> <p>where the <code class="docutils literal notranslate"><span class="pre">__boolean_and__</span></code> 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).</p> <p>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.</p> <p>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&gt;b:” is massively ambiguous.</p> <p>The inlining already present which deals with integer comparisons would still apply, resulting in no performance cost for the most common cases.</p> </section> </section> </section> </section> <hr class="docutils" /> <p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0207.rst">https://github.com/python/peps/blob/main/peps/pep-0207.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0207.rst">2023-09-09 17:39:29 GMT</a></p> </article> <nav id="pep-sidebar"> <h2>Contents</h2> <ul> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#motivation">Motivation</a></li> <li><a class="reference internal" href="#previous-work">Previous Work</a></li> <li><a class="reference internal" href="#concerns">Concerns</a></li> <li><a class="reference internal" href="#proposed-resolutions">Proposed Resolutions</a></li> <li><a class="reference internal" href="#implementation-proposal">Implementation Proposal</a><ul> <li><a class="reference internal" href="#c-api">C API</a></li> <li><a class="reference internal" href="#changes-to-the-interpreter">Changes to the interpreter</a></li> <li><a class="reference internal" href="#classes">Classes</a></li> </ul> </li> <li><a class="reference internal" href="#copyright">Copyright</a></li> <li><a class="reference internal" href="#appendix">Appendix</a></li> <li><a class="reference internal" href="#id1">Abstract</a></li> <li><a class="reference internal" href="#id2">Motivation</a></li> <li><a class="reference internal" href="#current-state-of-affairs">Current State of Affairs</a><ul> <li><a class="reference internal" href="#proposed-mechanism">Proposed Mechanism</a></li> <li><a class="reference internal" href="#chained-comparisons">Chained Comparisons</a><ul> <li><a class="reference internal" href="#problem">Problem</a></li> <li><a class="reference internal" href="#solution">Solution</a></li> </ul> </li> </ul> </li> </ul> <br> <a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0207.rst">Page Source (GitHub)</a> </nav> </section> <script src="../_static/colour_scheme.js"></script> <script src="../_static/wrap_tables.js"></script> <script src="../_static/sticky_banner.js"></script> </body> </html>

Pages: 1 2 3 4 5 6 7 8 9 10