2018/12/12

ライブラリ: Poetry

今回は Python のパッケージ「 Poetry 」を紹介します。



最初に「 Poetry とは何ぞや」( WHAT )の部分を説明した後に「 Poetry の使い方」( HOW )について説明します。最後に「 Pipenv からの移行方法」についてかんたんに説明します。

Poetry とは


Poetry とは 2018 年 2 月頃から開発がスタートした Python パッケージの管理ツールです。


pipvenv を組み合わせて、 npmbundlecomposer 等他の言語で定番のパッケージ管理ツールと似た使用感で使える Python パッケージ管理機能を提供しています。私自身はまだアプリケーションでしか使ったことがありませんが、アプリケーションでも配布用のパッケージでも使えるとのことです。

Pipenv をご存知の方には「 Pipenv の代替物」という説明の方がわかりやすいかもしれません。


Pipenv は当初大きな期待を持って迎えられましたが(私も期待しましたが)、さまざまな問題が出ており最近はどうも先行きが怪しい感じです。私自身 Pipenv の導入によって便利になったところがある一方で Pipenv を使っていなかったら抱えていなかった新たなストレスも増えた感じがします。 Poetry は私のような「脱 Pipenv 」を検討している方にとって有力な移行先候補のひとつです。

Poetry の使い方


ここでは Poetry のバージョン 0.12.10 における使い方を説明します。

尚、私は Poetry をアプリケーションのプロジェクトにしか使っていない(配布パッケージのプロジェクトには使っていない)ので、配布パッケージの依存を管理するための情報はまったくカバーできていません。また、 Poetry のバージョンが進むと使い方が変わると思いますので、参考にされる際はそのあたりににご注意ください。

インストール

公式に推奨されているインストール方法は、専用のスクリプト get-poetry.py を使ったものです。

$ curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python

異なるバージョンの Python が同居している環境では、適切な python コマンドを選択する必要があります。

$ curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python3.7

このコマンドを実際に実行すると私の場合は次のようなメッセージが出ました。

$ curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | python
Retrieving Poetry metadata

# Welcome to Poetry!

This will download and install the latest version of Poetry,
a dependency and package manager for Python.

It will add the `poetry` command to Poetry's bin directory, located at:

$HOME/.poetry/bin

This path will then be added to your `PATH` environment variable by
modifying the profile files located at:

$HOME/.profile
$HOME/.bash_profile

You can uninstall at any time with `poetry self:uninstall`,
or by executing this script with the --uninstall option,
and these changes will be reverted.

Installing version: 0.12.10
  - Downloading poetry-0.12.10-darwin.tar.gz (7.12MB)

Poetry (0.12.10) is installed now. Great!

To get started you need Poetry's bin directory ($HOME/.poetry/bin) in your `PATH`
environment variable. Next time you log in this will be done
automatically.

To configure your current shell run `source $HOME/.poetry/env`

スクリプトの実行が成功すると、 Unix 系の OS なら $HOME/.poetry/bin に実行可能な poetry ファイルが置かれます。このディレクトリを環境変数 PATH に追加しておくと、その後はフルパスを指定しなくても poetry コマンドが実行できるようになります(ちなみに、私の環境では get-poetry.pyPATH に追加するコードを ~/.bash_profile に自動的に追加しました)。

~/.bashrc:
# for Python/Poetry
export PATH="$HOME/.poetry/bin:$PATH"

PATH の追加が終わったら poetry コマンドが使用できるようになっているはずです。例えば次のようにして正しく実行できればインストールは完了です。

$ poetry --version
Poetry 0.12.10

アンインストール

Poetry そのものをアンインストールしたくなったときは、インストールのときと同じ get-poetry.py スクリプトが使えます。

公式のドキュメントには、次のように get-poetry.py をいったんダウンロードしてから実行する方法が紹介されています。

$ python get-poetry.py --uninstall

get-poetry.py をファイルとして保存せずに直接実行する次のような方法も利用可能です。

$ curl -sSL https://raw.githubusercontent.com/sdispater/poetry/master/get-poetry.py | POETRY_UNINSTALL=1 python

自動補完の設定

サブコマンドの自動補完のための設定も公式に用意されています。

例えば、 Bash の場合は次のコマンドの出力を ~/.bash_profile 等に追加しておけば次回 ~/.bash_profile を読み込んだ後は自動補完が有効になります。

$ poetry completions bash

1. プロジェクトを作成する

Poetry を実際に利用し始めるときに最初に行うべきことはプロジェクトの作成です。プロジェクトを作成するときに使えるコマンドには次の 2 つがあります。

  • poetry init
  • poetry new

poetry init はインタラクティブな入力 UI を提供した後入力内容に沿って pyproject.toml ファイルを生成します。入力項目はおおよそ次のとおりです。

  • Package name
  • Version
  • Description
  • Author
  • License
  • Compatible Python versions

生成されるファイル pyproject.toml は、ちょうど npm における package.json と同等の役割を果たすファイルです。

他方の poetry newpyproject.toml を含む、パッケージの雛形(ファイル一式)を生成します。

パッケージをゼロから作成する場合には poetry new が使えますが、おそらくほとんどのプロジェクトでは poetry init から始めることになるかと思います。

2. パッケージを追加する

poetry init 等でプロジェクトを作成したら、パッケージを追加します。パッケージの追加には poetry add を使用します。

$ poetry add flask

引数にはパッケージ名を指定します。パッケージ名は複数指定することもできます。

$ poetry add flask peewee gunicorn

開発用でパッケージを追加したいときは --dev オプションを使用します。

$ poetry add --dev pytest

ちなみに、パッケージの extras を追加したい場合は --extras オプションを使用します。

# pip なら
pip install django-anymail[mailgun]

# poetry なら
poetry add django-anymail --extras mailgun

3. パッケージをインストールする

poetry add で追加されたパッケージをインストールするには poetry install を使用します。

$ poetry install

パッケージはグローバルにではなく Poetry が管理する virtualenv の中にインストールされます。指定されたパッケージの依存パッケージも含めて、すべてのパッケージ情報は poetry.lock に保存されます。同じパッケージ環境を複数の環境で共有したい場合は poetry.lock を Git に登録するとよいかと思います。

ちなみに、私が実行したかぎりでは、 Poetry が現在のプロジェクトに対して用意している virtualenv の場所は poetry shellpoetry debug:info コマンドで確認することができました。

4. パッケージを表示する

利用パッケージとして登録されているパッケージを確認するときは poetry show を使用します。

$ poetry show

poetry show コマンドはさまざまなオプションを受け付けます。例えば、更新可能なパッケージを確認したい場合は --outdated オプションが使えます。

$ poetry show --outdated

5. パッケージを利用する

Poetry がインストールしたパッケージは Poetry がプロジェクト単位で用意した virtualenv 内にインストールされています。そのため、パッケージを利用したいときには専用のコマンドを使用します。利用できるコマンドは次の 2 つです。

  • poetry run
  • poetry shell

poetry run は、一コマンド単位で virtualenv を有効化して実行したいときに使用します。コマンドが終了したら対象の virtualenv を自動的に deactivate してくれます。

$ poetry run コマンド
$ # 例
$ poetry run python manage.py runserver

poetry shell は、対象の virtualenv を有効化します。 poetry run は対象コマンドを実行後に即 virtualenv から脱出しますが、 poetry shell は明示的に exit しないかぎり virtualenv が有効になったままとなります。

$ poetry shell
$ # virtualenv 内で実行したいコマンドを実行する
$ # ...
$ exit

6. パッケージ更新

すでに登録されているパッケージを更新するときは poetry update コマンドを使用します。

$ poetry update flask

原則、指定されたパッケージのみが更新されるようです。 Pipenv とは異なり直感的な動作をしてくれます。

7. パッケージ削除

登録されているパッケージを削除するときは poetry remove を使用します。

$ poetry remove flask

アプリケーションの依存パッケージの管理についてはおおよそ以上のコマンドを覚えておけば十分ではないかと思います。

私自身まだ実用する機会がありませんが、アプリケーションではなく配布パッケージ管理のためのコマンドとして次のもの等が用意されています。配布パッケージの管理に Poetry を利用したい方はこれらもチェックされてみるとよいでしょう。

  • poetry build
  • poetry publish

その他代表的なコマンドとして以下のコマンド等も用意されています。

  • poetry list サブコマンドの一覧を表示
  • poetry cache:clear poetry のキャッシュ(?)を削除
  • poetry self:update poetry 自身を更新
  • poetry help ヘルプを表示

このあたりは公式のドキュメントは poetry list で確認することもできるので、丸覚えしなくても必要に応じて確認すればよいのではないかと個人的には思います。

Pipenv からの乗り換え


おそらく Poetry を使い始める理由として多いのは Pipenv からの乗り換えでしょう。ここでは私の経験を踏まえて Pipenv からの乗り換えに際して必要な作業をかんたんにご紹介します。

1. Poetry をインストール

上の手順のとおりに Poetry 本体をインストールします。

2. Poetry プロジェクトを作成

Pipenv から Poetry に切り替えたいプロジェクトのディレクトリに移動し、 poetry init を実行します。

$ poetry init

各入力項目に適宜回答したら pyproject.toml ファイルが作成されます。

3. Pipfile から pyproject.toml への移行

Pipfile の中身を確認しながら、パッケージと開発用パッケージをすべて poetry add で追加します。

$ poetry add 利用パッケージの一覧
$ poetry add --dev 開発用パッケージの一覧

通常の利用パッケージと開発用のパッケージは別々に登録します。

4. スクリプトのコマンドを書き換え

Makefilefabfile.py の中に pipenv run 等のコマンドがあればそれらを poetry run に変更します。主要なコマンドはおおよそ次のとおりに書き換えるとよいでしょう。

  • pipenv runpoetry run
  • pipenv installpoetry install
  • pipenv shellpoetry shell
  • pipenv updatepoetry update

以上です。複雑なことをやっていなければ Pipenv から Poetry への移行は高々数ステップで問題なく行えるのではないかと思います。

おわりに


というわけで、 Python のパッケージ管理ツール Poetry の使い方についてでした。 Pipenv と同様まだできてから日の浅いツールなのでこの先どのようになるかはわかりません。ただ、導入の学習コストは少なく Pipenv のような非直感的なふるまいも特に見られない感じなので、興味のある方は気軽に試してみてよいのではないかと思います。

Poetry についてより詳しく知りたい方は公式のドキュメントをご覧になってみてください。


次の日本語の記事もとてもわかりやすくておすすめです。各サブコマンドのオプションや関連する PEP ( Python Enhancement Proposals )等にも言及されています。


Pipenv の問題点とそれにまつわる議論については次のページ等が参考になります。興味のある方は読んでみてください。

2018/11/10

Python Tips: unittest で例外のテストをしたい

今回は Python の標準ライブラリのひとつ unittest で例外関係の部分をテストする方法についてまとめてみました。

対象の Python のバージョンは Python 3.7 です。

import unittest

例外を含むパターンの説明に入る前に、かんたんに unittest の基本の使い方を見ておきましょう。 unittest の基本的な書き方は次のとおりです。

test_sample.py:
import unittest


class TestRange(unittest.TestCase):
    def setUp(self):
        super().setUp()

    def tearDown(self):
        super().tearDown()

    def test_length(self):
        self.assertEqual(len(range(5)), 5)

    def test_min_max(self):
        self.assertIn(0, range(10))
        self.assertIn(9, range(10))
        self.assertNotIn(10, range(10))

unittest.TestCase を継承したクラス TestXXX を作成し、名前が test で始まる名前のメソッドを作成しその中にテストケースを書きます。すると、各メソッドが独立したテストケースとなります。

setUp() は各テストケースの前に、 tearDown() は各テストケースの後に実行されます。

テストケースが書かれたファイルを実行するにはコマンド python -m unittest を使用します。

python -m unittest test_sample.py

上の test_sample.py に対して実行すると次のような出力が表示されます。

python -m unittest test_sample.py
..
----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

デフォルトではテストケースの成功 1 件につき 1 つの . が出力されます。

-v オプションをつけることで、どのテストが実行されたかを確認することができます。

python -m unittest -v test_sample.py
test_length (test_exception.TestRange) ... ok
test_min_max (test_exception.TestRange) ... ok

----------------------------------------------------------------------
Ran 2 tests in 0.000s

OK

クラス単位・メソッド単位で絞り込んで個別のテストだけを実行することも可能です。

python -m unittest test_exception.TestRange.test_length
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

名前に特定の文字列を含むテストだけを絞って実行することなんかもできます。

python -m unittest test_exception -k min_max
.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

本当にざっくりとだけですが、テストの書き方と実行方法を押さえました(以下のコードを手元で実行したい方等はご参考にしてください)。続いて本題である例外のテストの仕方を見ていきます。

1. 特定の例外があがることをテストする


特定の例外があがることをテストしたいときは unittest.TestCaseassertRaises() メソッドを使用します。 assertRaises() はコンテキストマネージャとして作られているので、 with キーワードとともに使用します。

import unittest


def periodic_table(sign):
    amap = {
        'H': '水', 'He': '兵', 
        'Li': 'リー', 'Be': 'ベ',
        'B': 'ぼ', 'C': 'く', 
        'N': 'の', 'O': 'ぉ', 
        'F': 'ふ', 'Ne': 'ね',
    }
    try:
        return amap[sign]
    except KeyError:
        raise ValueError('不正な元素記号が指定されました: {} 。'.format(sign))


class TestPeriodicTable(unittest.TestCase):
    def test_invalid_key(self):
        with self.assertRaises(ValueError):
            periodic_table('Ubn')

テスト test_invalid_key() では、関数 periodic_table() に不正な値を与えると例外が上がることをチェックしています。

このテストを上の python -m unittest で走らせると無事成功することが確認できます。

ちなみに、 test_invalid_key() は次のように書いても( ValueErrorException に変更しても)成功します。

def test_invalid_key(self):
        with self.assertRaises(Exception):
            periodic_table('Ubn')

なぜなら、 assertRaises() は通常の tryexcept の仕組みと同じように、例外クラスの継承ツリーにおける親クラスは子孫の例外クラスも捕捉するようになっているためです。

つまり、 assertRaises()Exception のような大きめの例外を渡すと、想定と異なる例外があがったのにそれをキャッチし、本来失敗すべきケースを成功とみなしてしまうことがあります。そのため、 assertRaises() には適切な粒度の例外を渡さなくてはなりません。

2. 例外のメッセージも含めてテストする


例外のメッセージも含めてテストするには assertRaises()msg 引数を使用します(ちなみに、 msg 引数は Python 3.3 で追加されたので、 Python 3.2 以前では使用することができません)。

class TestPeriodicTable(unittest.TestCase):
    def test_invalid_key(self):
        with self.assertRaises(ValueError, msg='不正な元素記号が選択され'):
            periodic_table('Ubn')

msg は完全なメッセージである必要はありません。 msg が例外のメッセージの中に含まれていればテストはパスしたものとみなされます。

例外をメッセージも含めてテストする方法は他にもあります。そのひとつは、コンテキストマネージャ変数を使う方法です。

class TestSillyCase(unittest.TestCase):
    def test_silly_check(self):
        with self.assertRaises(NotImplementedError) as cm:
            raise NotImplementedError('実装してから呼ぶのじゃ')

        self.assertIsInstance(cm.exception, NotImplementedError)
        self.assertEqual(cm.exception.args[0], '実装してから呼ぶのじゃ')

コンテキストマネージャ変数(この場合は cm )にはアトリビュート exception がありキャッチされた例外がその中に格納されています。

さらにもうひとつの方法として、 assertRaises() の代わりに assertRaisesRegex() メソッドを使う方法もあります。

class TestShop(unittest.TestCase):
    def test_lack_of_gold(self):
        with self.assertRaisesRegex(ValueError, '^手持ちは \d+ ゴールドです。 \w+ は買えません。$'):
            raise ValueError('手持ちは 15 ゴールドです。 ひのきの棒 は買えません。')

assertRaisesRegex() はその名のとおり正規表現でメッセージのチェックを行う機能を提供します。

・・・というわけで、 unittest で例外を含むパターンをテストする方法についてかんたんにではありますがまとめてみました。

ちなみに、 assertRaises()assertRaisesRegex() ともに、コンテキストマネージャではなく通常のメソッドとして利用することもできます。

class TestPeriodicTable(unittest.TestCase):
    def test_invalid_key(self):
        self.assertRaises(ValueError, periodic_table, 'Ubn')

この使い方をする場合は、第 1 引数に想定される例外クラスを、第 2 引数に対象の callable を、第 3 引数以降は第 2 引数の callable に渡したい引数を渡します。結果はコンテキストマネージャパターンと同じになります。

以上です。

参考

2018/10/21

ライブラリ: dataclasses

今回は Python 3.7 で標準ライブラリに追加された dataclasses について見ていきたいと思います。

import dataclasses

目次


  • dataclasses とは
  • dataclasses の使い方
  • dataclasses の使いどころ

dataclasses とは


dataclasses は、独自のクラスを定義するときに定型的な特殊メソッド( special methods )( __init__() 等)の記述を省略できる機能を提供するモジュールです。 Python 3.7 から標準ライブラリとして Python 本体に同梱されるようになりました。

代表的な機能は次の 2 つです。

  • dataclasses.dataclass: クラスのデコレータ。対象のクラスのクラス変数をもとに特殊メソッドを追加する。
  • dataclasses.field: クラス変数に使う。 dataclass とあわせて使うことでアトリビュートごとの挙動を細かく設定できる。

dataclasses の使い方


まずインストールについてですが、 dataclasses は Python 3.7 以降に同梱されているためインストールする必要はありません。

代表的な機能である次の 2 つから見ていきましょう。

  • dataclasses.dataclass
  • dataclasses.field

dataclasses.dataclass

dataclass はクラスに対して使うデコレータです。

dataclass でデコレートされたクラスには、 __init__()__repr__()__eq__() の 3 つの特殊メソッドが自動的に追加されます。

from dataclasses import dataclass

TAX_RATE = 0.1


@dataclass
class Book:
    title: str
    isbn13: str
    price: int

    def price_with_tax(self):
        return int(self.price * (1 + TAX_RATE))


# __init__() が自動的に定義されている:
book1 = Book('小僧の神様・城の崎にて', '978-4101030050', 562)

# クラス変数と同名のアトリビュートが利用できる:
print(book1.title)
# => '小僧の神様・城の崎にて'
print(book1.isbn13)
# => '978-4101030050'
print(book1.price)
# => 562
print(book1.price_with_tax())
# => 618

# __repr__() も自動的に定義されている:
print(repr(book1))
# => Book(title='小僧の神様・城の崎にて', isbn13='978-4101030050', price=562)

# __repr__() の出力は eval() すると新しいインスタンスが作れる形になっている:
book2 = eval(repr(book1))

# __eq__() も自動的に定義されている:
print(book1 == book2)
# => True

dataclass の最もシンプルな使い方は上のサンプルのように @dataclass という形でデコレートするものですが、 @dataclass にはオプションを渡すこともできます。オプションでは、特殊メソッドの追加の有無等を指定することができます。

from dataclasses import dataclass


@dataclass(repr=False, eq=False)
class Book:
    title: str
    isbn13: str
    price: int


# __init__() が自動的に定義されている:
book1 = Book('暗夜行路', '978-4101030074', 961)

# repr=False により __repr__() は定義されていないので object のデフォルトの出力になる:
print(repr(book1))
# => <__main__.Book object at 0x106fa94a8>

book2 = Book('暗夜行路', '978-4101030074', 961)

# eq=False により __eq__() は自動追加されていないので、デフォルトの object.__eq__() で判定される:
print(book1 == book2)
# => False

ちなみに、 dataclass デコレータが @dataclass でも @dataclass() でも同じような使い方ができるのは、 dataclass がそのように(シンプルに使えるように)工夫して作られているからであり、一般のデコレータはこのようには行きません。

dataclass デコレータがアトリビュート化する要素として認識するのは タイプアノテーションのあるクラス変数だけ です。タイプアノテーションの無いクラス変数は無視されるので、そこは注意が必要です。次のコードではタイプアノテーションの無い isbn13 は自動追加されません。

from dataclasses import dataclass


@dataclass
class Book:
    title: str
    isbn13 = ''
    price: int


# isbn13 は dataclass には無視される:
book1 = Book('和解', 464)
print(repr(book1))
# => Book(title='和解', price=464)

また、 dataclass は「タイプアノテーションがあるかどうか」という点だけを見ており、具体的な型が何なのかは見ていないという点もポイントです。つまり、 int と定義されている変数に str 型の値を代入しても何も問題ありません(バリデーション等は特に追加されません)。

# 次のコードも特に問題なし
book1 = Book('和解', '464')

尚、 Python 3.7 における dataclass の宣言部は次のようになっています。

@dataclasses.dataclass(
    *, 
    init=True, 
    repr=True, 
    eq=True, 
    order=False, 
    unsafe_hash=False, 
    frozen=False)

引数がすべて *, の後に並んでいるので、引数はすべてキーワードを明示的に指定した渡し方をする必要があります。

dataclassesdataclass でデコレートされたクラスとそのインスタンスに対して使える関数をいくつか用意しています。次のコードでは fields()asdict()astuple() の 3 つを試しています。

from dataclasses import dataclass, fields, asdict, astuple


@dataclass
class Book:
    title: str
    isbn13: str
    price: int


# fields() 関数はそのクラスあるいはインスタンスのフィールドの一覧をタプルで返す:
# タプルの各要素は dataclasses.Field クラスのインスタンス
print(type(fields(Book)))
print([x.name for x in fields(Book)])
# => ['title', 'isbn13', 'price']

book1 = Book('小僧の神様・城の崎にて', '978-4101030050', 562)
print([x.name for x in fields(book1)])
# => ['title', 'isbn13', 'price']

# asdict() 関数は辞書を生成する
print(asdict(book1))
# => {'title': '小僧の神様・城の崎にて', 'isbn13': '978-4101030050', 'price': 562}

# astuple() 関数は tuple を生成する
print(astuple(book1))
# => ('小僧の神様・城の崎にて', '978-4101030050', 562)

dataclasses のアイデアの概要は以上でおおよそ押さえられたかと思いますが、実際に利用する場合は詳細を確認してから使うようにしてください。


つづいて field を見てみましょう。

dataclasses.field

fielddataclass でデコレートされたクラスのアトリビュートの挙動を細かく指定するためのものです。

例えば、アトリビュートのデフォルト値に list を指定したいときには field が次のように使えます。

from dataclasses import dataclass, field
from typing import List


@dataclass
class Film:
    title: str = ''
    cast: List[str] = field(default_factory=list)


f1 = Film('ジュラシック・ワールド', ['クリス・プラット', 'ブライス・ダラス・ハワード'])
print(repr(f1))
# => Film(title='ジュラシック・ワールド', cast=['クリス・プラット', 'ブライス・ダラス・ハワード'])


f2 = Film('ジュラシック・ワールド 炎の王国', ['クリス・プラット', 'ブライス・ダラス・ハワード'])
print(repr(f2))
# => Film(title='ジュラシック・ワールド 炎の王国', cast=['クリス・プラット', 'ブライス・ダラス・ハワード'])

また、次のように書くと、一部の要素( subtitle )を __eq__() での比較の対象外にすることができます。

from dataclasses import dataclass, field


@dataclass
class Film:
    title: str
    subtitle: str = field(compare=False)


f1 = Film('007', 'ワールド・イズ・ノット・イナフ')
f2 = Film('007', '慰めの報酬')
f3 = Film('007', 'スカイフォール')

# __eq__() には subtitle が使われないので、 == 演算で title のみが比較された結果、 True が返る
print(f1 == f2 == f3)
# => True

field を使うとその他さまざまなカスタマイズを行うことができます。 Python 3.7 における field の宣言部は次のようになっているので、実際に利用するときは事前に詳細を確認するようにしてください。

dataclasses.field(
    *, 
    default=MISSING, 
    default_factory=MISSING, 
    repr=True, 
    hash=None, 
    init=True, 
    compare=True, 
    metadata=None)


かんたんにですが、以上 dataclasses の基本的な使い方についてでした。

最後に使いどころについて。

dataclasses の使いどころ


私の認識では dataclassesnamedtuple の進化版的な位置づけのものです。

使い捨てのスクリプト作成等の場合は、単純に特殊メソッドの作成を省略したいときに気軽に使うとよさそうです。規模が大きめの開発では、アーキテクチャの境界をまたぐ情報の受け渡しに使用するシンプルなデータ構造やプレーンなドメインモデルクラス等で使うと便利かと思います。

dataclass デコレータが追加してくれる特殊メソッドのうち、 __init__()__eq__() は(自分で定義したいので)不要な場合も多そうですが、 __repr__() は多くの場合そのまま使えて便利なのではないでしょうか。

dataclasses の機能を濫用すると「 Explicit is better than implicit. 」に反するコードになりそうな気もしますが、今後 dataclasses がクラス定義における定番と言われるくらいよく使われるようになれば、( Python に不慣れな人にはわかりづらいけれど)ある程度経験のある Pythonista には implicit というほどのものではなくなってくる、のかも、しれません。

というわけで、 Python 3.7 で導入された dataclasses の説明でした。

同じく「特殊メソッドの自動定義」で人気のライブラリ attrs との違いや dataclasses が標準ライブラリに追加された経緯等について興味のある方には以下のページ等が参考になるかと思います。


関連記事


参考