note.nkmk.me

Pythonのスライスによるリストや文字列の部分選択・代入

Date: 2018-05-31 / tags: Python, 文字列操作, リスト
このエントリーをはてなブックマークに追加

Pythonではコロンを使って表すスライス(例: [2:5:2])によって、リストや文字列、タプルなどのシーケンスオブジェクトの一部分を選択して取得したり別の値を代入したりできる。

以下の内容について説明する。

  • スライスの基本的な使い方
    • 開始位置startと終了位置stopを指定
    • 増分stepを指定
  • マイナスの値で後ろから指定
    • 開始位置startと終了位置stopをマイナスで指定
    • 増分stepをマイナスで指定
  • slice()関数によるスライスオブジェクトの生成
  • スライスによる値の代入
  • 二次元配列(リストのリスト)の場合
  • 変数に代入した場合の浅いコピーと深いコピー
  • 文字列やタプルの場合
スポンサーリンク

スライスの基本的な使い方

開始位置startと終了位置stopを指定

スライスでは選択範囲の開始位置startと終了位置stop[start:stop]のように書く。

start <= x < stopの範囲が選択される。start番目の値は含まれるがstop番目の値は含まれないので注意。

l = [0, 10, 20, 30, 40, 50, 60]
print(l)
# [0, 10, 20, 30, 40, 50, 60]

print(l[2:5])
# [20, 30, 40]

開始位置startを省略した場合は最初から、終了位置stopを省略した場合は最後までが選択される。両方とも省略した場合はすべての値が選択される。

print(l[:3])
# [0, 10, 20]

print(l[3:])
# [30, 40, 50, 60]

print(l[:])
# [0, 10, 20, 30, 40, 50, 60]

範囲外を指定した場合

要素数を超える位置を指定してもエラーにはならず無視される。負の値を指定した場合については後述。

print(l[2:10])
# [20, 30, 40, 50, 60]

空のリストが返る場合

どの要素も選択されないstartstopを指定した場合もエラーにはならない。空のリストが返る。

print(l[5:2])
# []

print(l[2:2])
# []

print(l[10:20])
# []

増分stepを指定

開始位置startと終了位置stopに加えて、増分stepも指定可能。[start:stop:step]のように書く。

例えばstep2とした場合、奇数個目の要素のみ、または偶数個目の要素のみを取得できる。

print(l[::2])
# [0, 20, 40, 60]

print(l[1::2])
# [10, 30, 50]

そのほかの例。

print(l[::3])
# [0, 30, 60]

print(l[2:5:2])
# [20, 40]

これまでの例のように、省略した場合はstep=1となる。

マイナスの値で後ろから指定

開始位置startと終了位置stopをマイナスで指定

開始位置startと終了位置stopを負の値で指定すると、末尾からの位置となる。

-1が最後の要素を示す。

stop=-1とするとstop番目の値は含まれないため後ろから2番めの値までが選択される。

print(l[3:-1])
# [30, 40, 50]

そのほかの例。

print(l[-2:])
# [50, 60]

print(l[-5:-2])
# [20, 30, 40]

増分stepをマイナスで指定

増分stepを負の値で指定すると、後ろから逆順で要素を取得する。

startから逆向きに値を取得していくため、startのほうがstopより後ろの位置を示していないと空になってしまうので注意。

print(l[5:2:-1])
# [50, 40, 30]

print(l[2:5:-1])
# []

そのほかの例。

print(l[-2:-5:-1])
# [50, 40, 30]

print(l[-2:2:-1])
# [50, 40, 30]

print(l[5:2:-2])
# [50, 30]

startstopを省略しstep-1とすることで、もとのオブジェクトの順番を逆転したオブジェクトが取得できる。

print(l[::-1])
# [60, 50, 40, 30, 20, 10, 0]

slice関数によるスライスオブジェクトの生成

組み込み関数slice()を使うとスライスオブジェクトを生成できる。同じ位置の要素を繰り返し取得したい場合はスライスオブジェクトを一度生成しておくだけでよいので便利。

slice(start, stop, step)start:stop:stepに等しい。

sl = slice(2, 5, 2)
print(sl)
# slice(2, 5, 2)

print(type(sl))
# <class 'slice'>

print(l[sl])
# [20, 40]

引数を2つ指定した場合はstep=Noneとなりstart:stopと等価。

sl = slice(2, 5)
print(sl)
# slice(2, 5, None)

print(l[sl])
# [20, 30, 40]

引数を1つだけ指定した場合はstart=None, step=Noneとなり:stopと等価。

sl = slice(2)
print(sl)
# slice(None, 2, None)

print(l[sl])
# [0, 10]

引数をすべて省略するとエラーTypeErrorとなる。

# sl = slice()
# TypeError: slice expected at least 1 arguments, got 0

全体を表すスライス:slice()関数で生成したい場合は明示的にNoneを指定する。

sl = slice(None)
print(sl)
# slice(None, None, None)

print(l[sl])
# [0, 10, 20, 30, 40, 50, 60]

スライスによる値の代入

スライスで選択した範囲に値を代入することが可能。

スライスで選択した範囲の要素数と代入する要素数(右辺のオブジェクトの要素数)は一致していなくてもOK。

print(l)
# [0, 10, 20, 30, 40, 50, 60]

l[2:5] = [200, 300, 400]
print(l)
# [0, 10, 200, 300, 400, 50, 60]

l[2:5] = [-2, -3]
print(l)
# [0, 10, -2, -3, 50, 60]

l[2:4] = [2000, 3000, 4000, 5000]
print(l)
# [0, 10, 2000, 3000, 4000, 5000, 50, 60]

l[2:6] = [20000]
print(l)
# [0, 10, 20000, 50, 60]

右辺にはリストなどのイテラブルオブジェクトのみ指定できる。スカラー値を指定するとエラーTypeErrorとなる。

# l[2:3] = 200
# TypeError: can only assign an iterable

右辺が空だとスライスで選択した範囲の要素が削除される。

l[1:4] = []
print(l)
# [0, 60]

空の範囲に対しても代入できる。指定した位置に右辺の値が挿入される。

l[20:60] = [-1, -2, -3]
print(l)
# [0, 60, -1, -2, -3]

l[2:2] = [-100]
print(l)
# [0, 60, -100, -1, -2, -3]

増分stepを指定した飛び飛びの範囲に対しては要素数が等しくないとエラーValueErrorとなる。

print(l[:5:2])
# [0, -100, -2]

l[:5:2] = [100, 200, 300]
print(l)
# [100, 60, 200, -1, 300, -3]

# l[:5:2] = [100, 200]
# ValueError: attempt to assign sequence of size 2 to extended slice of size 3

なお、リストの途中や末尾に要素を追加する場合はinsert(), append()などのメソッドが用意されているので、そちらを使ったほうがコードの可読性は高い。

二次元配列(リストのリスト)の場合

リストのリストで構成された以下のような二次元配列にスライスを適用したい場合。

l_2d = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
print(l_2d)
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]

あくまでもリストを要素としたリストなので、スライスによって選択されるのは要素であるリスト。

print(l_2d[1:3])
# [[3, 4, 5], [6, 7, 8]]

選択されたリストにさらにスライスを適用するにはリスト内包表記を使う。

print([l[:2] for l in l_2d[1:3]])
# [[3, 4], [6, 7]]

列を取得したい場合は転置する方法もある。

l_2d_t = [list(x) for x in zip(*l_2d)]
print(l_2d_t)
# [[0, 3, 6, 9], [1, 4, 7, 10], [2, 5, 8, 11]]

print(l_2d_t[1])
# [1, 4, 7, 10]

なお、配列のサイズや実現したいことにもよるが、NumPyをインストールできる環境であれば多次元配列の操作はNumPyを使うほうが楽。

NumPyではarr[1:4, 2:5]のように各次元のスライスをカンマで区切って指定できる。

変数に代入した場合の浅いコピーと深いコピー

スライスで取得した結果は浅いコピーとなる。

例えば、数値のリストの場合などは、スライスで取得した結果を変数に代入しその変数の要素を更新しても元のオブジェクトは変更されない。

l = [0, 10, 20, 30, 40, 50, 60]
print(l)
# [0, 10, 20, 30, 40, 50, 60]

l_slice = l[2:5]
print(l_slice)
# [20, 30, 40]

l_slice[1] = 300
print(l_slice)
# [20, 300, 40]

print(l)
# [0, 10, 20, 30, 40, 50, 60]

要素としてリストなどを含んでいる複合オブジェクトの場合は、リストの要素を更新すると元のオブジェクトも変更される。

l_2d = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
print(l_2d)
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]

l_2d_slice = l_2d[1:3]
print(l_2d_slice)
# [[3, 4, 5], [6, 7, 8]]

l_2d_slice[0][1] = 400
print(l_2d_slice)
# [[3, 400, 5], [6, 7, 8]]

print(l_2d)
# [[0, 1, 2], [3, 400, 5], [6, 7, 8], [9, 10, 11]]

これを防ぐためには標準ライブラリのcopyモジュールをインポートし、deepcopy()を使う。

import copy

l_2d = [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]
print(l_2d)
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]

l_2d_slice_deepcopy = copy.deepcopy(l_2d[1:3])
print(l_2d_slice_deepcopy)
# [[3, 4, 5], [6, 7, 8]]

l_2d_slice_deepcopy[0][1] = 400
print(l_2d_slice_deepcopy)
# [[3, 400, 5], [6, 7, 8]]

print(l_2d)
# [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11]]

文字列やタプルの場合

これまではリスト(list型)の例を示してきたが、文字列やタプルなどほかのシーケンスオブジェクトでも同様にスライスが使える。

ただし、文字列、タプルはイミュータブル(更新不可)なので代入はできない。

s = 'abcdefg'
print(s)
# abcdefg

print(s[2:5])
# cde

print(s[::-1])
# gfedcba

# s[2:5] = 'CDE'
# TypeError: 'str' object does not support item assignment

t = (0, 10, 20, 30, 40, 50, 60)
print(t)
# (0, 10, 20, 30, 40, 50, 60)

print(t[2:5])
# (20, 30, 40)

# t[2:5] = (200, 300, 400)
# TypeError: 'tuple' object does not support item assignment

文字列の分割や置換については以下の記事を参照。

スポンサーリンク
シェア
このエントリーをはてなブックマークに追加

関連カテゴリー

関連記事