Tracking queries, object and Session Changes with Events

SQLAlchemyは、CoreとORM全体で使用される広範な Event Listening システムを特徴としています。ORM内には多種多様なイベントリスナフックがあり、 ORM Events のAPIレベルで文書化されています。このイベントのコレクションは長年にわたって成長し、非常に有用な新しいイベントや、以前ほど関連性のない古いイベントも含まれるようになりました。このセクションでは、主要なイベントフックと、それらがいつ使用されるかについて紹介します。

Execute Events

New in version 1.4: Session に、ORMのために作成されたすべてのSELECT文と、バルクのUPDATE文とDELETE文をインターセプトするように設計された単一の包括的なフックが追加されました。このフックは、以前の QueryEvents.before_compile() イベント、 QueryEvents.before_compile_update() および QueryEvents.before_compile_delete() よりも優先されます。

Session は、 Session.execute() メソッドによって呼び出された全ての問い合わせをインターセプトして変更することができる包括的なシステムを特徴としています。このシステムは SessionEvents.do_orm_execute() イベントフックと ORMExecuteState オブジェクトを利用してイベントの状態を表します。

Basic Query Interception

SessionEvents.do_orm_execute() はまず、 Query1.x style で発行したものや、ORM対応の 2.0 styleselect(),:func:_sql.update,:func:_sql.delete 構文が Session.execute() に渡された場合など、あらゆる種類の問い合わせのインターセプトに役立ちます。 ORMExecuteState 構文は、文、パラメータ、オプションの変更を可能にするアクセサを提供します:

Session = sessionmaker(engine)

@event.listens_for(Session, "do_orm_execute")
def _do_orm_execute(orm_execute_state):
    if orm_execute_state.is_select:
        # add populate_existing for all SELECT statements

        orm_execute_state.update_execution_options(populate_existing=True)

        # check if the SELECT is against a certain entity and add an
        # ORDER BY if so
        col_descriptions = orm_execute_state.statement.column_descriptions

        if col_descriptions[0]["entity"] is MyEntity:
            orm_execute_state.statement = statement.order_by(MyEntity.name)

上記の例は、SELECT文に対する簡単な変更を示しています。このレベルでは、 SessionEvents.do_orm_execute() イベントフックは、以前の QueryEvents.before_compile() イベントの使用を置き換えることを意図しています。これは、さまざまな種類のローダーで一貫して起動されたわけではありません。さらに、 QueryEvents.before_compile() は、 1.x styleQuery の使用にのみ適用され、 2.0 styleSession.execute() の使用には適用されません。

Adding global WHERE / ON criteria

最も要求されているクエリ拡張機能の1つは、すべてのクエリ内のエンティティのすべての出現にWHERE条件を追加する機能です。これは、 with_loader_criteria() クエリオプションを使用することで実現できます。このオプションは単独で使用することも、 SessionEvents.do_orm_execute() イベント内で使用するのに適しています:

from sqlalchemy.orm import with_loader_criteria

Session = sessionmaker(engine)

@event.listens_for(Session, "do_orm_execute")
def _do_orm_execute(orm_execute_state):
    if (
        orm_execute_state.is_select
        and not orm_execute_state.is_column_load
        and not orm_execute_state.is_relationship_load
    ):
        orm_execute_state.statement = orm_execute_state.statement.options(
            with_loader_criteria(MyEntity.public == True)
        )

上記では、すべてのSELECT文にオプションが追加され、 MyEntity に対するすべてのクエリが public == True でフィルタリングされるように制限されます。条件は、直接のクエリの範囲内で、そのクラスの すべての ロードに適用されます。デフォルトでは、 with_loader_criteria() オプションは自動的に関係ローダにも伝播され、遅延ロードやselectinloadsなどを含む後続の関係ロードに適用されます。

いくつかの共通の列構造を特徴とする一連のクラスでは、クラスが declarative mixin を使って構成されている場合、Python lambdaを利用して、mixinクラス自体を with_loader_criteria() オプションと組み合わせて使うことができます。Python lambdaは、クエリのコンパイル時に、条件に一致する特定のエンティティに対して呼び出されます。 HasTimestamp と呼ばれるmixinに基づいた一連のクラスが与えられます:

import datetime

class HasTimestamp:
    timestamp = mapped_column(DateTime, default=datetime.datetime.now)

class SomeEntity(HasTimestamp, Base):
    __tablename__ = "some_entity"
    id = mapped_column(Integer, primary_key=True)

class SomeOtherEntity(HasTimestamp, Base):
    __tablename__ = "some_entity"
    id = mapped_column(Integer, primary_key=True)

上記のクラス SomeEntitySomeOtherEntity はそれぞれ、現在の日付と時刻をデフォルトとする列 timestamp を持ちます。イベントは、 HasTimestamp から拡張されたすべてのオブジェクトをインターセプトし、1か月以内の日付の timestamp 列をフィルタするために使用できます。:

@event.listens_for(Session, "do_orm_execute")
def _do_orm_execute(orm_execute_state):
    if (
        orm_execute_state.is_select
        and not orm_execute_state.is_column_load
        and not orm_execute_state.is_relationship_load
    ):
        one_month_ago = datetime.datetime.today() - datetime.timedelta(months=1)

        orm_execute_state.statement = orm_execute_state.statement.options(
            with_loader_criteria(
                HasTimestamp,
                lambda cls: cls.timestamp >= one_month_ago,
                include_aliases=True,
            )
        )

Warning

with_loader_criteria() の呼び出し内でのlambdaの使用は、 一意のクラスごとに1回のみ 呼び出されます。カスタム関数は、このlambda内で呼び出されるべきではありません。 Using Lambdas to add significant speed gains to statement production で”lambda SQL”機能の概要を参照してください。これは高度な使用のみを目的としています。

See also

ORM Query Events - 上記の with_loader_criteria() レシピの実例が含まれています。

Re-Executing Statements

Deep Alchemy

ステートメントの再実行機能には、少し複雑な再帰的シーケンスが含まれており、SQLステートメントの実行をさまざまな非SQLコンテキストに再ルーティングできるというかなり難しい問題を解決することを目的としています。以下にリンクされている”ドッグパイルキャッシング”と”水平シャーディング”の2つの例は、このかなり高度な機能を使用するのが適切な場合のガイドとして使用する必要があります。

ORMExecuteState は与えられた文の実行を制御することができます。これには、文をまったく呼び出さず、代わりにキャッシュから取得された事前に構築された結果セットを返すことができる機能や、複数のデータベース接続に対して同じ文を呼び出し、結果をメモリにマージするなど、異なる状態で同じ文を繰り返し呼び出す機能が含まれます。これらの高度なパターンの両方は、以下に詳述するように、SQLAlchemyのサンプルスイートで実証されています。

SessionEvents.do_orm_execute() イベントフック内では、 ORMExecuteState.invoke_statement() メソッドを使用して、 Session.execute() の新しいネストされた呼び出しを使用してステートメントを呼び出すことができます。これにより、進行中の現在の実行の後続の処理がプリエンプトされ、代わりに内部実行によって返される Result が返されます。このプロセス内の SessionEvents.do_orm_execute() フックに対してこれまでに呼び出されたイベントハンドラも、このネストされた呼び出し内ではスキップされます。

ORMExecuteState.invoke_statement() メソッドは、 Result オブジェクトを返します。このオブジェクトは、キャッシュ可能な形式に「凍結」し、新しい Result オブジェクトに「凍結解除」する機能と、そのデータを他の Result オブジェクトのデータとマージする機能を備えています。

例えば、 SessionEvents.do_orm_execute() を使ってキャッシュを実装します:

from sqlalchemy.orm import loading

cache = {}

@event.listens_for(Session, "do_orm_execute")
def _do_orm_execute(orm_execute_state):
    if "my_cache_key" in orm_execute_state.execution_options:
        cache_key = orm_execute_state.execution_options["my_cache_key"]

        if cache_key in cache:
            frozen_result = cache[cache_key]
        else:
            frozen_result = orm_execute_state.invoke_statement().freeze()
            cache[cache_key] = frozen_result

        return loading.merge_frozen_result(
            orm_execute_state.session,
            orm_execute_state.statement,
            frozen_result,
            load=False,
        )

上記のフックを配置すると、キャッシュの使用例は次のようになります。:

stmt = (
    select(User).where(User.name == "sandy").execution_options(my_cache_key="key_sandy")
)

result = session.execute(stmt)

上記では、 SessionEvents.do_orm_execute() フックによってインターセプトされる「キャッシュキー」を確立するために、カスタム実行オプションが Select.execution_options() に渡されます。このキャッシュキーは、キャッシュ内に存在する可能性のある FrozenResult オブジェクトと照合され、存在する場合はそのオブジェクトが再利用されます。レシピでは Result.freeze() メソッドを使用して Result オブジェクトを「フリーズ」します。上記ではORM結果が含まれているため、キャッシュに保存して複数回使用できます。「フリーズされた」結果からライブ結果を返すために、 merge_frozen_result() 関数を使用して、結果オブジェクトから現在のセッションに「フリーズされた」データをマージします。

上の例は完全な例として Dogpile Caching に実装されています。

ORMExecuteState.invoke_statement() メソッドも複数回呼び出すことができ、 ORMExecuteState.invoke_statement.bind_arguments パラメータに異なる情報を渡して、 Session が毎回異なる Engine オブジェクトを使用するようにします。これは毎回異なる Result オブジェクトを返します。これらの結果は Result.merge() メソッドを使用してマージできます。これは Horizontal Sharding 拡張で採用されているテクニックです。詳しくはソースコードを参照してください。

Persistence Events

おそらく最も広く使用されている一連のイベントは、 flush process に対応する”persistence”イベントです。flushでは、オブジェクトに対する保留中の変更に関するすべての決定が行われ、INSERT、UPDATE、およびDELETE文の形式でデータベースに出力されます。

before_flush()

SessionEvents.before_flush() フックは、フラッシュが進行したときにデータベースに対して追加の永続性の変更が行われることをアプリケーションが確実にしたい場合に使用する、最も一般的に有用なイベントです。 SessionEvents.before_flush() を使用して、オブジェクトを操作してその状態を検証し、追加のオブジェクトと参照を永続化する前に構成します。このイベント内では、セッションの状態を 安全に操作できます 。つまり、新しいオブジェクトをセッションにアタッチしたり、オブジェクトを削除したり、オブジェクトの個々の属性を自由に変更したりできます。これらの変更は、イベントフックが完了したときにフラッシュプロセスにプルされます。

典型的な SessionEvents.before_flush() フックは、 Session.new,:attr:.Session.dirty,:attr:.Session.deleted コレクションをスキャンして、何かが起こりそうなオブジェクトを探します。

SessionEvents.before_flush() の図については、 Versioning with a History TableVersioning using Temporal Rows などの例を参照してください。

after_flush()

SessionEvents.after_flush() フックは、フラッシュプロセスのためにSQLが発行された後に呼び出されますが、 フラッシュされたオブジェクトの状態が変更される 前に呼び出されます。つまり、 Session.newSession.dirty 、および Session.deleted コレクションを調べて、フラッシュされた内容を確認することができます。また、 AttributeState で提供されているような履歴追跡機能を使用して、どの変更が保持されたかを確認することもできます。 SessionEvents.after_flush() イベントでは、何が変更されたかを監視して、追加のSQLをデータベースに発行できます。

after_flush_postexec()

SessionEvents.after_flush_postexec() は、 SessionEvents.after_flush() の直後に呼び出されますが、直前に行われたフラッシュを説明するためにオブジェクトの状態が変更された に呼び出されます。 Session.new,:attr:.Session.dirty および Session.deleted コレクションは、通常ここでは完全に空です。 SessionEvents.after_flush_postexec() を使用して、最終決定されたオブジェクトのIDマップを検査し、場合によっては追加のSQLを出力します。このフックには、オブジェクトに新しい変更を加える機能があります。つまり、 Session は再び「ダーティ」状態になります。ここでの Session の仕組みでは、フラッシュが Session.commit() のコンテキストで呼び出された場合、このフックで新しい変更が検出されると、 再び フラッシュされます。そうでない場合、保留中の変更は次の通常のフラッシュの一部としてバンドルされます。フックが Session.commit() 内で新しい変更を検出すると、 SessionEvents.after_flush_postexec() フックが呼び出されるたびにフラッシュされる新しい状態を継続的に追加する場合に、この点に関する無限ループが100回の反復後に停止されることをカウンタが保証します。

Mapper-level Flush Events

フラッシュレベルのフックに加えて、オブジェクトごとに呼び出され、フラッシュプロセス内のINSERT、UPDATE、またはDELETEに基づいて分割されるという点で、よりきめ細かいフックのスイートもあります。これらはマッパー永続性フックであり、非常に一般的ですが、これらのイベントは、すでに進行中のフラッシュプロセスのコンテキスト内で進行するため、より慎重にアプローチする必要があります。多くの操作は、ここで進行するのは安全ではありません。

イベントは次のとおりです。:

* :meth:`.MapperEvents.before_insert`
* :meth:`.MapperEvents.after_insert`
* :meth:`.MapperEvents.before_update`
* :meth:`.MapperEvents.after_update`
* :meth:`.MapperEvents.before_delete`
* :meth:`.MapperEvents.after_delete`

Note

これらのイベントは、session flush operationのみ 適用され、 ORM-Enabled INSERT, UPDATE, and DELETE statements で説明されているORMレベルのINSERT/UPDATE/DELETE機能には 適用されない ことに注意してください。ORMレベルのDMLをインターセプトするには、 SessionEvents.do_orm_execute() イベントを使用します。

各イベントには、 Mapper 、マップされたオブジェクト自身、そしてINSERT、UPDATE、DELETE文を発行するために使用されている Connection が渡されます。これらのイベントの魅力は明らかで、もしアプリケーションが、ある特定の型のオブジェクトがINSERTで永続化されているときに何らかのアクティビティを結び付けたい場合、フックは非常に特殊です。 SessionEvents.before_flush() イベントとは異なり、ターゲットを見つけるために Session.new のようなコレクションを検索する必要はありません。しかし、発行されるすべてのINSERT、UPDATE、DELETE文の完全なリストを表すフラッシュ計画は、これらのイベントが呼び出されたときに*すでに決定され*ており、この段階では変更は行われません。したがって、与えられたオブジェクトに対して可能な変更は、オブジェクトの行の ローカル 属性に対してのみ行われます。オブジェクトまたは他のオブジェクトに対するその他の変更は、 Session の状態に影響を与え、適切に機能しなくなります。

マッパー・レベルのパーシスタンス・イベント内でサポートされない操作には、以下のものがあります。:

  • Session.add()

  • Session.delete()

  • マップされたコレクションのappend、add、remove、delete、discardなど

  • マップされた関係属性のset/delイベント

    i.e. someobject.related = someotherobject

Connection が渡される理由は、カウンタのインクリメントやログテーブルへの追加行の挿入など、 単純なSQL操作がここで 直接 Connection で行われることが推奨されているからです。

また、フラッシュイベント内で処理する必要のないオブジェクトごとの操作も数多くあります。最も一般的な方法は、新しいオブジェクトに関連付けられる追加のオブジェクトを作成するなど、 __init__() メソッド内のオブジェクトとともに追加の状態を単純に確立することです。 Simple Validators で説明されているバリデータを使用する方法もあります。これらの関数は属性の変更をインターセプトし、属性の変更に応じてターゲットオブジェクトに追加の状態の変更を確立できます。どちらの方法でも、オブジェクトはフラッシュステップに到達する前に正しい状態になります。

Object Lifecycle Events

イベントのもう1つの使用例は、オブジェクトのライフサイクルを追跡することです。これは Quickie Intro to Object States で最初に導入された状態を参照します。

上記のすべての状態は、イベントで完全に追跡できます。各イベントは明確な状態遷移を表します。つまり、開始状態と宛先状態は両方とも追跡対象の一部です。最初の一時的なイベントを除いて、すべてのイベントは Session オブジェクトまたはクラスで表されます。つまり、特定の Session オブジェクトのいずれかに関連付けることができます:

from sqlalchemy import event
from sqlalchemy.orm import Session

session = Session()

@event.listens_for(session, "transient_to_pending")
def object_is_pending(session, obj):
    print("new pending: %s" % obj)

または、 Session クラス自体、および特定の sessionmaker (おそらく最も有用な形式です):

from sqlalchemy import event
from sqlalchemy.orm import sessionmaker

maker = sessionmaker()

@event.listens_for(maker, "transient_to_pending")
def object_is_pending(session, obj):
    print("new pending: %s" % obj)

一般的なように、1つの機能の上にリスナーをスタックすることもできます。たとえば、持続状態に入るすべてのオブジェクトを追跡するには、次のように指定します。:

@event.listens_for(maker, "pending_to_persistent")
@event.listens_for(maker, "deleted_to_persistent")
@event.listens_for(maker, "detached_to_persistent")
@event.listens_for(maker, "loaded_as_persistent")
def detect_all_persistent(session, instance):
    print("object is now persistent: %s" % instance)

Transient

マップされたすべてのオブジェクトは、最初に構築されたときに transient として始まります。この状態では、オブジェクトは単独で存在し、 Session との関連付けはありません。この初期状態では、 Session が存在しないので、特定の”遷移”イベントはありませんが、一時的なオブジェクトが作成されたときにインターセプトしたい場合は、 InstanceEvents.init() メソッドがおそらく最適なイベントです。このイベントは特定のクラスまたはスーパークラスに適用されます。たとえば、特定の宣言ベースのすべての新しいオブジェクトをインターセプトするには:

from sqlalchemy.orm import DeclarativeBase
from sqlalchemy import event

class Base(DeclarativeBase):
    pass

@event.listens_for(Base, "init", propagate=True)
def intercept_init(instance, args, kwargs):
    print("new transient: %s" % instance)

Transient to Pending

一時的なオブジェクトは、 Session.add() または Session.add_all() メソッドによって最初に Session に関連付けられたときに pending になります。オブジェクトは、明示的に追加された参照オブジェクトからの “cascade” の結果として、 Session の一部になることもあります。一時的な遷移から保留中の遷移は、 SessionEvents.transient_to_pending() イベントを使用して検出できます:

@event.listens_for(sessionmaker, "transient_to_pending")
def intercept_transient_to_pending(session, object_):
    print("transient to pending: %s" % object_)

Pending to Persistent

フラッシュが進み、インスタンスに対してINSERT文が実行されると、 pending オブジェクトは persistent になります。これで、オブジェクトにはIDキーが設定されました。 SessionEvents.pending_to_persistent() イベントを使用してpendingからpersistentを追跡します:

@event.listens_for(sessionmaker, "pending_to_persistent")
def intercept_pending_to_persistent(session, object_):
    print("pending to persistent: %s" % object_)

Pending to Transient

pending オブジェクトは、pendingオブジェクトがフラッシュされる前に Session.rollback() メソッドが呼び出された場合、またはオブジェクトがフラッシュされる前に Session.expunge() メソッドが呼び出された場合に、 transient に戻すことができます。 SessionEvents.pending_to_transient() イベントでpendingからtransientを追跡します:

@event.listens_for(sessionmaker, "pending_to_transient")
def intercept_pending_to_transient(session, object_):
    print("transient to pending: %s" % object_)

Loaded as Persistent

オブジェクトは、データベースからロードされたときに、 persistent 状態で直接 Session に現れることができます。この状態遷移を追跡することは、オブジェクトがロードされたときに追跡することと同義であり、 InstanceEvents.load() インスタンスレベルのイベントを使用することと同義です。ただし、 SessionEvents.loaded_as_persistent() イベントは、オブジェクトがこの特定の手段を介して永続状態に入るときにオブジェクトをインターセプトするためのセッション中心のフックとして提供されています:

@event.listens_for(sessionmaker, "loaded_as_persistent")
def intercept_loaded_as_persistent(session, object_):
    print("object loaded into persistent state: %s" % object_)

Persistent to Transient

オブジェクトが最初に保留として追加されたトランザクションに対して Session.rollback() メソッドが呼び出された場合、永続オブジェクトは一時的な状態に戻ることができます。ROLLBACKの場合、このオブジェクトを永続的にしたINSERT文はロールバックされ、オブジェクトは再び一時的になるために Session から削除されます。 SessionEvents.persistent_to_transient() イベントフックを使用して、永続的から一時的に戻されたオブジェクトを追跡します:

@event.listens_for(sessionmaker, "persistent_to_transient")
def intercept_persistent_to_transient(session, object_):
    print("persistent to transient: %s" % object_)

Persistent to Deleted

削除対象としてマークされたオブジェクトがフラッシュプロセス内でデータベースから削除されると、永続オブジェクトは deleted 状態になります。これは、ターゲットオブジェクトに対して Session.delete() メソッドが呼び出されたときと 同じ ではないことに注意してください。 Session.delete() メソッドは、オブジェクトを削除対象として マーク するだけです。実際のDELETE文は、フラッシュが進行するまで発行されません。ターゲットオブジェクトに対して”削除済み”状態が存在するのは、フラッシュの後です。

“削除された”状態では、オブジェクトは Session にほんのわずかしか関連付けられていません。このオブジェクトはIDマップにも、削除が保留されていたときを参照する Session.deleted コレクションにも存在しません。

“削除された”状態から、オブジェクトは、トランザクションがコミットされたときにデタッチ状態になるか、またはトランザクションがロールバックされた場合に永続状態に戻ることができます。

SessionEvents.persistent_to_deleted() を使用して、永続から削除への遷移を追跡します:

@event.listens_for(sessionmaker, "persistent_to_deleted")
def intercept_persistent_to_deleted(session, object_):
    print("object was DELETEd, is now in deleted state: %s" % object_)

Deleted to Detached

セッションのトランザクションがコミットされると、削除されたオブジェクトは detached になります。 Session.commit() メソッドが呼び出された後、データベーストランザクションはfinalとなり、 Session は削除されたオブジェクトを完全に破棄し、そのオブジェクトへのすべての関連付けを削除します。 SessionEvents.deleted_to_detached() を使用して、削除からデタッチへの遷移を追跡します:

@event.listens_for(sessionmaker, "deleted_to_detached")
def intercept_deleted_to_detached(session, object_):
    print("deleted to detached: %s" % object_)

Note

オブジェクトが削除された状態にある間は、 InstanceState.deleted 属性は、 inspect(object).deleted を使ってアクセスでき、Trueを返します。しかし、オブジェクトがデタッチされると、 InstanceState.deleted は再びFalseを返します。デタッチされたかどうかに関わらず、オブジェクトが削除されたことを検出するには、 InstanceState.was_deleted アクセッサを使います。

Persistent to Detached

Session.expunge()Session.expunge_all() 、または Session.close() メソッドによって、オブジェクトと Session との関連付けが解除されると、永続オブジェクトは detached になります。

Note

オブジェクトを所有する Session がアプリケーションによって参照解除され、ガーベッジコレクションによって破棄された場合、オブジェクトは 暗黙的にデタッチ されます。この場合、 イベントは発生しません

SessionEvents.persistent_to_detached() イベントを使用して、永続からデタッチに移動するオブジェクトを追跡します:

@event.listens_for(sessionmaker, "persistent_to_detached")
def intercept_persistent_to_detached(session, object_):
    print("object became detached: %s" % object_)

Detached to Persistent

デタッチされたオブジェクトは、 Session.add() または同等のメソッドを使用してセッションに再関連付けられると、永続的になります。 SessionEvents.detached_to_persistent() イベントを使用して、デタッチされた状態から永続的な状態に戻るオブジェクトを追跡します:

@event.listens_for(sessionmaker, "detached_to_persistent")
def intercept_detached_to_persistent(session, object_):
    print("object became persistent again: %s" % object_)

Deleted to Persistent

DELETEd オブジェクトは、それが削除されたトランザクションが Session.rollback() メソッドを使用してロールバックされたときに、 persistent 状態に戻すことができます。 SessionEvents.DELETEd_to_persistent() イベントを使用して、削除されたオブジェクトが永続状態に戻ったことを追跡します:

@event.listens_for(sessionmaker, "deleted_to_persistent")
def intercept_deleted_to_persistent(session, object_):
    print("deleted to persistent: %s" % object_)

Transaction Events

トランザクションイベントを使用すると、 Session レベルでトランザクション境界が発生したときや、 SessionConnection オブジェクトのトランザクション状態を変更したときに、アプリケーションに通知することができます。

  • SessionEvents.after_transaction_create() , SessionEvents.after_transaction_end() - これらのイベントは、個々のデータベース接続に固有ではない方法で、 Session の論理的なトランザクションスコープを追跡します。これらのイベントは、 zope.sqlalchemy のようなトランザクション追跡システムの統合を助けることを意図しています。アプリケーションが外部スコープを Session のトランザクションスコープと整列させる必要がある場合に、これらのイベントを使用します。これらのフックは、論理的な”サブトランザクション”と”ネストされた”(例えばSAVEPOINT)トランザクションを追跡するという点で、 Session の”ネストされた”トランザクション動作を反映しています。

Attribute Change Events

属性変更イベントは、オブジェクトの特定の属性が変更されたときのインターセプトを可能にします。これらのイベントには AttributeEvents.set(),:meth:.AttributeEvents.append ,および AttributeEvents.remove() があります。これらのイベントは、特にオブジェクトごとの検証操作に非常に便利です。しかし、これらのフックを背後で使用する「バリデータ」フックを使用する方がはるかに便利なことがよくあります。この背景については Simple Validators を参照してください。属性イベントは、後方参照の仕組みの背後にもあります。属性イベントの使用例は Attribute Instrumentation にあります。