CINXE.COM

PEP 211 – Adding A New Outer Product Operator | 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 211 – Adding A New Outer Product Operator | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0211/"> <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 211 – Adding A New Outer Product Operator | peps.python.org'> <meta property="og:description" content="This PEP describes a proposal to define @ (pronounced “across”) as a new outer product operator in Python 2.2. When applied to sequences (or other iterable objects), this operator will combine their iterators, so that:"> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0211/"> <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 describes a proposal to define @ (pronounced “across”) as a new outer product operator in Python 2.2. When applied to sequences (or other iterable objects), this operator will combine their iterators, so that:"> <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 211</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 211 – Adding A New Outer Product Operator</h1> <dl class="rfc2822 field-list simple"> <dt class="field-odd">Author<span class="colon">:</span></dt> <dd class="field-odd">Greg Wilson &lt;gvwilson&#32;&#97;t&#32;ddj.com&gt;</dd> <dt class="field-even">Status<span class="colon">:</span></dt> <dd class="field-even"><abbr title="Formally declined and will not be accepted">Rejected</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">15-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="#introduction">Introduction</a></li> <li><a class="reference internal" href="#background">Background</a></li> <li><a class="reference internal" href="#iterators">Iterators</a></li> <li><a class="reference internal" href="#discussion">Discussion</a></li> <li><a class="reference internal" href="#alternatives">Alternatives</a></li> <li><a class="reference internal" href="#acknowledgments">Acknowledgments</a></li> <li><a class="reference internal" href="#references">References</a></li> </ul> </details></section> <div class="pep-banner sticky-banner deprecated rejected admonition warning"> <p class="admonition-title">Warning</p> <p>This PEP has been rejected.</p> <p class="close-button">×</p> <p>The approach in the later <a class="pep reference internal" href="../pep-0465/" title="PEP 465 – A dedicated infix operator for matrix multiplication">PEP 465</a> was eventually accepted in lieu of this PEP. The <a class="pep reference internal" href="../pep-0465/#rejected-alternatives-to-adding-a-new-operator" title="PEP 465 – A dedicated infix operator for matrix multiplication § Rejected alternatives to adding a new operator">Rejected Ideas</a> of that PEP explains the rationale in more detail.</p> <p></p> </div> <section id="introduction"> <h2><a class="toc-backref" href="#introduction" role="doc-backlink">Introduction</a></h2> <p>This PEP describes a proposal to define <code class="docutils literal notranslate"><span class="pre">&#64;</span></code> (pronounced “across”) as a new outer product operator in Python 2.2. When applied to sequences (or other iterable objects), this operator will combine their iterators, so that:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">for</span> <span class="p">(</span><span class="n">i</span><span class="p">,</span> <span class="n">j</span><span class="p">)</span> <span class="ow">in</span> <span class="n">S</span> <span class="o">@</span> <span class="n">T</span><span class="p">:</span> <span class="k">pass</span> </pre></div> </div> <p>will be equivalent to:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="n">S</span><span class="p">:</span> <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="n">T</span><span class="p">:</span> <span class="k">pass</span> </pre></div> </div> <p>Classes will be able to overload this operator using the special methods <code class="docutils literal notranslate"><span class="pre">__across__</span></code>, <code class="docutils literal notranslate"><span class="pre">__racross__</span></code>, and <code class="docutils literal notranslate"><span class="pre">__iacross__</span></code>. In particular, the new Numeric module (<a class="pep reference internal" href="../pep-0209/" title="PEP 209 – Multi-dimensional Arrays">PEP 209</a>) will overload this operator for multi-dimensional arrays to implement matrix multiplication.</p> </section> <section id="background"> <h2><a class="toc-backref" href="#background" role="doc-backlink">Background</a></h2> <p>Number-crunching is now just a small part of computing, but many programmers — including many Python users — still need to express complex mathematical operations in code. Most numerical languages, such as APL, Fortran-90, MATLAB, IDL, and Mathematica, therefore provide two forms of the common arithmetic operators. One form works element-by-element, e.g. multiplies corresponding elements of its matrix arguments. The other implements the “mathematical” definition of that operation, e.g. performs row-column matrix multiplication.</p> <p>Zhu and Lielens have <a class="pep reference internal" href="../pep-0225/" title="PEP 225 – Elementwise/Objectwise Operators">proposed</a> doubling up Python’s operators in this way. Their proposal would create six new binary infix operators, and six new in-place operators.</p> <p>The original version of this proposal was much more conservative. The author consulted the developers of GNU Octave <a class="footnote-reference brackets" href="#id5" id="id1">[1]</a>, an open source clone of MATLAB. Its developers agreed that providing an infix operator for matrix multiplication was important: numerical programmers really do care whether they have to write <code class="docutils literal notranslate"><span class="pre">mmul(A,B)</span></code> instead of <code class="docutils literal notranslate"><span class="pre">A</span> <span class="pre">op</span> <span class="pre">B</span></code>.</p> <p>On the other hand, when asked how important it was to have infix operators for matrix solution and other operations, Prof. James Rawlings replied <a class="footnote-reference brackets" href="#id6" id="id2">[2]</a>:</p> <blockquote> <div>I DON’T think it’s a must have, and I do a lot of matrix inversion. I cannot remember if its <code class="docutils literal notranslate"><span class="pre">A\b</span></code> or <code class="docutils literal notranslate"><span class="pre">b\A</span></code> so I always write <code class="docutils literal notranslate"><span class="pre">inv(A)*b</span></code> instead. I recommend dropping <code class="docutils literal notranslate"><span class="pre">\</span></code>.</div></blockquote> <p>Based on this discussion, and feedback from students at the US national laboratories and elsewhere, we recommended adding only one new operator, for matrix multiplication, to Python.</p> </section> <section id="iterators"> <h2><a class="toc-backref" href="#iterators" role="doc-backlink">Iterators</a></h2> <p>The planned addition of iterators to Python 2.2 opens up a broader scope for this proposal. As part of the discussion of <a class="pep reference internal" href="../pep-0201/" title="PEP 201 – Lockstep Iteration">PEP 201</a>, Lockstep Iteration, the author of this proposal conducted an informal usability experiment <a class="footnote-reference brackets" href="#id7" id="id3">[3]</a>. The results showed that users are psychologically receptive to “cross-product” loop syntax. For example, most users expected:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">S</span> <span class="o">=</span> <span class="p">[</span><span class="mi">10</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">30</span><span class="p">]</span> <span class="n">T</span> <span class="o">=</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="k">for</span> <span class="n">x</span> <span class="ow">in</span> <span class="n">S</span><span class="p">;</span> <span class="n">y</span> <span class="ow">in</span> <span class="n">T</span><span class="p">:</span> <span class="nb">print</span> <span class="n">x</span><span class="o">+</span><span class="n">y</span><span class="p">,</span> </pre></div> </div> <p>to print <code class="docutils literal notranslate"><span class="pre">11</span> <span class="pre">12</span> <span class="pre">13</span> <span class="pre">21</span> <span class="pre">22</span> <span class="pre">23</span> <span class="pre">31</span> <span class="pre">32</span> <span class="pre">33</span></code>. We believe that users will have the same reaction to:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">for</span> <span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">y</span><span class="p">)</span> <span class="ow">in</span> <span class="n">S</span> <span class="o">@</span> <span class="n">T</span><span class="p">:</span> <span class="nb">print</span> <span class="n">x</span><span class="o">+</span><span class="n">y</span> </pre></div> </div> <p>i.e. that they will naturally interpret this as a tidy way to write loop nests.</p> <p>This is where iterators come in. Actually constructing the cross-product of two (or more) sequences before executing the loop would be very expensive. On the other hand, <code class="docutils literal notranslate"><span class="pre">&#64;</span></code> could be defined to get its arguments’ iterators, and then create an outer iterator which returns tuples of the values returned by the inner iterators.</p> </section> <section id="discussion"> <h2><a class="toc-backref" href="#discussion" role="doc-backlink">Discussion</a></h2> <ol class="arabic"> <li>Adding a named function “across” would have less impact on Python than a new infix operator. However, this would not make Python more appealing to numerical programmers, who really do care whether they can write matrix multiplication using an operator, or whether they have to write it as a function call.</li> <li><code class="docutils literal notranslate"><span class="pre">&#64;</span></code> would have be chainable in the same way as comparison operators, i.e.:<div class="highlight-default notranslate"><div class="highlight"><pre><span></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="o">@</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="o">@</span> <span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">)</span> </pre></div> </div> <p>would have to return <code class="docutils literal notranslate"><span class="pre">(1,</span> <span class="pre">3,</span> <span class="pre">5)</span> <span class="pre">...</span> <span class="pre">(2,</span> <span class="pre">4,</span> <span class="pre">6)</span></code>, and <em>not</em> <code class="docutils literal notranslate"><span class="pre">((1,</span> <span class="pre">3),</span> <span class="pre">5)</span> <span class="pre">...</span> <span class="pre">((2,</span> <span class="pre">4),</span> <span class="pre">6)</span></code>. This should not require special support from the parser, as the outer iterator created by the first <code class="docutils literal notranslate"><span class="pre">&#64;</span></code> could easily be taught how to combine itself with ordinary iterators.</p> </li> <li>There would have to be some way to distinguish restartable iterators from ones that couldn’t be restarted. For example, if <code class="docutils literal notranslate"><span class="pre">S</span></code> is an input stream (e.g. a file), and <code class="docutils literal notranslate"><span class="pre">L</span></code> is a list, then <code class="docutils literal notranslate"><span class="pre">S</span> <span class="pre">&#64;</span> <span class="pre">L</span></code> is straightforward, but <code class="docutils literal notranslate"><span class="pre">L</span> <span class="pre">&#64;</span> <span class="pre">S</span></code> is not, since iteration through the stream cannot be repeated. This could be treated as an error, or by having the outer iterator detect non-restartable inner iterators and cache their values.</li> <li>Whiteboard testing of this proposal in front of three novice Python users (all of them experienced programmers) indicates that users will expect:<div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="s2">&quot;ab&quot;</span> <span class="o">@</span> <span class="s2">&quot;cd&quot;</span> </pre></div> </div> <p>to return four strings, not four tuples of pairs of characters. Opinion was divided on what:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="p">(</span><span class="s2">&quot;a&quot;</span><span class="p">,</span> <span class="s2">&quot;b&quot;</span><span class="p">)</span> <span class="o">@</span> <span class="s2">&quot;cd&quot;</span> </pre></div> </div> <p>ought to return…</p> </li> </ol> </section> <section id="alternatives"> <h2><a class="toc-backref" href="#alternatives" role="doc-backlink">Alternatives</a></h2> <ol class="arabic"> <li>Do nothing — keep Python simple.<p>This is always the default choice.</p> </li> <li>Add a named function instead of an operator.<p>Python is not primarily a numerical language; it may not be worth complexifying it for this special case. However, support for real matrix multiplication <em>is</em> frequently requested, and the proposed semantics for <code class="docutils literal notranslate"><span class="pre">&#64;</span></code> for built-in sequence types would simplify expression of a very common idiom (nested loops).</p> </li> <li>Introduce prefixed forms of all existing operators, such as <code class="docutils literal notranslate"><span class="pre">~*</span></code> and <code class="docutils literal notranslate"><span class="pre">~+</span></code>, as proposed in <a class="pep reference internal" href="../pep-0225/" title="PEP 225 – Elementwise/Objectwise Operators">PEP 225</a>.<p>Our objections to this are that there isn’t enough demand to justify the additional complexity (see Rawlings’ comments <a class="footnote-reference brackets" href="#id6" id="id4">[2]</a>), and that the proposed syntax fails the “low toner” readability test.</p> </li> </ol> </section> <section id="acknowledgments"> <h2><a class="toc-backref" href="#acknowledgments" role="doc-backlink">Acknowledgments</a></h2> <p>I am grateful to Huaiyu Zhu for initiating this discussion, and to James Rawlings and students in various Python courses for their discussions of what numerical programmers really care about.</p> </section> <section id="references"> <h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2> <aside class="footnote-list brackets"> <aside class="footnote brackets" id="id5" role="doc-footnote"> <dt class="label" id="id5">[<a href="#id1">1</a>]</dt> <dd><a class="reference external" href="http://bevo.che.wisc.edu/octave/">http://bevo.che.wisc.edu/octave/</a></aside> <aside class="footnote brackets" id="id6" role="doc-footnote"> <dt class="label" id="id6">[2]<em> (<a href='#id2'>1</a>, <a href='#id4'>2</a>) </em></dt> <dd><a class="reference external" href="http://www.egroups.com/message/python-numeric/4">http://www.egroups.com/message/python-numeric/4</a></aside> <aside class="footnote brackets" id="id7" role="doc-footnote"> <dt class="label" id="id7">[<a href="#id3">3</a>]</dt> <dd><a class="reference external" href="https://mail.python.org/pipermail/python-dev/2000-July/006427.html">https://mail.python.org/pipermail/python-dev/2000-July/006427.html</a></aside> </aside> </section> </section> <hr class="docutils" /> <p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0211.rst">https://github.com/python/peps/blob/main/peps/pep-0211.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0211.rst">2024-04-14 20:08:31 GMT</a></p> </article> <nav id="pep-sidebar"> <h2>Contents</h2> <ul> <li><a class="reference internal" href="#introduction">Introduction</a></li> <li><a class="reference internal" href="#background">Background</a></li> <li><a class="reference internal" href="#iterators">Iterators</a></li> <li><a class="reference internal" href="#discussion">Discussion</a></li> <li><a class="reference internal" href="#alternatives">Alternatives</a></li> <li><a class="reference internal" href="#acknowledgments">Acknowledgments</a></li> <li><a class="reference internal" href="#references">References</a></li> </ul> <br> <a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0211.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