31年目のRealize

技術に関する詳しい記事や分かりやすい記事は世の中にたくさんあるので、自分なりのアウトプットを試行錯誤。

pythonでDB操作

DBMSに依存しないDB操作プログラミング

SQLiteMySQL, PostgreSQL, Oracle Databaseなど色々DBMSがあり、それぞれプログラムから使用する際はDBMS毎に異なったコーディングが必要。覚えきれない…。

そこで、オブジェクト指向マッピング(ORM)という技法を使えば、DBMS毎にコードを変える必要が無くなるのです!

pythonにはSQLAlchemyというパッケージがあり、それで実現できます。やったね!

ということで、これは是非身につけたい。

まずはSQLite用のコーディング

SQLite用に書くとこんな感じなります。 sqlite3pythonに標準でインストールされていて、軽くて使いやすい。らしい。

import sqlite3

# DBのコネクションを生成
# :memory:を指定すると、DBがメモリ上に保存され、接続が終了すると消える。DBファイルに出力するときはファイル名を指定する。
conn = sqlite3.connect(':memory:')

# DBを操作するためのカーソルを生成
curs = conn.cursor()

# executeでSQLを実行
curs.execute(
    'CREATE TABLE persons(id INTEGER PRIMARY KEY AUTOINCREMENT, name STRING)')
curs.execute(
    'INSERT INTO persons(name) values("Mike")'
)
curs.execute(
    'INSERT INTO persons(name) values("Nancy")'
)
conn.commit()

curs.execute('SELECT * FROM persons')

# クエリ結果をコンソールに表示
print(curs.fetchall())

conn.close()

コンソールの表示結果は↓

[(1, 'Mike'), (2, 'Nancy')]

これがMySQLPostgreSQLになるとまた別のパッケージをインストールして、そのパッケージの使い方に合わせたコーディングが必要。

SQLAlchemyを使ってみる

SQLAlchemyを使ってSQLiteのDBを操作するコーディングはこうなる。

import sqlalchemy
import sqlalchemy.ext.declarative
import sqlalchemy.orm

# データベース接続のためのEngineオブジェクトを取得
engine = sqlalchemy.create_engine('sqlite:///:memory:')

# Baseにオブジェクトを生成
Base = sqlalchemy.ext.declarative.declarative_base()

# Baseに入ったオブジェクトのクラスを継承して、テーブルの定義クラスを作成
class Person(Base):
    __tablename__ = 'perons'
    
    # テーブルのカラムを定義
    id = sqlalchemy.Column(
        sqlalchemy.Integer, primary_key=True, autoincrement=True)
    name = sqlalchemy.Column(sqlalchemy.String(14))

# 使用するデータベースのEngineをBaseに設定
Base.metadata.create_all(engine)

# DBにアクセスするため、Engineのセッションを作成
Session = sqlalchemy.orm.sessionmaker(bind=engine)

session = Session()

# DBに追加するオブジェクトを作成し、セッションに追加
person1 = Person(name='Mike')
person2 = Person(name='Nancy')
session.add(person1)
session.add(person2)

session.commit()

# クエリの結果全てをリストで取得
persons = session.query(Person).all()

# リストの中身をコンソールに表示
for person in persons:
    print(person.id, person.name)

出力結果は↓

1 Mike
2 Nancy

UPDATEする時は次のように記述

# データを更新
# まずは更新対象のデータの1行目を取得
person3 = session.query(Person).filter_by(name='Mike').first()

# データを書き換え、セッションに追加
person3.name = 'Michel'
session.add(person3)
session.commit()

ここで色々と疑問

ひとまず、使い方の流れを理解した。…が、ちょっといくつか分からないコードがある。 基礎的な話です。

分からないポイント

pythonのクラスとかその辺がまだ身に付いていない感じ。

1つ目

import sqlalchemy
engine = sqlalchemy.create_engine('sqlite:///:memory:')

sqlalchemyをimportしてるんで、sqlalchemyていうモジュールがあって、その中でcreate_engine関数が定義されてるんですよね?

探してみた結果。

sqlalchemyパッケージ(フォルダ)があり、その配下のengineパッケージの__init__.pyの中に定義されていました。 つまり、普通に呼ぶときは

import sqlalchemy.engine
engine = sqlalchemy.engine.create_engine('sqlite:///:memory:')

となるはず?これで実行しても同じ結果でした。 ここで気付いたのが、「あれ、importする時って、パッケージ名.モジュール名だよね?」

あー、__init__.pyだとモジュール名が省略されるということか。

なので、import sqlalchemyの一文で、sqlalchemy配下の__init__.pyが呼ばれる。 そこには

from .engine import create_engine, engine_from_config

と記述がある。 ここでengineが出てきました。でもこれはパッケージなので、またモジュール名が無い。 そして__init__.pyの中にcreate_engineがいる。

つまり、import sqlalchemyだけでその配下のengineパッケージの__init__.pyまで読み込まれるってことかー…。慣れないorz

はい、次

2つ目

Base = sqlalchemy.ext.declarative.declarative_base()
…
Base.metadata.create_all(engine)

この流れ…。 あるクラスのオブジェクトを生成して、そのクラスのインスタンスメソッドcreate_allが呼ばれてるんだろうか…でも間の.metadata.は何者か。

Base.metadataBaseに入れたクラスオブジェクトで、そのインスタンス変数matadataにまたクラスのオブジェクトが入ってて そのクラスがcreate_allを定義しているってことかな。

sqlalchemyのドキュメントで調べると、そのクラスはclass sqlalchemy.schema.MetaDataということになっているが、いない。 pythonista3アプリだと宣言箇所に飛べないから 探すのが大変…。(修行にはなりそう)

そして、sqlalchemy.sql.schemaにいたーーー!

実態はsqlalchemy.sql.schema.MetaDataでした。

なんで違うかと考えたところ、ドキュメントには「MetaData API」という文字があったんで、これはあくまでAPIとしてのマニュアルってことか。

ドキュメント上は、実態とか関係なしにモジュールの呼び出し方だけ記載してあるってことなんですねー。知らなかった。

ドキュメント(英語)ちゃんと読めって話ですが、今はまだ時間がかかりすぎると思うので、もうちょいスキル上がってから読みます! 一旦モヤモヤは落ち着いたので良しとしよう。

最後

Session = sqlalchemy.orm.sessionmaker(bind=engine)
session = Session()

Sessionsessionの違いとは…。 Sessionはクラスのオブジェクトが入ってるとして、sessionはそれをインスタンス化したものだよねきっと。

ドキュメントに

A configurable Session factory.

The sessionmaker factory generates new Session objects when called, creating them given the configurational arguments established here.

て書いてある。訳すと

設定可能なセッションファクトリ。

sessionmakerファクトリは呼び出されると新しいSessionオブジェクトを生成し、ここで設定された構成引数を与えられてそれらを作成します。

うん、それっぽい!(疲れてきた)

まとめ

なるほど、こうやってDBMSの種類に合わせたクラスの構造自体を作っていって、操作する時はその出来上がったクラスをインスタンス化して使うってことなんだなぁ。きっと。

今はそう理解しておこう…。またスキルが上がった時にさらに深掘りしたいと思います

詳しい人いたら是非指摘して欲しい!