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使いでも一度は目を通すべき良書。
- 作者: Paolo Perrotta,角征典
- 出版社/メーカー: アスキー・メディアワークス
- 発売日: 2010/08/28
- メディア: 大型本
- 購入: 18人 クリック: 533回
- この商品を含むブログ (125件) を見る