文字列の一部だけを取り出したいとき、Pythonではスライスを使うと簡潔かつ安全に実現できます。
本記事では、start:end:step
という記法の読み方から、よく使うパターン、注意点、slice
オブジェクトの再利用まで、初心者の方がつまずきやすいポイントを順序立てて解説します。
Pythonの文字列スライスの基本
文字列とインデックスの基礎
Pythonの文字列はシーケンス型で、左から0始まりのインデックスでアクセスできます。
右から数える負のインデックスも使えます。
以下の例では、文字列 abcdefg
に対するインデックスを対応づけます。
文字とインデックスの対応を一覧にすると次のようになります。
文字 | 正のインデックス | 負のインデックス |
---|---|---|
a | 0 | -7 |
b | 1 | -6 |
c | 2 | -5 |
d | 3 | -4 |
e | 4 | -3 |
f | 5 | -2 |
g | 6 | -1 |
インデックスで1文字を取り出す簡単な例です。
# 基本のインデックス参照
s = "abcdefg"
print(s[0]) # 先頭の文字
print(s[1]) # 2番目の文字
print(s[-1]) # 末尾の文字
a
b
g
slice記法 start:end:step の読み方
スライスは s[start:end:step]
という形で書きます。
意味は次のとおりです。
- start(開始位置): 取り出しを始めるインデックス
- end(終了位置): 取り出しをやめる位置(この位置の直前まで)
- step(ステップ): 何文字おきに取り出すか(デフォルトは1)
# slice記法の基本
s = "abcdefg"
print(s[1:5:2]) # インデックス1から4までを2文字おきに
print(s[0:7:3]) # 0から6までを3文字おきに
bd
adg
終了位置は含まない半開区間
end
は含まれません。
これを半開区間と呼びます。
この性質により、隣接区間を連結すると元の文字列に重複や欠落が生じません。
s = "abcdefg"
print(s[0:3]) # 0,1,2番目まで -> 'abc'
print(s[3:7]) # 3,4,5,6番目 -> 'defg'
print(s[0:3] + s[3:7] == s) # 連結すると元に戻る
abc
defg
True
省略できる開始位置・終了位置・ステップ
start
と end
は省略すると先頭や末尾が既定され、step
は省略すると1になります。
s = "abcdefg"
print(s[:3]) # 先頭から3文字 -> 'abc'
print(s[3:]) # 3番目から最後まで -> 'defg'
print(s[:]) # 全体のコピー -> 'abcdefg'
print(s[::2]) # 1文字おき -> 'aceg'
abc
defg
abcdefg
aceg
負のインデックスで末尾から切り出す
負のインデックスは末尾から数える指定に便利です。
末尾N文字の取得や末尾を落とす処理が簡単に書けます。
s = "abcdefg"
print(s[-3:]) # 末尾3文字 -> 'efg'
print(s[:-1]) # 末尾を1文字落とす -> 'abcdef'
print(s[-5:-2]) # 末尾から5番目(=2)から末尾から2番目(=5)の直前まで -> 'cde'
efg
abcdef
cde
逆順スライスで文字列を反転
step
に負の値を指定すると左向きにたどるので、反転や逆方向の抽出ができます。
s = "abcdefg"
print(s[::-1]) # 全体を反転 -> 'gfedcba'
print(s[5:1:-1]) # インデックス5から2まで逆順 -> 'fedc'
gfedcba
fedc
スライスは新しい文字列を返す
スライスは元の文字列を変更せず、新しい文字列(同じ内容)を返します。
元の変数を再代入してもスライスで得た文字列には影響しません。
s = "abcdefg"
t = s[:] # 全体コピー
s = s.upper() # sを別の文字列に差し替え(元の't'はそのまま)
print("t:", t)
print("s:", s)
t: abcdefg
s: ABCDEFG
注: オブジェクトの同一性(is
)は実装の最適化の影響を受ける場合があります。
スライスの「新しさ」は、元の文字列を変更しないという意味で理解すると安全です。
文字列スライスの実用パターン
先頭N文字を取得
先頭からN文字を取りたい場合は s[:N]
と書きます。
Nが長さを超えていても安全に処理されます。
text = "Hello, World!"
N = 5
head = text[:N]
print(head)
Hello
末尾N文字を取得
末尾からN文字は s[-N:]
です。
ログの拡張子や末尾の識別子の抽出に便利です。
filename = "report_2025_08_31.csv"
ext = filename[-3:] # 拡張子(単純な例)
print(ext)
csv
任意位置から固定長を切り出す
開始位置と長さが決まっているときは、s[pos:pos+length]
と書くのが定番です。
text = "ID:ABC12345;USER:alice"
pos = 3 # 'A' の位置
length = 4
part = text[pos:pos+length] # 'ABC1'
print(part)
ABC1
範囲外でも安全に切り出す
pos+length
が長さを超えてもエラーにならず、可能な範囲で切り出されます。
text = "short"
pos = 2
length = 10
print(text[pos:pos+length]) # 'ort'
ort
先頭や末尾を削って中間を取得
前後の余白や共通プレフィックス/サフィックスを落として中間だけを取りたいときは、長さから差し引く形が分かりやすいです。
s = "[payload:12345]"
prefix_len = 1 # '['
suffix_len = 1 # ']'
middle = s[prefix_len:len(s)-suffix_len]
print(middle)
payload:12345
一文字おきに抽出するステップ指定
ステップを2にすると1文字おき、3にすると2文字飛ばしで取り出せます。
s = "0123456789"
print(s[::2]) # 偶数インデックス -> '02468'
print(s[1::2]) # 奇数インデックス -> '13579'
02468
13579
全体のコピーを作る s[:]
[:]
は全体コピーです。
元の変数とは独立して保持しておきたいときに使えます。
original = "immutable string"
copy = original[:]
print(copy == original) # 内容は同じ
True
逆順の一部を取り出す
末尾からN文字を逆順で取り出す場合は、負のステップを使うと1本で書けます。
s = "ABCDEFGHIJ"
# 末尾5文字を逆順で -> 'JIHGF'
print(s[:-6:-1])
# インデックス8から3へ向けて2文字おきに逆順 -> 'IGE'
print(s[8:2:-2])
JIHGF
IGE
スライスの注意点とベストプラクティス
範囲外でもエラーにならない挙動
開始や終了が範囲外でも例外は発生せず、可能な範囲だけが返ります。
意図せず空文字になるケースに気づきにくい点に注意します。
s = "abc"
print(s[:100]) # 範囲外の終了 -> 'abc'
print(s[100:]) # 範囲外の開始 -> ''
abc
開始が終了を超えると空文字になる
step
が正のとき、start >= end
だと空文字になります。
負のステップでは比較の向きが逆になります。
s = "abcdef"
print(s[5:2]) # 正方向で5から2へは進めない -> ''
print(s[2:5:-1]) # 負方向で2から5へは進めない -> ''
print(s[5:2:-1]) # これはOK -> 'fed'
fed
ステップ0はValueErrorになる
step
に0は指定できません。
実行時に ValueError
が発生します。
動的にステップを計算する場合は事前チェックが安全です。
s = "abc"
step = 0
try:
print(s[::step]) # ここで例外
except ValueError as e:
print("エラー:", e)
エラー: slice step cannot be zero
文字列はイミュータブルで代入不可
文字列はイミュータブル(不変)です。
インデックス代入やスライス代入はできません。
新しい文字列を作って置き換える発想が必要です。
s = "abc"
try:
s[0] = "X" # 1文字の代入は不可
except TypeError as e:
print("エラー1:", e)
try:
s[1:3] = "YZ" # スライス代入も不可
except TypeError as e:
print("エラー2:", e)
# 置き換えたい場合は新しい文字列を作る
t = "X" + s[1:]
print("置換結果:", t)
エラー1: 'str' object does not support item assignment
エラー2: 'str' object does not support item assignment
置換結果: Xbc
多バイト文字や絵文字の分割に注意
Pythonのインデックスは「コードポイント」単位であり、人が1文字と認識する「書記素(グラフェム)」を分断することがあります。
特に結合文字(濁点やスキントーン修飾、国旗の合成など)を含む場合に注意が必要です。
text = "A👍🏼B🇯🇵C" # 見た目は5文字に近いが、コードポイントはもっと多い
for i, ch in enumerate(text):
# 各要素はコードポイント単位で1文字扱い
print(i, ch, hex(ord(ch)))
# コードポイント単位のスライスは「書記素」を分断し得る
print("分断の例1:", repr(text[1])) # '👍'
print("分断の例2:", repr(text[2])) # '🏼' (スキントーン修飾のみ)
print("分断の例3:", repr(text[4:6])) # 国旗の一部だけが取れる可能性
0 A 0x41
1 👍 0x1f44d
2 🏼 0x1f3fc
3 B 0x42
4 🇯 0x1f1ef
5 🇵 0x1f1f5
6 C 0x43
分断の例1: '👍'
分断の例2: '🏼'
分断の例3: '🇯🇵'
上の例ではたまたま text[3:5]
が完全な国旗になっていますが、常に安全という保証はありません。
表現形式(NFC/NFD)や合成の仕方によっては分断されます。
見た目の「1文字」単位で切り出したい場合は、サードパーティの regex
モジュールの書記素単位パターン \X
を使うのが現実的です。
regex
のインストールregexはインストールが必要なライブラリです。未インストールの場合はインストールしておきましょう。
pip install regex
# pip install regex が必要です
import regex as re
text = "A👍🏼B🇯🇵C"
# 書記素(ユーザーが知覚する1文字)単位に分割
graphemes = re.findall(r"\X", text)
print(graphemes) # ['A', '👍🏼', 'B', '🇯🇵', 'C']
print("書記素スライス:", "".join(graphemes[1:4])) # '👍🏼B🇯🇵'
['A', '👍🏼', 'B', '🇯🇵', 'C']
書記素スライス: 👍🏼B🇯🇵
境界値は変数で管理して意図を明確に
マジックナンバーを避け、len(s)
や意味のある変数名を使って境界を表現すると読みやすくなります。
範囲をクリップする場合は min
と max
を併用するのが簡潔です。
def safe_slice(s: str, start: int, length: int) -> str:
n = len(s)
a = max(0, start)
b = min(n, a + max(0, length))
return s[a:b]
s = "abcdef"
print(safe_slice(s, 2, 3)) # 'cde'
print(safe_slice(s, -10, 4)) # 'abcd' (負の開始は0にクリップ)
print(safe_slice(s, 3, 100)) # 'def' (終了は長さにクリップ)
cde
abcd
def
sliceオブジェクトの活用
sliceオブジェクトで範囲を再利用
slice(start, end, step)
はスライス範囲をオブジェクトとして表現します。
複数の文字列に同じスライスを適用する場合に便利です。
sl = slice(2, 5) # [2:5)
a = "ABCDEFGHIJ"
b = "0123456789"
print(a[sl]) # 'CDE'
print(b[sl]) # '234'
CDE
234
関数にスライスを渡して使い回す
関数の引数として slice
を渡すと、どのようなシーケンスでも同じ「切り出しポリシー」を共有できます。
from typing import Sequence, TypeVar
T = TypeVar("T")
def take_slice(seq: Sequence[T], sl: slice) -> Sequence[T]:
# 文字列、リスト、タプルなどに対して動作
return seq[sl]
sl = slice(1, None, 2) # 1から末尾まで2おき
print(take_slice("abcdefgh", sl)) # 'bdfh'
print(take_slice([0,1,2,3,4,5,6], sl)) # [1, 3, 5]
bdfh
[1, 3, 5]
スライスとインデックス計算の組み合わせ
slice.indices(length)
は、与えた長さに対して start
, stop
, step
を正規化(負の値や範囲外を補正)したタプルを返します。
ログ出力やデバッグで「実際にどの範囲が切られるのか」を確認するのに役立ちます。
s = "abcdefg"
sl = slice(-5, 100, 2) # 負の開始や範囲外の終了を含む
start, stop, step = sl.indices(len(s))
print("正規化:", start, stop, step) # 実際には [2:7:2] と等価
print(s[sl], "==", s[start:stop:step])
正規化: 2 7 2
ace == ace
また、負方向のスライスでも同様に正規化を確認できます。
s = "abcdefg"
sl = slice(5, -10, -2)
print("正規化:", sl.indices(len(s))) # (5, 0, -2)
print(s[sl]) # 'fedb'
正規化: (5, 0, -2)
fedb
まとめ
Pythonのスライスは、短く読みやすいコードで文字列の一部を安全に取り出せる強力な機能です。
start:end:step
の基本と、終了位置が含まれない半開区間であること、開始・終了・ステップの省略規則、負のインデックスや負のステップの扱いを押さえると、実用的な抽出パターンをほぼ網羅できます。
一方で、範囲外がエラーにならないため空文字が紛れ込む点、step=0
はエラーになる点、文字列のイミュータビリティ、そして多バイト文字や絵文字の分断には注意が必要です。
再利用性を高めたい場合は slice
オブジェクトや indices
を活用して、境界の正規化やポリシーの共有を行うとよいでしょう。
最小限の記述で最大限の可読性を目指し、スライスを日常の文字列処理に活かしていきます。