python-peps/pep-0211/index.html

306 lines
22 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!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 Pythons 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 DONT think its 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 couldnt 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 isnt 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>