シーケンスとイテレータのまとめ
Pythonのデータ並びはシーケンスとイテレータの2種類ある。どこまでの操作で関数を実装すれば、引数として受け取れるという観点から操作をまとめる。ここで考える具体的な型は、list型とstr型のみとする。バージョンは2.5.xとする。引数で受け取る値をLとする。
操作 | list,str | xrange | シーケンス | イテレータ |
---|---|---|---|---|
L[0] | OK | OK | OK | NG |
L[0:1] | OK | NG | OK | NG |
enumerate,zip,map | OK | OK | OK | OK |
islice,izip,imap | OK | OK | OK | OK |
for x in L | OK | OK | OK | OK |
x in L | OK | OK | OK | OK |
not L | OK | OK | OK | NG |
if L | OK | OK | OK | NG |
len | OK | OK | OK | NG |
L == xs | OK | NG | NG | NG |
L.next() | NG | NG | NG | OK |
おいしい操作としては、list,strとシーケンスとイテレータの全てOKになっている項目。つまり、この操作だけ使用していれば、どの型でも受け入れられる。しかし、共通の操作だけで実装しようとすると、ほとんどイテレート操作程度しかできないことが分かる。特に、Lの中身が空であるかどうかを共通の操作として調べられないことに注意。
関数の先頭で、L = iter(L)としておけば全てのケースでLをイテレータとして扱えて、イテレータのL.next()が使用できるようになるが、動作がイテレータの動作となるので値注意が必要。つまり、一度L.next()とすると、現在指している要素のポインタが次に進む。もちろん、すべてのケースに対応するには、その点も注意して関数を実装する必要がある。
以下はテクニック編。
- シーケンスを複数の引数に展開する
シーケンスに*(アスタリスク)を付けて渡す。
def foo(*L): print L L = [1,2,3] foo(*L) foo(1, 2, 3)
定義側が固定個の場合、以下のようにスライスして展開する。
def foo(a,b,c): print a,b,c L = iter([1,2,3,4,5]) foo(*islice(L, 3))
- 複数の引数を1つのシーケンスにまとめる
もちろん定義側が、def foo(*L)のように可変長を受け取れば、1つのシーケンスに自動的にまとまるが、問題は定義側でシーケンスを要求しているのに、何らかの操作で複数に展開されてしまう場合(例えば後述するmapなど)。
-
- 引数が文字列(およびシーケンス)の場合
文字列はシーケンスであることを利用する。
chain('a', 'b', 'c')
-
- 引数が文字列(およびシーケンス)以外の場合
これは不可能。
def foo(*L): return L >>> foo(1, 2, 3) (1, 2, 3)
のような関数を自作するしかない。
- シーケンスを複製する
tee(L, n) # Lをn個複製 >>> map(list, tee([1,2,3], 3)) [[1, 2, 3], [1, 2, 3], [1, 2, 3]] >>> map(int.__add__, *tee([1,2,3], 2)) [2, 4, 6]
- 平行に回すもの: enumerate、zip、map
enumerate = lambda L: izip(count(), L) >>> zip([1,2,3],[4,5,6]) [(1, 4), (2, 5), (3, 6)] >>> map(int.__add__, [1,2,3],[4,5,6]) [5, 7, 9] >>> zip([1,2,3],[4,5,6],[7,8,9]) [(1, 4, 7), (2, 5, 8), (3, 6, 9)] >>> map(lambda a,b,c: (a,b,c), [1,2,3],[4,5,6],[7,8,9]) [(1, 4, 7), (2, 5, 8), (3, 6, 9)] >>> map(partial(reduce, int.__add__), zip([1,2,3],[4,5,6],[7,8,9])) [12, 15, 18]
zipはタプルのリストを返すが、mapはタプルでなくタプルが展開されたものが関数の引数として渡されることに注意。上で述べた通り複数の引数を1つのシーケンスにまとめる一般的な方法はないため注意が必要(変換してからでは元に戻せない)。
つまり、タプルを引数として受け取りたい場合は、zipしてmapする必要がある。タプルが展開されたものを引数として受け取りたい場合は、mapのみを利用する。
- ずらして平行に回す
>>> r = 'abcd' >>> x, y = tee(r) >>> y.next() 'a' >>> for a,b in zip(x,y): ... print a,b ... a b b c c d
- 階層的に回す
2.6、3.0からのcombinations、permutations、productを利用する。
- リストを辞書に変換
>>> L = 'abc' >>> dict(enumerate(L)) {0: 'a', 1: 'b', 2: 'c'} >>> dict.fromkeys(L) {'a': None, 'b': None, 'c': None} >>> D = _ >>> 'a' in D, 'd' in D # 'a' in Lより速い(但し小さいリストの場合、辞書を作るコストが高くつく) (True, False)
- 辞書のキーと値を逆転させる
但し、値が重複している場合や、値がunhashableな場合は注意。なぜならキーは重複できず、hashableな値しか取れない。
>>> D=dict(a=1, b=2, c=3) #=> {'a': 1, 'b': 2, 'c': 3} >>> dict(zip(D.values(), D)) #=> {1: 'a', 2: 'b', 3: 'c'}
2.5.xでは、dict(izip(D.itervalues(), D.iterkeys()))が最速。