読者です 読者をやめる 読者になる 読者になる

何かを書き留める何か

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

Python 3.6における『Effective Python』 項目35はこう変わる

項目35は言語に取り込まれた

Python 3.6がリリースされた。 Python 3.6で導入された新機能の一つに__init_subclass__がある、というのは前回の流れと同じ。

項目35「クラス属性をメタクラスで注釈する」はクラス定義後にプロパティを修正したり注釈を加えるといった機能をメタクラスで実現するという内容である。

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 Field(object):
    def __init__(self):
        self.name = None
        self.internal_name = None

    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 DatabaseRow(object, metaclass=Meta):
    pass


class Customer(DatabaseRow):
    first_name = Field()
    last_name = Field()
    prefix = Field()
    suffix = Field()


if __name__ == '__main__':  
    foo = Customer()
    print('Before:', repr(foo.first_name), foo.__dict__)
    foo.first_name = 'Euler'
    print('After: ', repr(foo.first_name), foo.__dict__)

この項目も PEP 487 -- Simpler customisation of class creation | Python.orgにある__set_name__を使うと簡単に書くことができる。

class Field(object):
    def __init__(self):
        self.name = None
        self.internal_name = None

    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)

    def __set_name__(self, owner, name):
        self.name = name
        self.internal_name = '_' + name


class Customer(object):
    first_name = Field()
    last_name = Field()
    prefix = Field()
    suffix = Field()


if __name__ == '__main__':
    foo = Customer()
    print('Before:', repr(foo.first_name), foo.__dict__)
    foo.first_name = 'Euler'
    print('After: ', repr(foo.first_name), foo.__dict__)

このように__set_name__を使えばメタクラスを定義せずに所望の内容を実現できる。

一連のエントリを振り返ると、項目33,34そして35はいずれもメタクラスを利用する内容であるが、Python 3.6からは__init_subclass__及び__set_name__で書き換えることができる。 メタクラスを使うとどうしても複雑になってしまうが、__init_subclass__及び__set_name__を使えばシンプルに書くことができる。 将来、『Effective Python』の項目33, 34, 35はメタクラスを使わない内容に更新されるであろう。