何かを書き留める何か

数学や読んだ本について書く何かです。最近は社会人として生き残りの術を学ぶ日々です。

『Effective Python』Item 34: メタクラスを使ってクラスの存在を登録をしよう

『Effective Python』の続き。末端の人間頑張れメソッドダメ、ゼッタイ。 www.effectivepython.com

メタクラスの活用第二弾として、クラスの登録を挙げている。 PythonのオブジェクトをJSON文字列へシリアライズJSON文字列からPythonオブジェクトへデシリアライズする機能を備えたクラスを定義する。

class BetterSerializable(object):
    def __init__(self, *args):
        self.args = args

    def serialize(self):
        return json.dumps({
            'class': self.__class__.__name__,
            'args': self.args,
        })

    def __repr__(self):
        return '%s(%s)' % (
            self.__class__.__name__,
            ', '.join(str(x) for x in self.args))

registry = {}

def register_class(target_class):
    registry[target_class.__name__] = target_class

def deserialize(data):
    params = json.loads(data)
    name = params['class']
    target_class = registry[name]
    return target_class(*params['args'])

このクラスを継承するとシリアライズ及びデシリアライズが可能となる。

class EvenBetterPoint2D(BetterSerializable):
    def __init__(self, x, y):
        super().__init__(x, y)
        self.x = x
        self.y = y

register_class(EvenBetterPoint2D)

問題は、毎回毎回継承して作ったクラスをregister_classで登録しなければならないことである。 単純に面倒である。

そこで、メタクラスを使えば解決しますよ、という話である。

class Meta(type):
    def __new__(meta, name, bases, class_dict):
        cls = type.__new__(meta, name, bases, class_dict)
        register_class(cls)
        return cls

class RegisteredSerializable(BetterSerializable, metaclass=Meta):
    pass

こうすればRegisteredSerializableを定義した時点で自動的にregister_classが走って登録してくれる。

よく、何らかのトラブルの解決案でダブルチェックをするとか、よく注意するとか、「末端の人間頑張れメソッド」が採用される。 何事も自動化、とは行かないが、このようにスマートな解決策を見出したいものである。

『Effective Python』Item 35: メタクラスを使ってクラス属性に注釈をつけよう

『Effective Python』の続き。メタクラスディスクリプタの華麗なる関係。 www.effectivepython.com

メタクラスの応用例、そしてChapter 4の最後を飾るのはクラス属性の注釈、またはメタクラスディスクリプタの合わせ技の話である。

データベースのレコードをPythonのクラスで表したいという状況を考える。 レコードの要素をFieldディスクリプタで、レコードをCustomerという具合である。

class Field(object):
    def __init__(self, name):
        self.name = name
        self.internal_name = '_' + self.name

    def __get__(self, instance, instance_type):
        if instance is None: return self
        return getattr(instance, self.internal_name, '')

    def __set__(self, instance, value):
        setattr(instance, self.internal_name, value)

class Customer(object):
    # Class attributes
    first_name = Field('first_name')
    last_name = Field('last_name')
    prefix = Field('prefix')
    suffix = Field('suffix')

これでも上手く動作するが、Customer.first_nameField('first_name')を渡していてfirst_nameを二重に登録するはめになる。 コレは面倒だ、ということでメタクラスの出番である。

class Meta(type):
    def __new__(meta, name, bases, class_dict):
        for key, value in class_dict.items():
            if isinstance(value, Field):
                value.name = key
                value.internal_name = '_' + key
        cls = type.__new__(meta, name, bases, class_dict)
        return cls

class DatabaseRow(object, metaclass=Meta):
    pass

これを使えば、Fieldに引数first_nameを渡すことなくやりたいことを実現できる。

これでChapter 4が終わった。Chapter 3が1ヶ月掛かったが、これは10日で終わった。 夏季休暇で十分な時間が取れたからであろうか。 学部生か院生の頃か忘れてしまったが、恩師は僕に対して「本をゆっくり読めるのは学生時代だけである」とおっしゃった。 今、その意味をかみ締めながら日々を過ごしている。