Python3.10で新たに追加されたdataclass()の引数

# はじめに
Python3.10でdataclassに新たに追加された引数について少し調べたので、まとめてみた。


# 参考文献
https://docs.python.org/ja/3.10/library/dataclasses.html


# dataclassとは
Python3.7より標準ライブラリとして搭載された、データを格納するためのクラスが簡単に定義可能となったモジュール。
dataclassの簡単な使用方法は以下。


```Python

from dataclasses import dataclass

@dataclass
class User:
    name: str
    age:int

user1 = User('Jun', 20)

print(user1)

# User(name='Jun', age=20)

```


# dataclass()の引数
Python3.10現在、dataclass()に使用できる引数には以下のようなものがある。

- init
- repr
- eq
- order
- unsafe_hash
- frozen
- match_args (3.10で新たに追加)
- kw_only (3.10で新たに追加)
- slots (3.10で新たに追加)

Python3.10で新たに追加されたものは、`match_args`, `kw_only`, `slots` の3種類である。この記事では、これらについてを簡単にまとめる。

# match_args

`macth_args = True` と指定したとき、クラス変数 `__match_args__` を生成する。

`__match_args__` によって、Python3.10で導入されたmatch, case文での条件分岐時に、クラス変数としてキーワード指定することなくタプルで条件指定が可能となる。

通常のclassでのmatch, case文では、

```python

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

user1 = User('Jun', 20)

match user1:
    case User(name='Bob', age=34):
        print('name: Bob, age: 34')
    case User(name='Jun', age=20):
        print('name: Jun, age: 20')

# name: Jun, age: 20

```

と、case文内において、`User(name='Bob', age=34)` と、キーワードを指定する必要がある。

以下のように、`__match_args__` を定義すると

```Python

class User:

    __match_args__ = 'name', 'age'

    def __init__(self, name: str, age: int):
        self.name = name
        self.age = age


user1 = User('Jun', 20)

match user1:
    case User('Bob', 34):
        print('name: Bob, age: 34')
    case User('Jun', 20):
        print('name: Jun, age: 20')

# name: Jun, age: 20

```

`User('Bob', 34)` のように、キーワード指定をする必要がなくなる。

`@dataclass(match_args=True)` とすると、この `__match_args__` を自動で生成してくれる。

```Python

from dataclasses import dataclass

@dataclass(match_args=True)
class User:
    name: str
    age: int

user1 = User('Jun', 20)

match user1:
    case User('Bob', 34):
        print('name: Bob, age: 34')
    case User('Jun', 20):
        print('name: Jun, age: 20')

# name: Jun, age: 20

```


# kw_only

`kw_only = True` と指定したとき、全てのフィールドをキーワード指定専用にすることができる。


```Python

from dataclasses import dataclass

@dataclass(kw_only=True)
class User:
    name: str
    age: int

user1 = User(name='Jun', age=20)

print(user1)

# User(name='Jun', age=20)

```

以下のようにキーワード指定をしないと、エラーになる。

```Python

from dataclasses import dataclass

@dataclass(kw_only=True)
class User:
    name: str
    age: int

user1 = User('Jun', 20)  # TypeError: User.__init__() takes 1 positional argument but 3 were given

```

# slots

`slots = True` と指定したとき、クラス変数 `__slots__` を生成する。

`__slots__` とは、固定長のクラス変数保管用配列を指定するクラス変数である。

Pythonでは、クラスのフィールドを保持するためにデフォルトでは辞書が用いられている。

```Python

from dataclasses import dataclass

@dataclass()
class User:
    name: str
    age: int

user1 = User('Jun', 20)

print(user1.__dict__)

# {'name': 'Jun', 'age': 20}

```

フィールドを辞書で保持することのデメリットとして、

- 間違った代入が起きてしまう

ことが挙げられる。

例えば、以下のように、`name` に値を再代入しようとした際に、誤って `nama` と記述してしまったとする。

```Python

from dataclasses import dataclass

@dataclass()
class User:
    name: str
    age: int

user1 = User('Jun', 20)

user1.nama = 'Haru'

print(user1.__dict__)

# {'name': 'Jun', 'age': 20, 'nama': 'Haru'}

```

このとき、`name` への再代入は行われず、`nama` への代入が行われてしまう。

これを防ぐのが、`__slots__` である。

```Python

from dataclasses import dataclass

@dataclass()
class User:
    name: str
    age: int

    __slots__ = ['name', 'age']

user1 = User('Jun', 20)

user1.nama = 'Haru'  # AttributeError: 'User' object has no attribute 'nama'

```

上記のように、`__slots__` を定義すると、フィールドの名前を定義することが可能になるので誤った代入が起きる心配はなくなる。


また、`__slots__` を定義すると、フィールドが固定長配列に格納されるので、処理速度が速くなるというメリットもあるようだ。(参考：https://www.python.jp/news/wnpython310/dataclass-with-slots.html)

```Python

from dataclasses import dataclass

@dataclass(slots=True)
class User:
    name: str
    age: int


user1 = User('Jun', 20)

print(user1.__slots__)

# ('name', 'age')

```

以上のようにdataclassでは、`slots = True` とすると、`__slots__` を自動的に生成してくれる。


# まとめ

今回は、Python3.10でdataclassに新たに追加された引数について簡単にまとめてみた。
特に、 `slots` は便利だと感じたので、今後は積極的に使用していこうと思った。