Pythonでbash風のパイプやリダイレクトが使えるライブラリ(syntax_sugar)

パイプ、リダイレクト、中置関数、関数合成演算子、俺が好きな構文糖衣をPythonで書けるようにしてくぜ!という姿勢が素敵。

GitHub - czheo/syntax_sugar_python: A library adding some anti-pythonic syntatic sugar to Python

いやREADMEにはmatzのstreemに影響されたって書いてあるんだけど、やっぱpipeといったらbashがまず連想されたのでこのタイトルにしたわけです(どうでもいい)。

使い方はREADME見れば一目瞭然で、

# 10をパイプの中に入れてデータが流れるに任せる
pipe(10) | range | each(lambda x: x ** 2) | print

こんな感じです。パイプライン演算子というとF#とかElixirとかあるけど、こういう左から右にデータが流れていく書き方好きなんですよね。全然Pythonicじゃないけど!

pipeの実装はどうなってるのか

まだまだお遊びレベルのプロジェクトであることは作者も認めるところなのだが、私にはpipeをどうやって実装してるのか想像がつかなかった。というわけでGitHubの実装を見ることにした。

class pipe:
    """ ... 略 ... """
    def __or__(self, rhs):
        if isinstance(rhs, dump):
            # pipe end, return/dump data
            return self.data
        elif not self.pipein:
            self.start(rhs)
        elif isinstance(rhs, tuple):
            self.partial(rhs)
        elif isinstance(rhs, list):
            if len(set(rhs)) != 1:
                raise SyntaxError('Bad pipe multiprocessing syntax.')
            poolsize = len(rhs)
            func = rhs[0]
            self.multithread(func, poolsize)
        elif isinstance(rhs, MultiProcess):
            self.multiprocess(rhs.func, rhs.poolsize)
        elif isinstance(rhs, MultiThread):
            self.multithread(rhs.func, rhs.poolsize)
        else:
            self.function(rhs)
    return self

pipeは関数じゃなくてクラスだったのか〜。小文字だからクラスに見えなかった。それで__or__演算子を定義してパイプ処理を実現してるのね。スッキリ。

しかし内実を見ると、isinstanceでデータの型別にかなり特異的な分岐をしている。タプルが来たら部分適用する、みたいなの恣意的なんだけどたしかに楽ちんさを感じる。

あとリストにデータを流そうとするとスレッドが起動するのも面白い。

pipe([1,2,3,4,5]) | [print] * 3

プロセスも以下のようなシュガー風のやりかた(実態はProcessSyntaxインスタンス)で起動できる

pipe(10) | p[print]

p[print]ってなんだ?マクロか何かか?と思ったが、ソースをみると__getitem__してるだけという種あかし。

class ProcessSyntax:
    def __getitem__(self, func):
    return MultiProcess(func)

p = ProcessSyntax()

ホッとしたようながっかりしたような…

他にも中値演算子風関数、関数合成を__mul__から定義する(これはどこかでみた事があった)など、ややトリッキーなコードが堪能できてよかったです。こういうものはおそらく実用上なんの役にも立たないのでしょうが、今のところ作者はやる気があるみたいなので面白いコードが書かれる様子をウォッチしたいと思います。