俺的モダンなPythonのOSS開発環境

趣味プログラマです、こんにちわ。一応生きてます。

さて、Pythonista各位におかれましてはmoratoriumをエンジョイされていますでしょうか。そろそろライブラリも著名なものはPython3に対応してきましたし、そろそろ本格的にPython3、となっているころ合いですね。というか、Python3に対応してないとちょっと恥ずかしくなってきましたね。

とはいえ、Python2は根強く残るでしょう。というわけでPython2とPython3両方で動くコードを書きましょう。テストも書くのは当たり前ですし、せっかくなのでCIもしましょう。

と私も最近思ったので快適に開発を進めるための環境についてメモです。

  • github : 言わずもがなですね。
  • travis : githubと連携してCIできるサービス。P言語にも対応していて、最近アツいです。
  • tox : 複数のPython処理系でvirtualenvを作りテストを流せる。
  • distribute : 改良版setuptools。
  • pytest : toxと親和性の高いテストツール。noseでもよいですが私はpytestを押します。

Python2とPython3のインストール

あたりまえですが、Python2系とPython3系両方入れておきましょう。

toxのインストール

さて、早速toxを入れていくわけですがこれにはPython2系を使います。

 code
  1. $ wget http://python-distribute.org/distribute_setup.py
  2. $ python27 distribute_setup.py
  3. $ easy_install virtualenv tox
  4.  

簡単ですね。

プロジェクトを始める - ファイル・ディレクトリ構成

次に、プロジェクトの基本形を作りましょう。私は以下のような構成にしています。

 code
  1. /
  2.   + - .tox : toxにより生成されるディレクトリ。
  3.   + - docs : ドキュメント系はココ。
  4.            + - source : ドキュメントのソース。
  5.            + - build
  6.                      + - html(link to gh-pages)
  7.                                   : sphinxで生成されたHTMLドキュメント。
  8.                                     gh-pagesブランチへのリンク。
  9.   + - src : ソースファイル類。
  10.            + - プロジェクト名
  11.            + - tests
  12.   - tox.ini : tox設定ファイル。
  13.   - .travis.yml : travis設定ファイル。
  14.   - setup.py : セットアップスクリプト。
  15.   - MANIFEST.in : 配布パッケージ生成用定義ファイル。
  16.   - README.rst : 私を読んで。
  17.   - CHANGES.rst : 変更履歴。
  18.  

普通です。ポイントはdocs/build/htmlをgh-pagesへのリンクにすること。これでドキュメントの管理が非常に楽になります。

tox.iniの作成

tox.iniに依存関係と、どの処理系でテストするか、テストはなにを使ってやるかを書きましょう。だいたいの場合、それだけで十分です。

 code
  1. [tox]
  2. envlist = py27,py32
  3.  
  4. [testenv]
  5. changedir=src/tests
  6. deps=pytest
  7.      pytest-cov
  8.      その他依存パッケージ
  9. commands=
  10.   py.test \
  11.     -rxs \
  12.     --cov-report term-missing \
  13.     --cov テスト対象パッケージ名\
  14.     --basetemp={envtmpdir} \
  15.     []
  16.  

見ての通りの内容です。Pytnon27とPython32でテストします、と。この時処理系のパス(python.exeやpython27などのパス)はOSごとにデフォルトの場所が使われます。--configureでprefixを指定していたり、Windowsで別フォルダにインストールしている場合は以下のようにします。

 code
  1. [tox]
  2. envlist = py27,py32
  3.  
  4. [testenv:py27]
  5. basepython=処理系へのパス
  6.  
  7. [testenv:py32]
  8. basepython=処理系へのパス
  9.  
  10. [testenv]
  11. changedir=src/tests
  12. deps=pytest
  13.      pytest-cov
  14.      その他依存パッケージ
  15. commands=
  16.   py.test \
  17.     -rxs \
  18.     --cov-report term-missing \
  19.     --cov テスト対象パッケージ名\
  20.     --basetemp={envtmpdir} \
  21.     []
  22.  

とりあえず開発

とりあえず何かコードを書いて、テストも書きましょう。

toxでテスト

ではtoxでテストしましょう。tox.iniがあるディレクトリに移動してから

 code
  1. $ tox
  2.  

これで

  • それぞれの処理系のvirtualenvの作成
  • 依存ライブラリのインストール
  • テストの実行

が行われ、結果が表示されます。py27だけ流したい場合はtox -e py27とするか環境変数TOXENV=py27を設定したうえでtoxとすればOK.

拙作のwebフレームワークraypheですと以下のような感じです(長いのでpy32の結果のみ)。pytest-covを入れているのでC0カバレッジも表示されます。

 code
  1. _________________________________________________________________________________________ [tox sdist] __________________________________________________________________________________________
  2. [TOX] ***creating sdist package
  3. [TOX] /home/yuin/github/rayphe$ /opt/python2.7.2/bin/python2.7 setup.py sdist --formats=zip --dist-dir .tox/dist >.tox/log/0.log
  4. [TOX] ***copying new sdistfile to '/home/yuin/.tox/distshare/rayphe-0.4.0.zip'
  5. ______________________________________________________________________________________ [tox testenv:py32] ______________________________________________________________________________________
  6. [TOX] ***creating virtualenv py32
  7. [TOX] /home/yuin/github/rayphe/.tox$ /opt/python3.2.2/bin/python3.2 /opt/python2.7.2/lib/python2.7/site-packages/virtualenv-1.7.1.2-py2.7.egg/virtualenv.py --no-site-packages py32 >py32/log/0.log
  8. [TOX] ***installing dependencies: pytest, pytest-cov, webtest, sphinx
  9. [TOX] /home/yuin/github/rayphe/.tox/py32/log$ ../bin/pip install --download-cache=/home/yuin/github/rayphe/.tox/_download pytest pytest-cov webtest sphinx >1.log
  10. [TOX] ***installing sdist
  11. [TOX] /home/yuin/github/rayphe/.tox/py32/log$ ../bin/pip install --download-cache=/home/yuin/github/rayphe/.tox/_download /home/yuin/github/rayphe/.tox/dist/rayphe-0.4.0.zip >2.log
  12. [TOX] /home/yuin/github/rayphe/src/tests$ ../../.tox/py32/bin/py.test -rxs --cov-report term-missing --cov rayphe --basetemp=/home/yuin/github/rayphe/.tox/py32/tmp
  13. ===================================================================================== test session starts ======================================================================================
  14. platform linux2 -- Python 3.2.2 -- pytest-2.2.3
  15. collected 120 items
  16.  
  17. test_application.py ........................
  18. test_async_extension.py sss
  19. test_database.py .......
  20. test_defaultattrdict.py .....
  21. test_extension.py .
  22. test_functions.py .........
  23. test_hookable.py .....
  24. test_request.py .....................
  25. test_response.py ...............
  26. test_session.py ............
  27. test_staticfile_extension.py .....
  28. test_templating.py .............
  29. ----------------------------------------------------------------------- coverage: platform linux2, python 3.2.2-final-0 ------------------------------------------------------------------------
  30. Name Stmts Miss Cover Missing
  31. ----------------------------------------------------------------------------
  32. /home/yuin/github/rayphe/src/rayphe/__init__ 1648 190 88% 82, 259, 807-808, 824-839, 842-888, 892-901, 909-917, 925-933, 946-954, 1408, 1509, 1595-1597, 1934-1943, 1973, 2033-2063, 2088-2089, 2547-2560, 2563-2571, 2574-2575, 2579-2585, 2589, 2593-2596, 2600, 2604-2615
  33. /home/yuin/github/rayphe/src/rayphe/compat 98 42 57% 11-13, 17, 19-20, 67-118
  34. ----------------------------------------------------------------------------
  35. TOTAL 1746 232 87%
  36. =================================================================================== short test summary info ====================================================================================
  37. SKIP [3] /home/yuin/github/rayphe/.tox/py32/lib/python3.2/site-packages/_pytest/skipping.py:118: condition: SkipIf._no_gevent
  38.  
  39. ============================================================================ 117 passed, 3 skipped in 14.81 seconds ============================================================================
  40. ________________________________________________________________________________________ [tox summary] _________________________________________________________________________________________
  41.  

.travis.ymlの作成

無事、複数のインタプリタでテストできました。次はCIです。Travis CIを使うと、githubにpushしたタイミングでビルド&テストが自動でできます。

まずは、Travis CIにgithubのアカウントでログインして、対象のレポジトリでCIを有効にします。

次に、Travis CIでのビルド&テスト設定を.travis.ymlに書きます。ここではtoxを使っていますから、以下のようになります。

 code
  1. language: python
  2. env:
  3.   - TOXENV=py27
  4.   - TOXENV=py32
  5. install:
  6.   - pip install --use-mirrors tox
  7. script: tox
  8.  

TOXENVでテストする環境を指定します。今回の例ではpy27とpy32です。

これでgithubにpushするとTravis CIが動くようになりました。テストが終わると以下のようにそれぞれの環境が緑色になります。

title=travis

Travis CI + tox快適です

ガンガンPython3対応をしましょう!

04.11.12/07pm

個人的Go雑感&メモ

GoogleがGoという新しいプログラミング言語を出したようで。早速、インストールして軽くドキュメントを流し読みしてみました。

英語なんて読みたくないよ、という人もいるかもしれないし、誰かの役に立つかもしれないので自分用メモおいときます。完全に自分用なんである程度他の言語の知識がある人向けな上、ざっくり流し読みなんで間違ってるかも。

どんな言語?

  • ネイティブコードを吐く、コンパイル型。
  • 速度はCレベル。
  • GC搭載。ポインタはあるけど、ポインタ演算はできません。
  • 各種アーキに最適化された、それぞれのコンパイラセットを持ちます。例:
    • 6g, 6l : amd64
    • 8g, 8l : i386
  • linux, mac, naclに対応。
  • 動的型言語と静的型言語のおいしいとこどり。
  • concurrent処理が組み込まれてます。

個人的雑感

  • こんな言語設計思想かなあと感じたり
    • とにかく、シンプルな言語に。
      • C++の複雑な部分などはできるだけはずしているような。
        • いわゆるクラスベースのオブジェクト指向はない。
          • 継承はない。
          • あるのはフラットなインタフェース空間のみ。
        • 例外もない。
    • 低レイヤからは離れすぎたくない。
    • concurrentを言語的にサポートしたい。
  • 以下の言語からの影響を感じたり
    • 言語コアはC。
    • C++はパッケージの書き方、記法に影響がみられる。
    • 命名規則や記法にPythonの影響がみられる。
    • concurrentな部分はErlangから影響をうけている。

言語仕様

自分なりに簡単にまとめて見ます。

変数への代入

下へ行くほど省略形。varもあるからconstもあるでよ。

 code
  1. var s string = "";
  2. var s = "";
  3. s := "";
  4.  

オブジェクト構造

値扱いの型と参照型があります。

  • 値扱い : 代入、関数への私はコピーとなる。配列もまるごとコピーされる。
    • 各種数値 : int, floatといったプラットフォームごとにサイズがきまる型とint32のようにサイズ固定の型。
    • string : 不変、UTF8、ただのバイト配列
    • 配列
    • struct : ユーザ定義型
    • などなど
  • 参照 : 3つのみ
    • map : 辞書
    • slice : 明示的なサイズを持たない、配列のようなもの
    • channel : concurrentで使う

値扱い型のアロケート方法 : new -> T*を返す

 code
  1. var t *T = new(T);
  2.  

参照型のアロケート方法 : make -> Tを返す

 code
  1. m := make(map[string]int);
  2. // これはダメ
  3. var m map[string]int;
  4.  

配列とスライス

  • 配列は「値」。でユーザがメモリ構造などを読める。スライスはコンパイラがよしなに確保する、参照型。

例えばint型配列のポインタを受け取る場合。

標準配列(&がいる)

 code
  1. s := sum(&[3]int{1,2,3});
  2. s := sum(&[...]int{1,2,3});
  3.  

スライス

 code
  1. s := sum([]int{1,2,3});
  2.  

辞書型

 code
  1. timeZone := map[string]int{
  2.   "UTC": 0*60*60,
  3.   "EST": -5*60*60,
  4.   "CST": -6*60*60,
  5.   "MST": -7*60*60,
  6.   "PST": -8*60*60,
  7. }
  8.  
  9. seconds, ok = timeZone[tz]
  10. //値がなければokはfalse
  11. if seconds, ok := timeZone[tz]; ok {
  12.     return seconds
  13. }
  14. //消す場合
  15. timeZone["UTC"] = 0, false;
  16.  

パッケージ、制御構造、型定義、関数とメソッド、インタフェース

命名規則に特徴があります。大文字始まり以外は外部から見えないが原則。単なる命名規則ではなく、そういう仕様。

パッケージ

パッケージ文でファイルの先頭に書きます。これが基本的な単位となります。

 code
  1. package file
  2.  
制御構造

特徴は

  • ()がいらない。
  • ifswitchなどの条件部に複数の文がかける
  • ループはforのみ(whiledoはない)
  • switchが強力に(内容的にif-else if-elseチェイン)
  • concurrent用にselectがある

という点。かと。

ifの例

 code
  1. if i % prime != 0 {
  2.     fmt.Printf("%d", i);
  3. }
  4.  

switchの例

 code
  1. switch nr, er := f.Read(&buf); true {
  2.            case nr < 0:
  3.                fmt.Fprintf(os.Stderr, "cat: error reading from %s: %s\n", f.String(), er.String());
  4.                os.Exit(1);
  5.            case nr == 0: // EOF
  6.                return;
  7.            case nr > 0:
  8.                if nw, ew := file.Stdout.Write(buf[0:nr]); nw != nr {
  9.                    fmt.Fprintf(os.Stderr, "cat: error writing from %s: %s\n", f.String(), ew.String());
  10.                }
  11. }
  12.  

caseに条件式がかける。breakは自動でされる。

型定義

type,structキーワードを組み合わせる。メンバにメソッドは含みません。

 code
  1. type File struct {
  2.     fd int; // file descriptor number
  3.     name string; // file name at Open time
  4. }
  5.  

メンバ名が小文字なので、パッケージ外からは見えません。

関数とメソッド
  • 両方ともfuncキーワードで定義。
  • 違いはレシーバを指定するか否か。 レシーバを明示的に書くところはPythonっぽい。
  • 多値が返せる。

この多値がかえせる、というのがGoでは非常に重要な意味をもっています。

関数定義: 多値を返しています。大文字始まりなので、外部に公開されます。

 code
  1. func Open(name string, mode int, perm int) (file *File, err os.Error) {
  2.      r, e := syscall.Open(name, mode, perm);
  3.      if e != 0 {
  4.          err = os.Errno(e);
  5.      }
  6.      return newFile(r, name), err
  7. }
  8.  

返す型に(file *File, err os.Error)と変数名がついてますね。これをつけておけばreturn;ってかくだけでその名前の変数を返してくれます。

 code
  1. func Open(name string, mode int, perm int) (file *File, err os.Error) {
  2.      r, e := syscall.Open(name, mode, perm);
  3.      if e != 0 {
  4.          err = os.Errno(e);
  5.      }
  6.      file = newFile(r, name)
  7.      return;
  8. }
  9.  

こんな風にもかけるってことです。おそらく、エラー処理が絡む場合とかこっちのほうが書きやすかったりするんじゃないですかね。

クロージャにもなります。いわゆる関数ポインタをとるようなところで、関数がそのままかけて外部変数も見えます。

 code
  1. startServer(func(a, b int) int { return a + b });
  2.  

メソッド定義。(file *File)がレシーバの指定。

 code
  1. func (file *File) Close() os.Error {
  2.     if file == nil {
  3.         return os.EINVAL
  4.     }
  5.     e := syscall.Close(file.fd);
  6.     file.fd = -1; // so it can't be closed again
  7.     if e != 0 {
  8.         return os.Errno(e);
  9.     }
  10.     return nil
  11. }
  12.  

メソッド定義がstructでの型定義時ではないことに注目。つまり組み込み型などに対してもあとからメソッドを作成できるのです。こんな感じ。

 code
  1. type IntArray []int
  2. func (p IntArray) Len() int { return len(p); }
  3.  
インタフェース

これ重要。Goはいわゆるクラスがないので、継承などもなくインタフェースによるダックタイピングでそれらを片付けますinterfaceキーワードで定義。

 code
  1. type reader interface {
  2.      Read(b []byte) (ret int, err os.Error);
  3.      String() string;
  4. }
  5.  

このように、ReadStringが定義されていればそれはreaderなんだ、と考えます(ダックタイピング)。

実行時、動的にインタフェースが実装されているかも検査できます。

 code
  1. s, ok := v.(Stringer);
  2.  

vStringerインタフェースを満たしていれば

  • sStringerオブジェクトとしてのv
  • oktrue

がかえってきます。

Concurrency

concurrentプログラミングはGoの大きな特徴。CSP(Communicating Sequential Processes)に基づいてます。並行して動く「goroutines」という軽量プロセスが「channel」を介してやりとり。ガードと多重化のためにselect文があります。

channelは単体ではなくchan 受け渡しする型という感じで書きます。以下はチュートリアルのコードまんまです。

 code
  1. func generate(ch chan int) {
  2.     for i := 2; ; i++ {
  3.         ch <- i // Send 'i' to channel 'ch'.
  4.     }
  5. }
  6.  

intを扱うchannelを受け取ってそれにiを送っていきます。

 code
  1. func filter(in, out chan int, prime int) {
  2.     for {
  3.         i := <-in; // Receive value of new variable 'i' from 'in'.
  4.         if i % prime != 0 {
  5.             out <- i // Send 'i' to channel 'out'.
  6.         }
  7.     }
  8. }
  9.  

送られたiが一定の条件を満たしていたら、intを扱うoutというchannelに送ります。

 code
  1. func main() {
  2.     ch := make(chan int); // Create a new channel.
  3.     go generate(ch); // Start generate() as a goroutine.
  4.     for {
  5.         prime := <-ch;
  6.         fmt.Println(prime);
  7.         ch1 := make(chan int);
  8.         go filter(ch, ch1, prime);
  9.         ch = ch1
  10.     }
  11. }
  12.  

channelは参照型なのでmakeで作ります。goで実行します。これは丁寧に書いた感じ。クロージャを使えばもっとシンプル。

 code
  1. func generate() chan int {
  2.     ch := make(chan int);
  3.     go func(){
  4.         for i := 2; ; i++ {
  5.             ch <- i
  6.         }
  7.     }();
  8.     return ch;
  9. }
  10.  
多重化とガード

複数のチャンネルをとりあつかって、それらをガードにより振り分けられます。ErlangやScalaでおなじみの書き方です。

 code
  1. func server(op binOp, service chan *request, quit chan bool) {
  2.     for {
  3.         select {
  4.         case req := <-service:
  5.             go run(op, req); // don't wait for it
  6.         case <-quit:
  7.             return;
  8.         }
  9.     }
  10. }
  11.  

request型のポインタを扱うchannelとbool型を扱うchannelを使って、多重化しています。quitチャンネルに値が送られてくるまでは、送られてきたものから良しなに処理してくれる、という感じですね。

というわけで

なぐり書きしたメモでした。変なことかいてたらすみません。まぁこんな感じな言語かなあ、という程度で。

繰り替えしになりますが、C言語を元にシンプルに保ちながらconcurrentプログラミングしやすくしてます、って感じですね。完全にダックタイピングベースで多値を多様するスタイルはおもしろいですね。なんとなく見た目がキモく感じるのは私の気のせいでしょう。

というか数ヶ月もブログ放置してたのかー。コード書いてないわけじゃないんですけど、たいしたもんかいてないんですよね。割合的には8割がたCかな。月一くらいはブログ書いていきたいなあ・・・

01.06.10/12am

rayphe - 軽量Python web framework

  • 2010-2-10: v0.3.0リリースしました。またプロジェクト名を変更しています。

ちょっと前にRubyでSinatraが取り上げられて、結構注目されたように思います。ということはRailsだと大げさすぎるなあ、と思うような場合に対する需要というのはやっぱりそれなりにあるんですよね。

Pythonで軽量、というとweb.pyが一番有名ですよね。他にはJunoBottleなんかがあります。

このブログで使っているのはweb.pyです。結構昔から使っています。が、不満もおおくweb.pyを拡張するようなライブラリを作っていて、それがそこそこの量あったりします。

そこで、これくらい量があるなら自分でフレームワーク作っても大してかわんなくね?と思い始めました。あれ、そういえば俺、テンプレートエンジンもつくっちゃってるじゃん、簡易O/Rマッパも自分用につくってあるじゃん、と次々に気づき始め、それらをまとめて作っちゃいました。軽量フレームワーク。

rayphe

「rayphe」はPython用軽量ウェブフレームワークです。ルーティング、テンプレート、O/Rマッパなどが1ファイルにまとめられていて、依存するライブラリもありません。

また、共通の処理をまとめる「フィルタ」があったり、やアプリケーションの各フェーズをフックできたりと柔軟です。

raypheはGitHubにおいてあります。それなりにドキュメントも書いていて、テストもしてあります。詳しくは以下をどうぞ。

たとえば、サンプルアプリのコードの一部はこんな感じです。

python code
  1. with app.filter([context_setup_filter, {"except":["static_file"]}]):
  2.   @app.get("static/(unicode:.*)")
  3.   def static_file(c, path):
  4.     c.res.send_file(os.path.join(app.static_path, path.replace("..", "")))
  5.  
  6.   @app.get("")
  7.   def index(c):
  8.     c.res.redirect(app.url.show_pages())
  9.  
  10.   @app.get("page/(int:\d+)")
  11.   def show_page(c, page_id):
  12.     c.page = app.db.select_one_by_id(Page, page_id)
  13.     c.title += c.page.title
  14.     c.comments = app.db.select([Comment],
  15.       cond="page_id=? order by created_at asc",
  16.       values=[page_id])
  17.     c.comment = getattr(c, "comment", Comment(name="", body=""))
  18.     return app.renderer.show_page({"c":c})
  19.  

という感じでわかりやすくかけます。また、「単純化しすぎない」ということにも気を使っていたりします。selfと書くのが好きなPythonistaらしく、status 404とかじゃなくc.res.notfound()です。

というわけで

Pythonで小さなウェブアプリをつくるときは是非。

03.02.10/04am

About

Author:yuin(http://inforno.net/)

文学部文化学科卒という生粋の文系趣味プログラマ。

主にRuby、Javascript、PHP、JAVA,Python,C,Scala,Schemeなどを使っています。今はPythonな感じかもしれない。今後作曲活動なども復活するかもしれない。

Pages