銀月の符号

Python 使い見習いの日記・雑記

レシピ追加、ブロック…そんなものは無いけれども

Python Recipe は『Rubyレシピブック 第2版 268の技』が元ネタなので Python にはない、もしくは Python に不要なものもある。「012:関数の呼び出し制限」、「237:汚染モード(taintモード)を使う」、「155:ワンライナでファイルを更新する」、「015:特異メソッド」などなど。でも、これらのレシピにも Python 的代替案は示したいなぁ、と長らく考えていた。

そして、ある意味無茶なレシピの代替案、第一弾は『010:ブロック』となった。 Ruby の目玉のひとつ、ブロック構文に挑戦。これはまだなんとかなるはず、と思い至り。

結果は…微妙か。

Python にもクロージャの概念はあるので同等のことはできるが、読みやすさではかなわず。ブロック変数を明記したものと比べるならば(まて、普通 yield を使うだろ)、そしてブロックの中身が Python の式一つで書き表せるものならば(制限きついって)、似ないこともない(こら、 each_with_index ブロック付きメソッドを勝手に for 文に直すな)。

# Ruby ブロック引数明記
# 定義
def map_with_index(list, &b)
    result = []
    list.each_with_index{|item, idx|
        value = b.call(item, idx)
        result << value
    }
    return result
end
# 呼び出し
p map_with_index([1, 2, 3]){|item, idx| item * idx} # => [0, 2, 6]
# Python
# 定義
def map_with_index(list_, b):
    result = []
    for idx, item in enumerate(list_):
        value = b(item, idx)
        result.append(value)
    return result
# 呼び出し
print map_with_index([1, 2, 3], lambda item, idx: item * idx) # => [0, 2, 6]

ブロックの内容が式一つで収まらない場合は、事前に def 文で関数定義することになる。

# Python
# 呼び出し
def block(item, idx):
    return item * idx

print map_with_index([1, 2, 3], block) # => [0, 2, 6]

追記、 each_with_index 込みで真似するとこうなる? 本当は組み込み型 list に each_with_index メソッドを加えられればよいのだが、基本的な組み込み型(list, int など)には属性、メソッドの追加はできないので関数にして模倣。

# Python
def each_with_index(self, b):
    for idx, item in enumerate(self):
        value = b(item, idx)

def map_with_index(list_, b):
    result = []
    def block(item, idx):
        value = b(item, idx)
        result.append(value)
    each_with_index(list_, block)
    return result

print map_with_index([1, 2, 3], lambda item, idx: item * idx) # => [0, 2, 6]