リスト内包表記 - 静かなる名辞 https://www.haya-programming.com/archive/category/%E3%83%AA%E3%82%B9%E3%83%88%E5%86%85%E5%8C%85%E8%A1%A8%E8%A8%98 pythonとプログラミングのこと Thu, 07 May 2020 20:42:34 +0900 http://blogs.law.harvard.edu/tech/rss Hatena::Blog 【python】リストの各要素に違う処理をする https://www.haya-programming.com/entry/2018/04/27/235805 <div class="section"> <h3>問題設定</h3> <p> 想定しているのは、たとえばこんなシチュエーションです。</p> <pre class="code lang-python" data-lang="python" data-unlink>s = <span class="synConstant">&quot;hoge! 1234&quot;</span> tmp = s.split() lst = [tmp[<span class="synConstant">0</span>], <span class="synIdentifier">int</span>(tmp[<span class="synConstant">1</span>])] </pre><p> 要するに、比較的短いリストだが性質の違うものが入っており、それぞれ違う処理をして返したいのです。</p><p> それだけなら良いのですが、上の例だとs.split()の結果を一時変数に入れないとどうしようもないので(二回呼ぶと再計算されてしまう)、まどろっこしいことになります。ここでリスト内包を使おうとしても、まともな処理は書けません(ん、zipでlambdaと一緒に回せばできなくはないか)。</p><p> (私が思いつく)解決策は2つあります。どちらも関数を使います。</p> </div> <div class="section"> <h3>解決策1:愚直に関数定義</h3> <p> 関数の引数に渡せばローカル変数として束縛されるので、できるという考え方です。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synStatement">def</span> <span class="synIdentifier">f</span>(x): <span class="synStatement">return</span> [x[<span class="synConstant">0</span>], <span class="synIdentifier">int</span>(x[<span class="synConstant">1</span>])] s = <span class="synConstant">&quot;hoge! 1234&quot;</span> lst = f(s.split()) </pre><p> もういっそsを渡す関数にした方が素直ですが、趣旨からずれるのでNG。</p><p> この方法は記述が短くならないという欠点があります。</p> </div> <div class="section"> <h3>解決策2:lambda</h3> <p> lispのletと同じテクニックで一時変数を束縛しています。</p> <pre class="code lang-python" data-lang="python" data-unlink>s = <span class="synConstant">&quot;hoge! 1234&quot;</span> lst = (<span class="synStatement">lambda</span> t:[t[<span class="synConstant">0</span>], <span class="synIdentifier">int</span>(t[<span class="synConstant">1</span>])])(s.split()) </pre><p> とても短く書け、簡潔な記述です。可読性は人によって異なると思いますが、個人的には読みづらいと思います。</p> </div> <div class="section"> <h3>まとめ</h3> <p> このテクニックはリスト内包表記の中でどうしても一時変数が必要になったときに使えます。覚えておいて損はないです。</p> </div> Fri, 27 Apr 2018 23:58:05 +0900 hatenablog://entry/17391345971639015735 python Tips リスト内包表記 map・filterとリスト内包表記はどちらを使うべきか? https://www.haya-programming.com/entry/2018/04/22/002035 <div class="section"> <h3 id="はじめに">はじめに</h3> <p> pythonにはmap・filterという関数と、リスト内包表記という独自の記法があります。</p><p> どちらを使っても同じようなことができますが、どちらを使うべきなのでしょうか?</p><p> 色々な視点から考えてみます。</p><p> 目次</p> <ul class="table-of-contents"> <li><a href="#はじめに">はじめに</a></li> <li><a href="#返り値の型">返り値の型</a></li> <li><a href="#冗長さ">冗長さ</a></li> <li><a href="#可読性">可読性</a></li> <li><a href="#事故">事故</a></li> <li><a href="#まとめ">まとめ</a></li> </ul><p><br /> <span style="font-size: 80%">スポンサーリンク</span><br /> <script async src="//pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script></p> <p><ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-6261827798827777" data-ad-slot="1744230936" data-ad-format="auto" data-full-width-responsive="true"></ins><br /> <script> (adsbygoogle = window.adsbygoogle || []).push({}); </script></p><p></p> </div> <div class="section"> <h3 id="返り値の型">返り値の型</h3> <p> map・filterにはmapオブジェクト、filterオブジェクトというジェネレータのようなものを返すという厄介な性質があります。ただしこれはpython3以降の仕様なので、python2を使っている方には当てはまりません(python3とpython2のソースコード互換性に効いてくるので、それはそれで難しい問題ではある)。</p><p> リスト内包表記でジェネレータの返り値を望むのならジェネレータ式を使うことができますが、map・filterでリストを返したければlist()を使ってリストに変換するしかありません。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; [x*<span class="synConstant">2</span> <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>)] [<span class="synConstant">0</span>, <span class="synConstant">2</span>, <span class="synConstant">4</span>, <span class="synConstant">6</span>, <span class="synConstant">8</span>, <span class="synConstant">10</span>, <span class="synConstant">12</span>, <span class="synConstant">14</span>, <span class="synConstant">16</span>, <span class="synConstant">18</span>] &gt;&gt;&gt; <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(<span class="synStatement">lambda</span> x:x*<span class="synConstant">2</span>, <span class="synIdentifier">range</span>(<span class="synConstant">10</span>))) [<span class="synConstant">0</span>, <span class="synConstant">2</span>, <span class="synConstant">4</span>, <span class="synConstant">6</span>, <span class="synConstant">8</span>, <span class="synConstant">10</span>, <span class="synConstant">12</span>, <span class="synConstant">14</span>, <span class="synConstant">16</span>, <span class="synConstant">18</span>] </pre><p> list()は厄介で、何しろ6文字も余計なものが増えてしまいます。ソースコードが冗長になり、list()が増えすぎると可読性も損ないます。</p><p> なお、star operatorを使って3文字で済ませる裏技もあります。これができるのは比較的新しいバージョンのpythonだけのはずですが・・・。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; [*<span class="synIdentifier">map</span>(<span class="synStatement">lambda</span> x:x*<span class="synConstant">2</span>, <span class="synIdentifier">range</span>(<span class="synConstant">10</span>))] [<span class="synConstant">0</span>, <span class="synConstant">2</span>, <span class="synConstant">4</span>, <span class="synConstant">6</span>, <span class="synConstant">8</span>, <span class="synConstant">10</span>, <span class="synConstant">12</span>, <span class="synConstant">14</span>, <span class="synConstant">16</span>, <span class="synConstant">18</span>] </pre><p> 可読性は悪いです(というか初見殺し。そしてこの書き方は流行っていない)。</p> </div> <div class="section"> <h3 id="冗長さ">冗長さ</h3> <p> 上述の通り、map・filterはリストに変換してやる必要があるので、基本的に冗長です。lambdaが必要になるとなおさら、という気がします。再掲しますが、</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; [x*<span class="synConstant">2</span> <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">10</span>)] [<span class="synConstant">0</span>, <span class="synConstant">2</span>, <span class="synConstant">4</span>, <span class="synConstant">6</span>, <span class="synConstant">8</span>, <span class="synConstant">10</span>, <span class="synConstant">12</span>, <span class="synConstant">14</span>, <span class="synConstant">16</span>, <span class="synConstant">18</span>] &gt;&gt;&gt; <span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>(<span class="synStatement">lambda</span> x:x*<span class="synConstant">2</span>, <span class="synIdentifier">range</span>(<span class="synConstant">10</span>))) [<span class="synConstant">0</span>, <span class="synConstant">2</span>, <span class="synConstant">4</span>, <span class="synConstant">6</span>, <span class="synConstant">8</span>, <span class="synConstant">10</span>, <span class="synConstant">12</span>, <span class="synConstant">14</span>, <span class="synConstant">16</span>, <span class="synConstant">18</span>] </pre><p> リスト内包表記の方は24文字、mapの方は34文字(どちらもスペース込み)ですから、勝ち目はなさそうです。</p><p> しかし、「返り値はジェネレータのようなもので構わない(もしくは積極的にジェネレータを使いたい)」かつ「既存の関数を使い、lambdaを使わない」という条件だと、mapも輝き始めるようです。数字の文字列を一文字ずつintのリストに変換する例。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; (<span class="synIdentifier">int</span>(c) <span class="synStatement">for</span> c <span class="synStatement">in</span> <span class="synConstant">&quot;1234&quot;</span>) &lt;generator <span class="synIdentifier">object</span> &lt;genexpr&gt; at <span class="synConstant">0x7fefabac23b8</span>&gt; &gt;&gt;&gt; <span class="synIdentifier">map</span>(<span class="synIdentifier">int</span>, <span class="synConstant">&quot;1234&quot;</span>) &lt;<span class="synIdentifier">map</span> <span class="synIdentifier">object</span> at <span class="synConstant">0x7fefaba4b0f0</span>&gt; </pre><p> 上は24文字、下は16文字です。内包表記の「for * in」がなくなるのと、呼び出しを省けるので短くなります。</p><p> filterも事情は同様です。よって、冗長さについては「ケースバイケース」であり、より詳しく言うと「返り値がジェネレータで構わず、使う関数がすでに定義されている」場合はmap・filterが有利と言い切れます。逆に言うと、それ以外のケースでは内包表記の方が簡潔に書けるようです。</p><p> たとえば、入力された数値n個をint型に変換する、といったコードでは、</p> <pre class="code lang-python" data-lang="python" data-unlink>a,b,c = <span class="synIdentifier">map</span>(<span class="synIdentifier">int</span>, <span class="synIdentifier">input</span>()) </pre><p> のように、極めて簡潔な記述がmapを使うことで可能になります。</p> </div> <div class="section"> <h3 id="可読性">可読性</h3> <p> 可読性ははっきり言って、一長一短です。</p><p> 一般的なプログラミング言語の構文をベースに考えると、わかりやすいのはmap・filterの方で、何しろ普通の関数です。初心者でも安心して使えると言えます(無名関数と高階関数の概念さえ理解すれば)。ただし、現実的には増えまくるlist()のせいでコードがごちゃごちゃし、かえって可読性が下がります。また、mapとfilterはそれぞれ独立に使う必要があるので、なおさらコードがごちゃごちゃします。</p><p> リスト内包表記は一見するとわかりづらいですが、慣れてしまえば簡潔で、書きやすく、読みやすいと言えます(本当か? 個人差は確実にあると思う)。また、一つの内包表記でfilterとmapを同時に適用できるので簡潔になります。</p><p> 多重の複雑なものになると、そもそも簡潔に書け、改行とインデントでかなりわかりやすく整理できる内包表記の方が相対的にかなり有利と感じています。</p> <pre class="code lang-python" data-lang="python" data-unlink><span class="synPreProc">from</span> pprint <span class="synPreProc">import</span> pprint pprint([[x*y <span class="synStatement">for</span> y <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">1</span>,<span class="synConstant">10</span>)] <span class="synStatement">for</span> x <span class="synStatement">in</span> <span class="synIdentifier">range</span>(<span class="synConstant">1</span>,<span class="synConstant">10</span>)]) pprint(<span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>( <span class="synStatement">lambda</span> x:<span class="synIdentifier">list</span>(<span class="synIdentifier">map</span>( <span class="synStatement">lambda</span> y:x*y, <span class="synIdentifier">range</span>(<span class="synConstant">1</span>,<span class="synConstant">10</span>))), <span class="synIdentifier">range</span>(<span class="synConstant">1</span>,<span class="synConstant">10</span>)))) </pre><p> かけ算九九の表を表示するプログラムですが、mapの方ははっきり言ってひでーと思います。この程度ならまだ読めなくはありませんが、条件が加わってfilterを追加するとかやり始めるともはや読めなくなります。</p> </div> <div class="section"> <h3 id="事故">事故</h3> <p> これはmap・filterの欠点というよりはジェネレータの欠点ですが、割と事故のもとになってくれます。</p> <pre class="code lang-python" data-lang="python" data-unlink>&gt;&gt;&gt; result = <span class="synIdentifier">map</span>(<span class="synStatement">lambda</span> x:x*<span class="synConstant">2</span>, <span class="synIdentifier">range</span>(<span class="synConstant">10</span>)) &gt;&gt;&gt; result[<span class="synConstant">2</span>] Traceback (most recent call last): File <span class="synConstant">&quot;&lt;stdin&gt;&quot;</span>, line <span class="synConstant">1</span>, <span class="synStatement">in</span> &lt;module&gt; <span class="synType">TypeError</span>: <span class="synConstant">'map'</span> <span class="synIdentifier">object</span> <span class="synStatement">is</span> <span class="synStatement">not</span> subscriptable &gt;&gt;&gt; <span class="synStatement">for</span> x <span class="synStatement">in</span> result: ... <span class="synIdentifier">print</span>(x) ... <span class="synConstant">0</span> <span class="synConstant">2</span> <span class="synConstant">4</span> <span class="synConstant">6</span> <span class="synConstant">8</span> <span class="synConstant">10</span> <span class="synConstant">12</span> <span class="synConstant">14</span> <span class="synConstant">16</span> <span class="synConstant">18</span> &gt;&gt;&gt; <span class="synStatement">for</span> x <span class="synStatement">in</span> result: ... <span class="synIdentifier">print</span>(x) ... &gt;&gt;&gt; <span class="synComment"># 何も表示されない</span> </pre><p> 「んなもん、ちゃんと理解して使えば良いだろ」という意見も当然あると思います。それはそれで正しいと思いますが、理想論です。私は自分自身を含めて人間を100%信頼はしていません。事故る要素があれば、必ず事故るのです。</p><p> そして、これが嫌という理由で私は普段、ほとんどリスト内包表記を使っています。<a href="#f-9c834d83" name="fn-9c834d83" title="余談ですが、私はpythonのジェネレータというものはあまり好きではありません。mapやfilterやzip, rangeなどの返り値は、基本的にすべてlistでも別に良いと思います。これらの関数にはgeneratorオプションを追加してdefault=Falseとするか、python2よろしく対応するxrangeなどを作る、あるいはジェネレータを示す糖衣構文を新しく作って、その糖衣構文で囲むと値の評価時にジェネレータとして処理されるような仕組みを導入すれば良いと思います。これらについては、python4に期待です。">*1</a><br /> </p> </div> <div class="section"> <h3 id="まとめ">まとめ</h3> <p> 基本的には内包表記がずいぶん有利です。特別な理由がなければ内包表記で書けば良いと思います。</p><p> map・filterはリストに既存の関数を適用するジェネレータを作るときに威力を発揮する可能性があります。あるいは、代入でunpackするときとか。</p><p> というか、それ以外なさそうです。無名関数と組み合わせてまで使う必然性はないと思います。</p> </div><div class="footnote"> <p class="footnote"><a href="#fn-9c834d83" name="f-9c834d83" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">余談ですが、私はpythonのジェネレータというものはあまり好きではありません。mapやfilterやzip, rangeなどの返り値は、基本的にすべてlistでも別に良いと思います。これらの関数にはgeneratorオプションを追加してdefault=Falseとするか、python2よろしく対応するxrangeなどを作る、あるいはジェネレータを示す糖衣構文を新しく作って、その糖衣構文で囲むと値の評価時にジェネレータとして処理されるような仕組みを導入すれば良いと思います。これらについては、python4に期待です。</span></p> </div> Sun, 22 Apr 2018 00:20:35 +0900 hatenablog://entry/17391345971637137573 python Tips リスト内包表記