flask_sqlalchemyのコンストラクタについて

はじめに

flask_sqlalchemyのチュートリアルをやっていたらコンストラクタの記述でちょっと気になったので調べてみた。

コード

これが今回テストに使うコード。これを実行すると、pythonファイルと同階層に"TestPerson.db"というsqliteデータベースができる。

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///TestPerson.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_ECHO'] = False

db = SQLAlchemy(app)

class Person(db.Model):
    __tablename__ = 'person'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)
    age = db.Column(db.Integer)

    def __init__(self, name, age):
        self.name = name
        self.age = age

if __name__ == '__main__':
    db.create_all()

    man1 = Person('john', 29)
    man2 = Person('adam', 19)
    man3 = Person('David', 55)

    db.session.add(man1)
    db.session.add(man2)
    db.session.add(man3)

    db.session.commit()

データベースの中身を確認すると

id name age
1 john 29
2 adam 19
3 David 55

こうなってる。

気になった個所

Personクラスのコンストラクタ(init)の部分ってどう解釈すればいい?

class Person(db.Model):
    __tablename__ = 'person'
    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.Text)
    age = db.Column(db.Integer)

    def __init__(self, name, age):  # ここが凄い気になる
        self.name = name
        self.age = age

ほかのチュートリアル見るとコンストラクタを書かないパターンもあった。
name = db.Column(db.Text), age = db.Column(db.Integer)はクラス変数として定義されている。よってコンストラクタを書かなければ、
man1 = Person(name='john', age = 29) でクラス変数に値が入るはず。ところがコンストラクタでインスタンス変数self.name, self.ageを定義しているため、man1 = Person(name='john', age = 29)インスタンス変数に値が入ってしまう。これはSQLAlchemyの処理的に問題ないのか?

このへんに関してはSQLAlchemy公式サイトにちらっと記載があった。

公式サイトの説明

以下公式サイトから抜粋

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/test.db'
db = SQLAlchemy(app)


class User(db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)

    def __repr__(self):
        return '<User %r>' % self.username

Note how we never defined a __init__ method on the User class? That’s because SQLAlchemy adds an implicit constructor to all model classes which accepts keyword arguments for all its columns and relationships. If you decide to override the constructor for any reason, make sure to keep accepting **kwargs and call the super constructor with those **kwargs to preserve this behavior:

class Foo(db.Model):
    # ...
    def __init__(self, **kwargs):
        super(Foo, self).__init__(**kwargs)
        # do custom stuff

参考にしたサイト

Quickstart — Flask-SQLAlchemy Documentation (2.x)

python - Flask-SQLAlchemy Constructor - Stack Overflow