dataclassのフィールド名一覧を取得する方法

## 結論

フィールド名一覧を取得する[^f]には、`dataclasses.fields` を使いましょう。

[^f]: ユースケースとして例えばデータベースから取得したテーブルデータを`iterrows`でレコードごとにインスタンス化し、その際にバリデーションしたい場合など。バリデーションをするならPydanticを使ったほうが良いですが。

```python
from typing import ClassVar
from dataclasses import dataclass, fields

@dataclass
class MyDataClass:
    x: int
    y: str
    default_val: str = "hoge"
    classvar_any = 10
    classvar_annotated: ClassVar[int] = "fuga"

all_field_names = [field.name for field in fields(MyDataClass)]
# => ['x', 'y', 'default_val']
```

`Field` オブジェクトとして一覧が得られるので `.name` でアクセスします。

生成AIに「Pythonでdataclassのフィールド名一覧を取得する方法は？」と質問すると結構な確率で `__dataclass_fields__` を使うよう回答してくる[^r]のですが、これは公式に用意されたインターフェースではなく[^d]、また `dataclasses.fields` と微妙に挙動の違いがあり意図しない動作の元になるため、使わない方が良いです。

[^d]: [公式ドキュメント](https://docs.python.org/ja/3/library/dataclasses.html)に記載がなく、[実装](https://github.com/python/cpython/blob/3.13/Lib/dataclasses.py)にも明示的に定義されていません。
[^r]: いくつかの技術ブログで `__dataclass_fields__` を使う方法が紹介されているのを学習してしまっているのだと思います。

生成AIの回答例：
より高級なモデルほど `__dataclass_fields__` への"こだわり"が薄れていく傾向にあります[^a]。

[^a]: 内省ができるモデルは「`__` で始まるのはprivateな変数で、特段理由がなければ外部から使うべきものではない」ということをなんとなく推論できているんじゃないかという気がします。

<details><summary>Google の AI 要約</summary>

がっつり `__dataclass_fields__` の使用を勧めてくる（回による）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2760163/27074d0d-4f94-4a1b-8510-d7f131666dd5.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2760163/b44412d0-85a5-4169-b094-227307514745.png)

</details>

<details><summary>ChatGPT (o4-mini)</summary>

`dataclasses.fields` と `__dataclass_fields__` の二択を提示

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2760163/8a7d20b6-4827-4115-a2be-ab8b46461d7c.png)

</details>

<details><summary>ChatGPT (4o)</summary>

サブの選択肢として `__dataclass_fields__` も提示（どちらの方法でも同じというのは誤り）

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2760163/6f30ae51-bfe7-4adf-aa5f-1dda57607ad0.png)

</details>


<details><summary>Gemini (2.5 Flush)</summary>

`__dataclass_fields__` への言及なし

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2760163/3788448a-dc5d-4dbf-bcc3-c51a0cdfe2d4.png)
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2760163/305c08f2-c239-4580-a754-1852311c3532.png)

</details>

## 挙動の差

`__dataclass_fields__` は「型アノテーションをつけたクラス変数」が除外されません。
型アノテーションの有無で挙動が変わるというのはデータ構造として望ましくないので、型アノテーションあり・なしの両方とも除外される `dataclass.fields` の方が"正常な"挙動といっていいでしょう。

```python
>>> fields(MyDataClass)
(Field(name='x',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), Field(name='y',type=<class 'str'>,default=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), Field(name='default_val',type=<class 'str'>,default='hoge',default_factory=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD))
>>> [field.name for field in fields(MyDataClass)]
['x', 'y', 'default_val']
>>>
>>> MyDataClass.__dataclass_fields__
{'x': Field(name='x',type=<class 'int'>,default=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'y': Field(name='y',type=<class 'str'>,default=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,default_factory=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'default_val': Field(name='default_val',type=<class 'str'>,default='hoge',default_factory=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=False,_field_type=_FIELD), 'classvar_annotated': Field(name='classvar_annotated',type=typing.ClassVar[int],default='fuga',default_factory=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,init=True,repr=True,hash=None,compare=True,metadata=mappingproxy({}),kw_only=<dataclasses._MISSING_TYPE object at 0x7f8138fa9e80>,_field_type=_FIELD_CLASSVAR)}
>>> MyDataClass.__dataclass_fields__.keys()
dict_keys(['x', 'y', 'default_val', 'classvar_annotated'])
```

いかにも `dataclass.fields` と等価な dunder（特殊メソッド）のような命名の割に中身が違うというのがちょっとした罠ですね。実際には `__dataclass_fields__` はアノテーションがついている変数をまとめて保持しておくだけの一時変数であり、`fields` ではさらに `if f._field_type is _FIELD` というフィルターでフィールド変数以外を除外する処理が追加されています。