何かを書き留める何か

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

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

項目34はかなりシンプルになる

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

xaro.hatenablog.jp

クラスが定義された際に自動で何らかの辞書に登録したいという場面でメタクラスを活用すればできるよというのが項目34の主題である。

import json

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 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


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


class Vector3D(RegisteredSerializable):
    def __init__(self, x, y, z):
        super().__init__(x, y, z)
        self.x, self.y, self.z = x, y, z

if __name__ == '__main__':
    
    v3 = Vector3D(10, -7, 3)
    print('Before:    ', v3)
    data = v3.serialize()
    print('Serialized:', data)
    print('After:     ', deserialize(data))

これを__init_subclass__を使うとかなりシンプルに書くことができる。 具体的には、メタクラスが不要となり、直接BetterSerializableに記述することができる。

import json


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

    def __init_subclass__(cls, **kwargs):
        register_class(cls)

    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 Vector3D(BetterSerializable):
    def __init__(self, x, y, z):
        super().__init__(x, y, z)
        self.x, self.y, self.z = x, y, z


if __name__ == '__main__':
    
    v3 = Vector3D(10, -7, 3)
    print('Before:    ', v3)
    data = v3.serialize()
    print('Serialized:', data)
    print('After:     ', deserialize(data))

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

項目33「サブクラスをメタクラスで検証する」はこう書き換わる

Python 3.6がリリースされた。

Python 3.6で導入された新機能の一つに__init_subclass__がある。 PEP 487 -- Simpler customisation of class creation | Python.org か石本さんのエントリを参照してほしい。

atsuoishimoto.hatenablog.com

『Effective Python』の項目33「サブクラスをメタクラスで検証する」ではメタクラスを使ってサブクラスの検証(値の整合性チェックなど)を行うテクニックが解説されている。 多角形のクラスを定義する際に、頂点数が3未満の場合はValueErrorを送出する、というサンプルがある。

class ValidatePolygon(type):

    def __new__(meta, name, bases, class_dict):
        if bases != (object,):
            if class_dict["sides"] < 3:
                raise ValueError("polygons need 3+ sides.")
        return type.__new__(meta, name, bases, class_dict)


class Polygon(object, metaclass=ValidatePolygon):
    sides = None

    @classmethod
    def interior_angles(cls):
    """内角の総和を求める"""
        return (cls.sides - 2) * 180


class Triangle(Polygon):
    sides = 3


if __name__ == '__main__':
    triangle = Triangle()
    print(triangle.interior_angles())

Python 3.6では、__init_subclass__を利用することでメタクラスを使うことなくサブクラスの検証が実現できる。

class Polygon(object):

    def __init_subclass__(cls, sides, **kwargs):
        cls.sides = sides
        if cls.sides < 3:
            raise ValueError("polygons need 3+ sides.")

    @classmethod
    def interior_angles(cls):
        return (cls.sides - 2) * 180


class Triangle(Polygon, sides=3):
    pass


if __name__ == '__main__':
    triangle = Triangle()
    print(triangle.interior_angles())

__init_subclass__がクラスメソッドである、ということを押さえておけばメタクラスを使う例よりもわかりやすくなっている。

ベストプラクティスを収めた『Effective Python』も時間がたつと変わってしまう、利用している道具の変化は常にを追わないといけない、というのを強く感じる次第である。

ブログ設立4周年

ブログ設立4周年、だそうです。

この「何かを書き留める何か」というブログも設立して4年が経過したそうである。 そうである、という伝聞調なのははてなから送られたメールによって知らされたからである。

私は何を書き留めてきたのか。

最初はProject EulerLaTeXと学生生活に関係するようなことを書いていた。 次に来たのはPythonである。 元々Project Eulerを解くためにSage Mathを使っていて、その勉強のためにPythonを始めたのであった。 今はPythonにまつわる話や読んだ本の話がメインとなっている。 プログラミングに関してはQiitaの方が書きやすいのかもしれないが、何だかんだではてなブログで書き続けている。

ブログを書き続けたことで新たなる出会いもあった。 『Effective Python』はその典型である。

最近は更新頻度が減ってしまった。 頻度を上げるために机の掃除も行った。 2017年度は積極的にアウトプットを続けたい。