potisanのプログラミングメモ

趣味のプログラマーがプログラミング関係で気になったことや調べたことをいつでも忘れられるようにメモするブログです。はてなブログ無料版なので記事の上の方はたぶん広告です。記事中にも広告挿入されるみたいです。

Python 3.8 スライス関係の覚書

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