読者です 読者をやめる 読者になる 読者になる

タオルケット体操

サツバツいんたーねっと

Pythonのラムダ辛い問題を解決する暗黙のプレースホルダ

* ネタ元

Rubyのブロックつらい問題を解決する暗黙のブロックパラメータ - Qiita

RubyPythonのブロックラムダつらい問題

Pythonでショートコードをしようとおもうと、時々こういうことが起きます。

map(lambda it: it.upper(), ['foo', 'bar', 'baz'])

それぞれの要素に対してupcaseを適用する、ただそれだけのためにitを2回も記述しなければなりません。っていうかそもそもlambda:って読みにくいです。
Pythonはラムダをあまり使わない言語なのでこの様なコードを書く機会は少ないですが、それでもちょくちょく出番があり、やがてあなたは辟易するはずです。

<中略>

参考になる例として、ClojureやScalaでは暗黙のパラメータ(プレースホルダ)を導入することでこの問題を上手く解決しています。

<例は省略>

やりましょう

そこでまず、Pythonにプレースホルダの仕組み(のように使えるオブジェクト)をでっちあげてみました。
限定的ではありますが、これでPythonでもScalaのような見た目でラムダ式を使えます。

import operator
from itertools import chain


def _does_arguments_has_placeholder(arg):
    return isinstance(arg, LambdaBuilder.__class__)


def _methodbuilder(name):
    def _(*a, **kw):
        all_args = chain(a, kw.values())
        num_of_placeholder = sum(1 for arg in all_args if _does_arguments_has_placeholder(arg))
        if not num_of_placeholder:
            return operator.methodcaller(name, *a, **kw)
        a = list(a)
        for i in range(num_of_placeholder):
            a.pop(0)
        # TODO: meta level generation
        if num_of_placeholder == 1:
            return lambda self, a1: operator.methodcaller(name, a1, *a, **kw)(self)
        elif num_of_placeholder == 2:
            return lambda self, a1, a2: operator.methodcaller(name, a1, a2, *a, **kw)(self)
        elif num_of_placeholder == 3:
            return lambda self, a1, a2, a3: operator.methodcaller(name, a1, a2, a3, *a, **kw)(self)
        else:
            raise NotImplementedError('not max len of placeholder is 3 yet')
    return _


def _opp_builder(op, doc):
    def _dummy_method(dummy_self, other):
        if isinstance(other, LambdaBuilder):
            return lambda trueself, trueother: op(trueself, trueother)
        else:
            return lambda trueself: op(trueself, other)
    return _dummy_method


class LambdaBuilder(object):
    '''
    '''
    __slots__ = ()

    @classmethod
    def __getattr__(self, name):
        return _methodbuilder(name)

    def __call__(self, *args, **kw):
        return lambda func: func(*args, **kw)

    # TODO: implement all operators
    __add__ = _opp_builder(operator.add, "self + other")
    __mul__ = _opp_builder(operator.mul, "self * other")
    __sub__ = _opp_builder(operator.sub, "self - other")
    __mod__ = _opp_builder(operator.mod, "self %% other")
    __pow__ = _opp_builder(operator.pow, "self ** other")

    __and__ = _opp_builder(operator.and_, "self & other")
    __or__ = _opp_builder(operator.or_, "self | other")
    __xor__ = _opp_builder(operator.xor, "self ^ other")

    if PY2:
        __div__ = _opp_builder(operator.div, "self / other")
    else:
        __div__ = _opp_builder(operator.truediv, "self / other")
    __divmod__ = _opp_builder(divmod, "self / other")
    __floordiv__ = _opp_builder(operator.floordiv, "self / other")
    __truediv__ = _opp_builder(operator.truediv, "self / other")

    __lshift__ = _opp_builder(operator.lshift, "self << other")
    __rshift__ = _opp_builder(operator.rshift, "self >> other")

    __lt__ = _opp_builder(operator.lt, "self < other")
    __le__ = _opp_builder(operator.le, "self <= other")
    __gt__ = _opp_builder(operator.gt, "self > other")
    __ge__ = _opp_builder(operator.ge, "self >= other")
    __eq__ = _opp_builder(operator.eq, "self == other")
    __ne__ = _opp_builder(operator.ne, "self != other")

長いですね。試してみましょう。

_ = LambdaBuilder()
map(_.upper(), ['foo', 'bar', 'baz'])
# => ['FOO', 'BAR', 'BAZ']

やったぜ。

_はパラメータのプレースホルダとして扱われますが、二つ以上現れるとそれぞれ先頭から順に割り当てられます。明示的にパラメータを渡したりとかそういうのは疲れたので無理です。

(_ + _)(1, 2)
# => 3
reduce(_ + _, range(5))
# => 10

こんな感じです。

まとめ

「演算子やメソッドを呼び出すと、引数をとってそこに該当の演算子やメソッド呼び出しを行うクロージャを返すメソッドを実装している」とかそんな感じです。
前に遊びで作ったライブラリからひっぱってきたので若干余計な実装も混ざってますね。

たのしい

その他

Pythonでブロック構文めいたことをするネタ

Pythonでパターンマッチめいたモノを作ってみる with Rubyのブロック渡しっぽい見た目 - タオルケット体操


メタプログラミングRubyは、Python使いでも一度は目を通すべき良書。

メタプログラミングRuby

メタプログラミングRuby