2021/3/10:この記事は別のブログで投稿した記事を移動したものです。
Python 3.8のスライス操作a[1:2:3]
、スライスクラス<class 'slice'>
、__getitem__
メソッドに関する覚書きです。主な用語は公式ドキュメントの6.3.3. スライス表記 (slicing)、6.17. 演算子の優先順位、3.2. 標準型の階層に従っています。
スライス表記・操作
x[1:2]
のような表現をスライス表記、[slice_list]
の手前への適用をスライス操作と呼びます(6.17. 演算子の優先順位で使用されます)。次のような表現がスライス表記、操作がスライス操作です。[x:y:z]
のx、y、zはそれぞれ省略することができます。
スライス表記・操作
[0,1,2,3,4][0:1] #[0] [0,1,2,3,4][2:4] #[2, 3] [0,1,2,3,4][2:] #[2, 3, 4] [0,1,2,3,4][:4] #[0, 1, 2, 3] [0,1,2,3,4][::] #[0, 1, 2, 3, 4] [0,1,2,3,4][::-1] #[4, 3, 2, 1, 0] [0,1,2,3,4][2:4:-1] #[] [0,1,2,3,4][4:2:-1] #[4, 3] "abcdefghij"[0:5:2] #'ace' "abcdefghij"[::-2] #'jhfdb'
なお、スライス表記は[x:y:z]
以外の形式でも記述できます。詳細は公式ドキュメント 6.3.3. スライス表記 (slicing)を参照ください。
スライスクラス
スライスリストx:y:z
は変数に代入できませんが、同じ機能を持ちインスタンスが変数へ代入できるスライスクラス<class 'slice'>
が存在します。例えば次のコードはひとつめがエラーとなり、ふたつめはエラーとなりません。
t = 1:2:3 #SyntaxError: invalid syntax
t = slice(1, 2, 3) [0, 1, 2, 3, 4][t] #[1] [0, 1, 2, 3, 4][1:2:3] #[1]
slice(x, y, z)
はスライスクラス<class 'slice'>
のインスタンス化(公式ドキュメント 9.3.2. クラスオブジェクト)であり、左辺にはスライスインスタンスが代入されています。
t = slice(1, 2, 3) type(t) #<class 'slice'>
スライスクラスの性質およびスライスリストとの同等性は次のコードで確かめることができます。コード中のエラーでコメントアウトされた部分はスライスクラスコンストラクタの引数は記入しないことで省略することはできず、Noneで省略を知らせる必要があることを示します。
スライスクラスによるスライス表記(コメント中の「==/!=」右側はスライスリストによるスライス表記)
[0,1,2,3,4][slice(0,1)] #[0] == [0,1,2,3,4][0:1] [0,1,2,3,4][slice(2,4)] #[2, 3] == [0,1,2,3,4][2:4] [0,1,2,3,4][slice(2)] #[0, 1] != [0,1,2,3,4][2:] [0,1,2,3,4][slice(2,)] #[0, 1] != [0,1,2,3,4][2:] [0,1,2,3,4][slice(2,None)] #[2, 3, 4] == [0,1,2,3,4][2:] #[0,1,2,3,4][slice(,4)] #SyntaxError: invalid syntax != [0,1,2,3,4][:4] [0,1,2,3,4][slice(None, 4)] #[0, 1, 2, 3] == [0,1,2,3,4][:4] #[0,1,2,3,4][slice()] #TypeError: slice expected at least 1 argument, got 0 != [0,1,2,3,4][::] [0,1,2,3,4][slice(None, None)] #[0, 1, 2, 3, 4] == [0,1,2,3,4][::] [0,1,2,3,4][slice(None, None, None)] #[0, 1, 2, 3, 4] == [0,1,2,3,4][::] #[0,1,2,3,4][slice(,,-1)] #SyntaxError: invalid syntax != [0,1,2,3,4][::-1] [0,1,2,3,4][slice(None, None, -1)] #[4, 3, 2, 1, 0] == [0,1,2,3,4][::-1] [0,1,2,3,4][slice(2,4,-1)] #[] == [0,1,2,3,4][2:4:-1] [0,1,2,3,4][slice(4,2,-1)] #[4, 3] == [0,1,2,3,4][4:2:-1]
__getitem__とスライス表記
スライス表記は実行時に__getitem__
メソッドの呼び出しに変換され、[]
内のスライスリストはスライスオブジェクトslice(x, y, z)
として扱われます。これは次のコードで確認することができます。
class SliceTestClass: def __getitem__(self, key): print(key, type(key)) raise IndexError #for in文の無限ループを回避します。 t = SliceTestClass() t[1:2:3] #[slice(1, 2, 3), <class 'slice'>] #IndexError
補足:__getitem__
とfor in
の無限ループとIndexError
公式ドキュメントで__getitem__
メソッドは不適切なインデックスの呼び出しに対してIndexError
エラーを送出することが期待されています。次のようなコードは無限ループを引き起こします。
class SliceTestClass1: def __getitem__(self, key): print(key) return key + 1 for a in SliceTestClass1(): print(a) #無限に「n(改行)n+1」が出力されます。
適切にIndexError
を送出することでfor in
文を終了させることができます。
#公式ドキュメントでもforは__getitem__メソッドにIndexErrorを期待することが記載されています。 class SliceTestClass2: def __getitem__(self, key): if key >= 10: raise IndexError return key for a in SliceTestClass2(): print(a) #0~9(10でIndexErrorが発生して停止)
補足:__getitem__
とその他の表記
__getitem__
メソッドはスライス表記で呼び出されますが、その他の表記でも呼び出されることに注意してください。実装する場合はkeyの型によって条件分岐する必要があります。例えば以下のコードはいずれも__getitem__
を呼び出します。
class SliceTestClass: def __getitem__(self, key): print([key, type(key)]) t = SliceTestClass() t[1] #[1, <class 'int'>] t[1:2] ##[slice(1, 2, None), <class 'slice'>] t["abc"] #['abc', <class 'str'>] t[{"abc"}] #[{'abc'}, <class 'set'>]