python/3 の class/metaclass のDSL的応用
別記事にも書いたが、python2/3では、「class定義」は厳密には、typeクラスインスタンスの作成であり、「いわゆるインスタンス生成」に見えるものは、typeクラスの実行である。
逆説的に、「class定義を流用して「classでないもの」を作ることができる」たとえば、そのひとつが、enumである。
lib/enumの場合
話のとっかかりとしては、適当な実装例があったほうが都合がよいので、オフィシャルのドキュメントを使わせてもらう。
python3.5からenumが追加された。とここでは述べている。のだが、実際に試してみると、表面上はclass定義にしか観えない。例えば上記ドキュメントには次のようなソースが載ってる。
class Color(Enum):
red = 1
green = 2
blue = 3
Colorは、この定義単体で完結しており、コンストラクタを持たないし、呼ぼうとしても機能しない。その意味では「クラス」ではない。しかしpython3の構文上はどう見てもclass定義である。
しかもColor.redには整数1を定義したはずが、使う時点ではオブジェクトになっている。
リバースエンジニアリングしてみる
jupyter-notebook等で、適当なenumを作成して、そのインスタンスをダンプしてみると面白いことがわかる。
まずは適当に定義してみる。
from enum import Enum,auto
class httpmethod(Enum):
HEAD=0
GET=auto()
POST=auto()
PUT=auto()
DELETE=auto()
そしてそれをダンプする。
httpmethod.__dict__
すると、定義より遥かに大量の情報が現れる。
mappingproxy({'_generate_next_value_': <function enum.Enum._generate_next_value_(name, start, count, last_values)>,
'__module__': '__main__',
'__doc__': 'An enumeration.',
'_member_names_': ['HEAD', 'GET', 'POST', 'PUT', 'DELETE'],
'_member_map_': {'HEAD': <httpmethod.HEAD: 0>,
'GET': <httpmethod.GET: 1>,
'POST': <httpmethod.POST: 2>,
'PUT': <httpmethod.PUT: 3>,
'DELETE': <httpmethod.DELETE: 4>},
'_member_type_': object,
'_value2member_map_': {0: <httpmethod.HEAD: 0>,
1: <httpmethod.GET: 1>,
2: <httpmethod.POST: 2>,
3: <httpmethod.PUT: 3>,
4: <httpmethod.DELETE: 4>},
'HEAD': <httpmethod.HEAD: 0>,
'GET': <httpmethod.GET: 1>,
'POST': <httpmethod.POST: 2>,
'PUT': <httpmethod.PUT: 3>,
'DELETE': <httpmethod.DELETE: 4>,
'__new__': <function enum.Enum.__new__(cls, value)>})
この中で定義した覚えがあるのは後半の数個だけだが、色々便利そうな値が追加されているのがわかるだろう。
- _member_names_ 定義の文字列名の列挙
- _member_map_ 名前→定数インスタンスへの参照表
- _value2member_map_ 定数インスタンスから数値への参照表
ではenum.Enumは一体なにをやったのだろうか?
/usr/lib/python/enum.py の一部を抜粋する
class EnumMeta(type):
class Enum(metaclass=EnumMeta):
"""Generic enumeration.
前述のクラスが直接継承してるのはEnumだが、そのEnumには更にmetaclass=EnumMetaという記述があり、EnumMetaクラスはtypeから継承されてる。
このmetaclassという名前付きパラメタが肝なのだが、実はclass定義時にはmetaclassパラメタを毎回使うことができる。
しかもデフォルトはtypeだったりする。
これこそ通常は「class定義をするとtypeインスタンスができる」所以である。
class定義時の、EnumMeta.__new__ が介入して、class定義の中身を加工する。
githubのソースから容易に参照できる。先の表群を作ってるのもわかるだろう。
また auto() の列挙数展開もここで行われている。
多言語信者から見れば、「イケてない」と言われることが多いが、実に簡素な機能で、多様性を実現してるのには驚きというほかない。
Discussion