📖

Pythonで辞書型をクラスに変換したい!

2023/02/05に公開

やりたいこと

このようなdict型のデータがあったとします。

data = {
    "name": "tinyowl",
    "age": 5,
    "language": [
        "japanise",
        "english",
        "owl-lang"
    ]
}

上のデータを、自作クラスに変換したいのです。

def to_user_class(data: dict) -> User:
	# なんやかんや

user1 = to_user_class(data)

print(type(user1)) # <class '__main__.User'>
print(user1.name) # tinyowl
print(user1.language[0]) # japanise

結論

調べてみたところ、以下のようなコードが一番シンプルでよさそうでした。
外部ライブラリを使う方法もあるようですが、できるだけ標準ライブラリでやりたかったので使いませんでした。

class User(object):
    def __init__(self, name: str, age: int, language: list[str]):
        self.name = name
        self.age = age
        self.language = language

def to_user_class(d: dict) -> User:
    # __init__を呼ばずにインスタンスを作成
    u = User.__new__(User)
    u.__dict__.update(d)
    return u
    
user1 = to_user_class(data)

print(type(user1)) # <class '__main__.User'>
print(user1.name) # tinyowl
print(user1.language[0]) # japanise

__dict__.updateが何をしているかというと、インスタンス変数の値を上書きしています。
object型は__dict__という変数を持っていて、これがインスタンス変数の状態を保持しているようです。
updateはdict型同士を結合するメソッドです。(詳細)

なので自作クラスで定義していない変数がデータの中にあった場合にも、勝手に変数が生成されてしまいます。

data = {"a": 0, "b": 2}

class Test(object):
    def __init__(self, a: int):
        self.a = a
    
test = Test.__new__(Test)
test.__dict__.update(data)
print(test.b) # 2

dataclassを使う

dataclassを使えばシンプルにクラスを実装できます。
dataclassについてはこちらの記事がわかりやすかったです。

from dataclasses import dataclass

# 上のUserクラスと中身は同じ
@dataclass
class User:
    name: str
    age: int
    language: list[str]

初期値を設定

データに特定の要素がなかった場合、エラーが発生してしまいます。

data = {
    "name": "tinyowl",
    # "age": 5,
    "language": [
        "japanise",
        "english",
        "owl-lang"
    ]
}	

user1 = to_user_class(data)
print(user1.age) # error!
AttributeError: 'User' object has no attribute 'age'

初期値を指定することでこの問題を回避できます。

@dataclass
class User:
    name: str
    language: list[str]
    # 初期値がある変数は、初期値がない変数の後に宣言する必要があります。
    age: int = 0  

Noneを指定することもできます。

from typing import Optional

@dataclass
class User:
    name: str
    language: list[str]
    age: Optional[int] = None

まとめ

僕にしか需要なさそう…?

最後に

最後まで読んでいただきありがとうございます。
Zenn初記事なので色々とおかしいところがあるかもしれません。
間違いやアドバイス等あれば、コメントでご指摘頂けるとありがたいです。

Discussion