map_between をやってみる -slice使ったっていいじゃない編-

[1,2,3,4,5]を与えられたら[3,5,7,9]を返すようなプログラムを書く。

元の記事は以下。

map_between をやってみる - Study08.net 対シンバシ殲滅用人型機動兵器
リストの隣接要素を次々に処理する - 今川館

#! /usr/bin/env python
# -*- coding: utf-8 -*-     

from itertools import islice, imap

def mapbtw(func, it):
    return imap(func, it, islice(it, 1, None))

if __name__ == '__main__':
    import operator

    print list(mapbtw(operator.add, range(1,6)))
    print list(mapbtw(operator.add, xrange(1,6)))

意外と知られていないitertoolsの関数群。

追記

イテレータは前に突き進むよりトラックバックいただきました。
id:imagawa_yakataさんどうもです。
で、本題ですが、上記のmapbtwだとイテレータに対応していないとのこと。
確かにおっしゃる通りです。手抜きですw
一応改良版書いときます。完全にitertoolsの紹介になってますが。。。

#! /usr/bin/env python
# -*- coding: utf-8 -*-

from itertools import islice, imap, tee

def mapbtw(func, it):
    iter1, iter2 = tee(it)
    return imap(func, iter1, islice(iter2, 1, None))

if __name__ == '__main__':
    import operator

    def listlike():
        yield 1
        yield 2
        yield 3
        yield 4
        yield 5

    print list(mapbtw(operator.add, range(1,6)))
    print list(mapbtw(operator.add, xrange(1,6)))
    print list(mapbtw(operator.add, listlike()))

本来はitの型見て動作変えるのがいいのかしらね。ま、気にしない。

teeはイテレータをコピーしちゃう関数。tee(it,n)で好きなだけコピーできる。
ただし、注意点は、itとteeの戻り値の1個目は同じイテレータを指していること。(意外とハマる)

まぁ〜、あくまでも別解ってことで。

RPythonで正規表現を使えるようにする。 PyPy Advent Calendar 19日目

PyPy Advent Calendar 19日目の担当として、ピンチヒッター[twitter:@yanolab]行きます!本来2週目は24日に担当ということでのんびりしてたら前の方々が忙しいらしく急遽回って参りました。精一杯がんばるので、どうかいじめないでください。。。 > pypyjaな方々。

今回のテーマ

RPythonではrsreという正規表現インタプリタに提供するための実装はありますが、RPython自身で使用するための正規表現実装がありません。そこで、今回はタイトルにもあるようにRPythonで正規表現を使えるようにするところまでを書きたいと思います。

ことの発端

PyPy Advent Calendar 15日目 - 低レベルっぽいことをやってみるで[twitter:@shoma4a]さんがRPython Toolchain側の低レベルを紹介していることでした。
ちょっとだけ記事を引用してみます。

ffi とは、Foreign Function Interface と呼ばれるもので、日本語に訳すと他言語関数インタフェイスというらしいです。
要は別の言語の関数を呼び出すための仕組みです。
clibffi の場合は Python から C の関数を呼ぶものです。
そもそも C の関数を呼べないことにはソケット通信もできませんし、スレッドも立てられません。
この clibffi を使うことで ctypes やスレッド、ソケットなどのライブラリ実装したりしているんですね。多分。

そーなんですよね、Cの関数が呼べないと何もできないはず。これと、前に自分もツイッターで呟いたんだけども、

http://twitter.com/#!/yanolab/status/125560987129618435:twitter
http://twitter.com/#!/yanolab/status/125561313270312960:twitter
http://twitter.com/#!/yanolab/status/125561997302562816:twitter

PyPyをビルドするときはlibffi.aを静的リンクしたりしているし、他のライブラリを使って正規表現を実装できないなんてことはないはず!この仮説が正しいことを証明しちゃる!ってことで書きます。

アプローチ

上述しているように、低レベルなAPIを提供するC言語用ライブラリをリンクしffiを使ってラップし、それを使ってRPythonに正規表現の機能を提供します。

使用ライブラリについて

上記のツイッターのつぶやきには鬼車を使用するとありますが、筆者の独断と偏見により、PCREというライブラリを選ばせていただきました。ライブラリ選定の際には最近googleからリリースされた?re2というライブライと鬼車とPCREを比較しましたが、ubuntuのパッケージになっており、C言語用のインターフェースがあるということでPCREを使うことにしました。PCREとはC言語においてPerlと互換な正規表現をライブラリレベルで提供するものらしいです。詳しくは本家サイトをご覧ください。http://www.pcre.org/

準備

前置きが長くなりましたが、ライブラリの準備はubuntuであればlibpcre3-devパッケージをインストールするだけです!楽ちん!

sudo aptitude install libpcre3-dev

次にこれは飛ばしてもよいですが、やっておくと楽な設定です。

vi ~/.bashrc
alias rpython='PYTHONPATH=/pypy-src-path/ python'
alias translate='rpython /pypy-src-path/translator/goal/translate.py'

これをやっておくと、いちいちPYTHONPATHを設定したりしなくていいので何回も実行するときに楽です。

次に筆者作のpypy用正規表現ライブラリをクローンします。

hg clone https://bitbucket.org/yanolab/pypypcre

このレポジトリにはライブラリ本体(pypypcre.py)とsample.pyの2点だけが含まれています。(適当ライブラリですいません。。。モジュールの構造にすらなってなくてすいません。。。)
確認ができたら準備は終了です。

実行

translate sample.py

としてみてください。sample-cができるはずです。usage出力も実装しておいたので、sample-cと実行して試してみてください。

解説

sample.pyの解説はいいですよね?pythonのとほぼ同じですから。ということで、ライブラリ本体の解説をします。
追記:すいません、Gistの行出てないですね・・・。gist右下のpypyprce.pyを開いて見てもらえればと思います。

  • line:17 PC環境の中からlibpcre.aまたはlibpcre.soを探します。完全にlinux決め打ちです。macやその他のディストリビューションでは適宜ライブラリの検索先を変更するといいと思います。libpcre.aが見つかればトランスレートしたときに静的リンクし、見つからない場合は、libpcre.soを動的リンクします。どちらも見つからないときはpypypcreを利用することはできません。ここはPyPyで使っているlibffiと同じです。
  • line:43 - 92 C言語のライブラリ用にインターフェースを定義しています。ctypesと同じような感じです。おもしろいのは70-71行目のループです。CConfigの値を実際にC言語ソースをはき出して、コンパイル+実行(ダンプ)し、PCRE_ERROR_NOMATCHの値を取得します。PCRE_ERROR_NOMATCHはリンクするライブラリのヘッダに書かれている定数です。./configureを実行しているような感じですね。
  • line:94pcre_externalでC言語のインターフェースをRPythonレベルまで昇華させています。実際にPythonで実行するとここはctypesが使われ、translateするとインタフェース使用部分がC言語に落とされます。
  • line:113 - Python正規表現クラスに合わせるための実装です。現在はmatch,compileの2つのみサポートです。もうちょっと完成度あげたかったけど時間がないのでしかたありませんw

もし低レベルなライブラリを作成しようと思ったら

基本Pythonみたいなので、読みやすそうですが、気をつけなければならない点があります。まず、変換後のC言語ソースを想像しながらかかないと間違いなくtype missmatchになります。また、ポインタの扱いがrffiの場合はひじょーーーに難しいです。nullを渡したいところでNoneをしたりするとだめですし、参照渡しも書けません(知らないだけかも)。また、意識しなければならない型が、C言語の型、rffiの型、RPythonの型と3種類(PyPyはさらにそれをラップしてるので4種類)を意識しなければなりません。どれか一つでもずれていると、rpythonでは動くけどtranslateはできないといった自体になります。また、rffiの層で確保したメモリ等は自分で解放しないといけません。

感想

慣れないと正直きついです。つらいです。でもだんだん楽しくなってきます。きっとwしかし、このエントリを読んでうれしい人っているのかどうか・・・たぶんいても日本に2〜3人w
まー誰かの知識の糧となれれば幸いです。

それでは、enjoy your pypy and python life!

オリジナルのPyPyを手に入れよう。 ~~PyPy Advent Calendar 11日目~~

PyPy Advent Calendar 11日目の担当として[twitter:@yanolab]が書きます。

PyPyとは

PyPyにはややこしいことに、以下の2つの意味があります。公式に別々の名前で呼ぶように呼びかけているみたいです。

1番目をPyPyと呼んで、2番目をRPython Toolchainなんて呼ぶみたいです。

PyPyとRPython Toolchainについての基本的な情報については、[twitter:@ymotongpoo] さんがPyPy Advent Calendar 6日目としてFAQを翻訳されたので、そちらを参照されるのがよいかと思います。

本記事のテーマ

みなさん、PyPyはビルドしたことありますか?おそらくほとんどの人がNoと答えると思います。なぜなら・・・・PyPyのビルドにはものすごーーーく時間がかかります。さらに、ビルドには多量(4GB以上を推奨)のメモリも必要です。ノートパソコンでは絶望的です。以前、2GBのCore2Duoのviaoでビルドを試みたところ、6時間近くかかったあげく、エラーで失敗しました。バキバキと、ものすごい勢いで心が折れます。粉砕されます。ですので、PyPyのビルドには覚悟が必要です(ぉぃ
しかし、プログラマたるもの自分で使う道具(プログラミング言語)にはお気に入りも存在するはずですし、厳選して使っているはずです。職人ともなれば自分で自分の道具をカスタマイズし、自分仕様にして使うなんてことはざらのはずです。
そこで、今回はPyPyをカスタマイズして、自分独自のPyPy(Python)を作ろうじゃないかというテーマで記事を書きます。

準備

まずはレポジトリをローカルに持ってきましょう。

hg clone https://bitbucket.org/pypyja/pypy

上記のアドレスは日本のPyPy使いが利用しているPyPyのレポジトリです。翻訳したドキュメント等をコミットしています。

本題

今回はPythonrubyでおなじみのunless文を追加してみます。
unless文とは、if文の条件が偽の時のみ実行される構文です。要は"if not 条件"と同意です。

unless False:
  print "False"

unless True:
  print "True"
else:
  print "False"

val = 1 unless True else 0
print val

の様に使います。他にもunless elif elseも使えたりします。

RPython ToolchainによるPythonの実装はpypy/interpreter以下に存在します。ですので、unless文を追加する場合はここを編集します。
Pythonの文法はpypy/interpreter/pyparser/data/Grammer2.7に存在します。ここにunless文を追加すれば、字句解析は既存のフレームワークがやってくれるので、pypy/interpreter/astcompiler/astbuilder.pyの構文木を生成する処理に手を加えれば見事完成です。
と、これが正攻法なのですが、今回はちょっとしたチートをおこないますw
(め、めんどくさいからってわけじゃないんだから!シ、シンプルな方法を選んだんだよ!)
上にも書いたようにunless文はif notと同意なので、トークンを切り出すと時にunlessをif notに置き換えてしまいます。字句解析はpypy/interpreter/pyparser/pytokenizer.pyで行っているので、ここに処理を追加します。追加する処理は下記の通りです。

たったこれだけです。もしくは下記のレポジトリをクローンしてもOKです。

hg clone https://bitbucket.org/yanolab/pypy

やっていることは、字句解析処理でトークンに切り出したときに、unlessを見つけたらトークン一覧に追加する前に、解析行のunlessをif notに置き換えてから、もう一度if notのところの処理をやり直させているだけです。これでunlessがif notに置き換わります。if not自体はPythonで元から使えるので他に処理はいりません!なんと楽なことでしょう。
あとはPyPyをビルドするだけでunless文を追加した独自のPython処理系が手に入ります。では早速ビルドしてみましょう!と言いたいところですが、絶対にやめましょうw何時間もかけてビルドして失敗したらたまりません。
PyPyではtestコードを書くことを激しく推奨していますので、pypy/interpreter/test以下にテストコードを書いて実装するのが一番です。
また、以外と便利なのがpy.pyです。PyPyにはPythonで動くPyPy実装があります。それがpy.pyです。実行は至って簡単で、

python pypy/bin/py.py

と実行するだけです。最初にC言語ファイルのビルドが走りますが、PyPyのビルドに比べたら圧倒的に短時間で済みます。このインタプリタで動作を確認したらいよいよビルドです。(testは残念ながらはしょりますw)

PyPyのビルド方法に関しては公式ドキュメントがあるのでそこを参照するといいでしょう。

ubuntuであれば下記のパッケージをインストールしておけばOKです。

sudo apt-get install libffi-dev libssl-dev libncurses-dev libexpat-dev libbz2-dev zlib1g-dev

準備ができたら下記のコマンドを実行します。

cd pypy/translator/goal
python translate.py -Ojit targetpypystandalone.py

後は延々とマンデルブロが表示されるので終わるのを待ちます。ビルド中はひたすら他の作業に打ち込むといいでしょう。
ちなみにRPythonの解析はシングルスレッドで動くのでコアは1個しか使いません。なので、デュアルコアとかクアッドコアよりもシングルで速いCPUの方がPyPyのビルドは速く終わると思います。解析が終わったあとのC言語ファイルのビルドはコア数で動くのでコア数が多い方がお得です。解析だけCPUの速いマシンで行って、C言語ファイルのビルドは別マシンでとかいうのもありかもしれませんね。

以上で今回のエントリは終わりです。

(本当はあと、独自ビルトインモジュールの作り方と独自ビルトイン関数の作り方、基底クラスのいじり方と書こうと思っていましたが、長くなりすぎるのでまたいつかの機会に。)

次は[twitter:@kozo2]さんの担当です!よろしくお願いします。

enjoy your pypy and python life!

pythonをソースからコンパイルするときのメモ

コンパイル環境はUbuntu10.10

pythonコンパイル時にzlibなどの開発ライブラリがないとモジュール作ってくれない。
なので、以下のコマンドを実行して必要そうなライブラリをインストールしておく。

sudo aptitude install libzip-dev libssl-dev libreadline-dev libncurses-dev libsqlite3-dev libexpat-dev libbz2-dev libsqlite-dev tcl-dev libgdbm-dev libbsd-dev

他にもあるかもしれない。。。

pythonで__line__を使う

データの定義位置を取得したいみたいなことを清水川さんのページで見た。pythonにはC言語のマクロみたいに__line__がないので、frameオブジェクトからファイル行数取ってとかを関数でやるのが一般的みたい。また、atsuoishimotoの日記の記事では、簡易DSLみたいな感じで実装してる。個人的には行数を取得するぐらいだったら清水川さんがやってるようにファクトリー関数でラップするのはありだと思う。なんだけど、ただ行数が欲しいだけだったらそもそも__line__があればいいんだよ!!ということでチャレンジしてみた。最初はインタプリターいじらないとだめかな〜と思ってたけど、全然そんなことはなかった。

で、作ってみたコードがこれ。

# -*- coding: utf-8 -*-

def trace(frame, event, arg):
    if event == 'line':
        frame.f_globals['__line__'] = frame.f_lineno
    return trace

def apply(module):
    if hasattr(module, 'settrace'):
        module.settrace(trace)
    
if __name__ == '__main__':
    import sys

    args = sys.argv[1:]
    if len(args) == 1 and args[0].endswith('.py'):
        apply(sys)
        execfile(args[0])

tracebackの表示内容を変えるとかそんな用途には使えないけど、__line__が使いたいだけだったらこれで十分かな?意外と綺麗にまとまった気がします。

使い方はコマンドラインから

python lineon.py somefile.py

とするか、

# -*- coding: utf-8 -*-                                                         

import sys
import lineon 

lineon.apply(sys)                                                                   

def gendata():
    return [{'data':'hoge', 'line': __line__},
            {'data':'fuga', 'line': __line__}]

data = gendata()

print data

のようにするだけ。ただ、一つ注意点があって、コードの中でlineon.applyを呼ぶ場合は__line__は関数の中でしか使えないという点。困ったことにglobal領域では動かない。global領域でも__line__を使いたい場合はコマンドラインからやる必要がある。なんか実装の仕方があると思いますが、そこまでpython詳しくないのでわからないw+調べてないw+そこは誰かに期待wせっかくだからpypiにでも登録してみようかな?python hack-a-thonで習ったし。あ、それと本番用コードにこれを使うのもおすすめできません。関数コールとかの度にフックするんで速度低下を招きます。普段使いも__debug__とかで使い分けること推奨。(なんだかC言語チックですがw)

そんでもって、どうやって実現しているかというとsysモジュールまたはthreadingモジュールのsettrace関数を使ってます。このsettraceというのはインタープリターを動的にフックするようにできる関数で、デバッガ(pdb)なんかで使われているようです。言語的にデバッガを支援してるのは素晴らしいですね。いっそのこと、IDE支援もやって欲しい気がしますが。コード補完支援とかコード補完支援とかコード補完支援とかwこのsettraceを使うと関数の呼び出しのタイミングでフックできたり、新しい行を実行するときにフックしたり、例外時や値返却時などのタイミングでフックできます。詳しくはPython Documentをご覧ください。この関数を使えばプロファイラとかコードカバレッジでも、簡単なものなら比較的楽に作れそうな気がしないでもないですね。

ディスクリプタとプロパティ

昨日のエキPy読書会でをお勉強しました。

ディスクリプタとはオブジェクトの属性が参照されたときの動作をカスタマイズするためのものです。特に__get__と__set__を両方持つものをデータディスクリプタというらしいです。

このディスクリプタはクラスの属性として設定してあげると、=で代入するときは__set__が呼ばれ、参照するときは__get__が呼ばれるようです。サンプルをエキスパートPythonプログラミングから抜粋して載っけときます。

class UppserString(object):
    def __init__(self):
        self._val = ''
    def __get__(self):
        return self._val
    def __set__(self, val):
        self._val = val.upper()

class MyClass(object):
    attr = UperString()

このディスクリプタの考えを基にして作られているのがプロパティ。シンタックスシュガー的なものかな?

このプロパティの書き方は一昔前までは、下記のように書いていたようです。

class MyClass(object):
    def __init__(self):
        self._val = ''
    def fget(self):
        return self._val
    def fset(self, val):
        self._val = val.upper()

    attr = property(fget, fset)

デコレータができてからはでこれたーでやりたいよねってことで、下記のようになったらしいです。

class MyClass(object):
    def __init__(self):
        self._val = ''

    @property
    def attr(self):
        return self._val
    
    @attr.setter
    def attr(self, val):
        self._val = val.upper()

最近はほとんど上記の書き方のようですが、マニアックな人は下記のような書き方をするそうです。

class MyClass(object):
    def __init__(self):
        self._val = ''

    @apply
    def attr():
        def fget(self):
            return self._val
        def fset(self, val):
            self._val = val.upper()
        return property(fget, fset)

僕はこの書き方が一番気に入ったのですが、applyってなんだよとかいまいち人気がないみたいです。
親クラスの名前空間汚さないし、プロパティで処理がまとまってるし綺麗だと思ったんですけどね。
@propertyの方だと、定義順番が縛られるのがいやだーwリファクタリングのときとか下手に移動できなさそう。自分はいいけど後で読む人とか改変する人とかちょっと困らないかな〜と。

で、@applyの方をできるだけ気持ち悪くないようにしてみたんですが、どうでしょうか?逆に気持ち悪いかも?w

property, propgen = apply, property

class Sample(object):
    def __init__(self):
        self._val = 0
    
    @property
    def x():
        doc = "this is property"
        def fget(self):
            return self._val
        def fset(self, val):
            self._val = val * val
        def fdel(self):
            self._val = 0
        
        return propgen(**locals())

print Sample.x.__doc__
s = Sample()
print s.x
s.x = 5
print s.x
del s.x
print s.x

[注] 20101119 propertyのgetter,setterのメソッド名を__get__->fget,__set__->fsetに変更