本実装はともかく、ちょっと試しにprint
してみたいときあるじゃないですか。そういうときに、
print(f(g(h(1, 2))))
などと、括弧が多すぎて死にそうになることがある。
まあこれくらい我慢しろっても思うんだけど、ちょっと機嫌が悪くてブチ切れた
魔法少女になって括弧を消し去りたいッ!
せめて関数合成を
functools
モジュールに関数合成のためのツールあるかなと思ったらなかった…作るしかない…。
というわけで作ったのがこれ。Composable
クラス。
class Composable: def __init__(self, callable_obj): self.__callable = callable_obj def __call__(self, *args, **kwds): return self.__callable(*args, **kwds) def __matmul__(self, other): @Composable def composite_function(*args, **kwds): return self(other(*args, **kwds)) return composite_function def __imatmul__(self, other): return self @ other def __getattr__(self, name): return getattr(self.__callable, name)
Composable
を関数デコレータとして使うことで@
演算子を2関数に使うことができるようになる。
@Composable def add_10(n): return 10 + n @Composable def product_3(n): return 3 * n print((add_10 @ product_3)(10)) # => 40
これで入れ子の括弧はかなり無くなる!嬉しさがある。
def chain(*func): composite = Composable(lambda x: x) for func in func_rest: composite @= Composable(func) return composite print(chain(add_10, add_10, product_3)(3)) # => 29
ついでにこういうのも作っておいたので、これでなんとかしてもいい。
殲滅させたい
でも括弧を憎みすぎて、もっと括弧を減らしたかった。というわけで魔改造したのがこれ。
class Composable: def __init__(self, callable_obj): self.__callable = callable_obj def __call__(self, *args, **kwds): return self.__callable(*args, **kwds) def __matmul__(self, other): @Composable def composite_function(*args, **kwds): return self(other(*args, **kwds)) return composite_function def __imatmul__(self, other): return self @ other def __rshift__(self, other): return other(self) def __rrshift__(self, other): return self(other) def __mod__(self, other): return self.apply(other) def apply(self, *applied_args, **applied_kwds): @Composable def applied_function(*args, **kwds): return self(*applied_args, *args, **applied_kwds, **kwds) return applied_function def __getattr__(self, name): return getattr(self.__callable, name)
@Composable def xruti(func): return func() @Composable def add_10(n): return 10 + n @Composable def product_3(n): return 3 * n @Composable def add(n, m): return n + m @Composable def sub(n, m): return n - m @Composable def product(n, m): return n * m print((sub % 200 @ add % 50 @ product % 4 @ add % 5)(10)) #(1) print(10 >> add % 5 >> product % 4 >> add % 50 >> sub % 200) #(2) print(sub % 5 % 1 >> xruti) #(3)
多分3日後の自分は理解できない
まず (1) はさっきと同じで関数合成してるけど、部分適用の %
をぶち作った。
(2)では、>>
を導入。
こういうときって、「引数にほにゃして、もにゃして、ぽちゃして、どみゃして」って思考なのに関数で書くと逆になるのがイヤ!
ならメソッドチェーンで書けばいいんだけど、Python は絶妙にメソッドチェーンできなくて辛いし…。
なので、>>
に左の値を右の関数にぶち込んで値を返すということをしてもらって、括弧をなくしました。
(3) は %
がちょっとポンコツで、全部値が埋まってても値を返さずになお関数オブジェクトを返してくるので、xruti
関数(ロジバンで "return")を使って値を返すように改造してある。
「キーワード引数」?知らん!!!!!!!!!
お気を確かに
確かに関数で包みまくってると嫌になりますが、まあ流石に上はやり過ぎ感がある。
もう少し穏当にするならまあこんな感じだろうか。
from functools import reduce def compose(callables): callables.reverse() def apply(*args, **kwds): accum = callables.pop(0)(*args, **kwds) return reduce((lambda x, f: f(x)), callables, accum) return apply
print(compose([product_3, add_10, product_3, add_10])(10)) #=> 210
引数はアンパックしてもいいかもね。まあこれくらいが平和かも