Python学習ノート:Day 3作成ORM


前言
最近深く勉强して、すでにいくつかの模型を出しましたが、Pyhtonの基础がしっかりしていないので、Pythonの补习を始めました.Pythonのインストールについては、Pythonのインストールについて説明します.Pythonのインストールについては、Pythonのインストールについて説明します.Pythonのインストールについては、Pythonの実行モードと入出力については、Python IO Pythonの基本概念について説明します.PythonベースのPython文字列とコードについて説明します.Python文字列と符号化Pythonの基本データ構造:listとtupleの紹介を参照してください.この紹介を参照してください.Python listとtuple Python制御文の紹介:ifelse、この紹介を参照してください.Python条件判断Python制御文の紹介:ループ実装、Pythonループ文Pythonデータ構造:dictとset紹介Pythonデータ構造dictとset Python関数関連:Python関数Python高次特性:Python高級特性Python高次関数:Python高次関数Python匿名関数:Python匿名関数Python装飾器:Python装飾器Python偏関数:Python偏関数Pythonモジュール:PythonモジュールPythonオブジェクト向けプログラミング(1):Pythonオブジェクト向けPythonオブジェクト向けプログラミング(2):Pythonオブジェクト向け(2)Pythonオブジェクト向けプログラミング(3):Pythonオブジェクト向け(3)Pythonオブジェクト向けプログラミング(4):Pyhtonオブジェクト向け(4)Pythonオブジェクト向け高度プログラミング(上):Pythonオブジェクト向け高度プログラミング(上):Pythonオブジェクト向けプログラミング(上)Pythonオブジェクト向け高度プログラミング(中上):Pythonオブジェクト向け高度プログラミング(中上)Pythonオブジェクト向け高度プログラミング(中下):Pythonオブジェクト向け高度プログラミング(中下)Pythonオブジェクト向け高度プログラミング(完):Pythonオブジェクト向け高度プログラミング(完)Pythonエラーデバッグ(起):Pythonデバッグ:起Pythonエラーデバッグ(承):Pythonデバッグ:承Pythonエラーデバッグ(転):Pythonデバッグ:転Pythonエラーデバッグ(合):pythonデバッグ:合PythonファイルIOプログラミング:PythonファイルIO PythonファイルIOプログラミング2:PythonファイルIO 2 PythonファイルIOプログラミング3:PYthonファイルIO 3 Pythonプロセスとスレッド(起):Pythonプロセスとスレッド起Pythonプロセスとスレッド(承):Pythonプロセスとスレッド承Pythonプロセスとスレッド(転):Pythonプロセスとスレッド転Pythonプロセスとスレッド(合):Pythonプロセスとスレッド合わせPython正規表現:Python正規表現Python学習ノート:常用内蔵モジュール1:Python学習ノート:常用内蔵モジュール1 Python学習ノート:常用内蔵モジュール2:Python学習ノート:常用内蔵モジュール2 Python学習ノート:常用内蔵モジュール3:Python学習ノート:常用内蔵モジュール3 Python学習ノート:常用内蔵モジュール3 Python学習ノート:常用内蔵モジュール3 Python学習ノート内蔵モジュール4:Python学習ノート:常用内蔵モジュール4 Python学習ノート:常用内蔵モジュール5:Python学習ノート:常用内蔵モジュール5 Python学習ノート:常用内蔵モジュール6:Python学習ノート:常用内蔵モジュール6 Python学習ノート:サードパーティモジュール1:Python常用サードパーティモジュールPython学習ノート:サードパーティモジュール2:Python常用サードパーティモジュールPython学習ノート:サードパーティモジュール3:Python常用サードパーティモジュールPytho学習ノート:ネットワークプログラミング:PythonネットワークプログラミングPython学習ノート:電子メール:Python電子メール1 Python学習ノート:SMTPサーバ:Python SMTPサーバPython学習ノート:POP 3サーバ:PythonPOP 3サーバPython学習ノート:PythonデータベースPython数ライブラリ1 Python学習ノート:Pythonデータベース2 Pythonデータベース2 Python学習ノート:web開発1 Python学習ノート:web開発1 Python学習ノート:web開発2 Python学習ノート:web開発2 Python学習ノート:web開発2 Python学習ノート:web開発3 Python学習ノート:web開発3 Python学習ノート:web開発3 Python学習ノート:非同期IO(1)Python学習ノート:非同期IO(1)Python学習ノート:非同期IO(2)Python学習ノート:非同期IO(2)Python学習ノート:非同期IO(3)Python学習ノート:非同期IO(3)Python学習ノート:Day 1-2開発Python学習ノート:Day 1-2開発
目次
  • 前言
  • ディレクトリ
  • ORM
  • を記述
  • 接続プール
  • を作成する
  • Select
  • ORM
  • 定義Model
  • ORMの作成
    1つのWeb Appでは、ユーザー情報、公開されたログ、コメントなど、すべてのデータがデータベースに格納されます.awesome-python 3-webappでは、データベースとしてMySQLを選択します.
    Webアプリにはデータベースにアクセスする場所がたくさんあります.データベースにアクセスするには、データベース接続、カーソルオブジェクトを作成し、SQL文を実行し、最後に例外を処理し、リソースをクリーンアップする必要があります.これらのデータベースにアクセスするコードが各関数に分散すると、メンテナンスが不可能になり、コード多重化にも不利になります.
    そこで,まずよく用いられるSELECT,INSERT,UPDATE,DELETE操作を関数でカプセル化する.
    Webフレームワークはasyncioベースaiohttpを用いるため,これはコヒーレントベースの非同期モデルである.コプロセッサでは、すべてのユーザが1つのスレッドによってサービスされるため、通常の同期IO操作を呼び出すことはできません.コプロセッサの実行速度は、大量のユーザの要求を処理するために非常に速くなければなりません.一方、時間がかかるIOオペレーションは、コプロセッサで同期的に呼び出すことはできません.そうしないと、IOオペレーションを待つと、システムは他のユーザーに応答できません.
    これが非同期プログラミングの原則です.非同期を使用することを決定すると、システムの各層は非同期でなければなりません.「弓を開けても矢がありません」.
    幸いなことにaiomysqlはMySQLデータベースに非同期IOの駆動を提供しています.
    接続プールの作成
    各HTTPリクエストが接続プールから直接データベース接続を取得できるグローバル接続プールを作成する必要があります.接続プールを使用する利点は、頻繁にデータベース接続を開いたり閉じたりする必要がなく、多重化できるだけ多重化することです.
    接続プールはグローバル変数_poolストレージ、デフォルトでは符号化をutf 8に設定し、トランザクションを自動的にコミットします.
    @asyncio.coroutine
    def create_pool(loop, **kw):
        logging.info('create database connection pool...')
        global __pool
        __pool = yield from aiomysql.create_pool(
            host=kw.get('host', 'localhost'),
            port=kw.get('port', 3306),
            user=kw['user'],
            password=kw['password'],
            db=kw['db'],
            charset=kw.get('charset', 'utf8'),
            autocommit=kw.get('autocommit', True),
            maxsize=kw.get('maxsize', 10),
            minsize=kw.get('minsize', 1),
            loop=loop
        )

    Select
    SELECT文を実行するには、select関数で実行します.SQL文とSQLパラメータを入力する必要があります.
    @asyncio.coroutine
    def select(sql, args, size=None):
        log(sql, args)
        global __pool
        with (yield from __pool) as conn:
            cur = yield from conn.cursor(aiomysql.DictCursor)
            yield from cur.execute(sql.replace('?', '%s'), args or ())
            if size:
                rs = yield from cur.fetchmany(size)
            else:
                rs = yield from cur.fetchall()
            yield from cur.close()
            logging.info('rows returned: %s' % len(rs))
            return rs

    SQL文のプレースホルダは?MySQLのプレースホルダは%sで、select()関数は内部で自動的に置き換えられます.自分でSQL文字列をつづるのではなく、パラメータ付きSQLを常に使用することに注意してください.これにより、SQL注入攻撃を防ぐことができます.
    yield fromは、1つのサブコヒーレンス(すなわち、1つのコヒーレンスで別のコヒーレンスを呼び出す)を呼び出し、サブコヒーレンスの戻り結果を直接取得することに注意してください.
    sizeパラメータが入力されると、最大指定数のレコードがfetchmany()で取得され、そうでない場合、fetchall()ですべてのレコードが取得されます.Insert, Update, Delete
    INSERT、UPDATE、DELETE文を実行するには、共通のexecute()関数を定義します.この3つのSQLの実行には同じパラメータが必要であり、影響を表す整数の行数を返します.
    @asyncio.coroutine
    def execute(sql, args):
        log(sql)
        with (yield from __pool) as conn:
            try:
                cur = yield from conn.cursor()
                yield from cur.execute(sql.replace('?', '%s'), args)
                affected = cur.rowcount
                yield from cur.close()
            except BaseException as e:
                raise
            return affected

    execute()関数とselect()関数が異なるのは、cursorオブジェクトが結果セットを返さずrowcountで結果数を返すことです.
    ORM
    基本的なselect()とexecute()関数があれば、簡単なORMの作成を始めることができます.
    設計ORMは上層呼び出し者の観点から設計する必要がある.
    まず、Userオブジェクトを定義し、データベース・テーブルusersを関連付けます.
    from orm import Model, StringField, IntegerField
    
    class User(Model):
        __table__ = 'users'
    
        id = IntegerField(primary_key=True)
        name = StringField()

    Userクラスに定義されているtable、id、nameはクラスの属性であり、インスタンスの属性ではないことに注意してください.したがって、クラスレベルで定義された属性は、Userオブジェクトとテーブルのマッピング関係を記述するために使用され、インスタンス属性はinit()メソッドで初期化する必要があるため、両者は干渉しません.
    #     :
    user = User(id=123, name='Michael')
    #      :
    user.insert()
    #     User  :
    users = User.findAll()

    Modelの定義
    まず、すべてのORMマッピングのベースクラスModelを定義します.
    class Model(dict, metaclass=ModelMetaclass):
    
        def __init__(self, **kw):
            super(Model, self).__init__(**kw)
    
        def __getattr__(self, key):
            try:
                return self[key]
            except KeyError:
                raise AttributeError(r"'Model' object has no attribute '%s'" % key)
    
        def __setattr__(self, key, value):
            self[key] = value
    
        def getValue(self, key):
            return getattr(self, key, None)
    
        def getValueOrDefault(self, key):
            value = getattr(self, key, None)
            if value is None:
                field = self.__mappings__[key]
                if field.default is not None:
                    value = field.default() if callable(field.default) else field.default
                    logging.debug('using default value for %s: %s' % (key, str(value)))
                    setattr(self, key, value)
            return value

    Modelはdictから継承されるため、すべてのdictの機能を備え、特殊な方法getattr()とsetattr()を実現するため、通常のフィールドを参照するように書くことができます.
    >>> user['id']
    123
    >>> user.id
    123
    
      Field   Field  :
    
    class Field(object):
    
        def __init__(self, name, column_type, primary_key, default):
            self.name = name
            self.column_type = column_type
            self.primary_key = primary_key
            self.default = default
    
        def __str__(self):
            return '' % (self.__class__.__name__, self.column_type, self.name)

    varcharをマッピングするStringField:
    class StringField(Field):
    
        def __init__(self, name=None, primary_key=False, default=None, ddl='varchar(100)'):
            super().__init__(name, ddl, primary_key, default)

    Modelはベースクラスにすぎないことに気づいたが,Userのような具体的なサブクラスのマッピング情報をどのように読み出すのか.答えはmetaclass:ModelMetaclass:
    class ModelMetaclass(type):
    
        def __new__(cls, name, bases, attrs):
            #   Model   :
            if name=='Model':
                return type.__new__(cls, name, bases, attrs)
            #   table  :
            tableName = attrs.get('__table__', None) or name
            logging.info('found model: %s (table: %s)' % (name, tableName))
            #      Field    :
            mappings = dict()
            fields = []
            primaryKey = None
            for k, v in attrs.items():
                if isinstance(v, Field):
                    logging.info('  found mapping: %s ==> %s' % (k, v))
                    mappings[k] = v
                    if v.primary_key:
                        #     :
                        if primaryKey:
                            raise RuntimeError('Duplicate primary key for field: %s' % k)
                        primaryKey = k
                    else:
                        fields.append(k)
            if not primaryKey:
                raise RuntimeError('Primary key not found.')
            for k in mappings.keys():
                attrs.pop(k)
            escaped_fields = list(map(lambda f: '`%s`' % f, fields))
            attrs['__mappings__'] = mappings #            
            attrs['__table__'] = tableName
            attrs['__primary_key__'] = primaryKey #      
            attrs['__fields__'] = fields #         
            #      SELECT, INSERT, UPDATE DELETE  :
            attrs['__select__'] = 'select `%s`, %s from `%s`' % (primaryKey, ', '.join(escaped_fields), tableName)
            attrs['__insert__'] = 'insert into `%s` (%s, `%s`) values (%s)' % (tableName, ', '.join(escaped_fields), primaryKey, create_args_string(len(escaped_fields) + 1))
            attrs['__update__'] = 'update `%s` set %s where `%s`=?' % (tableName, ', '.join(map(lambda f: '`%s`=?' % (mappings.get(f).name or f), fields)), primaryKey)
            attrs['__delete__'] = 'delete from `%s` where `%s`=?' % (tableName, primaryKey)
            return type.__new__(cls, name, bases, attrs)

    これにより、Modelから継承されたクラス(Userなど)は、自動的にModelMetaclassを介してマッピング関係をスキャンし、独自のクラス属性に格納されます.table_、_mappings_に表示されます.
    次に、Modelクラスにclassメソッドを追加すると、すべてのサブクラスにclassメソッドを呼び出すことができます.
    class Model(dict):
    
        ...
    
        @classmethod
        @asyncio.coroutine
        def find(cls, pk):
            ' find object by primary key. '
            rs = yield from select('%s where `%s`=?' % (cls.__select__, cls.__primary_key__), [pk], 1)
            if len(rs) == 0:
                return None
            return cls(**rs[0])

    Userクラスは、クラスメソッドを使用してプライマリ・キー検索を実行できるようになりました.
    user = yield from User.find('123')

    Modelクラスにインスタンスメソッドを追加すると、すべてのサブクラスにインスタンスメソッドを呼び出すことができます.
    class Model(dict):
    
        ...
    
        @asyncio.coroutine
        def save(self):
            args = list(map(self.getValueOrDefault, self.__fields__))
            args.append(self.getValueOrDefault(self.__primary_key__))
            rows = yield from execute(self.__insert__, args)
            if rows != 1:
                logging.warn('failed to insert record: affected rows: %s' % rows)

    これにより、Userインスタンスをデータベースに格納できます.
    user = User(id=123, name='Michael')
    yield from user.save()

    最後のステップは、ORMを改善することです.検索については、次の方法を実現できます.
        findAll() -   WHERE    ;
    
        findNumber() -   WHERE    ,       ,   select count(*)   SQL

    およびupdate()およびremove()メソッド.
    これらの方法はすべて@asyncioを使用する必要があります.coroutine装飾は、協程になります.
    呼び出しには特に注意が必要です.
    user.save()

    save()を呼び出すのは、コヒーレンスが作成されただけで、実行されていないため、何の効果もありません.必ず次のものを使用します.
    yield from user.save()

    本当にINSERT操作を実行しました.
    最後に、私たちが実装したORMモジュールの合計数行のコードを見てみましょう.累計300行未満.PythonでORMを書くのは簡単ではないでしょうか.