概要
滅多に使わないのですが、たまーに使いたいシーンが出てきたりするのが動的にクラス定義するやり方。
よく忘れるので自分用にメモです。
環境
$ sw_vers
ProductName: Mac OS X
ProductVersion: 10.14.4
BuildVersion: 18E226
$ python --version
Python 3.7.2
Python では 「全てがオブジェクト」
よく言われるこの言葉ですが、Python の場合はクラス定義することもオブジェクトを作ることと同じことになります。
なんのオブジェクトかっていうと、 type
クラスのインスタンスとなります。
実際に実行してみるとすぐに分かるのですが
class MyClass: pass type(MyClass) Out[3]: type m = MyClass() type(m) Out[5]: __main__.MyClass
というように、クラスに対して type()
を呼び出すと type
って教えてくれます。
type()
を引数一つで呼び出すと、object.__class__
が呼ばれます。
なので
MyClass.__class__ Out[6]: type
となります。__class__
は、そのクラスインスタンスがどのクラスに属しているかを示します。
クラスインスタンスの属しているクラスは __class__
、継承先は __bases__
でわかります。
MyClass.__bases__ Out[7]: (object,)
ちなみに type(type)
ってすると type
って表示されます。こういうところがPythonいいですよね。
type
クラスとobject
クラスの関係については、以下の記事がわかりやすいです。
このあたりの仕組みは、Pythonでメタプログラミングする際にとても重要な概念です。
でもまあ、あんまり使わないに越したことはないテクニックですよね。魔術みたいになるし・・・。
シンプルなのが一番です。
type()
で動的クラス定義
で、type()
さんの出番なのですが、この人、関数に見えますが実はクラスです。
ドキュメント見ると
ちゃんとクラスって書いてあります。
コンストラクタが2パターンあって、引数一つのものがいつも使っているやつです。
引数一つだと、object.__class__
を返してくれます。
もう一つあって、こっちは3つの引数を受け取って、新しい型オブジェクトを作って返してくれます。
こちらを使うと動的にクラス定義できます。
3つの引数は、それぞれ「クラスの名前」「継承クラスのタプル」「属性の辞書」です。
こんな感じ
In [50]: DynClass = type('DynClass', (object,), dict(member1=10)) In [52]: d1 = DynClass() In [53]: d1.member1 Out[53]: 10
当然、いつものクラス定義と同じことしているのでちゃんとインスタンスも生成されます。
In [54]: d2 = DynClass() In [55]: d2.member1 = 20 In [56]: d1.member1, d2.member1 Out[56]: (10, 20)
サンプル
あまり、いいサンプルではないですが、type()
とtypes.new_class
を使ってクラス定義してるものです。
個人的には、types.new_class
を利用する方が多いですね。
""" type() を利用して動的にクラスを生成するサンプルです. REFERENCES:: http://bit.ly/2HSBbSG http://bit.ly/2HUeeP3 http://bit.ly/2HWSYb8 http://bit.ly/2I7IJBh http://bit.ly/2HSPZkd """ import numbers from trypython.common.commoncls import SampleBase from trypython.common.commonfunc import pr class Sample(SampleBase): # noinspection PyPep8Naming,PyUnresolvedReferences def exec(self): # ------------------------------------------------- # Python では、「全てがオブジェクト」である。 # この「全て」には、クラスも含まれる。 # クラスは type のインスタンスである。 # つまり、通常行っているクラス定義は type インスタンスを # 作成することと同じ。 # # クラスインスタンスが属しているクラスは # Class.__class__ で取得することが出来る # # 組み込み関数 type() は、 __class__ を呼び出している # (Pythonのドキュメントには 組み込み関数と記載されているが # 実際にはtypeはクラスである。) # ------------------------------------------------- class MyClass1: pass pr('type(MyClass1)', type(MyClass1)) # => <class 'type'> # type() に type を渡すと type となる # つまり、type は type クラス自身に属している pr('type(type)', type(type)) # => <class 'type'> # typeクラスは引数が一つのものと三つのものが存在する # 一つのものが通常利用しているもの。三つのものを利用すると # 新しい型オブジェクトを作成することが出来る。 # つまり、クラス定義をしているのと同じことになる。 # noinspection PyUnusedLocal,PyTypeChecker def func1(s, val): if isinstance(val, numbers.Number): return val + 1 return 0 clsname = 'DynClass1' baseclasses = (object,) classdict = dict(method1=func1) # 動的にクラス定義し、インスタンスを生成 DynClass1 = type(clsname, baseclasses, classdict) d1 = DynClass1() pr('d1.method1(10)', d1.method1(10)) # => 11 # 追記: # type() を利用する他に types モジュールを利用する方法もある # types モジュールは「動的な型生成と組み込み型に対する名前」と # タイトルが付いているので、そのまま動的型生成に利用できる # 動的に型生成する場合は、 types.new_class() を利用する import types # types.new_class() に渡す引数の exec_body にはちょっと注意が必要。 # この引数は「新規で作成されたクラスの名前空間を構築するためのコールバック」となっている # つまり、引数に 名前空間(イコール dict) を受け取り、更新した名前空間を返すようにする必要がある # 基本的には lambda ns: ns.update(classdict) のようになる。 def update_ns(ns: dict): ns.update(classdict) clsname = 'DynClass2' DynClass2 = types.new_class(clsname, baseclasses, exec_body=update_ns) d2 = DynClass2() pr('d2.method1(11)', d2.method1(11)) # => 12 # types.new_class() で メタクラスを指定する場合は kwds 引数に指定する class DynMeta(type): _count: int = 0 def __call__(cls, *args, **kwargs): cls._count += 1 return super().__call__(*args, **kwargs) @property def creation_count(cls): return cls._count clsname = 'DynClass3' kwds = dict(metaclass=DynMeta) DynClass3 = types.new_class(clsname, baseclasses, kwds=kwds, exec_body=update_ns) # 10 回 生成を繰り返してみる instances = [DynClass3() for _ in range(10)] pr('DynClass3.creation_count', DynClass3.creation_count) # => 10 # 10 回 メソッドを呼び出してみる _ = [instance.method1(index) for index, instance in enumerate(instances)] pr('DynClass3.creation_count', DynClass3.creation_count) # => 10 def go(): obj = Sample() obj.exec() if __name__ == '__main__': go()
try-python/dynamicclass01.py at master · devlights/try-python · GitHub
結果は以下のようになります。
type(MyClass1)=<class 'type'> type(type)=<class 'type'> d1.method1(10)=11 d2.method1(11)=12 DynClass3.creation_count=10 DynClass3.creation_count=10
過去の記事については、以下のページからご参照下さい。
- いろいろ備忘録日記まとめ
サンプルコードは、以下の場所で公開しています。
- いろいろ備忘録日記サンプルソース置き場