VisasQ Dev Blog

ビザスク開発ブログ

Pythonでデータを任意の形式に構造化できるライブラリcattrs

はじめに

こんにちは。フルサポート開発チームのshi_maです。
先日動物園に行ったらワオキツネザルから勢いよく餌を投げつけられました。好意的に解釈するとおそらくプレゼントだったのでしょう!

今回はcattrsという便利なPythonのライブラリを紹介します。

cattrsとは

GitHub: https://github.com/python-attrs/cattrs
公式ドキュメント: https://cattrs.readthedocs.io/en/latest/index.html

表題に書いた通り、cattrsとはデータを任意の形式に構造化できるPythonのライブラリです。

たとえば、CSVから読み込んだデータやJSON形式で受け取ったリクエストデータをPythonのclassに変換して扱いやすくできます。

実際にcattrsがどんな動きをするのか見ていきましょう。

使い方

今回利用するcattrsのバージョンは1.10.0です。

まずはstructureという関数を利用してデータを構造化します。

import cattr


print(cattr.structure([1.0, 2, '3'], tuple[int, int, int]))
> (1, 2, 3)

structure関数は第一引数に構造化するオブジェクト、第二引数はどのような構造にするのか表す型を定義します。

上記の例ではfloat、int、stringの3種類の値を持つリストが、3つのint値からなるタプルに変わりました。

次はdictをカスタムクラスに変換します。

Pythonにはカスタムクラスをシンプルに記述できるattrsという人気のライブラリがあるのですが、cattrsはこのライブラリとあわせて使うことを意識して作られており、併用するがオススメです。

なお逆にカスタムクラスをdictに変換したい時はunstructure関数が使えます。

import cattr
from attr import frozen


@frozen
class User:
     name: str
     age: int

user_dict = {
    'name': 'Taro',
    'age': 30,
}

# structure関数でdictをUserクラスに
user = cattr.structure(user_dict, User)
print(user.name)
> Taro

# unstructure関数でUserクラスをdictに
>>> print(cattr.unstructure(user))
{'name': 'Taro', 'age': 30}

もちろんdictをattrsを利用していない素のカスタムクラスに変えることも可能です。

その場合は、register_structure_hookという関数を使い、コンバータークラスに構造化のフックを登録する必要があります。

import cattr


class User:
    def __init__(self, name, age):
        self.name = name
        self.age = age

cattr.register_structure_hook(User, lambda d, t: User(**d))

user_dict = {
    'name': 'Taro',
    'age': 30,
}

user = cattr.structure(user_dict, User)

print(user.name)
> Taro

もしregister_structure_hook関数の処理がないと以下のようなエラーが起きます。

cattr.errors.StructureHandlerNotFoundError: Unsupported type: . Register a structure hook for it.

最後にCSVから読み込んだデータをPythonのカスタムクラスに変換してみます。

# animals.csv
# name,group
# 'ワオキツネザル','哺乳類'
# 'ウグイス','鳥類'
# 'マグロ','魚類'

import csv

import cattr
from attr import frozen


@frozen
class Animal:
    name: str
    group: str

@frozen
class Zoo:
    animals: list[Animal]

with open('animals.csv', 'r') as f:
    zoo = cattr.structure({'animals': list(csv.DictReader(f))}, Zoo)

    for animal in zoo.animals:
        print(animal)

> Animal(name='ワオキツネザル', group='哺乳類')
> Animal(name='ウグイス', group='鳥類')
> Animal(name='マグロ', group='魚類')

終わりに

今回はcattrsの基本的な使い方をご紹介しました。

現時点では少々マイナーなライブラリですが、attrsと併用するとかなり使い勝手が良いので、Pythonを書かれている方はぜひお試しください!