Python

2009年02月27日

WSGIServerを3行でマルチスレッド化する

はてなブックマークに登録

WSGIとは

PythonでWebアプリを作るときに必ず出てくる単語にWSGIがあります。 WSGIとは、Web Server Gateway Interface の略で、WebサーバーとPython製Webアプリを つなげる標準インタフェースです。

WSGIの上で動くようにアプリケーションを作ると、そのアプリケーションは修正無しに Apache+mod_wsgi, Apache+mod_python, fastcgi, scgi, cgi, 等の環境で動かせるように なります。

他にもミドルウェアという考え方があります。例えばOpenID認証機能をWebフレームワークの プラグインとして開発した場合では他のWebフレームワークでは利用できないのですが、 WSGIミドルウェアとして開発すればWebフレームワークを問わずに利用できるようになります。

標準ライブラリのwsgirefモジュール

Python2.5から、標準ライブラリにwsgirefモジュールが追加されました。その中にはWSGIサーバーも 入っているので、自分で書いたWSGIアプリを手軽に立ち上げたいときに重宝します。

次のコードは、簡単なWSGIサーバー+アプリの例です。

from wsgiref.simple_server import *

def myapp(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['hello']

server = make_server('0.0.0.0', 8000, myapp)
server.serve_forever()

WSGIServerのマルチスレッド化

上記のコードで立ち上がるWebサーバーは、シングルスレッド&シングルプロセスで動作しているので、 複数のクライアントからアクセスされた場合に問題があります。接続が遅かったり不安定だったりする クライアントが一つでも合った場合、その接続が完了するか切断するまで他の接続ができないので、 接続に失敗したり数十秒かかったりすることになります。

普通はWebアプリを公開する場合には標準ライブラリのWSGIServerではなくて別のサーバーを使う ものですが、お手軽な方法として3行追加と1行修正のみで、他のライブラリ無しに マルチスレッド化したりマルチプロセス化することが出来ます。 次のコードが上記のコードをマルチスレッド化したものになります。

from wsgiref.simple_server import *
from SocketServer import *
class ThreadingWsgiServer(ThreadingMixIn, WSGIServer):
    pass

def myapp(environ, start_response):
    start_response('200 OK', [('Content-Type', 'text/plain')])
    return ['hello']

server = make_server('0.0.0.0', 8000, myapp, ThreadingWsgiServer)
server.serve_forever()

このコードについて簡単に説明します。 wsgiref.simple_server.make_server は第三引数でサーバークラスを指定することができて、 デフォルト値は wsgiref.simple_server.WSGIServer になっています。 WSGIServerの継承関係は次のようになっています。

SocketServer.BaseServer
  ↑
SocketServer.TCPServer
  ↑
BaseHTTPServer.HTTPServer
  ↑
wsgiref.simple_server.WSGIServer

SocketServerモジュールには、ThreadingMixIn, ForkingMixIn というクラスがあり、 SocketServer.BaseServer を継承したサーバークラスであれば簡単にマルチスレッド化 したりfork化したりできるようになっています。 WSGIServer も BaseServer を継承しているので、ThreadingMixIn/ForkingMixIn クラスを Mix-in してやるだけでマルチスレッド化やマルチプロセス化が可能です。

小さなテクニックですが、Webアプリが完成するまではサーバーの設定の問題とアプリの 問題が混ざらないようにしたいときとか、Webサーバーの設定ファイルを書くのに慣れてなくて 後回しにしたいときに重宝します。

ちなみに、 flup というライブラリには、 thread pool や prefork を使ったWSGIサーバーがあって十分実用になる速度が出るので、 標準ライブラリのサーバーだと遅いという時に利用できます。


@methane
klab_gijutsu2 at 17:40|この記事のURLComments(0)TrackBack(0)
2008年11月11日

64bit版 Windows で 64bit版 Python の常用に挑戦してみた

はてなブックマークに登録

Python は早くから64bit版 Windows に対応した64bitの Python (amd64だけでなくia64も)を 配布してきましたが、一般的に使われる多くの拡張モジュールが64bit版のバイナリパッケージを 配布していないこともあり、ほとんど普及してきませんでした。

私はamd64版 Windowsを自宅で使っているのですが、 Python 2.5 まではやはり32bit版を 利用してきました。しかし、amd64版があるのに32bit版を使うのは、こう、なんというか、 負けた気分になるので、いつかはamd64版を使いたいと思っていました。

最近Windowsをクリーンインストールすることになり、 Python 2.6 を amd64でいれるか、それとも 今まで通りx86で入れるのか考えたのですが、win32 と wxPython という有名な拡張モジュールが amd64版を配布しはじめている事を知り、とうとうamd64版の Pythonを使い始めることにしました。

環境をそろえていく上でいくつか問題もあったので、環境構築手順を記録しておきます。

Pythonのインストール

Pythonの公式サイト から python-2.6.amd64.msi を ダウンロードしてインストールします。 デフォルトでは C:\Python にインストールされるのですが、私の趣味で C:\usr\Python にインストールしました。

インストールが完了したら、 C:\usr\PythonC:\usr\Python\Scripts を環境変数 PATH に設定しておきます。

setuptoolsのインストール

setuptools は ez_setup.py を使ってインストールするのが常套手段なのですが、同梱されているランチャの 実行ファイルが32bitでコンパイルされており、64bitコンソール上で利用するとエラーになって しまいます。 そこで、パッケージをダウンロードして、ランチャを64bitでコンパイルしなおした上でインストール することにします。

まず、amd64のバイナリを作成できる環境を用意します。私は Microsoft Windows SDK v6.1 を利用しました。コマンドプロンプトから、

"C:\Program Files\Microsoft SDKs\Windows\v6.1\Bin\SetEnv.Cmd" /Release

を実行して、clやlinkが使えるようにしておきます。

次に、Python Package Index から、 setuptools の tarballをダウンロードします。

先ほどのコマンドプロンプトでtarballを展開してできたディレクトリに入り、 launcher.c をコンパイルします。Windows SDK v6.1 でコンパイルするときには、 launcher.c に一行includeを追加してやる必要がありました。

 #include "windows.h"
+#include "process.h"

用意ができたら、ランチャをコンパイルします。gui版(実行してもプロンプトが表示されない) とcli版(プロンプトの中で実行される)の二種類を作成します。コンパイルオプションに注意してください。

C:\setuptools-0.6c9>cl /Ox /DGUI=0 /Fesetuptools\\cli.exe /MT launcher.c /link /SUBSYSTEM:CONSOLE /ENTRY:WinMainCRTStartup
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

launcher.c
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:setuptools\\cli.exe
/SUBSYSTEM:CONSOLE
/ENTRY:WinMainCRTStartup
launcher.obj

C:\setuptools-0.6c9>cl /Ox /DGUI=1 /Fesetuptools\\gui.exe /MT launcher.c /link /SUBSYSTEM:WINDOWS /ENTRY:WinMainCRTStartup
Microsoft (R) C/C++ Optimizing Compiler Version 15.00.21022.08 for x64
Copyright (C) Microsoft Corporation.  All rights reserved.

launcher.c
Microsoft (R) Incremental Linker Version 9.00.21022.08
Copyright (C) Microsoft Corporation.  All rights reserved.

/out:setuptools\\gui.exe
/SUBSYSTEM:WINDOWS
/ENTRY:WinMainCRTStartup
launcher.obj

これで、64bit版のランチャがビルドできたので、setuptools をインストールできます。

C:\setuptools-0.6c9>python setup.py install

IPythonのインストール

IPythonは、通常通り easy_install ipython でインストールできます。 ただし、これだけだと pyreadline がインストールされず、ipythonの力が 半減してしまいます。 pyreadline は普通にeasy_installするだけでは インストールに失敗したので、 http://ipython.scipy.org/dist/ から pyreadline-1.5.tar.gz をダウンロードしてきて、

>easy_install pyreadline-1.5.tar.gz

としてインストールしました。これで、ipythonのセットアップは完了です。

Linuxでは、bash等のシェルの代わりにIPythonを使う気にはならないのですが、 WindowsのコマンドプロンプトのシェルはLinux系のシェルよりも機能が少ないので、 単純なファイル操作だけでもIPythonが便利だったりします。

pywin32のインストール

pywin32はPythonからWindows APIを利用するための拡張ライブラリです。 これを使うと、たとえば win32file.CreateHardLink をつかって ハードリンクを作れるようになります。

sourceforge のダウロードページ から、 pywin32-{バージョン}.win-amd64-py2.6.exe をダウンロードして、インストールします。

拡張モジュールのコンパイル環境の設定

拡張モジュールが入ったソースパッケージを easy_install でインストールするには、 コンパイラの設定をしておく必要があります。32bit版では、Linux派に易しいMinGWを 使っていたのですが、amd64のためにWindows SDKを使う方法を紹介しておきます。

本来ならば、

>set DISTUTILS_USE_SDK=1
>"c:\Program Files\Microsoft SDKs\Windows\v6.1\Bin\SetEnv.Cmd" /Release /x64

とすることで、distutilsはコンパイラを自力で見つけようとせずに、環境変数をそのまま 使うようになります。

しかし、Python-2.6のdistutilsにはバグ (issue3741) があって、エラーになってしまいます。 Pythonをインストールしたディレクトリの下の、Lib/distutils/msvc9compiler.py を開いて、 316行目のtypoを修正します。

-        self.__path = []
+        self.__paths = []

これで、上記の方法でWindows SDKのコンパイラを利用できるようになりました。 32bit Windowsで使えるたいていの拡張ライブラリは、この設定をしてからソースパッケージを インストールすることで利用可能になります。


@methane
klab_gijutsu2 at 17:43|この記事のURLComments(0)TrackBack(0)
2008年11月10日

Bazaarの紹介

はてなブックマークに登録

私は以前までバージョン管理システムには Mercurial を利用していたのですが、 最近 Bazaar に移行しました。 先日 1.9 がリリースされましたので、これを機に Bazaar を紹介します。

現在、日本で一番使われているバージョン管理システムはSubversionだと思うのですが、 最近は分散型のバージョン管理システムがオープンソース界で人気になっていて、Bazaarはその一つです。

分散型バージョン管理システムの利点はたくさん有るのですが、個人的に便利だと 感じる点は、(1)ネットワークにつながっていなくてもバージョン管理できる、(2)Subversion よりもマージが楽、(3)リポジトリのバックアップが楽、というところです。

分散型バージョン管理システムでよく使われているものに、git, Mercurial (hg), Bazaar (bzr) があります。git は Linux Kernel や Ruby on Rails で、 Mercurial は OpenJDK や Open Solaris で、 Bazaar は Ubuntu Linux の開発や MySQL で使われています。

Bazaarの利点

いくつかのバージョン管理システムの中で Bazaar を選ぶ理由について、Bazaarのサイトに載っています。

個人的に「Bazaarエライ!」と思う点を挙げてみます。

1. 日本語ファイル名の扱い

gitやMercurialはファイル名を「バイト列」として扱います。 なので、Windowsユーザーがコマンドプロンプトでファイルをコミットしようとすると、 ファイル名はcp932エンコードでコミットされます。これをLinuxでcheckoutすると、 Linuxのlocaleを無視して、cp932ファイル名でファイルが作られてしまいます。

それに対して、Bazaarはファイル名を「Unicode文字列」として扱います。 コマンドライン引数のファイル名は、Windowsであればコードページ、Linuxであればlocaleに 基づいてデコードされます。 なので、Mercurialやgitのような問題は起こりませんし、「表」「ソ」といった 2バイト目に '\' (0x5c) が入っているパスでも問題が起きません。

2. TortoiseBzr の存在

Bazaarのダウンロードページ から、Windows Standalone Installer (まだ 1.9 は RC までしか installer がReleaseされていませんが、そのうち出ると思います) をインストールすると、TortoiseBzr や bzr-svn (BazaarをSubversionクライアントとして使えるプラグイン) をまとめたバイナリがダウンロードできます。

この TortoiseBzr は、TortoiseSVN のように使うことができます。1.9rc1のTortoiseBzrを少し使ってみたのですが、 特に日本語の化けもなく、普通に使えている様です。

3. Bazaarをインストールしていないサーバーにリポジトリを置ける

Bazaarは、ssh+bzrというプロトコルの他に、sftpやftpでサーバー上のリポジトリに対して push/pull が できます。 また、リポジトリを読み込み専用で公開したい場合、単にリポジトリをApacheによって公開されている ディレクトリに 置くだけで済みます。

なので、例えばFTPしか利用できないレンタルサーバーでも、Bazaarリポジトリ置き場にして、そのリポジトリを 公開することができます。sshでログインできるサーバーがあったとしても、セットアップ作業が要らないのは 便利です。

Launchpadについて

バージョン管理に Bazaar を使ったオープンソースプロジェクトのホスティングサービスに、 launchpad があります。 Bazaar リポジトリのホスティングの 他にも、バグ管理、アイデア管理、翻訳、メーリングリストが利用できます。Ubuntu Linux や MySQL もここで開発されています。

Bazaar ユーザー以外にも、例えば Ubuntu Linux ユーザーであれば、「このアプリの日本語訳間違ってる」と 思ったときに launchpad 上で翻訳の修正ができるので、アカウントを取ってみては如何でしょうか?

@methane
klab_gijutsu2 at 21:03|この記事のURLComments(1)TrackBack(0)
2008年09月22日

Python2.x/3.0のunicode内部表現について

はてなブックマークに登録

イントロ

Python2.6/3.0共にRC版がリリースされ、正式リリースが近づいて来ました。Python3.0の大きな変更の一つが、 Python2.xのstrとunicodeがunicode文字列のstrに統合され、従来のstrの代わりにbytesを導入することで、バイト列と文字列が明確に分けられたことです。

現在、Python2.5では、unicode文字列の内部表現がucs2のものとucs4のものがあり、それぞれの間では拡張 モジュールの互換性がなくなっています。Python2.6/3.0でこの状況がどう変化するのか調べてみました。

Python2.xのunicode内部表現について

Python2.5/2.6では、configureオプションに、--enable-unicode=ucs[24] というものがあり、デフォルトでは2になっています。 また、FedoraやUbuntuの最新版に入っているPythonのパッケージは、--enable-unicode=ucs4でビルドされています。

ucs2の利点として、Java/Windows/.NET上での相互運用性があります。 Javaや.NETの文字列オブジェクトを直接にPythonのUnicode文字列にマッピングできます。 Windows用のCPythonは、wfopenに直接ucs2のunicode文字列内部表現を渡すことで、unicodeパスに 対応しています。

もちろん、UCS-2を使う以上、Windows/Java/.NETも直面してきた、BMP外の文字への対応という問題もあります。 Windows/Java/.NETは、UCS-2にサロゲートペアを加えたUTF-16に対応することで、一文字2byteの内部表現のまま BMP外の文字へ対応してきました。Python2.5も同じくサロゲートペアに対応しています。

>>> u = u"\U00020b9f"
>>> print u
𠮟
>>> u
u'\U00020b9f'
>>> len(u)
2
>>> u[0]
u'\ud842'
>>> u[1]
u'\udf9f'

この用に、"\U"というエスケープを用いてBMP外の文字が入ったユニコードリテラルを生成すると、内部的には2つのcode unitと して扱われ、コード変換も正常に行われます。

ただし、code pointではなくてcode unitを単位とすると、for c in u: でサロゲートペアが別々にcに格納されたりして、 文字単位の操作が面倒になります。以前、小飼弾さんが perl, python & ruby - chr() vs. Unicode perl, python & ruby - ord() vs. Unicode でご指摘されたように、unichr()/ord()も、このcode unit単位の操作となり、BMP外の文字の扱いが面倒です。

Windows APIやJava/.NETの文字列との相互運用性を気にしなくてもよいLinux上のCPythonでは、ucs2を選ぶかucs4を選ぶかは メモリ消費量と使い勝手のトレードオフになります。Fedora/Ubuntuでは、使い勝手の良いucs4が選ばれたようです。先ほどの例を ucs4のPython2.5で実行するとこうなります。

>>> u = u"\U00020b9f"
>>> print u
𠮟
>>> len(u)
1
>>> u[0]
u'\U00020b9f'

Python2.6でも、Python2.5の動作を踏襲しているので、状況はまったく変わりません。(Pythonは2.x=>3.xのようなメジャー バージョンアップ以外で、下位互換性がなくなるような変更は基本的に入りません) ucs2/4間で拡張モジュールのバイナリ互換性はありませんし、ucs4のPythonで 1 code unit = 1 code point を前提に 作られたコードはucs2のPythonでBMP外の文字を正常に扱えません。

Python 3.0の変更点

Python 3.0では、configureオプションが変更され、 --with-wide-unicode になっています。 2.xに比べると、(1)unicode無しが選択できなくなった、(2)utf-16も扱えるのにconfigureオプションでucs2と書いていたのが修正された、 というだけの修正で、デフォルトでcode unitが2byteな事に変わりがありません。

>>> u = "\U00020b9f"
>>> print(u)
𠮟
>>> len(u)
2
>>> u[0]
'\ud842'
>>> u[1]
'\udf9f'
>>> u
𠮟

せっかく、文字列とバイト列が分離されたのに、文字列のAPIで「文字」ではなくて「code unit」などというものが単位になって しまったのは、個人的にすごく残念に思います。 python-devメーリングリストでも、私と同じように思った人が、utf-16のPython3.0でもcode unitではなくてcode pointを単位に してほしいという要望があったのですが、却下されてしまいました。 (該当する議論が行われたスレッド)

ただし、chr/ordについては、ucs2のPythonでもBMP外文字を扱えるようになりました。

>>> chr(0x00020b9f)
'𠮟'
>>> ord('\U00020b9f')
134047
>>> print(hex(_))
0x20b9f

ucs2で単位の操作をする方法ですが、今のところ標準の方法は用意されていないようです。先ほど紹介したMLのスレッドでも、 Guidoさんはこう書いています。(該当のポスト)

The one thing that may be missing from Python is things like interpretation of surrogates by functions like isalpha() and I'm okay with adding that (since those have to loop over the entire string anyway).

結論として、Python 2.6/3.0になっても、サロゲートペアへの対応は中途半端で、将来的に Javaと同じようなAPIの追加によってucs2のPythonでもBMP外の文字を含む文字列に対応する 可能性があるようです。

個人的には、これからもLinux上でucs4のPythonを利用することで、サロゲートペアへの対応は必要な時以外 考えないようにしようと思います。(だって面倒ですし…)


@methane
klab_gijutsu2 at 21:22|この記事のURLComments(0)TrackBack(0)
2008年07月14日

PasteDeployを使って複数のWSGIアプリを一つのプロセスに共存させる

はてなブックマークに登録

社内でWebアプリをデモしたいとか、開発環境としてLinuxサーバーが欲しくなったとき、「サーバー欲しい」と言えば すぐにLinuxの入った仮想環境をもらうことができます。

せっかくもらったrootを持ってるサーバーなので、MercurialやTracなど入れて遊んでいるのですが、 PCサーバー上の仮想マシンなので、メモリが何GBも載っている訳ではありません。 すこしメモリ使用量を削減してみることにしました。

いまどきのPython製Webアプリは、大抵WSGIという標準インタフェースを持っているので、 PasteDeploy(Pylonsが利用しているWSGIアプリ用のメタフレームワーク)を利用して、 一つのPythonプロセスに複数のWSGIアプリを同居させてみます。

PasteDeploy設定ファイル

Pylonsアプリの設定ファイルを流用して、複数のアプリをcompositeするように設定します。
# comp_exam.ini

# ...省略...

[composite:main]
use = egg:Paste#urlmap
/trac = tracapp
/hg = hgapp
/nanika = nanika

## factoryについては後述
[app:tracapp]
paste.app_factory = compapp:trac_factory
trac.env_parent_dir = /path/to/trac

[app:hgapp]
paste.app_factory = compapp:hg_factory
hgapp.rep_path = /path/to/hg/repo


## もとのPylonsアプリを、[app:main]から[app:適当な名前]に変更します。
[app:nanika]
use = egg:Nanika
lang = ja

# ...省略...

WSGIアプリケーションファクトリ

PasteDeployにWSGIアプリを渡すために、WSGIアプリのファクトリを作ります。このファクトリは、 引数にconfig(設定ファイルの内容)を受け取って、WSGIアプリのオブジェクトを返します。 上の設定ファイルに対応するcompapp.pyは以下の用になります。

#compapp.py
import mercurial.hgweb.hgweb_mod as hgweb
import trac.web.main as tracweb

def hg_factory(config, **localconfig):
    return hgweb.hgweb(localconfig['hgapp.rep_path'])

def trac_factory(config, **localconfig):
    def trac_app(environ, start_response):
        environ['trac.env_parent_dir'] = localconfig['trac.env_parent_dir']
        return tracweb.dispatch_request(environ, start_response)
    return trac_app
このファイルをPYTHONPATHが通ったところにおいて、通常のPylonsアプリを起動するのと 同じ感覚で起動します。
$ PYTHONPATH=. paster serve comp_exam.ini

これで、http://localhost/trac/ で Trac に、 http://localhost/hg/ で Mercurial にアクセスできるようになります。

追記: TracとPythonの最適化オプションについて

Trac-0.11が出たので入れてみたのですが、PYTHONOPTIMIZE=xという環境変数をセットしてPythonの最適化を有効にすると、Wikiのヘッダとフッタが出なかったりしてハマりました。Pythonの最適化オプションってあまり使われていないかもしれませんが、僕のように貧乏性な方はお気をつけください。


@methane
klab_gijutsu2 at 21:58|この記事のURLComments(0)TrackBack(0)
2008年07月10日

setuptoolsでモジュールのバージョンを指定する方法

はてなブックマークに登録

ほとんどのPythonistaがお世話になっているeasy_install (setuptools) ですが、よく、

$ easy_install "SQLAlchemy == 0.4.6"

のようにバージョン指定してモジュールをインストールすることがあります。

バージョン指定には不等号も使うことができるのですが、例えば0.4系の新バージョンが出てupgradeする場合に、

$ easy_install -U "SQLAlchemy < 0.5"

として最新版を自動的に探してupgradeしようとすると、0.5beta1がインストールされてしまいました。確かに 0.5betaは0.5よりも前のバージョンだろうけど…あぅあぅ…ということで、setuptoolsがバージョンをどう扱うか 調べてみました。

バージョン番号について

リリース番号
1.2.3 みたいな部分
タグ
1.2.3foobar のうち foobar の部分
プレリリースタグ
タグが辞書順で"final"よりも前だと、プレリリースタグになる。 ただし、rc、pre、preview は文字cと同じ扱い。 プレリリースタグがつくと、タグなしの同じリリース番号が示すバージョンよりも 過去のバージョンになる。 1.2.3pre3 < 1.2.3
ポストリリースタグ
タグが辞書順で"final"よりも後ろだと、ポストリリースタグになる。'-'も使える。 ポストリリースタグがつくと、より新しいバージョンを示す。 1.2.3 < 1.2.3r2 , 1.2.3rc1 < 1.2.3rc1-20080707

バージョン条件の指定の仕方

easy_install や、 setupスクリプトの依存関係記述において、バージョンに関する 複数の条件を、カンマで区切って指定できる。

$ easy_install "SQLAlchemy >= 0.4, < 0.5a"


いままで自分でsetupスクリプト(setup.py)を書くときに、○○以上というバージョン指定しか してなかったのですが、そのパッケージをeasy_installでインストールする人が依存パッケージの 自動インストールで想定したより新しい、しかもα版のパッケージをインストールしてしまったり していたので、これからは○○以上△△未満という形で書いていこうと思います。


@methane
klab_gijutsu2 at 18:38|この記事のURLComments(0)TrackBack(0)
2008年07月01日

Pythonで@deprecatedというデコレータを用意する

はてなブックマークに登録

@deprecatedというデコレータがあれば、

## そろそろ削除したい関数があるとき
@deprecated
def unsupportedfunc():
  nanika

## foo から bar に関数名を変えるとき
def bar():
  nanika
foo = deprecated(bar)
とできて便利です。

このdecoratorの実装例が、Cookbookに載っていました <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/391367> Cookbookのコメント欄にもありますが、stacklevelを設定することで、deprecatedな関数を呼んだのがどこかを調べることができます。
        warnings.warn("Call to deprecated function %s." % func.__name__,
-                      category=DeprecationWarning)
+                      category=DeprecationWarning,
+                      stacklevel=2)

Python 2.5にdeprecated decoratorを入れるかどうかがPython-Devで話題になっていたのですが、今のところ__builtin__にも標準ライブラリにも入ってないようです。(あまり深く追っていません)


@methane
klab_gijutsu2 at 14:15|この記事のURLComments(0)TrackBack(0)
2008年06月19日

Linux上のPythonのzipfileを使って、permissionを気にしないでzipファイルを作成する

はてなブックマークに登録

Pythonの標準ライブラリには、zipファイルを扱うためのzipfileモジュールがあります。以下のようにして、簡単にzipファイルを作成することが出来ます。
In [1]: from zipfile import ZipFile

In [2]: z = ZipFile('foo.zip', 'w')

In [3]: z.writestr('foo.txt', 'foo\nbar\nbaz')

In [4]: z.close()
しかし、これをunzipすると、permissionが全部0になってしまいます。
In [5]: !unzip foo.zip
Archive:  foo.zip
 extracting: foo.txt                

In [6]: !ls -l
total 8
---------- 1 naoki naoki  11 Jun 19 18:21 foo.txt
-rw-r--r-- 1 naoki naoki 123 Jun 19 18:21 foo.zip
いくつかのフラグを操作すれば任意のパーミッションを設定できるはずなのですが、特に設定したいパーミッションが無い場合は、zipファイルが Windows上で作成されたと偽装することで、unzip時にデフォルトのパーミッションが設定されるようにすることが出来ます。
In [7]: z = ZipFile('bar.zip', 'w')

In [8]: z.writestr('bar.txt', 'foo\nbar\nbaz')

In [9]: for zi in z.filelist:                    
   ...:     zi.create_system = 0   # Linuxだとデフォルトで3が設定されている。
   ...:    

In [10]: z.close()

In [11]: !unzip bar.zip
Archive:  bar.zip
 extracting: bar.txt                

In [12]: !ls -l bar.txt
-rw-r--r-- 1 naoki naoki 11 Jun 19 18:23 bar.txt

@methane
klab_gijutsu2 at 18:35|この記事のURLComments(0)TrackBack(0)
2008年04月01日

WindowsでPython Scriptの起動用exeを用意する

はてなブックマークに登録

MercurialやTaskCoachといったPython製ツールをWindows上で使うとき、py2exeでビルドして配布されているパッケージを利用するとインストールはとても簡単なのですが、アプリ毎にpython25.dllといったdllやPythonモジュール群が大量にインストールされるのが欠点です。

普段からPythonを利用している人にとっては、distutilsを利用したインストール(easy_installやsetup.py install)の方がメモリ効率の面でも起動速度等の面でも有利な筈です。僕はできるだけこちらの方法を使っていて、起動速度・最小化解除のレスポンス共に向上することが体感できます。(プラシーボ効果かもしれませんが)

ただし、Mercurialをeasy_installしたところ、hg.exeが生成されず、Pythonをインストールしたディレクトリの下のScriptsディレクトリに"#!"(shebang)を利用した起動スクリプトであるhgが生成されただけでした。また、TaskCoachも、taskcoach.pyとtaskcoach.pywがあるだけでした。もちろんこのままでも利用はできます。でも、例えばNetBeansでMercurialを利用しようとするとhg.exeの場所を指定しないといけなかったりするので、やはり起動用の実行ファイルがあったほうが便利です。

そこで、easy_install.exeのような起動用実行ファイルを作るにはどうしたら良いのだろうと、distutilsを拡張するモジュールであるsetuptoolsを調べてみました。これを使えば、非常に簡単にPythonスクリプトをexeから起動できるようになります。

起動用実行ファイルは、コマンドプロンプトを開かないWindowアプリ用とコマンドプロンプトを開くコマンドライン用の2種類があり、以下のような動作をします。

  1. 起動対象スクリプトのパスを、GetModuleFileName()の結果から求めます。
    パスから拡張子(.exe)を取り除いた後、Windowアプリ用なら"-script.pyw"を、コマンドライン用なら"-script.py"をつけたものが、起動対象のスクリプトになります。
  2. 起動対象スクリプトを開き、先頭に"#!"があるかどうかを調べます。"#!"があれば、続きを起動するPythonインタプリタとして扱い、"#!"が無ければ"python.exe"をPythonインタプリタとします。
  3. 引数を渡してsapwn()します。

setuptoolsをインストールしてあれば、この起動用実行ファイルが、Lib/site-packages/setuptools/の中にgui.exeとcli.exeの名前で入っています。名前から判るように、gui.exeがWindowアプリ用、cli.exeがコマンドラインアプリ用です。例えば、次のようにすれば、hg.exeとtaskcoach.exeを用意することができます。

C:\python\Scripts>copy hg hg-script.py
C:\python\Scripts>copy ..\Lib\site-packages\setuptools\cli.exe hg.exe
C:\python\Scripts>copy taskcoach.pyw taskcoach-script.pyw
C:\python\Scripts>vim taskcoach-script.pyw (先頭に #!C:\python\pythonw.exe を追加)
C:\python\Scripts>copy ..\Lib\site-packages\setuptools\gui.exe taskcoach.exe

同じ方法で、自作のスクリプトでもお手軽にexe化できます。これでWindowsユーザーのPythonライフが少し楽しくなればと思います。


@methane
klab_gijutsu2 at 17:12|この記事のURLComments(3)TrackBack(0)
Blog内検索
Archives
このブログについて
DSASとは、KLab が構築し運用しているコンテンツサービス用のLinuxベースのインフラです。現在5ヶ所のデータセンタにて構築し、運用していますが、我々はDSASをより使いやすく、より安全に、そしてより省力で運用できることを目指して、日々改良に勤しんでいます。
このブログでは、そんな DSAS で使っている技術の紹介や、実験してみた結果の報告、トラブルに巻き込まれた時の経験談など、広く深く、色々な話題を織りまぜて紹介していきたいと思います。
最新コメント