銀月の符号

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

気がついたら2012年10月(またかよ)

放置再び。ブログを書くことができない体質にでも変わってしまったのだろうか。

この半年はてなダイアリーに書くことはなかったけれども、ほかの人のを読んだり、はてなブックマークしたりとはてなにお世話になっている状態はつづいております。

この間 Python は 2.7.3 と 3.3.0 がリリースされ。 What's New よんで新しい要素の情報を仕入れ中。

気がついたら2012年3月

放置すること1年以上。前書いていたときには、こんなにあいてしまうなんて予想だにせず。

仕事急がしいし。でも全力で進みつづけた1年半。後悔はしていない。

Python で日数を数える

ところで、何日あけちゃったんだ? Python はこんな調べ物にもつかえます。

In [1]: import datetime

In [2]: print datetime.date(2012, 3, 19) - datetime.date(2010, 9, 20)
546 days, 0:00:00

546日だと? すごく残念な答えがかえってきた。

datetime.date 同士は減算できる。期間を表すdatetime.timedelta が得られる。

ちなみに、加算は TypeError 例外となる。可能な演算は以下。

date - date = timedelta
date + timedelta = date (交換可能 timedelta + date)
date - timedelta = date (交換可能 timedelta - date)
timedelta * int = timedelta (交換可能 int * timedelta)
timedelta / int = timedelta

SQLAlchemy で Firebird その3

クエリー関連(ORM 版)

詳しくは Object Relational Tutorial に載っている。

とりあえず DB 設定、テーブル設計、セッションの作成を。

from sqlalchemy import create_engine
from sqlalchemy import Column
from sqlalchemy import Integer, String
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Person(Base):
    __tablename__ = 'person'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(128))

db_path = 'firebird+kinterbasdb://user:password@host/dbalias'
engine = create_engine(db_path)
Base.metadata.create_all(engine)

Session = sessionmaker(bind=engine)
session = Session()

DB 設定できた、テーブルできた、セッションできた。準備 OK 。

INSERT

セッションの add メソッドに作成したオブジェクトを渡す。

person1 = Person()
person1.id = 1
person1.name = u'fgshun'

session.add(person1)
session.commit()
UPDATE

INSERT となんらかわらず。

person1.name = u'fgshun'
session.add(person1)
session.commit()

複数を対象とする UPDATE を行うには後述の Query オブジェクトの update メソッドを用いる。

DELETE

セッションの delete メソッドにオブジェクトを渡せばよい。

session.delete(person1)
session.commit()
SELECT

セッションの query メソッドを呼び出すと Query オブジェクトができる。これの all を呼び出すと、取り出したレコードすべてを含むリストが得られる。

for p in session.query(Person).all():
    print p.id, p.name

all のほかに first, one というのもなる。 first は最初の 1 レコードだけを返す。 1 レコードもなければ None を返す。 one は 1 レコードだけが得られたかどうかチェックし、ダメならば例外送出。あっていればこの 1 レコードを返す。これらの返すものはリストではない。

p = session.query(Person).first()
p = session.query(Person).one()
WHERE

Query オブジェクトはさまざまなメソッドをもつ。まずは filter_by 。 WHERE spam == ham のようなものを書くときに使える。また、多くの Query メソッドは Query オブジェクトを返すので、続けて all などの他の Query メソッドを呼ぶことができる。つまり、こうなる。

for p in session.query(Person).filter_by(id = 1).all():
    print p.id, p.name

次は filter 。

for p in session.query(Person).filter(Person.id == 1).all():
    print p.id, p.name

== 以外の演算子も用いることができる分、こちらのほうがいろいろな操作が可能。 ==, !=, >, <, >=, <= 。

for p in session.query(Person).filter(Person.id > 3).all():
    print p.id, p.name
AND,OR
from sqlalchemy import and_, or_
session.query(Person).filter(and_(Person.id == 1, Person.name == u'シュン')).all()
session.query(Person).filter(or_(Person.id == 1, Person.name == u'シュン')).all()
LIKE
for p in session.query(Person).filter(Person.name.like('%a%')).all():
    print p.id, p.name
IN
for p in session.query(Person).filter(
        Person.name.in_(['a', 'b'])).all():
    print p.id, p.name

NOT IN を行うには ~ を用いる。

for p in session.query(Person).filter(
        ~Person.name.in_(['a', 'b'])).all():
    print p.id, p.name
IS NULL
for p in session.query(Person).filter(Person.name == None).all():
    print p.id, p.name

IS NOT NULL を行うには != None とする。

COUNT
session.query(Person).count()
GROUP BY
print session.query(func.count(Person.name), Person.name).group_by(Person.name).all()
LIMIT,OFFSET

Python スライスといっしょ。

# limit 1
session.query(Person.id, Person.name).order_by(Person.id)[:1]
# offset 1
session.query(Person.id, Person.name).order_by(Person.id)[1:]

SQLAlchemy で Firebird その2

DB 接続まわり。

Engine

Firebird を使用するには create_engine にて firebird+kinterbasdb を指定する。次のようになる。

engine = create_engine('firebird+kinterbasdb://user:password@host/dbalias')

Firebird に限らない設定は Database Engine Options にて。とりあえず 3 つだけメモ。

connect_args
DBAPI の connect メソッドオプションを直接指定できる。 kinterbasdb だと connect={'charset': 'UTF8'} 指定で接続時のエンコーディングを指定したり。
echo
標準出力 sys.stdout に SQLAlchemy のログを出力する。どのような SQL 文が実行されたのかを知る最も簡単な方法だったりする。デフォルト False 。 dbengine-logging も参照。
encoding
SQLAlchemy レベルで 8 ビット文字列、ユニコード文字列間の変換を行う際に用いる文字コード。デフォルト 'utf-8' 。各 DBAPI 自身がユニコード変換機能を提供していることも多く、その場合は指定されていても無視される?

firebird+kinterbasdb 指定時のみ使える特有の設定は以下。

type_conv
type_conv code を指定する。デフォルトは 200 。 kinterbasdb 3.3 より 300 が加わった模様。
concurrency_level
concurrency-level を指定する。デフォルトは 1 。
enable_rowcount
Update, Delete クエリの後、実際に操作したデータ数を得られるようにする。デフォルトは True 。

マニュアルで関連する箇所は Engine Configuration とか Firebird

セッション

Using the Session

コネクションプーリング

Connection Pooling

SQLAlchemy で Firebird その1

テーブル定義関連。

まずは Object Relational Tutorial 。そのあとに読むべきは Mapper ConfigurationDescribing Databases with MetaDataColumn and Data Types って多いよ。うれしいけれど文章多すぎるよ。こんなところにも Python らしさがにじみ出ているというかなんというか。

しかも英語なのでオレの貧弱な英語力で読み直すには速度に難あり。なのでメモのこしたりリンク張ったりしておく。

基本

本来 SQLAlchemy ではテーブル定義(SQLAlchemy.Table のインスタンス)と対応する Python クラス(自作)を別々につくり、両者をマッピングして使用することとなっている*1

from sqlalchemy import MetaData
from sqlalchemy import Table, Column, Integer, Unicode
from sqlalchemy.orm import mapper

metadata = MetaData()

person_table = Table('person', metadata,
        Column('id', Integer, primary_key=True),
        Column('name', Unicode(256)),
        )

class Person(object):
    pass

mapper(Person, person_table)

しかし、そこまできっちり分ける必要はないというユーザーが多数ということで、 declarative_base が用意されている。これで Base クラスを作って継承し、すこし書き方を覚えれば OK 。これで前述のコードとほぼ同じになる。詳しくは declarative を。

from sqlalchemy import Column
from sqlalchemy import Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Person(Base):
    __tablename__ = 'person'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', Unicode(256))

これで次のようなテーブル person の定義となる。

CREATE TABLE person (
        id INTEGER NOT NULL,
        name VARCHAR(20),
        PRIMARY KEY (id)
)

テーブル定義クラスは自作クラスの __table__ 属性に隠れている。たとえば、 __table__ を用いてテーブル定義を確認したり、 CREATE TABLE, DROP TABLE を行うことができる。

>>> for column in Person.__table__.columns:
...   print column
...
person.id
person.name
>>>
>>> from sqlalchemy import create_engine
>>> engine = create_engine('firebird+kinterbasdb://user:password@host/db_alias')
>>> engine.create(Person.__table__)
>>> engine.drop(Person.__table__)

同様に、テーブルらのメタデータは Base.metadata に隠れている。 metadata.create_all でテーブル一括作成などのようなメタデータが必要なときは Base.metadata.create_all で OK。

>>> Base.meta.create_all()

declarative_base をつかってコードをはしょっていても、やれることが減るわけではないのがすてき。

そして、 Person クラスが Python 側でこのテーブルのデータを扱うための雛形になる。

>>> p = Person()
>>> p.id = 1
>>> p.name = u'シュン'

メソッドの追加

クラスには Python 側で扱いやすくするためのメソッド(クラスメソッド、スタティックメソッド含む)を自由に追加できる。テーブル定義には影響ないのでガンガン追加するべし。

from sqlalchemy import Column
from sqlalchemy import Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Person(Base):
    __tablename__ = 'person'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(20))

    @classmethod
    def create(cls, id_, name):
        obj = cls()
        obj.id = id_
        obj.name = name
        return obj

    def __repr__(self):
        return 'Person(id_=%r, name=%r)' % (self.id, self.name)
>>> p = Person.create(id_=1, name=u'シュン')
>>> print p
Person(id_=1, name=u'\u30b7\u30e5\u30f3')

それが __init__ だとしても。 SQLAlchemy がオブジェクトを生成するときに支障がありそうだが、 SQLAlchemy は __init__ を直接呼び出さないので問題ないとのこと。

class Person(Base):
    __tablename__ = 'person'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(20))

    def __init__(self, id_, name):
        self.id = id_
        self.name = name
>>> p = Person(1, u'シュン')
>>> print p.id, p.name
1 シュン

カラムの型

Column and Data Types の Generic Types だけをつかっていれば、 Firebird 以外のデータベースを使いたくなったとき、 SQLAlchemy がうまく型を選んでくれるので楽。命名則は 1 文字目だけ大文字。

Firebird で実際にテーブルを作って調べてみたところ、対応はこんな感じ。

SQLAlchemy.type FirebirdSQL
BigInteger BIGINT
Boolean SMALLINT, CHECK(column_name IN (0, 1))
Date DATE
DateTime TIMESTAMP
Enum CHECK 制約つき VARCHAR *2
Float FLOAT
Integer INTEGER
Interval TIMESTAMP
LargeBinary BLOB SUB_TYPE 0
Numeric NUMERIC
PickleType BLOB SUB_TYPE 0 *3
SmallInteger SMALLINT
String VARCHAR *4
Text BLOB SUB_TYPE 1
Time TIME
Unicode VARCHAR*5 *6
UnicodeText BLOB SUB_TYPE 1*7

SQL Standard Types は SQL 標準の型と 1 対 1 対応しているもの。生成される SQL がどうなるかもすぐにわかる。命名則はすべて大文字。

Vendor-Specific Types は扱うデータベースが完全に決まっているとき用。でも Firebird に関する文章には Firebird 用の型の話が一切出てこない罠。 sqlalchemy.dialects.firebird モジュールには用意はされているようだがドキュメント化されていないのか、 Generic Types だけですむからドキュメント化する必要がなかったのか…。

制約各種、インデックス、デフォルト値

より詳しくは Defining Constraints and Indexes 。CHECK 制約や複数カラムにまたがる制約についても載っている。

カラム定義である Column のコンストラクタにはさまざまなオプションがある。たとえば primary_key=True で NOT NULL と PRIMARY KEY がつく。

各オプションの Firebird での解釈は次のとおり。

Column option FirebirdSQL
autoincrement=True 無視される
default='x' 無視される
index=True CREATE INDEX ix_tablename_columnname ON tablename (columnname)
nullable=False NOT NULL
primary_key=True NOT NULL, PRIMARY KEY (columnname)
server_default='x' DEFAULT 'x'
unique=True UNIQUE (columnname)

default オプションは SQLAlchemy 側でデフォルト値を用意するものであるためデータベース側には現れない。 DEFAULT 句に対応するのは server_default オプション。

Foreign Key も制約ではあるが、これはリレーションのほうに書く。

オートインクリメント代わりにシーケンスを使う

Firebird には AUTOINCREMENT がないので別の手段が必要*8。とはいえ、シーケンスを使えば 1 ずつ増える値を用意することができるのでやってやれないことはない。

SQLAlchemy では Culumn の定義に Sequence('sequence_name') を加えるだけで、テーブル作成時にシーケンスが一緒に生成され、インサートする際には このシーケンスの値をつかってくれる。

from sqlalchemy import Sequence
from sqlalchemy import Column
from sqlalchemy import Integer, String
from sqlalchemy.ext.declarative import declarative_base

Base = declarative_base()

class Person(Base):
    __tablename__ = 'person'

    id = Column('id',
            Integer, Sequence('person_id'),
            autoincrement=True, primary_key=True)
    name = Column('name', String(20))

シーケンス名はユニークである必要があるため、ユーザーが名前を決めて管理しなくてはならないのが autoincrment よりもほんの少しだけ手間。

リレーション

http://docs.sqlalchemy.org/en/rel_0_6/orm/extensions/declarative.html#configuring-relationshipshttp://docs.sqlalchemy.org/en/rel_0_6/orm/relationships.html に詳しい情報あり。

ForeignKey 制約をかけるだけなら ForignKey を Column に設定すればよい。 Python オブジェクトから関連テーブルのデータにアクセスするための属性をつけるならば relationship を使うこととなる。逆参照用の属性をつけるならば relationship に backref オプションをつける。

ただし、これらの属性がついているテーブルに対し session.query(Table).all() のようなクエリを投げてオブジェクトを取得しようとしたとしても 、参照先テーブルに対して JOIN するようなクエリが発行されるということはない(というか、毎回勝手に参照されると困る)。こういった属性を参照しようとした際に始めて参照先テーブルを調べるクエリが発行される。

一対多

ForignKey を適切に張った上で、参照先へ relationnship を向ければよい。逆参照のために backref もつけておくとよい。

住所と人の例。 address.people で住んでいる人々が取れる。 backref をつけているため person.address で住所を取ることもできる。

class Address(Base):
    __tablename__ = 'address'

    id = Column('id', Integer, primary_key=True)
    address = Column('address', String(128))
    people = relationship('Person', backref='address')

class Person(Base):
    __tablename__ = 'person'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(128))
    address_id = Column('address_id', Integer, ForeignKey('address.id'))
多対一

一対多と比べて relationship を向ける方向が逆になっただけ。逆参照がいる場合は backref もつけておく。

人と住所の例。 person.address で住所がとれる。 backref をつけているため address.people で住んでいる人々をとることもできる。

class Person(Base):
    __tablename__ = 'person'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(128))
    address_id = Column('address_id', Integer, ForeignKey('address.id'))
    address = relationship('Address', backref='people')

class Address(Base):
    __tablename__ = 'address'

    id = Column('id', Integer, primary_key=True)
    address = Column('address', String(128))
多対多

両者の関連を保持するテーブルを用意して relationship の secondary 引数に指定すればよい。関連保持用のテーブルは Table 定義だけがあれば十分なので次のようにする。

人と好きな食べ物の例。

class Person(Base):
    __tablename__ = 'person'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(128))
    favorite_dishes = relationship(
            'FavoriteDish', secondary='person_favorite', backref='persons')

class FavoriteDish(Base):
    __tablename__ = 'favorite_dish'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(128))
    #persons = relationship('Person', secondary='person_favorite', backref='parson')

person_favorite = Table(
    'person_favorite', Base.metadata,
    Column('person_id', Integer, ForeignKey('person.id'), primary_key=True),
    Column('favorite_dish_id', Integer, ForeignKey('favorite_dish.id'), primary_key=True),
    )

しかし関連保持用のテーブルにマッピングされたオブジェクトを作れるようにすることもできる。この場合は secondary は使用しない。 Association Object 参照。

ON UPDATE と ON DELETE

Firebird は ON UPDATE, ON DELETE 句にも対応しているデータベースのひとつ。これを用いるには ForeignKey のコンストラクタにて onupdate, ondelete 引数を使う。詳しくは ON UPDATE and ON DELETE 参照。

class Person(Base):
    __tablename__ = 'person'

    id = Column('id', Integer, primary_key=True)
    name = Column('name', String(128))
    address_id = Column(
            'address_id', Integer,
            ForeignKey(
                'address.id', onupdate="CASCADE", ondelete="SET NULL"))
参照先の取得方法の指定

SQLAlchemy ではテーブル定義時、 relationship の lazy 引数を指定することで参照しているテーブルのデータをいつ、どのように取得するかを制御することができる。

class Address(Base):
    __tablename__ = 'address'

    id = Column('id', Integer, primary_key=True)
    address = Column('address', String(128))
    persons = relationship('Person', backref='address', lazy='joined')

テーブル定義にて指定した動作を各クエリでさらに上書きすることも可能。

from sqlalchemy.orm import joinedload

for p in session.query(Person).options(joinedload('address')).all():
    print p.name, p.address.address

デフォルトではオブジェクトを生成したときには参照テーブルのデータはとってこず、必要になったときにあらためて SELECT クエリを投げるようになっている。これを JOIN や SUBQUERY をつかって最初からとってくるよう指定することができる。

詳しくは Relationship Loading Techniques 。用途に応じてどれを用いればよいかの指針もここに書かれている。

*1:O/R マッパーとして使わない場合はテーブル定義だけでもよい。 SQL Expression Language Tutorial 参照。

*2:Enum('a', 'b') だと VERCHER(1), CHECK(column_name IN ('a', 'b') ) になる

*3:Python オブジェクトを pickle でバイナリ化したものが入る

*4:String(256) だと VARCHAR(256)

*5:Unicode(256) だと VARCHAR(256)

*6:SQLAlchemy による Unicode への変換が行われる

*7:SQLAlchemy による Unicode への変換が行われる

*8:sqlite3 にも AUTOINCREMEN はないが __table_args__ = {'sqlite_autoincrement': True} というクラス変数を定義(Table のコンストラクタに sqlite_autoincrement=True オプションを指定)するという手があるのが、ちょっとうらやましい。

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. とある。