スライス操作あれこれ

この記事は Python Tips Advent Calendar 2012 3日目の記事です。


今日は Python のスライス操作についてです。

基本的なスライス操作

>>> xs = range(10)
>>> xs[2:5]    # 2以上5未満までのインデックス
[2, 3, 4]
>>> xs[:2]     # 前を省略した場合は0と同じ
[0, 1]
>>> xs[7:]     # 後ろを省略した場合はlen(xs)、つまり10と同じ
[7, 8, 9]
>>> xs[-3:]    # -3 は後ろから 3 番目(つまり len(xs)-3 と同じ)
[7, 8, 9]
>>> xs[-3:-1]  # 当然 2 個目にもマイナスを入れられる
[7, 8]
>>> xs[::4]    # 3 個目にはスキップ数が入れられる
[0, 4, 8]
>>> xs[2:-2:3] # 組み合わせ
[2, 5]
>>> xs[::-4]   # マイナス値の場合は後ろからスキップする
[9, 5, 1]
>>> xs[8:1:-2] # 組み合わせ
[8, 6, 4, 2]

スライスでこんな風にリストを取得できます。

スライス操作で書き換える

スライスで取得したリストに代入をすると、その要素が代入したリストに置き換わります。

>>> xs = range(10)
>>> xs[:5] = [42] # [0, 1, 2, 3, 4] が [42] に置き換わる
>>> print xs
[42, 5, 6, 7, 8, 9]
>>> xs[:1] = range(5) # [42] が [0, 1, 2, 3, 4] に置き換わる
>>> print xs
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

要素の削除についても同様で、スライスで取得した要素が全て削除されます。

>>> del xs[::2] # [0, 2, 4, 6, 8] が削除される
>>> print xs
[1, 3, 5, 7, 9]

スキップ付きのスライスも同様です。スライスで取得した要素が代入したリストに置き換わります。

>>> xs[::2] = xs[::-2]   # [1, 5, 9] が [9, 5, 1] に置き換わる
>>> print xs
[9, 3, 5, 7, 1]

ただしスキップ付きは注意点があって、これは要素数が同じでない場合は例外を投げます。

>>> xs[::2] = xs    #doctest: +ELLIPSIS
Traceback (most recent call last):
   ...
ValueError: attempt to assign sequence of size 5 to extended slice of size 3

リストの反転

リストの反転は、スライスを使って簡単に書けます。

>>> xs = [1,2,3]
>>> list(reversed(xs))
[3, 2, 1]
>>> xs[::-1] # 短く書けるよ!
[3, 2, 1]

リストのコピー

リストの(浅い)コピーもスライスで書けます。

>>> xs = [1,2,3]
>>> list(xs)
[1, 2, 3]
>>> xs[:] # 短く書けるよ!
[1, 2, 3]

スライスの正体

スライスというのは単なる略記法で、実際はこれは以下の様な slice クラスのオブジェクトになっています。

>>> xs = [1,2,3,4]
>>> xs[1::2]
[2, 4]
>>> xs[slice(1, None, 2)]
[2, 4]

slice クラスは start, stop, step の3つのプロパティを持っているので、以下のように自分でスライス処理を書いたりもできます。

>>> class X(object):
...     def get(self, x):
...         if isinstance(x, slice):
...             return 'sliced object'
...         else:
...             return 'normal object'
>>> x = X()
>>> x.get(slice(1, 2, 3))
'sliced object'
>>> x.get(3)
'normal object'

ちなみに x.get(1:2:3) という書き方はできません。スライスの略記は [] の中だけで使えます。

Ellipsis

[] の中では、スライス以外にも、もう一つ、Ellipsis というクラスの略記法が使えます。

>>> class X(object):
...     def __getitem__(self, x):
...         if x is Ellipsis:
...             return 'ellipsis object'
...         else:
...             return 'normal object'
>>> x = X()
>>> x[...] # Ellipsis を渡す
'ellipsis object'
>>> x[Ellipsis] # x[...] はこれと同じ意味
'ellipsis object'
>>> x[42]
'normal object'

これは標準のリストでは使われていないですが、例えば行列とかを計算してくれる numpy パッケージでは、こんな風に使えます。

>>> import numpy
>>> x = numpy.array([[[1],[2],[3]], [[4],[5],[6]]])
>>> x[..., 0]
array([[1, 2, 3],
       [4, 5, 6]])
>>> x[:, :, 0] # x[..., 0] はこれと同じ意味
array([[1, 2, 3],
       [4, 5, 6]])


.