Pythonでメタクラスを使用する

一部のメタプログラミングツールは日常的に使用されないことが多い

OOPクラスまたは同じデコレータで通常どおり動作します。 目標を理解する

そのようなツールを言語に導入するには、産業用の特定の例を必要とします

アプリケーションの一部を以下に示します。







メタクラスの概要





したがって、従来のOOPはクラスとオブジェクトのみを意味します。

クラスはオブジェクトのテンプレートです。 クラスを宣言するとき、すべてのメカニズムが示されます

特定の各「実施形態」の作業:カプセル化されたデータが設定されます

オブジェクト内、およびこのデータを操作するためのメソッド。





Pythonは古典的なパラダイムを拡張し、その中のクラス自体も

変更して変数に割り当てることができるピアオブジェクト

関数を渡します。 しかし、クラスがオブジェクトの場合、どのクラスに対応しますか?

デフォルトでは、このクラス(メタクラス)はタイプと呼ばれます。



メタクラスから継承して、新しいメタクラスを取得できます。

キューは、新しいクラスを定義するときに使用できます。 このように

継承の新しい「ディメンション」が表示され、継承階層に追加されます

クラス:メタクラス->クラス->オブジェクト。



簡単な例





__init __(self、* args、

** kwargs)。 次のような方法でこのプロセスを高速化したい

クラスオブジェクトを作成するときに属性を直接設定する機能。 いつもと

そのようなクラスは機能しません:



   >>>クラスMan(オブジェクト):
   >>>パス
   >>> me = Man(身長= 180、体重= 80)
  トレースバック(最後の最後の呼び出し):
  ファイル「<stdin>」、20行目、<module>
       TypeError:object .__ new __()はパラメーターを取りません




オブジェクトは、「()」演算子を使用してクラスを呼び出すことにより構築されます。 から継承を作成する

このステートメントをオーバーライドするメタクラスを入力します。





   >>> AttributeInitTypeクラス(タイプ):
   >>> def __call __(self、* args、** kwargs):
   >>> "" "クラスを呼び出すと新しいオブジェクトが作成されます。" ""
   >>>#まず最初に、オブジェクト自体を作成します...
   >>> obj = type .__ call __(self、* args)
   >>>#...そして、それに属性として呼び出しで渡された引数を追加します。
   >>> kwargsの名前:
   >>> setattr(obj、名前、kwargs [名前])
   >>>#完成したオブジェクトを返します
   >>> objを返す


次に、新しいメタクラスを使用してクラスを作成します。



   >>>クラスMan(オブジェクト):
   >>> __metaclass__ = AttributeInitType


出来上がり:



   >>> me = Man(身長= 180、体重= 80)
   >>> me.heightを印刷
   180




言語拡張(抽象クラ​​ス)



Pythonコアは比較的小さくシンプルで、組み込みツールのセットです

これにより、開発者は言語をすばやく習得できます。





ただし、たとえばフレームワークや

関連する特別なサブ言語(ドメイン固有言語)が提供されます

非常に柔軟なツール。



抽象クラス(またはわずかに異なるフォーム-インターフェイス)-共通

プログラマーの間でインターフェース部分を決定する一般的な方法

クラス。 通常、このような概念は、言語のコア(JavaまたはC ++など)に組み込まれています。

Pythonを使用すると、独自の方法で、優雅かつ簡単に実装できます。

特に、メタクラスとデコレータの助けを借りて。



標準ライブラリの実装提案からabcライブラリの作業を検討してください。



abc




抽象クラスの使用は非常に簡単です。 抽象基本クラスを作成する

仮想メソッドを使用し、このメソッドを定義せずに継承クラスを作成しようとします。



 >>> abc import ABCMeta、abstractmethodから
 >>>クラスA(オブジェクト):
 >>> __metaclass __ = ABCMeta
 >>> @abstractmethod
 >>> def foo(self):パス
 >>> 
 >>> A()  
トレースバック(最後の最後の呼び出し):
  ファイル「<stdin>」、1行目、<module>
 TypeError:抽象メソッドfooで抽象クラスAをインスタンス化できません


うまくいきませんでした。 次に、目的のメソッドを定義します。



  
 >>>クラスC(A):
 >>> def foo(self):印刷(42)
 >>> C
 <クラス '__main __。C'>
 >>> a = C()
 >>> a.foo()
 42


これがメタクラスでどのように実装されているかを学びます(他のいくつかの機能は省略します)

abc)ABCMetaモジュール:



 >>>クラスABCMeta(タイプ):
 >>> def __new __(mcls、name、bases、namespace):
 >>> bases = _fix_bases(ベース)
 >>> cls = super(ABCMeta、mcls).__ new __(mcls、name、bases、namespace)
 >>>#適切な抽象メソッドの多くの(セットの)名前を見つけます
 >>>#先祖のメソッドとメソッド
 >>> abstracts = set(name
 >>>名前、namespace.items()の値
 >>> if getattr(値、 "__isabstractmethod__"、False))
 >>>ベースのベースの場合:
 >>> getattrの名前(base、 "__ abstractmethods __"、set()):
 >>> value = getattr(cls、name、None)
 >>> getattrの場合(値、「__ isabstractmethod __」、False):
 >>> abstracts.add(名前)
 >>> cls .__ abstractmethods__ = frozenset(抽象)
 >>> clsを返す


_fix_basesメソッドは、非表示の_Abstractクラスを祖先の数に追加します

抽象クラス。 _Abstract自体が、何かが残っているかどうかを確認します

セット(セット)__abstractmethods__; 残っている場合、例外をスローします。



 >>>クラス_Abstract(オブジェクト):
 >>> def __new __(cls、* args、** kwds):
 >>> am = cls .__ dict __。get( "__ abstractmethods__")
 >>> amの場合:
 >>> raise TypeError(「抽象クラス%sをインスタンス化できません」
 >>> "抽象メソッドを使用%s"%
 >>>(cls .__ name__、 "、" .join(sorted(am))))
 >>> return super(_Abstract、cls).__ new __(cls、* args、** kwds)
 >>>
 >>> def _fix_bases(ベース):
 >>>ベースのベースの場合:
 >>> issubclass(ベース、_Abstract)の場合:
 >>>#_Abstractはすでに祖先に含まれています
 >>>拠点を返す
 >>>ベースのオブジェクトの場合:
 >>>#クラスがオブジェクトを直接継承する場合は、オブジェクトを_Abstractに置き換えます
 >>>#および他の祖先にリストされていない
 >>>タプルを返します([_ベースがオブジェクトの場合は抽象、その他の場合はベース
 >>>ベースのベースの場合])
 >>>#_Abstractを最後に追加します
 >>> return bases +(_Abstract、)


各抽象クラスでは、「凍結」セット(凍結セット)に従って保存されます。

抽象メソッド; つまり、次のメソッド(関数オブジェクト)

対応するデコレータによって設定された__isabstractmethod__属性:



 >>> def abstractmethod(funcobj):
 >>> funcobj .__ isabstractmethod__ = True
 >>> funcobjを返す


したがって、抽象メソッドは、割り当てられたときに__isabstractmethod__属性を取得します

デコレータ。 抽象クラスから継承した後の属性は、

派生クラスのセット「__abstractmethods__」。 セットが空でない場合、および

プログラマがクラスオブジェクトを作成しようとすると、例外がスローされます

未定義のメソッドのリストを含むTypeError。



おわりに


ただ? シンプル。 言語は拡張されていますか? 拡張。 彼らが言うように、コメントは不要です。



DjangoのDSL



高度なDSLの例の1つは、Modelクラスを例として使用するDjango ORMメカニズムです。

メタクラスModelBase。 具体的には、ここで与えられたデータベースとの接続は面白くなく、持っています

Modelクラスの派生クラスのインスタンスの作成に集中することは理にかなっています。



次のサブセクションのほとんどはコード解析です。

ModelBase 詳細を必要としない読者は結論を読むだけでよい

Djangoセクションの最後。



ModelBaseメタクラスの解析


ModelBaseメタクラス作業のすべてのメカニズムが適切に集中している

作成直前に呼び出された__new__メソッドをオーバーライドします

モデルクラスインスタンス:



   >>>クラスModelBase(タイプ):
   >>> "" "
   >>>すべてのモデルのメタクラス。
   >>> "" "
   >>> def __new __(cls、name、bases、attrs):
   >>> super_new = super(ModelBase、cls).__ new__
   >>>ペアレント= [isinstance(b、ModelBase)の場合、ベースのbのb]]
   >>>親でない場合:
   >>>#これがModelのサブクラスでない場合、特別なことは何もしないでください。
   >>> super_newを返す(cls、name、bases、attrs)


メソッドの最初に、クラスのインスタンスが作成され、このクラスがそうでない場合

Modelを継承し、単に戻ります。



モデルクラスのすべての特定のオプションは、_metaクラスの属性に収集されます。

ゼロから作成したり、先祖から継承したり、

ローカルメタクラス:



   >>>#クラス作成
   >>> module = attrs.pop( '__ module__')
   >>> new_class = super_new(cls、name、bases、{'__ module__':module})
   >>> attr_meta = attrs.pop( 'Meta'、None)
   >>> abstract = getattr(attr_meta、 'abstract'、False)
   >>> attr_metaでない場合:
   >>> meta = getattr(new_class、 'Meta'、None)
   >>>その他:
   >>> meta = attr_meta
   >>> base_meta = getattr(new_class、 '_ meta'、なし)


さらに、クラスは抽象的であり、適切ではない可能性があることがわかります

データベース内の任意のテーブル。



モデルクラスを作成するプロセスの真実の瞬間は、モデルクラスを作成するときに発生します

デフォルトのオプション:



   >>> new_class.add_to_class( '_ meta'、Options(meta、** kwargs))


add_to_classは、引数のContributor_to_classメソッドを呼び出すか、

存在せず、名前付き属性をクラスに追加するだけです。



Contributorクラスは、contribute_to_classで、_meta属性を参照します

データベーステーブルの名前など、さまざまなパラメータを収集します

データ、モデルフィールドのリスト、仮想モデルフィールドのリスト、アクセス権、

その他。 また、他のモデルとの一意性もチェックします。

データベース内のフィールド名。



次に、__ new__メソッドで、非抽象クラスが名前付きで追加されます

例外:



   >>>抽象的でない場合:
   >>> new_class.add_to_class( 'DoesNotExist'、
   >>> subclass_exception( 'DoesNotExist'、ObjectDoesNotExist、モジュール))
   >>> new_class.add_to_class( 'MultipleObjectsReturned'、
   >>> subclass_exception( 'MultipleObjectsReturned'、
   >>> MultipleObjectsReturned、モジュール))


親クラスが抽象クラスではなく、パラメーターがローカルで明示的に設定されていない場合

クラスMetaで、orderingおよびget_latest_byパラメーターを継承します。



   >>> base_meta.abstractではなくbase_metaの場合:
   >>> hasattrでない場合(メタ、「順序付け」):
   >>> new_class._meta.ordering = base_meta.ordering
   >>> hasattrでない場合(メタ、「get_latest_by」):
   >>> new_class._meta.get_latest_by = base_meta.get_latest_by


デフォルトのマネージャーはゼロでなければなりません。 そのようなモデルが既に存在する場合、このモデルを返すことで処理を完了します。



   >>> getattrの場合(new_class、 '_default_manager'、None):
   >>> new_class._default_manager =なし
   >>>        
   >>> m = get_model(new_class._meta.app_label、name、False)
   >>> mがNoneでない場合:
   >>> mを返す




特別なことはありません。モデルクラスに追加されたばかりの属性

作成者:



   >>> obj_nameの場合、attrs.items()のobj:
   >>> new_class.add_to_class(obj_name、obj)


次に、モデルのフィールドを調べて、1対1の関係を見つける必要があります。

少し低くなります:



   >>>#モデルの親に対して適切なセットアップを行います。
   >>> o2o_map = dict([(f.rel.to、f)for f for new_class._meta.local_fields
   >>> if isinstance(f、OneToOneField)])


モデルの祖先を通過してさまざまなフィールドを継承し、それらを破棄します

それはモデルの相続人ではありません。 以下のコメントが翻訳されています

何が起こっているのかを理解するのに十分です:



   >>>親のベースの場合:
   >>> hasattr(ベース、 '_ meta')でない場合:
   >>>#_metaのないモデルは無効であり、関心がありません
   >>>続ける
   >>>
   >>>#このモデルの任意のタイプのすべてのフィールド
   >>> new_fields = new_class._meta.local_fields + \
   >>> new_class._meta.local_many_to_many + \
   >>> new_class._meta.virtual_fields
   >>> field_names = set([f.name for f in new_fields])
   >>>
   >>> base._meta.abstractでない場合:
   >>>#「具象」クラスを処理します...
   >>> o2o_mapのベースの場合:
   >>> field = o2o_map [base]
   >>> field.primary_key = True
   >>> new_class._meta.setup_pk(フィールド)
   >>>その他:
   >>> attr_name = '%s_ptr'%base._meta.module_name
   >>> field = OneToOneField(base、name = attr_name、
   >>> auto_created = True、parent_link = True)
   >>> new_class.add_to_class(attr_name、フィールド)
   >>> new_class._meta.parents [base] = field
   >>>
   >>>その他:
   >>>#.. and abstract。
   >>>
   >>>#クラス間の名前の衝突をチェックし、
   >>>#このクラスおよび抽象祖先で宣言されています
   >>> parent_fields = base._meta.local_fields + base._meta.local_many_to_many
   >>> parent_fieldsのフィールド:
   >>> field_namesのfield.nameの場合:
   >>> FieldErrorを発生させます(「クラス%rのローカルフィールド%rの衝突」\
   >>> '似た名前のフィールドから' \
   >>> '抽象基本クラス%r'%\
   >>>(field.name、name、base .__ name__))
   >>> new_class.add_to_class(field.name、copy.deepcopy(フィールド))
   >>>
   >>>#非抽象親はすべて相続人に転属
   >>> new_class._meta.parents.update(base._meta.parents)
   >>>
   >>>#Base Managerは抽象クラスを継承します
   >>> base_managers = base._meta.abstract_managers
   >>> base_managers.sort()
   >>> _、mgr_name、base_managersのマネージャー:
   >>> val = getattr(new_class、mgr_name、None)
   >>> valまたはvalがマネージャーでない場合:
   >>> new_manager = manager._copy_to_model(new_class)
   >>> new_class.add_to_class(mgr_name、new_manager)
   >>>
   >>>#親から取得した仮想フィールド(GenericForeignKeyなど)
   >>> base._meta.virtual_fieldsのフィールド:
   >>> base._meta.abstractおよびfield_nameがfield_namesの場合:
   >>> FieldErrorを発生させます(「クラス%rのローカルフィールド%rの衝突」\
   >>> '似た名前のフィールドから' \
   >>> '抽象基本クラス%r'%\
   >>>(field.name、name、base .__ name__))
   >>> new_class.add_to_class(field.name、copy.deepcopy(フィールド))
   >>>


抽象モデルクラスはどこにも登録されていません。



   >>>抽象の場合:
   >>> #Abstractモデルはインスタンス化できず、表示されません
   >>>#アプリケーションのモデルのリストで
   >>>#通常モデル
   >>> attr_meta.abstract = False
   >>> new_class.Meta = attr_meta
   >>> new_classを返す


通常のものは登録され、登録済みのリストからすでに返されています

モデルクラス:



   >>> new_class._prepare()
   >>> register_models(new_class._meta.app_label、new_class)
   >>> get_model(new_class._meta.app_label、name、False)を返します




おわりに


要約すると。 なぜメタクラスが必要ですか?



1)モデルクラスには、必須パラメーターのセット(テーブル名、名前

djangoアプリケーション、フィールドのリスト、他のモデルとのリンクなど)

_meta属性。継承する各クラスの作成時に定義されます

モデルから。



2)これらのパラメーターは、通常および抽象から複雑に継承されます

祖先クラス。これは、クラス自体に置くのはいです。



3)使用しているプログラマーに何が起こっているかを隠す機会があります

枠組み



お知らせ



1)オブジェクトからクラスの継承を明示的に指定しない場合、クラスは

グローバル変数__metaclass__で指定されたメタクラス。

内で独自のメタクラスを再利用するときに便利です

1つのモジュール。 メモの冒頭にある簡単な例をやり直すことができます

次のように:



  クラスAttributeInitType(タイプ):
       def __call __(self、* args、** kwargs):
       obj = type .__ call __(self、* args)
       kwargsの名前:
       setattr(obj、名前、kwargs [名前])
       objを返す

   __metaclass__ = AttributeInitType

  クラスの男:
      合格する

   me = Man(身長= 180、体重= 80)
   me.heightを印刷

  以下は標準ストリームに出力されます:
   180


2)そのようなpythonic supergur、Tim Petersがいます。 彼はとてもよく言った

ブラックマジックPythonのカテゴリのメタクラスおよび同様のツールの使用:



    メタクラスは、99%のユーザーが心配する必要があるよりも深い魔法です
    について。 あなたがそれらを必要とするかどうか疑問に思うなら、あなたは(
    実際にそれらを必要とする人々は、彼らが必要とすることを確実に知っています
    それら、そしてその理由についての説明は必要ありません)。


ロシア語では、次のように聞こえます。



    メタクラスはほとんどのユーザーにとって不要です。 不思議に思うなら
    問題は、それらが必要であるかどうかであり、それらは間違いなく必要ではありません。 人々だけがそれらを使用し、
    彼らが何をしているかを正確に知っており、説明を必要としません。


道徳は単純です:賢明ではありません。 ほとんどの場合、メタクラスは不要です。 pythonistは、最も驚きの少ない原則に導かれるべきです。

ナルシシズムのためだけに、古典的なOOP作業スキームを変更する価値はありません。



に基づく参照





英語版ウィキペディア -簡単な例がここから引用されています

PEP-3119-ここに

抽象クラスは、フルバージョンで説明されています。

クリップ

英語で、Pythonのメタクラスに関する詳細な会話と例

使用します。 そこで、リンクを介して、あなたは非常に例で記事自体を見つけることができます

有益です。




All Articles