銀月の符号

Python 使い見習いの日記・雑記

SQLAlchemy で Firebird その0

まる 1 日かかって Python, SQLAlchemy 経由で Firebird データベースが扱えるようになったけれど、なにを行ってきたのか忘れそうなので覚書を残す。記録をとっておかないと名前に惹かれてつかってみようと思い至ったもののあっさり挫折した 3 年前の自分にすぐ戻りそうだ。

今回は環境の準備について。タイトルに反して SQLAlchemy はまだでてこない。

環境

とりあえず Firebird 用意しないと始まらない

firebird2.1-super (version 2.1.3.18185-0.ds1-6build1(lucid)) パッケージがあるので入れるだけ。 configure; make; make install; よりも簡単ってどうなのよ?

ちなみに Python からアクセスするための kinterbasdb も python-kinterbasdb (version 3.3.0-2) という名でパッケージが用意されている。ついでに SQLAlchemy もあるけれど python-sqlalchemy(version 0.5.8-1) とバージョン 0.5.8 だったのでこちらは easy_install sqlalchemy で 0.6.3 にした。

パッケージから入れ終わったら、 SYSDBA のパスワードの設定。

$ sudo dpkg-reconfigure firebird2.1-super

適当に変更しておく(最大8文字)。以下、記事内でのパスワードは masterkey としておく。

その後は firebird のユーザーを作成する。

$ gsec -user SYSDBA -password masterkey

gsec のインタラクティブモードに入る。ここでは display で既存ユーザーの確認、 add 'spam' -pw 'spamspam' で パスワード spamspam のユーザー spam の作成、 delete 'spam' で ユーザー spam の削除となる。

最後にデータベースファイルの置き場所の用意。どこでもいいけれど Firebird 専用とするため既存のディレクトリは使わず mkdir する。できたらアクセス権限の設定。 sudo chgrp firebird .; sudo chmod 770 . して、 firebird グループがアクセスできるようにすれば OK 。

起動と停止

まずは Firebird の起動、停止、再起動を覚えねば…。

$ sudo /etc/init.d/firebird2.1-super start
$ sudo /etc/init.d/firebird2.1-super stop
$ sudo /etc/init.d/firebird2.1-super restart

isql-fb で初めての Firebird

次にデータベースの作成。 Firebird だと 1 データベースは 1 ファイルとなる。
データベースを作るときはファイルパスを指定することになる(エイリアスをつけておけばこれでも可能、後述)。
カレントディレクトリに spam.fdb というデータベースファイルを作るには次のようにする。

$ isql-fb -u spam -p spamspam
Use CONNECT or CREATE DATABASE to specify a database
SQL> CREATE DATABASE 'spam.fdb' DEFAULT CHARACTER SET UTF8;
SQL> COMMIT;

特別な意図がなければデータベースのデフォルト文字コードの指定をしておくと吉。今回はふつうに UTF8 で。

データベースができたので、早速接続してみる。

SQL> CONNECT 'spam.fdb';
Database:  'spam.fdb', User: spam

なにかテーブル作ってみる

SQL> CREATE TABLE abc(
CON>   a INTEGER PRIMARY KEY,
CON>   b INTEGER,
CON>   c INTEGER NOT NULL
CON> );

なにか INSERT してみる。

SQL> INSERT INTO abc(a, b, c) VALUES (1, 10, 100);
SQL> INSERT INTO abc VALUES (2, 20, 200);
SQL> INSERT INTO abc(a, c) VALUES (3, 300);
SQL> COMMIT;

カラムの指定をしなかった場合は全部の列を定義順に指定したものとして扱われる。カラムの値を指定していないものがある場合 DEFAULT の値が入る。 DEFAULT がなければ null が入る。NOT NULL 制約が入っているカラムの値を省略した場合、 null を入れようとするができないのでエラーとなる、とまぁイメージどおりの挙動。

とりあえず SELECT してみる。

SQL> SELECT * FROM abc;

           A            B            C 
============ ============ ============ 
           1           10          100 
           2           20          200 
           3       <null>          300 

うん、データベース作ってテーブル作って値入れることができることを確認。

Python, kinterbasdb で初めての Firebird

kinterbasdb は DB-API 2.0 準拠。 connect するさいは dsn にファイルパス(or エイリアス)、 user にユーザー名、 password にパスワードを指定するようにする。

$ python
Python 2.6.5 (r265:79063, Apr 16 2010, 13:09:56) 
[GCC 4.4.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import kinterbasdb
>>> conn = kinterbasdb.connect(dsn='spam.fdb', user='spam', password='spamspam')
>>> cur = conn.cursor()
>>> cur.execute('SELECT * FROM abc')
>>> for row in cur:
...   print row
... 
(1, 10, 100)
(2, 20, 200)
(3, None, 300)

問題なく。

でもデータベースにアクセスするのにファイルパスでってどうなのよ?

ファイルの場所が明らかになってしまっていたり、(Firebird がアクセスできる場所に)データベース作り放題だったり。そんな疑問をもったらエイリアスとアクセス制限を行う。 aliases.conf と firebird.conf の DetabaseAccess で設定できる。これらの設定ファイルは /etc/firebird/2.1 ディレクトリにある。

エイリアスを設定すればファイルパスを直接扱わなくてもよくなる。

DatabaseAccess に None を指定すればファイルパスでのアクセスを禁止できる。 Restrict を指定すればファイルパス指定でアクセスできるディレクトリを限定できる。

インストール直後の aliases.conf, firebird.conf には設定例が満載されているので一目見ればなんとなくわかるようにできている。

aliases.conf
# ------------------------------
# List of known database aliases
# ------------------------------

#
# Example Database:
#
employee.fdb = /var/lib/firebird/2.1/data/employee.fdb
employee = /var/lib/firebird/2.1/data/employee.fdb

#
# Live Databases:
#
firebird.conf(一部抜粋)
# ----------------------------
# Database Paths/Directories
#
# DatabaseAccess may be None, Full or Restrict. If you choose Restrict,
# provide ';'-separated trees list, where database files are stored.
# Relative paths are treated relative to RootDirectory entry
# (see above). Default value 'Full' gives full access to all files
# on your site. To specify access to specific trees, enum all required
# paths (for Win32 this may be something like 'C:\DataBase;D:\Mirror',
# for unix - '/db;/mnt/mirrordb'). If you choose 'None', then only
# databases listed in aliases.conf can be attached.
#
# Note: simple quotation marks shown above should *NOT* be used when
# specifying values and directory path names. Examples:
#
# DatabaseAccess = None
# DatabaseAccess = Restrict C:\DataBase
# DatabaseAccess = Restrict C:\DataBase;D:\Mirror
# DatabaseAccess = Restrict /db
# DatabaseAccess = Restrict /db;/mnt/mirrordb
# DatabaseAccess = Full
#
# UNCONTROLLED DATABASE ACCESS MAY COMPROMISE YOUR SYSTEM!
# IT IS STRONGLY RECOMMENDED THAT THIS SETTING BE USED TO LIMIT
# DATABASE LOCATIONS!
#
# Type: string (special format)
#
#DatabaseAccess = Full


# ----------------------------

これで準備 OK 。次は SQLAlchemy を、といったところで時間切れ。続く…。

おまけ Windows からも Python, kinterbasdb でアクセス

Firebird 2.1.3 のインストーラーを公式サイトより持ってきてインストール。途中でインストール内容を聞かれるけれども、クライアントだけで OK 。そして Python, kinterbasdb を用意したら準備完了。

リモートのデータベースにアクセスするには dsn に ipアドレス or ホスト名:パス名 or エイリアス と指定するようにする。

Python 2.6.4 (r264:75708, Oct 26 2009, 08:23:19) [MSC v.1500 32 bit (Intel)] on
win32
Type "help", "copyright", "credits" or "license" for more information.
>>> import kinterbasdb
>>> conn = kinterbasdb.connect(dsn='192.168.xxx.xxx:spam', user='spam', password=
'spamspam')

接続できたらあとは同じ。

おまけ VirtualBox のホストからゲストへのアクセス

VirtualBox のホストからゲストへアクセスできるようにするには設定、ネットワークアダプタからホストオンリーアダプタを追加する。

Python レシピ更新 ハッシュの要素を挿入した順に取り出す

118:ハッシュの要素を挿入した順に取り出す』を 「PEP 372 で順序つき辞書提案中」という内容から、「標準モジュール collections に OrderedDict があるよ」という内容に更新!

collections.OrderedDict 、その実装

collections.OrderedDict の実装は「順序の保持に双方向循環リストを用いる」というものだった。すべてのメソッドの計算量が通常の dict と同じオーダーとなっている*1とのこと。

なので「順序の保持に Python 組み込みの list*2 を用いる」実装のものとは特性が異なる操作があったりする。たとえば OrderedDict だと値のセット(__setitem__)は O(1)。 http://dev.pocoo.org/hg/sandbox/raw-file/tip/odict.py だと既存のキーがすでに存在しないかどうか線形探索するので O(n) 。代わりにインデックスアクセスは OrderedDict には用意されていなくて、無理に行えば O(n) (コード例は以下)。 odict だと byindex メソッドで O(1) 。

OrderedDict に無理やりインデックスアクセスする例1
>>> from collections import OrderedDict
>>> a = OrderedDict()
>>> a['x'] = 'X'
>>> a['y'] = 'Y'
>>> keys = list(a)
>>> keys[0]
'x'
>>> a[_]
'X'
OrderedDict に無理やりインデックスアクセスする例2

メモリ消費量および先頭に近いほうの値を取り出す際の速度が「例1」のものより有利ではあるものの、負の値のインデックスをつかうことはできない。

>>> from collections import OrderedDict
>>> from itertools import islice
>>> def nth(iterable, n, default=None):
...     return next(islice(iterable, n, None), default)
...
>>> a = OrderedDict()
>>> a['x'] = 'X'
>>> a['y'] = 'Y'
>>> nth(a, 0)
'x'
>>> a[_]
'X'
>>>
>>> nth(a, -1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in nth
ValueError: Indices for islice() must be None or an integer: 0 <= x <= maxint.

*1:原文:「Big-O running times for all methods are the same as for regular dictionaries.」

*2:CPython の list は PyObject へのポインタを保持する配列。 PyListObject 構造体は PyObject **ob_item; Py_ssize_t allocated; というメンバを持っていて、そのコメントには Vector of pointers to list elements. list[0] is ob_item[0], etc. とか ob_item contains space for 'allocated' elements. The number currently in use is ob_size. とある。

Windows Python 2.7 でも MeCab 0.98

先週土曜 2010/8/7 に WindowsMeCab 0.98 の Python 2.7 バインディングをビルドしました。方法はやっぱり以前と変わりなしです。

前々回
形態素解析エンジン MeCab 0.97 とその Python バインディングを MinGW でビルドする - 銀月の符号
前回
形態素解析エンジン MeCab 0.98pre3 野良ビルド - 銀月の符号

つまり、使い方も変わりなしです。そして、『ナイーブベイズを用いたブログ記事の自動分類 - 人工知能に関する断創録』によるとMeCabの本サイトmecab-0.98.exe と問題なく共存できるみたいです。これを入れてしまえば mecabrc の設定・指定が不要になり、また辞書を個別に落とす必要もないみたいです。id:aidiary さん、ありがとうございます。後日、自分でも確かめてみます。

ちなみに MinGW をつかっているけれど、理由は Python 2.5 公式版が VisualStudio.NET 2003 でコンパイルされていて入手できていなかったからだった。しかし Python 2.6, 2.7 の公式版は VisualStudio 2008 。 Express Edition なら手に入る、というかもう入っている。 MinGW でなければならない理由はなくなってしまってるんだよね。

次の休みには VS でビルドしてみるかな。こっちのほうがパッチ当てしなくても簡単にできてしまうかもしれない。

しかし最近の自分は仮想マシンUbuntu、 みたいなのにも慣れてきたので Windows だとビルドがめんどくさいライブラリを無理に Windows でビルドする意義も薄れつつある。今回も MeCab が欲しいというよりも、トラブルのありそうなところに突っ込んでみて勉強が目的だったり。そして何事もなくビルドできてしまい、得たものは少なめ。

う〜ん、せっかくビルドしたんだから MeCab をつかって何か作れないかな? 考えてみる

日記かいてない

Python 2.7 がリリースされたりと世の中は進み続けているのに、オレの日記は1月以上止まっていたようだ。はてダの入力インターフェースも気がついたら新しくなっていたし。

さて、なに書こうかな? 今週金曜夜から無事生還できたら、 Python 2.7 で遊ぶんだ…ってなんのフラグだコレ?

とりあえず今週末は Python Recipe に OrderedDict を反映させるのと、 Mecab Python バインディングWindows Python 2.7 環境でビルドをやる予定。

絵文字にマッチする正規表現 Unicode 版

携帯電話の絵文字に触れる機会があったので、絵文字にマッチする正規表現を。

絵文字は Unicode の外字領域にある。なので絵文字以外の外字が使われていないという前提があるならば、こうしてしまえばよい。

import re
private_use = re.compile(ur'[\uE000-\uF8FF]')

これだとあまりにも乱暴なので、以下の Wikipedia 記事を参考に範囲を狭めると、こうなる。

import re

_au = ur'\ue468-\ue5df\uea80-\ueb88'
_docomo = ur'\ue63e-\ue6a5\ue6ac-\ue6ae\ue6b1-\ue6ba\ue6ce-\ue757'
_softbank = ur'\ue001-\ue05a\ue101-\ue15a\ue201-\ue253\ue301-\ue34d\ue401-\ue44c\ue501-\ue537'
_emobile = ur'%s\ue600-\ue619' % _docomo

au = re.compile(u'[%s]' % _au)
docomo = re.compile(u'[%s]' % _docomo)
softbanck = re.compile(u'[%s]' % _softbank)
emobile = re.compile(u'[%s]' % _emobile)

== 文字コード ==
使用される文字コードは以下の通りである。

=== au ===
; Shift_JIS: 第1バイトがF3,F6,F7で始まる2バイト文字すべてと、F440-F47E, F480-F48D
; Unicode: E468-E5DF, EA80-EB88

=== DoCoMo ===
; Shift_JIS: F89F-F8FC, F940-F949, F950-F957, F95B-F95E, F972-F97E, F980-F9FC
; Unicode: E63E-E6A5, E6AC-E6AE, E6B1-E6BA, E6CE-E757

=== SoftBank ===
; Shift_JISEUC・JIS: ESC $ [G/E/F/O/P/Q] + [0x21-0x7E] + 0x0F
; Unicode: E001-E05A, E101-E15A, E201-E253, E301-E34D, E401-E44C, E501-E537

=== emobile ===
※F860-F879(E600-E619)の領域以外は、基本的にDoCoMoと同じコード体系である(一部の商標を除く)。

; Shift_JIS: F89F-F8FC, F940-F949, F950-F957, F95B-F95E, F972-F97E, F980-F9FC, F860-F879
; Unicode: E63E-E6A5, E6AC-E6AE, E6B1-E6BA, E6CE-E757, E600-E619

http://ja.wikipedia.org/wiki/%E6%90%BA%E5%B8%AF%E9%9B%BB%E8%A9%B1%E3%81%AE%E7%B5%B5%E6%96%87%E5%AD%97

100,000 ヒット

今日は引っ越してから11日目。だいぶ落ち着いてきたので1月ぶりに自分のはてダを…と思ったら。

アクセスカウンタが 100623 を表示。

とうとう 6 桁に到達。 2 年と 2 ヶ月かぁ。

このブログを始めたころは、いろいろとあせってたなぁ。なにも経歴が無い、と。なんでもいいから形に残さなければ、とおもったのが始めた理由のひとつ。

Python おもしろすぎる、というのも理由のひとつ。

名前がないと始まらないのでひねり出したタイトルは「色+月」で厨二病要素全開だけどいまさら変更できないし、するつもりもない。 2 年たった今でも厨二病継続中。不治の病。

MessagePack メモ

Python

help(msgpack) するとファイルライクオブジェクトに対して入出力する pack, unpack と、 Python 文字列を対象とする packb (= packs), unpackb (= unpacks) の説明あり。

import sys
import msgpack

msgpack.pack([1, True, 'example'], sys.stdout)
import sys
import msgpack

print msgpack.unpack(sys.stdin)
$ python write.py | python read.py
[1, True, 'example']

しかしコードを斜め読みすると Packer, Unpacker というのもあった。こちらのほうが本命で pack, unpack はショートカット用かな。

import sys
import msgpack

packer = msgpack.Packer()
sys.stdout.write(packer.pack([1, True, 'example']))
sys.stdout.write(packer.pack('spam'))
sys.stdout.write(packer.pack(range(5)))
sys.stdout.write(packer.pack({'a': 'b', 'c': 'd'}))
import sys
import msgpack

unpacker = msgpack.Unpacker()
while True:
    buf = sys.stdin.read()
    if not buf:
        break
    unpacker.feed(buf)
    for obj in unpacker:
        print obj
$ python write2.py | python read2.py
[1, True, 'example']
spam
[0, 1, 2, 3, 4]
{'a': 'b', 'c': 'd'}

Ruby

http://msgpack.sourceforge.net/ruby:snippets 。明日試してみる。 http://d.hatena.ne.jp/viver/20100324/p1 も読む。 Copy-on-Write によるゼロ・コピーデシリアライズ対応。

C

シリアライズhttp://msgpack.sourceforge.net/c:snippets のまま。

#include <msgpack.h>
#include <stdio.h>

int main(void) {
    msgpack_sbuffer sbuf;
    msgpack_packer pk;
    
    msgpack_sbuffer_init(&sbuf);
    msgpack_packer_init(&pk, &sbuf, msgpack_sbuffer_write);
    
    msgpack_pack_array(&pk, 3);
    msgpack_pack_int(&pk, 1);
    msgpack_pack_true(&pk);
    msgpack_pack_raw(&pk, 7);
    msgpack_pack_raw_body(&pk, "example", 7);
        
    fwrite(sbuf.data, sbuf.size, 1, stdout);
        
    msgpack_sbuffer_destroy(&sbuf);
            
    return 0;
} 

シリアライズhttp://msgpack.sourceforge.net/c:snippets の改悪。 Advanced streaming deserialization も読む必要がありそうだがこれは明日に。

#include <msgpack.h>
#include <stdio.h>

int main(void) {
    msgpack_zone *mempool;
    msgpack_object obj;
    msgpack_unpack_return ret;
    size_t offset = 0;
    char buf[8192];
    size_t size;

    size = fread(buf, 1, 8192, stdin);

    while (1) {
        mempool = msgpack_zone_new(MSGPACK_ZONE_CHUNK_SIZE);
        ret = msgpack_unpack(buf, size, &offset, mempool, &obj);
        switch(ret) {
        case MSGPACK_UNPACK_SUCCESS:
            printf("a object is unpacked and no buffer remain: ");
            msgpack_object_print(stdout, obj);
            printf("\n");
            msgpack_zone_free(mempool);
            return 0;
        case MSGPACK_UNPACK_EXTRA_BYTES:
            printf("a object is unpacked: ");
            msgpack_object_print(stdout, obj);
            printf("\n");
            msgpack_zone_free(mempool);
            break;
        case MSGPACK_UNPACK_CONTINUE:
            printf("buffer is to insufficient to unpack a object\n");
            return 1;
        case MSGPACK_UNPACK_PARSE_ERROR:
            printf("parse error\n");
            return 1;
        }
    }
}
$ ./write | ./read
a object is unpacked and no buffer remain: [1, true, "example"]

えっと、 msgpack_unpack の戻り値 MSGPACK_UNPACK_SUCCESS はぴったり読みきったことを示し MSGPACK_UNPACK_EXTRA_BYTES はまだデータが余っていることを示すのか。これは Python 版の Unpacker 同様、複数オブジェクトを詰め込んだファイルを綺麗に処理できる予感。 試してみる。

$ python write2.py | ./read
a object is unpacked: [1, true, "example"]
a object is unpacked: "spam"
a object is unpacked: [0, 1, 2, 3, 4]
a object is unpacked and no buffer remain: {"a"=>"b", "c"=>"d"}

うん、読めた。

C++

http://msgpack.sourceforge.net/cpp:snippets 。明日試してみる。