make_dataclass で動的にクラスを生成する|Python
dataclass は通常、デコレータを使ってクラス定義時に静的に作成する。しかし、フィールド構成が実行時まで確定しない場面もある。CSV のヘッダーから自動でクラスを作りたい、設定ファイルに応じてフィールドを変えたい、といったケースだ。make_dataclass はそうした動的なクラス生成を可能にする関数である。
make_dataclass の基本
dataclasses.make_dataclass は、クラス名とフィールドのリストを受け取り、dataclass を返す。
from dataclasses import make_dataclass
Point = make_dataclass("Point", ["x", "y"])
p = Point(x=1.0, y=2.0)
print(p) # Point(x=1.0, y=2.0)
print(p.x) # 1.0フィールドをリストで渡すだけで、@dataclass を付けたクラスと同等のものが生成される。__init__、__repr__、__eq__ などのメソッドもすべて自動で作られる。
これは以下のコードとほぼ同じ結果になる。
from dataclasses import dataclass
@dataclass
class Point:
x: ...
y: ...違いは、make_dataclass ではクラス名もフィールドも文字列として渡せる点だ。つまり、プログラムの実行中に決まる情報からクラスを組み立てられる。
フィールドに型を指定する
フィールド名だけを渡すと型は typing.Any になる。型を明示するには、タプルで (名前, 型) を渡す。
from dataclasses import make_dataclass
User = make_dataclass("User", [
("name", str),
("age", int),
("score", float),
])
u = User(name="Alice", age=30, score=95.5)
print(u) # User(name='Alice', age=30, score=95.5)型ヒントとして記録されるだけで、実行時の型チェックが走るわけではない。これは通常の dataclass と同じ挙動だ。
デフォルト値を設定する
3 要素のタプル (名前, 型, デフォルト値) でデフォルト値を指定できる。field() も使える。
from dataclasses import make_dataclass, field
Config = make_dataclass("Config", [
("host", str, "localhost"),
("port", int, 8080),
("tags", list, field(default_factory=list)),
])
c = Config()
print(c) # Config(host='localhost', port=8080, tags=[])通常の dataclass と同じく、デフォルト値のないフィールドはデフォルト値のあるフィールドより前に置かなければならない。順序を間違えると TypeError になる。
from dataclasses import make_dataclass
# これはエラーになる
Bad = make_dataclass("Bad", [
("name", str, "default"), # デフォルトあり
("age", int), # デフォルトなし → TypeError
])オプションの指定
make_dataclass は @dataclass デコレータと同じオプションを受け取れる。frozen、slots、order などをキーワード引数で渡す。
from dataclasses import make_dataclass
FrozenPoint = make_dataclass(
"FrozenPoint",
[("x", float), ("y", float)],
frozen=True,
slots=True,
)
p = FrozenPoint(x=1.0, y=2.0)
print(hash(p)) # ハッシュ可能
# p.x = 3.0 # FrozenInstanceErrorソースコード上でクラスを定義する。フィールド構成はコードに固定される。可読性が高く、IDE の補完も効く。
実行時にクラス名とフィールドを組み立てる。動的な生成が可能だが、IDE の型補完は効きにくい。
静的に定義できるなら @dataclass を使うべきだ。make_dataclass は「静的に書けない場面」のためのツールである。
CSV ヘッダーからクラスを生成する
make_dataclass の実用的な使い方として、CSV ファイルのヘッダーからデータクラスを自動生成するパターンがある。
import csv
from dataclasses import make_dataclass
csv_text = """name,age,city
Alice,30,Tokyo
Bob,25,Osaka
Charlie,35,Nagoya"""
reader = csv.reader(csv_text.strip().splitlines())
headers = next(reader)
Row = make_dataclass("Row", [(h, str) for h in headers])
rows = [Row(*values) for values in reader]
for row in rows:
print(row)
# Row(name='Alice', age='30', city='Tokyo')
# Row(name='Bob', age='25', city='Osaka')
# Row(name='Charlie', age='35', city='Nagoya')ヘッダーが変わってもコードを書き換える必要がない。列の追加や削除に自動で対応できるため、汎用的なデータ読み込みツールに向いている。
辞書からクラスを生成する
API レスポンスや設定ファイルの辞書から dataclass を作るパターンも有用だ。
from dataclasses import make_dataclass
def dict_to_dataclass(name, data):
fields = [(k, type(v), v) for k, v in data.items()]
cls = make_dataclass(name, fields)
return cls
schema = {"host": "localhost", "port": 8080, "debug": False}
Config = dict_to_dataclass("Config", schema)
c = Config()
print(c) # Config(host='localhost', port=8080, debug=False)
c2 = Config(host="0.0.0.0", port=3000, debug=True)
print(c2) # Config(host='0.0.0.0', port=3000, debug=True)辞書の値から型を推論し、同時にデフォルト値として設定している。簡易的なアプローチだが、設定ファイルのパースなどでは十分に実用的だ。
基底クラスの指定
bases 引数で親クラスを指定できる。既存のクラスを継承した dataclass を動的に作れる。
from dataclasses import make_dataclass, dataclass
@dataclass
class Base:
id: int
Extended = make_dataclass(
"Extended",
[("name", str), ("value", float)],
bases=(Base,),
)
e = Extended(id=1, name="test", value=3.14)
print(e) # Extended(id=1, name='test', value=3.14)
print(isinstance(e, Base)) # True既存の型階層に動的なクラスを組み込めるため、プラグインシステムやスキーマ駆動のコード生成で活用できる。
名前空間にメソッドを追加する
namespace 引数を使うと、生成されるクラスにメソッドやプロパティを追加できる。
from dataclasses import make_dataclass
import math
Circle = make_dataclass(
"Circle",
[("radius", float)],
namespace={
"area": property(lambda self: math.pi * self.radius ** 2),
"circumference": property(lambda self: 2 * math.pi * self.radius),
},
)
c = Circle(radius=5.0)
print(f"面積: {c.area:.2f}") # 面積: 78.54
print(f"周長: {c.circumference:.2f}") # 周長: 31.42ただし、namespace に多数のメソッドを詰め込むなら、通常の @dataclass で静的にクラスを定義するほうが可読性は高い。namespace は補助的な用途に留めるのが賢明だ。
make_dataclass でフィールドにデフォルト値を設定するとき、正しい書き方はどれか?
- ("name", "default", str)
- ("name", str, "default")
- {"name": str, "default": "value"}
- field("name", default="value")
make_dataclass の限界
動的生成は強力だが、トレードオフも明確にある。
mypy や pyright は make_dataclass で生成したクラスの型情報を追跡できない。静的解析の恩恵を受けられないため、大規模プロジェクトでは型安全性が低下する。
どんなフィールドを持つクラスなのかがソースコードを読むだけではわからない。デバッグ時にクラス定義を追いかけるのが難しくなる。
コード補完やリファクタリング支援が効かない。属性名のタイポに気づきにくく、開発効率に影響する。
設計時にフィールドが確定しているなら、迷わず @dataclass を使うべきだ。make_dataclass は、外部データやメタ情報に基づいてクラスを生成する必要がある場面に限定して使うのが適切である。動的生成の便利さと引き換えに失うものを理解した上で、意図的に選択することが重要だ。












フィールドは (名前, 型, デフォルト値) の 3 要素タプルで指定します。順番は名前 → 型 → デフォルト値です。