スライス - 静かなる名辞
pythonとプログラミングのこと
2020-05-07T20:42:34+09:00
hayataka2049
Hatena::Blog
hatenablog://blog/10328537792367869878
【python】反転させて先頭n個取るスライス
hatenablog://entry/17391345971652068226
2018-06-08T05:51:06+09:00
2018-11-28T13:48:41+09:00 タイトルの通りのものが必要になりました。一体どう書くのでしょう? とりあえず反転させる >>> lst = list(range(20)) >>> lst[::-1] [19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0] ま、これは常識(python廃人の皆さんには)。 n個取ってみる >>> lst[::-1][:10] [19, 18, 17, 16, 15, 14, 13, 12, 11, 10] こうするとリストオブジェクトの生成が二回繰り返されるので遅いはずです。 できればスライス一発で済ませたい…
<p> タイトルの通りのものが必要になりました。一体どう書くのでしょう?</p>
<div class="section">
<h3>とりあえず反転させる</h3>
<pre class="code lang-python" data-lang="python" data-unlink>>>> lst = <span class="synIdentifier">list</span>(<span class="synIdentifier">range</span>(<span class="synConstant">20</span>))
>>> lst[::-<span class="synConstant">1</span>]
[<span class="synConstant">19</span>, <span class="synConstant">18</span>, <span class="synConstant">17</span>, <span class="synConstant">16</span>, <span class="synConstant">15</span>, <span class="synConstant">14</span>, <span class="synConstant">13</span>, <span class="synConstant">12</span>, <span class="synConstant">11</span>, <span class="synConstant">10</span>, <span class="synConstant">9</span>, <span class="synConstant">8</span>, <span class="synConstant">7</span>, <span class="synConstant">6</span>, <span class="synConstant">5</span>, <span class="synConstant">4</span>, <span class="synConstant">3</span>, <span class="synConstant">2</span>, <span class="synConstant">1</span>, <span class="synConstant">0</span>]
</pre><p> ま、これは常識(python廃人の皆さんには)。</p>
</div>
<div class="section">
<h3>n個取ってみる</h3>
<pre class="code lang-python" data-lang="python" data-unlink>>>> lst[::-<span class="synConstant">1</span>][:<span class="synConstant">10</span>]
[<span class="synConstant">19</span>, <span class="synConstant">18</span>, <span class="synConstant">17</span>, <span class="synConstant">16</span>, <span class="synConstant">15</span>, <span class="synConstant">14</span>, <span class="synConstant">13</span>, <span class="synConstant">12</span>, <span class="synConstant">11</span>, <span class="synConstant">10</span>]
</pre><p> こうするとリストオブジェクトの生成が二回繰り返されるので遅いはずです。</p><p> できればスライス一発で済ませたい。</p>
</div>
<div class="section">
<h3>やってみる</h3>
<pre class="code lang-python" data-lang="python" data-unlink>>>> lst[:<span class="synConstant">9</span>:-<span class="synConstant">1</span>]
[<span class="synConstant">19</span>, <span class="synConstant">18</span>, <span class="synConstant">17</span>, <span class="synConstant">16</span>, <span class="synConstant">15</span>, <span class="synConstant">14</span>, <span class="synConstant">13</span>, <span class="synConstant">12</span>, <span class="synConstant">11</span>, <span class="synConstant">10</span>]
</pre><p> なんとなくできたような気になりますが、</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> lst[:<span class="synConstant">3</span>:-<span class="synConstant">1</span>]
[<span class="synConstant">19</span>, <span class="synConstant">18</span>, <span class="synConstant">17</span>, <span class="synConstant">16</span>, <span class="synConstant">15</span>, <span class="synConstant">14</span>, <span class="synConstant">13</span>, <span class="synConstant">12</span>, <span class="synConstant">11</span>, <span class="synConstant">10</span>, <span class="synConstant">9</span>, <span class="synConstant">8</span>, <span class="synConstant">7</span>, <span class="synConstant">6</span>, <span class="synConstant">5</span>, <span class="synConstant">4</span>]
</pre><p> 逆向きなのでした。スライスも左から順に評価される(切り出してから反転)のでしょうか。</p>
</div>
<div class="section">
<h3>正しい(?)やりかた</h3>
<pre class="code lang-python" data-lang="python" data-unlink>>>> lst[:-<span class="synConstant">4</span>:-<span class="synConstant">1</span>]
[<span class="synConstant">19</span>, <span class="synConstant">18</span>, <span class="synConstant">17</span>]
</pre><p> 微妙な違和感が・・・</p>
</div>
<div class="section">
<h3>先頭n個取って反転</h3>
<p> これもやってみよう。考え方は同じです。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> lst[<span class="synConstant">2</span>::-<span class="synConstant">1</span>]
[<span class="synConstant">2</span>, <span class="synConstant">1</span>, <span class="synConstant">0</span>]
</pre>
</div>
<div class="section">
<h3>測ってみた</h3>
<pre class="code lang-python" data-lang="python" data-unlink>>>> <span class="synPreProc">import</span> timeit
>>> timeit.timeit(<span class="synStatement">lambda</span> : lst[::-<span class="synConstant">1</span>][:<span class="synConstant">3</span>])
<span class="synConstant">0.35431641300010597</span>
>>> timeit.timeit(<span class="synStatement">lambda</span> : lst[:-<span class="synConstant">4</span>:-<span class="synConstant">1</span>])
<span class="synConstant">0.20682314900022902</span>
</pre><p> 計測誤差ではないようです。かくして約43%の高速化が実現されました。</p>
</div>
<div class="section">
<h3>追記:書き上げて投稿してから思いついた別解</h3>
<pre class="code lang-python" data-lang="python" data-unlink>>>> lst[:-<span class="synConstant">4</span>:-<span class="synConstant">1</span>]
[<span class="synConstant">19</span>, <span class="synConstant">18</span>, <span class="synConstant">17</span>]
>>> lst[-<span class="synConstant">3</span>:][::-<span class="synConstant">1</span>]
[<span class="synConstant">19</span>, <span class="synConstant">18</span>, <span class="synConstant">17</span>]
</pre><p> 結果は同じ。これだと反転するリストが小さいので速いのでは? と思いました。</p><p> 効果を見やすくするために、長さ1000のリストで試してみます。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> timeit.timeit(<span class="synStatement">lambda</span> : lst[:-<span class="synConstant">4</span>:-<span class="synConstant">1</span>])
<span class="synConstant">0.21827364299997498</span>
>>> timeit.timeit(<span class="synStatement">lambda</span> : lst[::-<span class="synConstant">1</span>][:<span class="synConstant">3</span>])
<span class="synConstant">2.8037329549997594</span>
>>> timeit.timeit(<span class="synStatement">lambda</span> : lst[-<span class="synConstant">3</span>:][::-<span class="synConstant">1</span>])
<span class="synConstant">0.33725221500026237</span>
</pre><p> 早い順に、</p>
<ol>
<li>1つのスライスで反転と切り出しをやる</li>
<li>後ろ3つを取ってから反転</li>
<li>ぜんぶ反転させてから先頭3つを取る</li>
</ol><p> となりました。</p><p> 「ぜんぶ反転させてから~」が遅いのはまあ、予想通りですが、「後ろ3つを取ってから~」は1つのスライスでやる方法に勝てておらず、リストが小さいときの「ぜんぶ反転させてから~」と同程度。つまりオーバーヘッド分が同程度あるということです。また、built-inのスライスはそうとう気が効いてるみたいです(恐らく、lenを見てどの順番で切っていくか決めているのでは? 確認していませんが・・・)。</p><p> 結論としては、とにかくできるだけ1つのスライスで書いちゃおう、ということになると思います。ただし、実際には大した時間じゃないので、可読性重視で分けるのも不可ではない、という程度です。</p>
</div>
hayataka2049
【python】1つおきにリスト・文字列などから抽出する
hatenablog://entry/17391345971642919469
2018-05-09T16:34:09+09:00
2018-11-28T01:33:44+09:00 スライスの基本的な話なんだけど、意外と知らない人が多いと思うので。 スライスで1つおきに取り出すには、こうする。 >>> "hogehoge~"[::2] 'hghg~' スライスで指定できるのはstart, stop, stepであり、上のように指定するとstart, stopはNoneでstepが2になる。 参考:【python】sliceのちょっと深イイ(かもしれない)話 - 静かなる名辞 ではstepを1にすると? これは元の文字列が返る。 >>> "hogehoge~"[::1] 'hogehoge~' たまにこれをreverseに悪用(?)する人がいる。[::-1]と指定するとそう…
<p> スライスの基本的な話なんだけど、意外と知らない人が多いと思うので。</p><p> スライスで1つおきに取り出すには、こうする。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> <span class="synConstant">"hogehoge~"</span>[::<span class="synConstant">2</span>]
<span class="synConstant">'hghg~'</span>
</pre><p> スライスで指定できるのはstart, stop, stepであり、上のように指定するとstart, stopはNoneでstepが2になる。</p><p> 参考:<a href="https://www.haya-programming.com/entry/2018/05/09/163234">【python】sliceのちょっと深イイ(かもしれない)話 - 静かなる名辞</a></p><br />
<p> ではstepを1にすると? これは元の文字列が返る。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> <span class="synConstant">"hogehoge~"</span>[::<span class="synConstant">1</span>]
<span class="synConstant">'hogehoge~'</span>
</pre><p> たまにこれをreverseに悪用(?)する人がいる。[::-1]と指定するとそういう挙動になるので。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> <span class="synConstant">"hogehoge~"</span>[::-<span class="synConstant">1</span>]
<span class="synConstant">'~egohegoh'</span>
</pre><p> でもこれは可読性が悪い、というか完全に初見殺しなので、やめた方が良いと思う。reversedというものがあるので、そっちを使おう。</p><p> ・・・と思って書いてみたけど、腹立たしいことに空気を読まない子(iteratorで返してくれるアレ)なので、こんな真似をする必要がある。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> <span class="synConstant">""</span>.join(<span class="synIdentifier">reversed</span>(<span class="synConstant">"hogehoge~"</span>))
<span class="synConstant">'~egohegoh'</span>
</pre><p> どっちがマシかなぁ・・・。</p><p> 話をもとに戻して、[::-1]でこうなるということは、これもできる。1つおきに取り出して逆転させる。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> <span class="synConstant">"hogehoge~"</span>[::-<span class="synConstant">2</span>]
<span class="synConstant">'~ghgh'</span>
</pre><p> 便利、なのだろうか(どんなときに?)。</p>
hayataka2049
【python】sliceのちょっと深イイ(かもしれない)話
hatenablog://entry/17391345971642857652
2018-05-09T16:32:34+09:00
2019-01-25T00:44:48+09:00 リスト(じゃなくてもだけど)に次のようにアクセスするとき、内部的には__getitem__が呼ばれていることは、歴戦のpythonistaの皆さんには常識でしょう。 >>> lst = [1,2,3,4,5] >>> lst[0] 1 この様子を自作クラスで観察してみましょう。 >>> class Hoge: ... def __getitem__(self, k): ... print("__getitem__!") ... return k ... >>> h = Hoge() >>> h[0] __getitem__! 0 >>> h["hogehoge~"] __getitem__! …
<p> リスト(じゃなくてもだけど)に次のようにアクセスするとき、内部的には__getitem__が呼ばれていることは、歴戦のpythonistaの皆さんには常識でしょう。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> lst = [<span class="synConstant">1</span>,<span class="synConstant">2</span>,<span class="synConstant">3</span>,<span class="synConstant">4</span>,<span class="synConstant">5</span>]
>>> lst[<span class="synConstant">0</span>]
<span class="synConstant">1</span>
</pre><p> この様子を自作クラスで観察してみましょう。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> <span class="synStatement">class</span> <span class="synIdentifier">Hoge</span>:
... <span class="synStatement">def</span> <span class="synIdentifier">__getitem__</span>(self, k):
... <span class="synIdentifier">print</span>(<span class="synConstant">"__getitem__!"</span>)
... <span class="synStatement">return</span> k
...
>>> h = Hoge()
>>> h[<span class="synConstant">0</span>]
__getitem__!
<span class="synConstant">0</span>
>>> h[<span class="synConstant">"hogehoge~"</span>]
__getitem__!
<span class="synConstant">'hogehoge~'</span>
</pre><p> Hogeクラスは__getitem__が呼ばれると、「__getitem__!」とprintしてから__getitem__の引数をそのまま返します。上の結果から、実際に__getitem__が呼ばれていることがわかります。</p><p> []は__getitem__の糖衣構文と言っても、まあ良いでしょう<a href="#f-1310f83c" name="fn-1310f83c" title="細かいことを言うと、lst[0] = "hoge"など代入する場合、del dct[0]などdel文を呼ぶ場合などは__getitem__とは異なったメソッドが呼ばれます。あくまでも単純に値を参照する場合の話です">*1</a>。</p><p> これで「ほーん、そうか」と納得しかけてしまいますが、「ちょっと待て、じゃあスライスはどうなるんだ・・・?」というのが疑問として浮かんできますね。</p>
<pre class="code lang-python" data-lang="python" data-unlink>lst[<span class="synConstant">0</span>:<span class="synConstant">5</span>]
</pre><p> 一体何が渡るというんでしょう。「0:5」なんてオブジェクトはありませんから(そのまま書いても構文エラー)、スライスの場合は__getitem__とは違う仕組みで処理されるのでしょうか?</p><p> そうはなっていません。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> h[<span class="synConstant">0</span>:<span class="synConstant">5</span>]
__getitem__!
<span class="synIdentifier">slice</span>(<span class="synConstant">0</span>, <span class="synConstant">5</span>, <span class="synIdentifier">None</span>)
>>> <span class="synIdentifier">type</span>(h[<span class="synConstant">0</span>:<span class="synConstant">5</span>])
__getitem__!
<<span class="synStatement">class</span> <span class="synConstant">'slice'</span>>
</pre><p> おおお、sliceオブジェクトなんていうのが渡っている・・・。</p><p> このsliceオブジェクトは、普通にpythonを書いている限り目にする機会はほとんどないと思います。</p><p> それでも、公式ドキュメントにはしっかり載っています。</p>
<blockquote>
<p>class slice(start, stop[, step])<br />
range(start, stop, step) で指定されるインデクスの集合を表す、スライス (slice) オブジェクトを返します。引数 start および step はデフォルトでは None です。スライスオブジェクトは読み出し専用の属性 start、stop および step を持ち、これらは単に引数で使われた 値 (またはデフォルト値) を返します。これらの値には、その他のはっきりと した機能はありません。しかしながら、これらの値は Numerical Python および、その他のサードパーティによる拡張で利用されています。スライスオブジェクトは拡張されたインデクス指定構文が使われる際にも生成されます。例えば a[start:stop:step] や a[start:stop, i] です。この関数の代替となるイテレータを返す関数、itertools.islice() も参照してください。</p>
</blockquote>
<p><a href="https://docs.python.jp/3/library/functions.html#slice">2. 組み込み関数 — Python 3.6.5 ドキュメント</a></p><p> なるほど~、こいつが渡ることで、あとは受けるオブジェクトの__getitem__が然るべき処理をしてくれればスライスが実現する仕組みになっているんですね。よくできてる・・・。</p><p>「いや、ちょっと待て。numpyで使うアレはどうなってるんだ」</p><p> こういう奴のことですね。</p>
<pre class="code lang-python" data-lang="python" data-unlink>a[:,<span class="synConstant">0</span>]
</pre><p> 上記sliceオブジェクトはstart, stop, stepしか持たないので、こういうスライスは手に負えなさそうです。</p><p> なんてこった、今度こそ__getitem__では処理しきれなくて、なにか違う仕組みで処理されているのか・・・</p><p> いません。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> h[:,<span class="synConstant">0</span>]
__getitem__!
(<span class="synIdentifier">slice</span>(<span class="synIdentifier">None</span>, <span class="synIdentifier">None</span>, <span class="synIdentifier">None</span>), <span class="synConstant">0</span>)
>>> <span class="synIdentifier">type</span>(h[:,<span class="synConstant">0</span>])
__getitem__!
<<span class="synStatement">class</span> <span class="synConstant">'tuple'</span>>
</pre><p> __getitem__なのは間違いないみたいですが、 なぜかtupleが返ります。0要素目は空のslice, 二番目は入力がそのまま・・・? 一体どうなっているんだ?</p><p> 実はこうなっています。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> h[::,<span class="synConstant">0</span>:<span class="synConstant">1</span>:<span class="synConstant">2</span>,<span class="synConstant">0</span>:-<span class="synConstant">1</span>:-<span class="synConstant">2</span>,<span class="synConstant">0</span>,<span class="synConstant">1</span>,<span class="synConstant">2</span>,<span class="synConstant">3</span>]
__getitem__!
(<span class="synIdentifier">slice</span>(<span class="synIdentifier">None</span>, <span class="synIdentifier">None</span>, <span class="synIdentifier">None</span>), <span class="synIdentifier">slice</span>(<span class="synConstant">0</span>, <span class="synConstant">1</span>, <span class="synConstant">2</span>), <span class="synIdentifier">slice</span>(<span class="synConstant">0</span>, -<span class="synConstant">1</span>, -<span class="synConstant">2</span>), <span class="synConstant">0</span>, <span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>)
</pre><p> なるほど、カンマで区切られたものごとにtupleの要素になっているのか!</p><p> ……と、書きましたが一応ちゃんと説明しておくと、そもそも「pythonのtuple」はカンマで区切られた要素によって成立する構文です。関数の引数リストなど、他の構文と被る場合はそちらが優先的に扱われますが。</p>
<pre class="code lang-python" data-lang="python" data-unlink>>>> <span class="synConstant">1</span>,<span class="synConstant">2</span>,<span class="synConstant">3</span>
(<span class="synConstant">1</span>, <span class="synConstant">2</span>, <span class="synConstant">3</span>)
</pre><p> これについてはドキュメントに説明があります。</p><p><a href="https://docs.python.jp/3/library/stdtypes.html#typesseq-tuple">4. 組み込み型 — Python 3.6.5 ドキュメント</a></p><br />
<p> 要するに、単に添字の中にtupleを書いているだけ、とみなしてもまあ良いでしょう<a href="#f-2262e9e0" name="fn-2262e9e0" title="sliceオブジェクトに変換されるので単純な処理ではないが">*2</a>。</p><p> わかっちゃえばなんてことはないですね。とても自然な仕様です。あとは受け取った側で然るべき処理をするだけ。</p><p> このように、スライスの裏ではsliceオブジェクトが暗躍しています。なんとなくこういうことを知っていると深イイと思えますね。また、なんとなくスライスの構文がよくわからなかった人も、この仕様が理解できれば自然に複雑なスライスを書けるようになることでしょう(その結果、難読コードが量産されてしまうかもしれないが・・・)。</p>
<div class="section">
<h3>余談</h3>
<pre class="code lang-python" data-lang="python" data-unlink>>>> lst = [<span class="synConstant">0</span>,<span class="synConstant">1</span>,<span class="synConstant">2</span>]
>>> lst[:,<span class="synConstant">0</span>]
Traceback (most recent call last):
File <span class="synConstant">"<stdin>"</span>, line <span class="synConstant">1</span>, <span class="synStatement">in</span> <module>
<span class="synType">TypeError</span>: <span class="synIdentifier">list</span> indices must be integers <span class="synStatement">or</span> slices, <span class="synStatement">not</span> <span class="synIdentifier">tuple</span>
</pre><p> 上のことがわかっていれば理解できますが、そうでないと絶対理解不能なエラーメッセージです(tupleなんかないじゃんかって)。</p>
</div>
<div class="section">
<h3>追記:2018/06/21</h3>
<p> この話はドキュメントのどこに載っているんだろうとずっと思っていましたが、見つけました。</p><p><a href="https://docs.python.jp/3/reference/expressions.html#slicings">6. 式 (expression) — Python 3.6.5 ドキュメント</a><br />
</p>
<blockquote>
<p>スライス表記に対する意味付けは、以下のようになります。プライマリの値評価結果は、以下に述べるようにしてスライスリストから生成されたキーによって (通常の添字表記と同じ __getitem__() メソッドを使って) インデクス指定できなければなりません。スライスリストに一つ以上のカンマが含まれている場合、キーは各スライス要素を値変換したものからなるタプルになります; それ以外の場合、単一のスライス要素自体を値変換したものがキーになります。一個の式であるスライス要素は、その式に変換されます。適切なスライスは、スライスオブジェクト (標準型の階層 参照) に変換され、その start, stop および step 属性は、それぞれ指定した下境界、上境界、およびとび幅 (stride) になります。式がない場所は None で置き換えられます。</p>
</blockquote>
<p> 説明の内容はこの記事と基本的に同じですが、グダグダなこの記事と比べると、さすがによくまとまっています。公式は偉い。</p>
</div><div class="footnote">
<p class="footnote"><a href="#fn-1310f83c" name="f-1310f83c" class="footnote-number">*1</a><span class="footnote-delimiter">:</span><span class="footnote-text">細かいことを言うと、lst[0] = "hoge"など代入する場合、del dct[0]などdel文を呼ぶ場合などは__getitem__とは異なったメソッドが呼ばれます。あくまでも単純に値を参照する場合の話です</span></p>
<p class="footnote"><a href="#fn-2262e9e0" name="f-2262e9e0" class="footnote-number">*2</a><span class="footnote-delimiter">:</span><span class="footnote-text">sliceオブジェクトに変換されるので単純な処理ではないが</span></p>
</div>
hayataka2049