動機
コードを書いているとよく
list(filter(map(...)))
みたいな関数が連続したものに出会います。
大抵コードは左上から読んでいくので, こうしたものもlist->filter->mapの順で読みたくなりますが, 実際は逆順に適用されるので混乱しやすいです。
cut hoge.tsv | uniq | sort
こんな感じでシェルスクリプトのように書けたら見やすいかなと思ったりします。
実装
class CompositableFunction:
def __init__(self, function):
self.function = function
def __call__(self, *args, **kwargs): # ()を後ろにつけたときに呼ばれる.
return self.function(*args, **kwargs)
def __or__(self, other): # |演算子
return CompositableFunction(lambda *args, **kwargs: other(self(*args, **kwargs)))
def __ror__(self, other):
return CompositableFunction(lambda *args, **kwargs: self(other(*args, **kwargs)))
呼び出すときは
newfunc = CompositableFunction(func) # 新しく関数オブジェクトを作る.
@CompositableFunction # デコレータで呼び出す.
def func(*args, **kwargs):
pass
のような感じで使えます。
functools.partialで部分関数を定義すればもうちょっと使いやすいと思います。
サンプルコード
ls = [2, 1, 0]
sub = CompositableFunction(lambda arr, x: [i - x for i in arr])
@CompositableFunction
def mul3(arr):
return [i * 3 for i in arr]
print((sub|mul3|sorted)(ls, 1))
# [-3, 0, 3]
問題点
関数を合成してしまうと, 元の関数の引数の名前が*args, **kwargsに置き換えられてしまうのでエディタとかが使いにくくなります。