PyPy の紹介

技術と実用性を兼ね備えた新たな実装

JIT コンパイラーを実装した PyPy を使用して Python による開発の効率を改善し、柔軟性を高めましょう。この記事では、PyPy の概要、PyPy を使用するメリット、そして PyPy を使用してハイパフォーマンス・アプリケーションの開発を迅速に行えるようにする方法について説明します。

Uche Ogbuji, Partner, Zepheira, LLC

Photo of Uche OgbujiUche Ogbuji は Zephira のパートナーであり、高度な Web カタログやその他のリッチなコンテキスト・データベースの作成を監督しています。彼は長年、XML、セマンティック Web や Web サービス、Akara などのオープンソース・プロジェクトといった高度な Web 技術を初期段階から扱ってきました (Akara は Web データ・アプリケーションのためのオープンソース・プラットフォームです)。彼はナイジェリア生まれのコンピューター・エンジニア兼ライターであり、米国コロラド州ボルダー近郊に住み、そこで働いています。彼についての詳細は彼のブログ、Copia をご覧ください。



2012年 3月 15日

概要

1994年に生まれた Python プログラミング言語は、2000年を過ぎた頃から本格的に定着し始めました。言語が成功しているかどうかを判断する目安の 1 つが実装の数です。Python の実装として最もよく知られ、最もよく使用されているのは、CPython という実装です。その他にも、成功したプロジェクトとして Jython (Java ランタイム上で動作する Python 言語) や IronPython (.NET プラットフォーム上で動作する Python 言語) などがあります。これらはすべてオープンソースであり、Python はオープンソース・ソフトウェアの世界で常に大きな存在でした。

Python の実装における長年の目標は、純粋に Python 言語のみで実装を設計することです。つまり Python 言語を C や Java など他の言語の用語ではなく Python 自身の用語で Python を規定することにより、Python そのもので Python を定義できるようにすることです。PyPy プロジェクトは、その目的に沿った Python の実装です。PyPy は「Python 言語で実装された Python」を意味しますが、実際には Python のサブセットである RPython として実装されています。もっと正確に言えば、PyPy は PyPy 自体がランタイムであり、任意の言語を PyPy に組み込むことができます。

理路整然とした言語設計の PyPy を使用すれば、下位レベルの最適化プログラムを組み込むことができるため、最適化に大きく貢献することができます。特に、PyPy には JIT (just-in-time) コンパイラーが統合されています。JIT コンパイラーは、Java のパフォーマンスに革命的変化を起こした HotSpot という有名な技術と同じ技術です。HotSpot は元々 Animorphic によって開発された技術で、1997年初頭に Animorphic が Sun Microsystems に買収されたことで、2000年初頭には Sun の Java 実装に組み込まれ、その結果 Java がほとんどの用途で実用的な言語となりました。Python は多くの用途で既に実用的ですが、パフォーマンスに関しては非常によく不満の声が聞かれます。PyPy には JIT コンパイラーをトレースする機能が用意されており、PyPy が Python プログラムのパフォーマンスにどれほどの革命的変化を起こす可能性があるかをこの機能が既に示しています。PyPy プロジェクトはまだベータ・フェーズの終盤にあると私は思いますが、既に Python プログラマーに必須のツールとなっており、すべての開発者が持つ価値のある非常に有用なツールとなっています。

この記事では、読者が Python について詳しくは理解していないという前提で PyPy を紹介します。

はじめに

まず、PyPy を PyPI と混同してはいけません。PyPy と PyPI とは非常に異なるプロジェクトです。PyPI は Python Package Index であり、標準ライブラリーを補完するサードパーティーの Python パッケージを取得するためのサイトとシステムです。正しい PyPy のサイト (「参考文献」を参照) を訪れると、PyPy の開発者達の努力により、PyPy がほとんどのユーザーにとって簡単に試せるものになっていることがわかるはずです。最近のハードウェア上で Linux、Mac、または Windows (まだサポートされていない Windows 64 を除きます) が稼働している環境であれば、バイナリー・パッケージの 1 つをダウンロードするだけで PyPy を実行できるはずです。

PyPy の最新バージョンは 1.8 で、Python 2.7.2 を完全に実装しています。つまり PyPy 1.8 の言語機能と動作は CPython 2.7.2 と同じはずです。ただし、PyPy 1.8 はベンチマークが行われる多くの用途で既に CPython 2.7.2 よりもはるかに高速であるため、私達の興味をそそるのです。以下のセッションは私がどのようにして自分の Ubuntu 11.04 マシンに PyPy をインストールしたかを示しています。このセッションは以前のリリースの PyPy で行ったものですが、PyPy 1.8 でも同じような結果が得られます。

$ cd Downloads/
$ wget https://bitbucket.org/pypy/pypy/downloads/pypy-1.6-linux.tar.bz2
$ cd ../.local
$ tar jxvf ~/Downloads/pypy-1.6-linux.tar.bz2
$ ln -s ~/.local/pypy-1.6/bin/pypy ~/.local/bin/

ここで ~/.local/bin/ を含むように $PATH を更新する必要があります。PyPy をインストールしたら、他のパッケージを容易にインストールできるように、Distribute と Pip もインストールすることをお勧めします。(この記事では説明しませんが、純粋な Python 環境を別途保持する手段である Virtualenv も使う必要があるかもしれません。) 以下のセッションは Distribute と Pip のセットアップ方法を示しています。

$ wget http://python-distribute.org/distribute_setup.py
$ wget https://raw.github.com/pypa/pip/master/contrib/get-pip.py
$ pypy distribute_setup.py
$ pypy get-pip.py

ライブラリー・ファイルが ~/.local/pypy-1.8/site-packages/ にインストールされ、実行可能ファイルが ~/.local/pypy-1.8/bin にインストールされているはずです。そのため、~/.local/pypy-1.8/bin を $PATH に追加する必要があります。また、システムに元々あった pip ではなく、必ず今インストールした pip を使う必要があります。そうすれば、この記事で後ほど使用するサードパーティーのパッケージをインストールできるようになります。

$ pip install html5lib
$ pip install pyparsing

リスト 1 には、Python の隠し機能である import this の実行後の PyPy インタープリターの出力が示されています。

リスト 1. PyPy の出力の例
uche@malatesta:~$ pypy
Python 2.7.1 (d8ac7d23d3ec, Aug 17 2011, 11:51:18)
[PyPy 1.6.0 with GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
And now for something completely different: ``__xxx__ and __rxxx__ vs operation
slots: particle quantum superposition kind of fun''
>>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
>>>>

PyPy の実際の動作を簡単に説明するために、ここでは 1 つのプログラムを紹介します。このプログラムは Web ページを構文解析し、ページ上に示されているリンクの一覧を出力します。このプログラムの考え方は、何らかの目的でページからページへと Web のリンクをたどるスパイダー・ソフトウェアの基本的な考え方と同じです。

私は Web ページの構文解析用に html5lib ライブラリーを選択しました。html5lib は純粋に Python のみで作られた構文解析ライブラリーであり、HTML5 仕様を規定している WHAT-WG グループの構文解析アルゴリズムを実装するように設計されています。HTML5 は、HTML5 仕様にまったく準拠していない Web ページに対しても後方互換性を維持するように設計されているため、html5lib は優れた汎用 HTML 構文解析ツールキットとしての役割も兼ねています。また html5lib は CPython と PyPy でベンチマークが測定されており、PyPy で実行した方が大幅に高速に実行される結果が得られています。

リスト 2 は、指定された Web ページを構文解析し、そのページのリンクを 1 行ずつ出力します。対象とするページの URL はコマンドラインで pypy listing1.py http://www.ibm.com/developerworks/opensource/ のように指定します。

リスト 2. ページ上のリンクを一覧表示する
#!/usr/bin/env pypy

#Import the needed libraries for use
import sys
import urllib2

import html5lib

#List of tuples, each an element/attribute pair to check for links
link_attrs = [
    ('a', 'href'),
    ('link', 'href'),
]

#This function is a generator, a Python construct that can be used as a sequence.
def list_links(url):
    '''
    Given a URL parse the HTML and yield a sequence of link strings
    as they are found on the page.
    '''
    #Open the URL and get back a stream of the content
    stream = urllib2.urlopen(url)
    #Parse the HTML content according to html5lib conventions
    tree_builder = html5lib.treebuilders.getTreeBuilder('dom')
    parser = html5lib.html5parser.HTMLParser(tree=tree_builder)
    doc = parser.parse(stream)

    #In the outer loop, go over each element/attribute set
    for elemname, attr in link_attrs:
        #In the inner loop, go over the matches of the current element name
        for elem in doc.getElementsByTagName(elemname):
            #If the corresponding attribute is found, yield it in sequence
            attrvalue = elem.getAttribute(attr)
            if attrvalue:
                yield attrvalue

    return

#Read the URL to parse from the first command line argument
#Note: Python lists start at index 0, but as in UNIX convention the 0th
#Command line argument is the program name itself
input_url = sys.argv[1]

#Set up the generator by calling it with the URL argument, then iterate
#Over the yielded link strings, printing each
for link in list_links(input_url):
    print link

リスト 2 のコードには、大量のコメントを付けました。読者は Python について深く理解している必要はありませんが、制御フローを表現するためのインデントの使い方など、基本的なことは理解している必要があります。関連する Python のチュートリアルについては「参考文献」を参照してください。

リスト 2 では簡単のため、上述のようなプログラムの規約の一部を省きましたが、初心者のプログラマーにとっても非常に有用と思える高度な機能として、ジェネレーターと呼ばれる list_links 関数を使用しました。list_links は項目を 1 つずつ計算してその項目を提供するという意味で、シーケンスのように動作する関数です。ここでは、値のシーケンスを提供する yield 文が鍵となります。


さらに複雑なスクリーン・スクレイピング

Web ページを構文解析するタスクの大半は単にリンクを見つけて表示するよりも複雑であり、典型的な「Web スクレイピング」タスクを支援するライブラリーがいくつかあります。pyparsing は純粋に Python のみで作成した汎用の構文解析ツールキットであり、HTML を構文解析するための機能をいくつか含んでいます。

次の例では、IBM developerWorks のインデックス・ページから記事の一覧をスクレイピングする方法を説明します。図 1 は対象とするページのスクリーンショットです。リスト 3 は、この HTML の中にあるレコードの例です。

図 1. 処理対象となる IBM developerWorks の Web ページ
スクリーンショットには developerWorks の検索ページで Open Source ゾーンの技術文書を検索した状態が表示されており、記事とその要約の一覧が表示されています。
リスト 3. HTML に含まれる処理対象のレコードの例
<tbody>
 <tr>
  <td>
   <a href="http://www.ibm.com/developerworks/opensource/library/os-wc3jam/index.html">
   <strong>Join the social business revolution</strong></a>
   <div>
    Social media has become social business and everyone from
    business leadership to software developers need to understand
    the tools and techniques that will be required.
    The World Wide Web Consortium (W3C) will be conducting a
    social media event to discuss relevant standards and requirement
    for the near and far future.
   </div>
  </td>
  <td>Articles</td>
  <td class="dw-nowrap">03 Nov 2011</td>
 </tr>
</tbody>

リスト 4 は、このページを構文解析するためのコードです。この場合も大量にコメントを付けるようにしましたが、この中にはいくつかの新しい重要な概念があります。それらの概念については、コード・リストの後に説明します。

リスト 4. Web ページから記事の一覧を抽出する
#!/usr/bin/env pypy

#Import the needed built-in libraries for use
import sys
import urllib2
from greenlet import greenlet

#Import what we need from pyparsing
from pyparsing import makeHTMLTags, SkipTo

def collapse_space(s):
    '''
    Strip leading and trailing space from a string, and replace any run of whitespace
    within with a single space
    '''
    #Split the string according to whitespace and then join back with single spaces
    #Then strip leadig and trailing spaces. These are all standard Python library tools
    return ' '.join(s.split()).strip()

def handler():
    '''
    Simple coroutine to print the result of a matched portion from the page
    '''
    #This will be run the first time the code switches to this greenlet function
    print 'A list of recent IBM developerWorks Open Source Zone articles:'
    #Then we get into the main loop
    while True:
        next_tok = green_handler.parent.switch()
        print ' *', collapse_space(data.title), '(', data.date, ')', data.link.href

#Turn a regular function into a greenlet by wrapping it
green_handler = greenlet(handler)

#Switch to the handler greenlet the first time to prime it
green_handler.switch()

#Read the search starting page
START_URL = "http://www.ibm.com/developerworks/opensource/library/"
stream = urllib2.urlopen(START_URL)
html = stream.read()
stream.close()

#Set up some tokens for HTML start and end tags
div_start, div_end = makeHTMLTags("div")
tbody_start, tbody_end = makeHTMLTags("tbody")
strong_start, strong_end = makeHTMLTags("strong")
article_tr, tr_end = makeHTMLTags("tr")
td_start, td_end = makeHTMLTags("td")
a_start, a_end = makeHTMLTags("a")

#Put together enough tokens to narrow down the data desired from the page
article_row = ( div_start + SkipTo(tbody_start)
            + SkipTo(a_start) + a_start('link')
            + SkipTo(strong_start) + strong_start + SkipTo(strong_end)("title")
            + SkipTo(div_start) + div_start + SkipTo(div_end)("summary") + div_end
            + SkipTo(td_start) + td_start + SkipTo(td_end)("type") + td_end
            + SkipTo(td_start) + td_start + SkipTo(td_end)("date") + td_end
            + SkipTo(tbody_end)
          )

#Run the parser over the page. scanString is a generator of matched snippets
for data, startloc, endloc in article_row.scanString(html):
    #For each match, hand it over to the greenlet for processing
    green_handler.switch(data)

リスト 4 は PyPy の Stackless Python 機能を意図的に導入するように作成してあります。Stackless Python とは、簡単に言えば、高度なフロー制御機能を実験するための Python の実装の一種であり、長い歴史があります。Stackless 機能の大部分は他の Python の実装には採用されていません。これは他のランタイムには制約があるためですが、PyPy ではその制約が緩くなっています。その一例が greenlet です。greenlet は非常に軽量のスレッドのようなものであり、明示的な呼び出しによって、ある greenlet から別の greenlet へとコンテンツを切り換えることにより、協調してマルチタスクで動作します。greenlet を使用すると、ジェネレーターで実現される便利な機能の一部を実行できるだけでなく、それ以外にもさらに多くの機能を実行することができます。

リスト 4 では greenlet を使用してコルーチンを定義しています。この関数の処理は、フローを構成しやすいように、またフローに従いやすいように、整然とインターリーブされます。greenlet は通常、イベント駆動システムなどの一般的なプログラミングの中でコールバックが使用されるような状況でよく使われます。コールバックを呼び出す代わりに、コルーチンにコンテキストを切り換えるのです。こうした機能による主なメリットは、面倒な状態管理を行わなくても、効率の良いプログラムを構成することができることです。

リスト 4 を見ると、コルーチンと greenlet の概要の一端がわかりますが、これは PyPy のコンテキストに徐々に慣れていく上で役立つ概念であり、PyPy には greenlet やその他の Stackless 機能がバンドルされています。リスト 4 では、pyparsing がレコードとの突き合わせを行うたびに greenlet が呼び出され、そのレコードが処理されます。

以下に示すのは、リスト 4 を実行した結果の出力の例です。

A list of recent IBM developerWorks Open Source Zone articles:
 * Join the social business revolution ( 03 Nov 2011 )
 http://www.ibm.com/developerworks/opensource/library/os-wc3jam/index.html
 * Spark, an alternative for fast data analytics ( 01 Nov 2011 )
 http://www.ibm.com/developerworks/opensource/library/os-spark/index.html
 * Automate development and management of cloud virtual machines ( 29 Oct 2011 )
 http://www.ibm.com/developerworks/cloud/library/cl-automatecloud/index.html

リスト 4 の方法を使用した理由と、この方法を使用すべきでない場合

私は意図的にリスト 4 の方法を使用しましたが、この方法について 1 つ警告しておきます。いつものことですが、その目的に極めて特化されたツールを使わずに HTML を処理しようとするのは危険です。XML に準拠したパーサーを使用しないとアンチパターンになってしまう XML の場合ほどではないにしても、HTML の場合は標準に準拠するページであっても (実際には、ほとんどのページは標準に準拠していませんが)、その処理は非常に複雑で面倒なものになります。汎用の HTML パーサーが必要な場合には、html5lib がより良い選択肢となります。とは言え、Web スクレイピングは通常、特定の状況に従って情報を抽出するのみの特殊な処理であるため、こうした限定的な使い方の場合には pyparsing を使用しても問題はなく、pyparsing に備わるいくつかの便利な機能を使用することができます。

リスト 4 に greenlet は必ずしも必要ありませんが、リスト 4 のようなコードを実際の多くのシナリオに合わせて拡張すると、リスト 4 で greenlet を導入した理由がもっと明確になります。構文解析と他の処理を多重化しているような状況では、greenlet を使用すると、UNIX コマンドラインのパイプとよく似た構造で処理を行うことができます。扱う対象のソース・ページが複数あって問題が複雑な場合には、もう 1 つ問題があります。それは、urllib2 による処理は非同期ではないため、urllib2 によって Web ページへのアクセスが行われるたびにプログラム全体がブロックされてしまうことです。この問題に対処する方法については、この記事では説明しませんが、リスト 4 の高度な制御フローを使用すると、そうした高度なアプリケーションをパフォーマンスに注意しながら連結していく方法を注意深く考える習慣が身につくようになります。


まとめ

PyPy は活発に保守が行われているプロジェクトであり、進化し続けていることは確かですが、PyPy を使用して行えることは既に数多くあり、また PyPy は CPython との互換性が高いことから、皆さんが PyPy を試そうとする場合には、おそらく既に作業用の確立されたバックアップ・プラットフォームをお持ちのはずです。この記事では PyPy を使い始めるにあたって必要な事項を十分に説明し、また PyPy の非常に魅力的な Stackless 機能についても少し紹介しました。皆さんは PyPy のパフォーマンスに驚き、好感を持ったことと思います。もっと重要な点として、PyPy を利用することで、処理速度を犠牲にせずにスマートなプログラミングを行うための新たな手がかりが得られるはずです。

参考文献

学ぶために

  • PyPy に関する他の重要なリソースとして、PyPy のパフォーマンスを示すページと、互換性に関するウィキがあります。このウィキでは、よく使われる Python ライブラリーと PyPy との互換性を追跡しています。
  • Python についてやさしく解説した Robert Brunner 氏による連載記事、「Discover python」を読んでください (developerWorks、2005年から2006年)。
  • Grace Walker 氏による連載、「HTML5 の基礎」(developerWorks、2011年) を読み、HTML5 について学んでください。
  • developerWorks の Open source ゾーンを訪れてください。活気に満ちたオープンソース・ソフトウェアの世界に参加するために必要なリソースが豊富に用意されています。
  • さまざまな IBM 製品や IT 業界のトピックに焦点を絞った developerWorks の Technical events and webcasts で最新情報を入手してください。
  • developerWorks Live! briefing に参加して、IBM の製品およびツールについての情報や IT 業界の動向についての情報を迅速に把握してください。
  • Twitter で developerWorks をフォローしてください。
  • developerWorks On demand demos をご覧ください。初心者のための製品インストール方法やセットアップのデモから、上級開発者のための高度な機能に至るまで、多様な話題が解説されています。

製品や技術を入手するために

議論するために

  • developerWorks コミュニティーに参加してください。ここでは他の developerWorks ユーザーとのつながりを持てる他、開発者によるブログ、フォーラム、グループ、ウィキを調べることができます。

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Open source
ArticleID=801285
ArticleTitle=PyPy の紹介
publish-date=03152012