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

Modified: | Tags: Python, 文字列, リスト

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

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

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

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

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

l = [0, 10, 20, 30, 40, 50, 60]

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

スライスで指定する位置(インデックス)は要素と要素の間を指す、と考えると理解しやすい。

スライスの使い方をおぼえる良い方法は、インデックスが文字と文字の あいだ (between) を指しており、最初の文字の左端が 0 になっていると考えることです。 3. 形式ばらない Python の紹介 - 文字列型 (string) — Python 3.11.3 ドキュメント

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
 0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1

上は文字列の例だが、リストやタプルなどでも要素と要素の間を指すと考えればよい。負の値については後述。

開始位置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を指定

startstopに加えて、増分stepも指定可能。[start:stop:step]のように書く。

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

l = [0, 10, 20, 30, 40, 50, 60]

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を負の値で指定すると、末尾からの位置となる。

スライスで指定する位置(インデックス)の考え方を再掲する。要素間を指すイメージ。

 +---+---+---+---+---+---+
 | P | y | t | h | o | n |
 +---+---+---+---+---+---+
 0   1   2   3   4   5   6
-6  -5  -4  -3  -2  -1
l = [0, 10, 20, 30, 40, 50, 60]

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

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

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

増分stepをマイナスで指定

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

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

l = [0, 10, 20, 30, 40, 50, 60]

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]

stepが負の値の場合、startを省略すると末尾から、stopを省略すると先頭までが選択される。両方とも省略するとすべての要素が選択される。

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

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

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

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

逆順に並べ替えるにはほかにreverse(), reversed()を使う方法がある。詳細は以下の記事を参照。

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

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

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

l = [0, 10, 20, 30, 40, 50, 60]

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となる。全体を表すスライス:slice()関数で生成したい場合は明示的にNoneを指定する。

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

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

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

スライスによる値の代入

スライスで選択した範囲に値を代入できる。

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

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となる。上の例のように、要素数が1つのリストなどを指定すればよい。

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

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

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

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

範囲外や空の範囲を指定して代入することもできる。指定した位置に右辺の値が追加・挿入される。

l = [0, 10, 20, 30, 40, 50, 60]

l[100:200] = [-1, -2, -3]
print(l)
# [0, 10, 20, 30, 40, 50, 60, -1, -2, -3]

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

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

l = [0, 10, 20, 30, 40, 50, 60]

l[1::2] = [100, 200, 300]
print(l)
# [0, 100, 20, 200, 40, 300, 60]

# l[1::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[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では[1:4, 2:5]のように各次元のスライスをカンマで区切って指定できる。

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

スライスで取得した結果は浅いコピーとなる。浅いコピーと深いコピーについての詳細は以下の記事を参照。

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

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]]

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]]

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[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[2:5])
# (20, 30, 40)

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

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

関連カテゴリー

関連記事