PIL 1.1.6

Python Imaging Libraryこと、PIL。
画像をいじったりできるアレです。

http://www.pythonware.com/products/pil/

さらーっとドキュメントを見た感じだと、いろいろできそうです。
ReviewBoardの必須モジュールになっていたりとPythonで画像扱う場合は有名なんでしょうか?

画像を正方形に切り出して縮小してJpegで保存するような処理を適当に書いてみるとこんな感じ。


import sys
import Image

src = Image.open('test.bmp')
if src.size[0] > src.size[1]:
delta = src.size[0] - src.size[1]
box = (delta / 2, 0, (src.size[0] - delta / 2), src.size[1])
elif src.size[1] > src.size[0]:
delta = src.size[1] - src.size[0]
box = (0, delta / 2, src.size[0], (src.size[1] - delta / 2))
else:
box = (0, 0, src.size[0], src.size[1])

try:
src.crop(box).resize( (300,300) ).save('out.jpg')
except IOError:
sys.exit(1)

sys.exit()

GIMPで同じように縮小して、クオリティ50%で保存したJpegと比較してみるとちょっと文字とかがつぶれすぎな印象。時間があるときにもうちょっと使ってみよう。

SQLAlchemy 続き

普通にSELECT文。

まずは適当にテーブルを作ってデータを適当に投入。


mysql> select * from maker;

                              • +
id name
                              • +
1 SOMY
2 MEC
3 HiTouth
                              • +


mysql> select * from goods;

                                                                                                        • +
id maker_id name
                                                                                                        • +
1 1 Walkman
2 1 AIBO
3 1 PSP
4 2 PC-FX
5 2 スパコン
6 3 パンポン
7 3 気になる木
                                                                                                        • +

Tableインスタンスマッピングするクラスを定義してある状態で...


for obj in sess.query(Maker).order_by(Maker.id):
print obj.id, obj.name

1 SOMY
2 MEC
3 HiTouth

という感じでSELECT。

この場合、実際にバックグラウンドで実行されるSQL

SELECT maker.id AS maker_id, maker.name AS maker_name 
FROM maker ORDER BY maker.id
こんな感じ。

次はSQLでいうとこのWHERE。


for obj in sess.query(Goods).filter_by(maker_id=1).order_by(Goods.id):
print obj.id, obj.maker_id, obj.name
1 1 Walkman
2 1 AIBO
3 1 PSP

別の書き方。


for obj in sess.query(Goods).filter(Goods.maker_id==1).order_by(Goods.id):
print obj.id, obj.maker_id, obj.name
結果はさっきと同じ。

LIKEを使う


for obj in sess.query(Maker).filter(Maker.name.like('%M%')).order_by(Maker.id):
print obj.id, obj.name

複数条件(AND)


from sqlalchemy import and_
for obj in sess.query(Goods).filter(and_(Goods.maker_id<3, Goods.name.like('%FX%'))).order_by(Goods.id):
print obj.id, obj.maker_id, obj.name

Zope3いれてみる

PyPIを見て常々、Zope関連のモジュール(特に3)が多いなーと思う。GoogleAppEngineに採用されてDjangoが大流行じゃなかったのかと。

というわけでZope3メモ。Windowsマシン前提です。

Zope2系とちがって、Windows用でもPython同梱ではない模様。あらかじめ2.4系のPythonを入れておきましょう。なければインストーラが進みません。

インストール先は問答無用でPythonインストールフォルダになります。レジストリの情報を参照するようです。

インストール後、まずはZopeインスタンスを作らないと始まらない。
PythonインストールフォルダがC:\Python24の場合、C:\Python24\Scriptsの下にmkzopeinstance.batがインストールされているはずなのでこいつを実行する。

対話形式で、

  1. インスタンス生成先
  2. 管理者のID
  3. パスワードの暗号形式(プレーンテキスト、MD5SHA1)
  4. 管理者のパスワード

が聞かれる。
全部ちゃんと答えると、インスタンスが生成。

インスタンスを生成したところにたくさんファイルが生成。
そこのbinディレクトリに、runzope.batがあるはずなので、そいつを実行。
ブラウザで
http://localhost:8080/にアクセスすると、そこがZope

しかしこれだけじゃ何をして良いのやらさっぱり(´・ω・`)
ぼちぼち英文のチュートリアルでも見ながらやってみよう。

SQLAlchemy 0.5.2

メインゴールはあなたのデータベースとSQLへの考え方を変えること!

ということを目標に作られている、O'REILLYから本(ISBN:0596516142)も出てる有名O/Rマッパです。
本家のチュートリアルWEB+DB PRESS Vol.46の記事でもSQLiteを使ってましたので、MySQL5.1で使ってみます。

準備

SQLAlchemyのインストール

普通にeasy_installで大丈夫です。これを書いている時点ではバージョン指定なしで、0.5.2がインストールされます。


easy_install sqlalchemy

DB APIのインストール

SQLAlchemy以外に、それぞれのRDBM用のDB APIが必要になります。
今のところサポートしているRDBMとそのAPIの一覧はここに。
今回MySQLなので、あらかじめMySQLdbをインストールしておきます。PyPIからの通常のやり方ではインストールできなかったので、DLページ指定でインストールします。


easy_install -f "http://sourceforge.net/project/showfiles.php?group_id=22307&package_id=15775&release_id=491012" mysql_python

セッションを作る

まずエンジンを定義して、それを元にセッションを生成〜という流れになります。
で、エンジンはsqlalchemy.create_engine関数に、RFC-1738形式のURLを渡すことで生成します。
MySQLlocalhosthogeデータベースに接続する場合は


engine = create_engine('mysql://user:password@localhost/hoge', echo=True)
という感じで。
echo=True としておくと実行したSQLやらを標準出力に出してくれます。デフォルトはFalseです。

sqlalchemy.orm.sessionmaker()でセッションクラスを生成します。
この戻り値がセッションのサブクラスになっているので、これをアプリケーションのグローバルスコープでやってくれとのことです。つまり、


Session = sqlalchemy.orm.sessionmaker(bind=engine)
としておけば、後はセッションがほしくなったときにその場所で

sess = Session()
すればセッションゲットだぜ!という寸法です。クラスをオブジェクトとして利用できるPythonらしい方法だなーと。

ちなみにsessionmaker()関数はキーワード引数をとり、エンジンを指定するbind以外にも
autocommit(bool型でデフォルトはFalse)やautoflushなどいろいろ指定できますようで。詳しくはこちら

テーブル定義

テーブルはsqlalchemy.schemaパッケージのTableクラスのインスタンスとして定義します。
インスタンス化するときに、カラムの定義としてColumnクラスを、型の定義に型用のクラスを使います。この辺のものはsqlalchemy/__init__.pyでインポートされているので、特に型についてはそれを見るとどんな型があるのか一目でわかると思います。

でまぁ、こんなソースを書いて


import sqlalchemy
from sqlalchemy import Table, Column, Integer, String
metadata = sqlalchemy.MetaData()
hoge_table = Table('hoges', metadata,
Column('id', Integer, primary_key=True),
Column('name', String(50)),
Column('level', Integer, server_default='1'),
mysql_engine='MyISAM',
mysql_charset='utf8')

if __name__ == '__main__':
from sqlalchemy import create_engine
engine = create_engine('mysql://user:pass@localhost/hoge', echo=True)
metadata.create_all(engine)

んでこれを実行すると、

CREATE TABLE hoges (
id INTEGER NOT NULL AUTO_INCREMENT,
name VARCHAR(50),
level INTEGER DEFAULT '1',
PRIMARY KEY (id)
)ENGINE=MyISAM CHARSET=utf8
というSQLが実行されて

mysql> describe hoges;

                                                                                                                            • +
Field Type Null Key Default Extra
                                                                                                                            • +
id int(11) NO PRI NULL auto_increment
name varchar(50) YES NULL
level int(11) YES 1
                                                                                                                            • +

となりました。

Columnクラスのキーワード引数、server_defaultに値を指定しておけば、デフォルトになるんですが、カラムの型がIntegerでもstr型で指定しないとエラーになります。
MySQLの場合はエンジンやらcharsetやらありますので、mysql_ほにゃらら=ごにょごにょ という感じで指定します。

metadataは、なんか特殊なことをするときに利用するそうで、引数なしでやっておけばデフォルト値が全部使われます。

Tableクラスのインスタンスとして定義したテーブルの情報はクラスとマッピングするときに使いますので、上で書いたソースは別のファイルからimportして使えるようにしています。

ちなみにテーブルを作るだけならセッションを生成する必要はありません。

テーブルとクラスのマッピング

Tableクラスのインスタンスとして定義したテーブルを、クラスに紐付けます。
クラスは全部の属性を定義する必要は必ずしもなく、中身がpassしてるだけのクラスでも、カラム名をそのままオブジェクトの属性として使ってくれます。

マッピングには sqlalchemy.orm.mapperを使い、


sqlalchemy.orm.mapper(クラス名, テーブルインスタンス)
とすることでマッピングできます。

レコードの追加

というわけでhoge_tableのレコードをHogeクラスとして定義し、マッピング
その後、てきとうなデータを3つインサートし、コミットしてみます。
マッピングするクラスはどうやら新スタイルクラスである必要があるみたいです。


import sqlalchemy.orm
from mytables import hoge_table

engine = sqlalchemy.create_engine('mysql://user:password@localhost/hoge', echo=True)
Session = sqlalchemy.orm.sessionmaker(bind=engine)
sess = Session()

class Hoge(object):
def __init__(self, name, level=None):
self.name = name
self.level = level

sqlalchemy.orm.mapper(Hoge, hoge_table)

hoge1 = Hoge('ジャン')
sess.add(hoge1)

hoge2 = Hoge('ポール', 3)
sess.add(hoge2)

hoge3 = Hoge('ベルモント', 5)
sess.add(hoge3)

sess.commit()

これを実行すると、


2009-02-01 05:13:04,062 INFO sqlalchemy.engine.base.Engine.0x...37f0 BEGIN
2009-02-01 05:13:04,078 INFO sqlalchemy.engine.base.Engine.0x...37f0 INSERT INTO hoges (name) VALUES (%s)
2009-02-01 05:13:04,092 INFO sqlalchemy.engine.base.Engine.0x...37f0 ['\xe3\x82\xb8\xe3\x83\xa3\xe3\x83\xb3']
2009-02-01 05:13:04,171 INFO sqlalchemy.engine.base.Engine.0x...37f0 INSERT INTO hoges (name, level) VALUES (%s, %s)
2009-02-01 05:13:04,171 INFO sqlalchemy.engine.base.Engine.0x...37f0 ['\xe3\x83\x9d\xe3\x83\xbc\xe3\x83\xab', 3]
2009-02-01 05:13:04,171 INFO sqlalchemy.engine.base.Engine.0x...37f0 INSERT INTO hoges (name, level) VALUES (%s, %s)
2009-02-01 05:13:04,171 INFO sqlalchemy.engine.base.Engine.0x...37f0 ['\xe3\x83\x99\xe3\x83\xab\xe3\x83\xa2\xe3\x83\xb3\xe3\x83\x88', 5]
2009-02-01 05:13:04,187 INFO sqlalchemy.engine.base.Engine.0x...37f0 COMMIT
と標準出力にアウトプットされます。

あら?と思うかもしれませんがテーブルのエンジンはMyISAMでも、commitしないとデータが反映されないみたい。commit()したときにSQL発行している模様です。


複数レコードを同時に投入する場合は


sess.add_all([hoge1, hoge2, hoge3])
という感じでリストで渡した方がスマートです。

PyYAML 3.0.8

いろんなフレームワークで設定ファイルなどに採用されているので、PythonYAMLを読んでみる。

おそらくメジャーどころのYAML用モジュールとしてはSyckを利用するPySyckもありますが、PyYAMLというのもあります。

ちなみに実際にインポートするパッケージ名は

  • PyYAMLだとyaml
  • PySyckだとsyck

です。

両方ともload()とdump()というかたちで入出力を実装しているので、扱い方はほとんど一緒かなと。

とりあえず読む

load()の第一引数にYAML形式の文字列か、file型を読み込めます。
ただしfileの場合は

エンコードで記述してある必要があるとのこと。
こうしてファイルから読み込んだ場合、マルチバイト文字列はUnicodeになります。

オプションですがキーワード引数で


yaml.load(file, Loader=yaml.CLoader)
とすればYAMLのパースにCで書かれたLibYAMLを利用するとのことなので、デフォルトのパーサの実装まで見てませんがそれがPurePythonなら、速度が向上すると思われます。

単純な例だとこんな感じ。


>>> import yaml
>>> src = yaml.load('hoge: [ [1,2,3], [a,b,c], {foo: FOO,bar: BAR}]')
>>> type(src)

>>> src
{'hoge': [[1, 2, 3], ['a', 'b', 'c'], {'foo': 'FOO', 'bar': 'BAR'}]}

書き出す

dump関数で出力できます。
単純な例だとこんな感じ。


>>> import yaml
>>> src = {'foo': [1,2,3], 'bar': {'a': 'aaa', 'b': 'bbb'}, 'baz': (11, 22)}
>>> yaml.dump(src)
'bar: {a: aaa, b: bbb}\nbaz: !!python/tuple [11, 22]\nfoo: [1, 2, 3]\n'
タプルもシリアライズされて出力されてます。オブジェクトもシリアライズしてくれるそうです。あんまり使う機会はないと思いますが。

ちなみに第2引数にファイル型をとると、そいつに出力してくれます。

ただ、マルチバイト文字列がある場合には


# -*- coding: utf-8 -*-
import yaml

hoge = [u'日本語']
print yaml.dump(hoge)

hoge = ['日本語']
print yaml.dump(hoge)

こういうコードを実行すると、

["\u65E5\u672C\u8A9E"]

[!!python/str "\u65E5\u672C\u8A9E"]

モロにこういう内容が書き出されるわけで・・・、多分別の実装からでは読み込めないんじゃないかなーと。

YAMLをプログラムから書き出すなんてあまりすることはないと思いますが、もうちょっとスマートなやり方とかないんでしょうかね。

Colubrid 0.10

PythonといえばWSGIWSGIといえばPython
というわけで、シンプルなWSGIパブリッシャーColubridでWSGIの体験です。
コードも短くてシンプルなのでサクッと読めてしまいます(WSGIミドルウェアが提供してくれる引数が得体が知れないのでそこら辺若干の読みづらさはあるかもですが)。

PyPIページ
開発元

あと、WSGIについては、ちょっと長いですが

http://wiki.pylonshq.com/display/pylonsja/PEP333-ja

を見ればどういったものかわかると思います。


PyPIのcolubridのページにHelloWorldのサンプルがあるんですが、そのソースだけではどう動かしてよいのやら?な感じで、また、開発元のドキュメントにあるサンプルソースもちょっと古いのか?うまく動かないっぽいです。

で、動くHelloWorldはこんな感じに。

from colubrid import BaseApplication, HttpResponse
from colubrid import execute

class HelloWorld(BaseApplication):
    def process_request(self):
        return HttpResponse('Hello World!')

app = HelloWorld

if __name__ == '__main__':
    execute()

このソースを実行すると、8080番ポートでhttpdが起動しますんで、ブラウザにアクセスすれば無事『Hello World!』が表示されるわけです。

このソース自体のメインの処理はexecute()関数ですが、これの本体は

colubrid.server.execute

でして、colubrid/__init__.pyで

from colubrid.server import execute

としてあるので、server.pyのexecute関数が実行されているわけですね。

server.pyのexecute()関数ではWebサーバモジュールを読み込んで実行しているんですが、

  • pasteのhttpserver
  • BaseWSGIServer
  • wsgiref

を上から探して、importできたものをWebサーバとして使ってくれます。

execute()関数に与える引数は

  • app WSGIでいうところのcallableなアプリケーション。デフォルトはNone
  • debug デフォルトでTrue
  • hostname 見てのとおりホスト名。デフォルトで'localhost'
  • port ポート番号。デフォルトで8080
  • reload リロードフラグ、デフォルトでFalse
  • evalex デフォルトでFalse

となってまして、基本的にapp以外デフォルト値が決まってるんでさっきのHelloWorldではexecute()関数に何も渡しませんでしたが動作したわけです。
一番下の二つは本筋から離れるので気にしない方向で。ていうかそこまで追っかけてみてません。。

appについては

app = frm.f_globals['app']

という具合でexecute()の呼び元から拾ってきてくれます。
つまりはHelloWorldクラスがappとして利用されるわけです。*1

ここで冒頭で紹介したPEP333に戻るわけですが、アプリケーションオブジェクトの定義について

アプリケーションオブジェクトは 2つの固定引数を受け付けなければならない。例証のために、それらを environ と start_response と名付けるが、これらの名前を持つ必要はない。サーバまたはゲートウェイは、固定引数 (キーワード引数ではなく) を使用してアプリケーションオブジェクトを呼び出さ なければならない。 (例えば、上に示されるように、result = application(environ, start_response) のようにして呼び出す)

とあります。

HelloWorldクラスの親クラスであるBaseApplicationクラスは、colubridのapplicationモジュールで定義されていて、そのコンストラクタは

def __init__(self, environ, start_response, request_class=Request):

なってます。変数名がPEPと同じ表現なので一目でわかりますね。

また、アプリケーションオブジェクトについて追加で

アプリケーションオブジェクトは、サーバによって呼ばれると、文字列を yield する iterable を返さなければならない。

とあります。

BaseApplicationクラスは

def __iter__(self):

メソッドが宣言されているため、この条件も満たす、というわけです。


単純な例でしたが、とどのつまり
WSGIに乗っかるフレームワーク(もしくはアプリケーション)側は最低限これだけ満たせば良いってことが実際のソースレベルでわかったつもりになれるかなと思います(´・ω・`)

*1:Pythonではクラスもオブジェクトとして扱えます

CleverCSS 0.1

何にするか物色するだけで2時間掛かってしまったので、Pythonらしくかつ簡単なのをチョイス。

紹介文の中に

inspired by Python

とあるように、{}を使わずにインデントでスタイルシートを表現して、それを通常の形式にコンバートしてくれるやつです。

インストールはeasy_installで一発でした。

easy_install clevercss


インデント形式なので、ぱっと見YAMLっぽい印象。同じ記述を何度も書く必要がないので、タイプ量は減らせるようです。
問題はデザイナーさんがこれを使うかというとこかとおもいますが(´ω`)

http://pypi.python.org/pypi/CleverCSS/0.1

英文を読まなくても簡単な例が書いてあるのでそれを見ると一目瞭然かと思います。

Pythonのパッケージらしく、モジュールとして利用する方法とコマンドラインから利用する方法、両方使えます。

  • モジュールとして使う場合

import clevercss
before = '''
#インデントの形式で記述したCSS
'''
print clevercss.convert(before)

python clevercss.py 元ファイル


てな具合です。