python でリストのパターンマッチっぽい何か
「実践Python3」のコラム見て、ハッと閃いたのでメモ。(Python3.5)
シーケンスのアンパックは引数定義のところでよく使われますね。
def func(*args, **keywords): pass
みたいな感じで。アンパックはまだ使いみちがあって、入れ子になったシーケンスの内側にあるシーケンスをアンパックして平たくできます。
[1, 2, 3, *[4, 5, 6]] # >> [1, 2, 3, 4, 5, 6]
ところで、Pythonではアンパック代入というのができて、・・・要はタプルの内部に変数を置けます(パターンマッチ的な代入?なんて言うのか分からない)
tpl = (1, 2, 3) a, b, c = tpl # (a, b, c) = tpl に同じ assert a == 1 and b == 2 and c == 3
で、このアンパック代入、対タプルならイミュータブルなので割と使えますが、ミュータブルなシーケンス(リストとか)だとふつうの使い方では使い勝手が悪い。
ここで*
の出番です。
seq = [1, 2, 3, 4, 5] x, *xs = seq assert x == 1 assert xs == [2, 3, 4, 5]
アンパック代入時、最後に*
を付した変数を入れておけば、余った部分は全部そこに収容されるので、多い日もあんしん!((少ないときはif
して))
で、これ使うと Haskell みたいなリストの取り扱い方(x:xs
みたいなの)ができそうだと思って、「すごいH本」4章の実装練習を Python でやってみました。
from typing import List, TypeVar, Tuple, Sequence, Any from abc import ABCMeta, abstractmethod T = TypeVar('T') S = TypeVar('S') CT = TypeVar('CT', bound=Comparable) class Comparable(metaclass=ABCMeta): @abstractmethod def __lt__(self, other: Any) -> bool: pass @abstractmethod def __gt__(self, other: Any) -> bool: pass def __le__(self, other: Any) -> bool: return not self > other def __ge__(self, other: Any) -> bool: return not self < other def sum_(xs: Sequence[float]) -> float: if xs: x, *xs = xs return x + sum(xs) else: return 0 def maximum_(xs: Sequence[T]) -> T: if xs: x, *xs = xs if xs: return max(x, maximum_(xs)) else: return x else: raise ValueError("maximum of empty list!") def take_(n: int, xs: Sequence[T]) -> List[T]: if n >= 0 and xs: x, *xs = xs return [x] + take_(n-1, xs) else: return [] def reverse_(xs: Sequence[T]) -> List[T]: if xs: x, *xs = xs return reverse_(xs) + [x] else: return [] def zip_(xs: Sequence[S], ys: Sequence[T]) -> List[Tuple[S, T]]: if xs and ys: x, *xs = xs y, *ys = ys return [(x, y)] + zip_(xs, ys) else: return [] def elem_(a: T, xs: List[T]) -> bool: if xs: x, *xs = xs if a == x: return True else: return elem_(a, xs) else: return False def quicksort_(xs: List[CT]) -> List[CT]: if xs: x, *xs = xs smaller_or_equal = [a for a in xs if a <= x] larger = [a for a in xs if a > x] return quicksort_(smaller_or_equal) + [x] + quicksort_(larger) else: return []
という感じで。え? pop
使えって?うるせーニシキヘビぶつけんぞ。とはいえ確かにジェネレータとかに対しては不向き(特に無限イテレータ)ですね。
とはいえ、ジェネレータで実装する場合はそもそも上のような書き方をしないのでは、とも思うので、もっぱらリスト向きの(トイな)書き方ではある。
ジェネレータで再帰するならyield from
を使うとよさそう
from typing import Iterable, Iterator def reverse_2(xs: Iterable[T]) -> Iterator[T]: xs = iter(xs) x = next(xs) yield from reverse_2(xs) yield x assert list(reverse_2(range(5))) == [4, 3, 2, 1, 0]
一周してジェネレータを積極的に使いたみというオチになってしまった。ちなみに、遅延評価の Haskell なら簡単にかけるrepeat
は Python ではジェネレータで書けます。
def repeat(x: T) -> Iterator[T]: yield x yield from repeat(x)
ジェネレータ便利やね。(全く同じ形式で、yield from repeat(x+1)
とすればカウンターになるのも面白い)
ジェネレータ、ループにはwhile
使うのが常套手段っぽいけど、再帰のほうがなんかかっこよくていいですよね(?)
P.S.
ところで、Comparable
の抽象基底クラスってなんでないんやろ