Keep on moving

あんまりまとまってないことを書きますよ

Windows10(WSL2)の開発環境

初めに

新しいPCを買ったこともあり、久々にwindowsでの開発環境を構築しようと思いました。 折角なのでWSL2などの最新の環境を試してみました。 作業メモを書いておこうと思います。

WSL2のインストール

今回はこれが目的なので何はなくともこれを導入. この辺を参考にしました。

docs.microsoft.com

一か所はまったところがあったので共有しときます。 wslを入れた後で、the Linux kernel update packageを入れる必要があるのですが、 日本語版のページからリンクがなくなってるっぽいです。英語版のほうにはリンクがあるのでこちらからダウンロードできました。 英語版の方とかなりドキュメントの構成がちがうのでその辺の影響なのかも。なるべく英語のほうを 今は参照したほうがよさそうです。

docs.microsoft.com

Windows Terminal の導入

Windowsの世界の住人になるのでWindows TerminalとPowerShellに移行しました。 この辺りを参考にしました。 Windows TerminalとWSL2のメモ - Qiita

Scoop の導入

chocolateyを入れるのが一般的だと思うんですが、いい機会なのでScoopをいれてみました。 Windows版でのhomwbrew的な感じですかね。 windows10のパッケージ管理 scoopをインストールする | mebee

gitなども簡単に導入できました。

wsl2 intellijの環境の準備

Development under Windows under Linux with WSL2 (IntelliJ) | by JoergF-Ragin | Medium

こちらの記事を参考に入れてみました。 流れとしてはこんなかんじ

  1. wsl2側でintellijをインストール
  2. wsl2側でXserverを導入する
  3. windows側でX client をインストールする

https://rin-ka.net/windows-x-server/#toc14

var.blog.jp

PythonでJSON Linesを扱う

key value
Python 3.8 or later
Last Update 2020/09/06

こんにちは、yaml苦手っ子です。PythonJSON Linesを扱う方法を調べたのでまとめておきます。

JSON Lines とは

JSONを行区切りでファイルに書き込んだものです。

JSON Lines ここによると以下が条件みたいです.

  1. UTF-8 Encoding
  2. Each Line is a Valid JSON Value
  3. Line Separator is '\n'

Python での扱い方

ファイルから読んで、一行ごとにばらして json.load する感じです。 コードで書くとこんな感じです。

import json
from typing import Any, Dict, Iterator, List, TextIO


def filter_not_empty(list: List[str]) -> Iterator[str]:
    return filter(lambda x: len(x) > 0, list)


# TODO: DictではなくTypedDictを使うと型ヒントの恩恵に預かれて便利
def parse_jsonl(fp: TextIO) -> List[Dict[str, Any]]:
    # fileが空行を含むことがあるので念の為フィルターする
    jlines = filter_not_empty(fp.read().splitlines())
    result = [json.loads(jline) for jline in jlines]
    return result


if __name__ == "__main__":
    with open("sample.jsonl", "r") as json_file:
        print(parse_jsonl(json_file))

jqコマンドの代わりにPythonJSON Linesをpretty printする

Python 3.8から json.toolでもJSON Linesに対応しました。こんな感じでつかえます。

json --- JSON エンコーダおよびデコーダ — Python 3.8.5 ドキュメント

$ python3.8 -mjson.tool --json-lines sample.jsonl 
{
    "name": "Gilbert",
    "wins": [
        [
            "straight",
            "7\u2663"
        ],
        [
            "one pair",
            "10\u2665"
        ]
    ]
}
{
    "name": "Alexa",
    "wins": [
        [
            "two pair",
            "4\u2660"
        ],
        [
            "two pair",
            "9\u2660"
        ]
    ]
}
{
    "name": "May",
    "wins": []
}
{
    "name": "Deloise",
    "wins": [
        [
            "three of a kind",
            "5\u2663"
        ]
    ]
}

先人に感謝します。

GitHub ActionsでGradle環境でCIを回してみる

こんにちは、yaml苦手っ子です。GitHub Actions+GradleでCIする方法をまとめます。

key value
Last Update 2020/07/06

Gradle

こちらを参考にすればOK.

docs.github.com

超シンプルに書くとこんな感じ。

name: Java CI

on: [push]

jobs:
  build:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v2
      - name: Set up JDK 1.8
        uses: actions/setup-java@v1
        with:
          java-version: 1.8
      - name: Build with Gradle
        run: ./gradlew test

Cacheを利用する

ここをみると以下のように説明されている。

依存関係をキャッシュしてワークフローのスピードを上げる - GitHub Docs

GitHubホストランナー上のジョブは、クリーンな仮想環境で開始され、依存関係を毎回ダウンロードしなければならず、ネットワークの利用率を増大させ、実行時間が長くなり、コストが高まってしまいます。

なるほど、というわけでキャッシュの使い方をまとめてみる。

actions/cacheを使う

actions cacheを使う方法。 github.com

A repository can have up to 5GB of caches. Once the 5GB limit is reached, older caches will be evicted based on when the cache was last accessed. Caches that are not accessed within the last week will also be evicted. どうやら5GiBの制限があるみたい

READMEにちゃんと設定が書いてある。例としては以下のような感じ

name: Run Gradle unit-test on Push
on: [pull_request, push]
jobs:
  gradle:
    strategy:
      matrix:
        os: [ubuntu-latest, macos-latest, windows-latest]
    runs-on: ${{ matrix.os }}
    steps:
      - uses: actions/checkout@v2
      - uses: actions/setup-java@v1
        with:
          java-version: 8
      - name: Cache Gradle packages
        uses: actions/cache@v2
        with:
          path: ~/.gradle/caches
          key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
          restore-keys: ${{ runner.os }}-gradle
      - name: Build with Gradle
        run: ./gradlew test

gradle pluginでcacheを使う

ちなみに eskatos/gradle-command-action@v1 でもcacheを使うことが可能だったりします。

github.com このActtionではcacheを以下の3つに分けてあるとのこと。(default でwrapper-cacheのみ有効.多分キャッシュの制限が原因)

wrapper-cache-enabled: true
dependencies-cache-enabled: true
configuration-cache-enabled: true

以下のような感じになる

github.com

name: "CI"

on: [pull_request, push]


jobs:
  ci:
    name: "CI"
    runs-on: ubuntu-latest
    steps:

      - name: Checkout sources
        uses: actions/checkout@v2

      - name: Setup Java 11
        uses: actions/setup-java@v1
        with:
          java-version: 11

      - name: Build with Gradle
        uses: eskatos/gradle-command-action@v1
        with:
          dependencies-cache-enabled: true
          configuration-cache-enabled: true
          arguments: test

参考

GradleでのJavaのビルドとテスト - GitHub Docs

GitHub ActionsでGradleの自動ビルド/テストをする [再掲] | Qrunch(クランチ)

Property-Based-TestingのShrinkingをkotlin testで学ぶ

こんにちは永遠のJVM初心者です。 Property-Based-TestingのShrinkingとはなんぞやがよくわかってなかったのでまとめます。

TL;DR

  • Property-Based-TestingにおけるShrinkingとは

Property-Based-TestingにおけるShrinkingとは

blog.johanneslink.net

jqwikの作者のブログを読んでる感じだと以下の点で重要とのこと

Unless you already have an inkling of what the issue might be, the number itself does not give you an obvious hint. Even the fact that it’s rather large might be a coincidence. At this point you will either add additional logging, introduce assertions or even start up the debugger to get more information about the problem at hand.

言ってしまえばpbtによる確認はrandomにgenerateされたものに過ぎないので、失敗した数値だけでてもよくわかんないよねー。
ロギングを追加したり、アサーションを導入したり、デバッガを起動しないと問題の原因はわからんよね。
なので「最も単純な」例をテスト結果で出してほしいよね。
この探索フェーズは、元のサンプルから開始し、それをだんだん小さくして再度propertyを確認しようとするので、Shrinking(縮小)と呼びます。

ということらしい。

Kotestでの例

こんな感じで超簡単な素数判定を考える。 これにPBTをかけると以下のような結果になる

gist.github.com

ここでは 592298304 でfailになったあと最小の数字までshrinkingしてくれているのがわかる。 最終的に4までShrinkingしてくれるおかげで、あぁなるほど4はそもそも素数じゃないなというのがわかってそもそもこのテスト自体が正しくないことが わかる。

KotestでのShrinkingの実装

現状でのInt型のShrinkerは以下のような実装らしい。

github.com

object IntShrinker : Shrinker<Int> {
   override fun shrink(value: Int): List<Int> =
      when (value) {
         0 -> emptyList()
         1, -1 -> listOf(0)
         else -> {
            val a = listOf(abs(value), value / 3, value / 2, value * 2 / 3)
            val b = (1..5).map { value - it }.reversed().filter { it > 0 }
            (a + b).distinct()
               .filterNot { it == value }
         }
      }
}

うーむコメントが少ないのでlistOfのルールがよくわかんない。 多分 value/3 がfailしたときのルールかな...(^見た感じだとfailしたら3でわって言っている様子が見えるかも) ちょっと自力で実装するにはもう少しコード読まないといけなそう

github.com

  • Returns the "next level" of shrinks for the given value, or empty list if a "base case" has been reached.
  • For example, to shrink an int k we may decide to return k/2 and k-1. とのことなので、なにかいい感じでルールを作るということらしい。ただ、そもそも順序関係が必要だよなぁ...

Shrinkingのルール変更

ShrinkingModeで設定可能

こんな感じ

        checkAll(PropTestConfig(shrinkingMode = ShrinkingMode.Bounded(50)),Arb.int(2..Int.MAX_VALUE)){
            i ->
            isPrimeNumber(i) shouldBe true
        }

やめたいときは ShrinkingMode.Offを指定すればいいらしい。

github.com

まとめ

PBTのツールではShrinkingが大事というのはよくドキュメントに書かれていたんだけど、実際に使ってみることで重要性がわかりました。 kotestでは今回の例でつかった intだけでなく long, string, listなどでも使用可能なのでbuild-inの方を組み合わせるだけでも十分テストでつかえそうです。 先人に感謝

KotestでProperty-Based-TestingするときのTips

こんにちは、Kotlin初心者です。

TL;DR

この記事は前日のこちらの記事の続きです。が、Property-Based-Testing知ってる方はそのまま読んでもらって問題ありません。

masahito.hatenablog.com

いろいろ書いてみた結果をまとめた

  • forAll() と checkAll()の使い分け
  • generationの回数を増やしたいor減らしたい
  • 失敗したときにもう少しテストを流したい
  • CustomGenerator

ひとことでいうと

github.com このドキュメント読むといいよ

forAll() と checkAll()の使い分け

ひとことで行ってしまうと関数の定義が違う

  • forAll : (a, ..., n) -> Boolean
  • check All : (a, ..., n) -> Unit

以上。例はこんな感じ

class PropertyExample: StringSpec({
    "String size" {
        forAll<String, String> { a, b ->
            (a + b).length == a.length + b.length
        }
    }

    "String size2" {
        checkAll<String, String> { a, b ->
            (a + b).length shouldBe  a.length + b.length
        }
    }
})

The checkAll approach will consider a test valid if no exceptions were thrown. なので、ちょっと考え方が違うとも言える。 自分はBooleanを返してほしい派なので大体forAllをつかっている。

generationの回数を増やしたいor減らしたい

標準では1000回生成するそう。増やしたいときには以下のようにする。

class PropertyExample: StringSpec({
   "some test" {
      checkAll<Double, Double>(iterations = 10000) { a, b ->
         // test here
      }
   }
})

失敗したときにもう少しテストを流したい

デフォルトでは失敗したら即打ち切りなんだけど、もっとテスト失敗パターンを出すことも可能。以下のようにする.

class PropertyExample: StringSpec({
   "some flakey test" {
      forAll<String, String>(PropTestConfig(maxFailure = 3)) { a,b ->
         // max of 3 inputs can fail
      }
   }
})

CustomGenerator

Generated values are provided by instances of the sealed class Gen. You can think of a Gen as kind of like an input stream but for property testing. Each Gen will provide a (usually) infinite stream of these values. Kotest has two types of generators - Arb for arbitrary (random) values and Exhaustive for a finite set of values in a closed space.

KotestではGenが基底クラスとして、2種類のジェネレータークラスが提供されている。

To write your own generator for a type T, you just create an instance of Arb or Exhaustive.

というわけで Arb<T> or Exhaustive<T>インスタンスを実装することで自分好みのGeneratorをつくれる

data class Yen(val amount: Long)
test("金額は5000円より高い") {
        val yenArb = arb { rs ->
            val nums = Arb.long(LongRange(5001, 10000)).values(rs)
            nums.map { num -> Yen(num.value) }
        }
        forAll<Yen>(yenArb) { yen1 ->
            yen1.amount > 5000L
        }
}

2変数以上のクラスをGenerateするためのTips

2変数くらいなら zip でOK.

        val userArb = arb { rs ->
            val names = Arb.string(minSize = 1).values(rs)
            val ages = Arb.int().values(rs)
            names.zip(ages).map { (name, age) ->
                User(
                    name.value,
                    age.value
                )
            }
        }

3変数以上なら Arb.bind() がおすすめ

        val orderArb: Arb<Order> =
            Arb.bind(
                Arb.long(0L),
                Arb.string(1, 24)
            ) { a, b -> Order(Item(a, b)) }

別解としてはflatmap+mapでもできたりします(for expression♫)

        val expiredCardArb: Arb<CreditCard> = arb { rs ->
            val nums = Arb.int(0, 9999).values(rs)
            val holders = Arb.string(minSize = 1).values(rs)
            val diffs = Arb.long(1, 24).values(rs)
            nums.flatMap { num ->
                diffs.flatMap { diff ->
                    holders.map { holder ->
                        val numStr = num.value.toString().padStart(4, '0')
                        CreditCard(
                            number = "${numStr}-${numStr}-${numStr}-${numStr}",
                            limit = today.minusMonths(diff.value),
                            holder = holder.value
                        )
                    }
                }
            }
        }

とはいえせっかくなので bindを使ったほうがよさそう。Sequenceで結果返してるのも実行効率考えると良さそう。

// 実装はこんな感じ
fun <A, B, T : Any> Arb.Companion.bind(genA: Gen<A>, genB: Gen<B>, bindFn: (A, B) -> T): Arb<T> = arb {
   val iterA = genA.generate(it).iterator()
   val iterB = genB.generate(it).iterator()
   generateSequence {
      val a = iterA.next()
      val b = iterB.next()
      bindFn(a.value, b.value)
   }
}

kotest/generators.md at master · kotest/kotest · GitHub

まとめ

いろいろ書いてみた結果をまとめた

  • forAll() と checkAll()の使い分け
  • generationの回数を増やしたいor減らしたい
  • 失敗したときにもう少しテストを流したい
  • CustomGenerator

ドキュメント読んでてscalaCheckなど先に出ているライブラリをちゃんと意識している印象です。 propertyをちゃんと見出すことができれば結構簡単に値をCustomeGenerator使えるのがいい感じです。 ぜひProperty-Based-Testing使ってみると良いと思います。 コードで変なところあったらぜひツッコミをください。

サンプルコード

github.com

Property-Based-Testingをkotlin testで学ぶ

こんにちは、Kotlin初心者です。Property-Based-Testingやってみようとおもったけど、あまり良くわかってなかったのでまとめてみました。

TL;DR

  • KotlinでProperty-Based-Testingやってみた
  • kotestでの流れ

サンプル

今回のサンプルはこちらにあります

github.com

Property-Based-Testing

そもそもProperty-Based-Testingとは

www.kzsuzuki.com

こちらのブログによるとこのブログを元ネタに説明してくれています。

jessitron.com

 記事では、Property-based testingの対照概念を、Example-based testingとしています。  Example-based testingとは、無数にある値から何がしかの基準で入力値を選択し、その入力値でコードを動かして、出力値と事後状態を確認する。つまり、普通に行われているテストですよね。

 Property-based testingでは、一つのテストで数百個の入力に対する結果を検証します。  ここで検証するのは、個々の入力に対するそれぞれの出力そのものではなく、(有効な)入力「群」に対する出力「群」のすべてが、statementで規定された性質(property)を満たしていることです。

jqwik作者の説明

Javaでの pbsツール jqwikの作者がpbsについてまとめてくれています。

jqwik.net

hypothesis.works

によるとFuzzingテストとpbsの違いは以下のような感じらしいです。

The main reason I’m drawing it here is that they do feel like they have a different character - property based testing requires you to reason about how your program should behave, while fuzzing can just be applied to arbitrary programs with minimal understanding of their behaviour - and also that fuzzing feels somehow more fundamental. [超訳] プロパティベースのテストでは、プログラムの動作を推論する必要がありますが、ファジングは、動作の理解が最小限の任意のプログラムに適用できます,そして、そのファジングは何かもっと根本的なもののようにに感じます。

ファジングテストはpbsとそんなにちがいはないのかもしれないというのがこの話で出てきています。実際に振る舞いをしっていてテストコードをつくるのが大事らしいですね。 プログラムの動作を推論というのは ^ の話でのstatement(property?)の話とほぼ同義の話をしているようにも思えます。

propertyを、「さまざまなデータポイントにおいて維持されるべき、ハイレベルな振る舞いの仕様」としています。

Property based testing is the construction of tests such that, when these tests are fuzzed, failures in the test reveal problems with the system under test that could not have been revealed by direct fuzzing of that system.

Kotlin testでは振る舞いを知っている上で、Example-based testingを補う概念として説明していますね。

GitHub - kotest/kotest: Powerful, elegant and flexible test framework for Kotlin

The problem is it's very easy to miss errors that occur when edge cases or inputs outside of what you considered are fed in. With property testing, hundreds or thousands of values are fed into the same test, and the values are randomly generated by your property test framework. [超訳] 問題は、エッジケースや想定外の入力が入力された場合に発生するエラーを見逃してしまうことです。プロパティテストでは、何百、何千もの値が同じテストに投入され、その値はプロパティテストフレームワークによってランダムに生成されます。

というわけで、これを使ってテストケースを考えてみます。

Kotlin Test

github.com

Kotlin と Kotlin Test(kotest)を使って実際のコードを書いてみます。

install

gradleでの例です。 Property-based-tesitingを使いたいときは kotestに追加で io.kotest:kotest-property-jvm を追加で入れる必要があります。

dependencies {
    testImplementation("io.kotest:kotest-runner-junit5-jvm:version") // for kotest framework
    testImplementation("io.kotest:kotest-assertions-core-jvm:version") // for kotest core jvm assertions
    testImplementation("io.kotest:kotest-property-jvm:version") // for kotest property test
}

こちらを参考に例を作ってみました。

テストの対象とするメソッドは以下のようなものです。

typealias Adder<T> = (T) -> T

object ListUtil {

    fun <T : Number> T.toAdder(): Adder<T> {
        return when (this) {
            is Long -> {
                { it -> (this as Long + it as Long) as T }
            }
            is Int -> {
                { it -> (this as Int + it as Int) as T }
            }
            is Double -> {
                { it -> (this as Double + it as Double) as T }
            }
            else -> throw AssertionError()
        }
    }

    fun <T : Number> sum(zero: T, list: List<T>): T {
        return list.map { it.toAdder() }.fold(zero) { acc, func -> func(acc) }
    }
}

テストコードは以下のようになります。 個々でのproperty(=「さまざまなデータポイントにおいて維持されるべき、ハイレベルな振る舞いの仕様」)は以下の2点です。 * "空のリストの合計は0" * "空ではないリストの合計はtailの合計にheadを足したもの"

class BusinessLogicTests : FunSpec({
    context("ListUtil.sum") {
        test("空のリストの合計は0") {
            ListUtil.sum(0, emptyList()).shouldBe(0)
        }
        test("空ではないリストの合計はtailの合計にheadを足したもの") {
            forAll<Int, List<Int>> { head, tail ->
                ListUtil.sum(0, listOf(head) + tail) == head + ListUtil.sum(0, tail)
            }
        }
    }
})

上の例では、forAllを使用することで、ランダムに生成されたIntとList[Int]の値を受け取ってテストを行っています。forAllに渡したテストの関数が(2, List()), (4, List(1, 4)のようなランダムな値で順番に呼び出されることでテストが実行されるイメージです。

まとめ

僕の理解だと以下のような流れで追加していくのが良さそうです。 1. サンプルベースのテスト 2. それを補完するためにPropertyをみつけ、Property-Based-Testingをする

propertyを、「さまざまなデータポイントにおいて維持されるべき、ハイレベルな振る舞いの仕様」としています。

もうすこし、複雑な例はまた明日書く予定です。 ビジネスロジックのテストに便利そうなので特定のdataclassを生成するときのTipsをまとめる予定です。

関連

github.com

gakuzzzz.github.io

taketoncheir.hatenablog.com

dev.classmethod.jp

attrsの使いどころとdataclass

masahito.hatenablog.com

この記事は^の記事の続きです。^を読んでから読むと良いと思います。 なお、今回のサンプルコードはこちらにあります。(間違いなどありましたらご指摘ください)

github.com

TL;DR

  • attrsのつかいどころ
    • 今Python3.5 or 3.6を使っている。今後dataclassを使いたい
    • dataclassで slotsなどを使いたい場合にボイラープレートコードを避けたい
  • slotsの効果

attrsとは

www.attrs.org 簡単にいうとPython でclassを書くときのボイラープレートになるところがスッキリかけるものです。 1 Overviewで書かれているコードを軽く解説します。

>>> import attr

>>> @attr.s
... class SomeClass(object):
...     a_number = attr.ib(default=42)
...     list_of_numbers = attr.ib(factory=list)
...
...     def hard_math(self, another_number):
...         return self.a_number + sum(self.list_of_numbers) * another_number


>>> sc = SomeClass(1, [1, 2, 3])
>>> sc
SomeClass(a_number=1, list_of_numbers=[1, 2, 3])

>>> sc.hard_math(3)
19
>>> sc == SomeClass(1, [1, 2, 3])
True
>>> sc != SomeClass(2, [3, 2, 1]) # classレベルでの比較が可能です
True

>>> attr.asdict(sc) # class -> dict 変換
{'a_number': 1, 'list_of_numbers': [1, 2, 3]}

>>> SomeClass() # default値も使えます. factory=list の場合はempty listになります。
SomeClass(a_number=42, list_of_numbers=[])

>>> C = attr.make_class("C", ["a", "b"]) # methodでクラスがつくれちゃいます。
>>> C("foo", "bar")
C(a='foo', b='bar')

attrsこぼれ話

じつはこのライブラリ Python 3.7 で入った dataclass のもとになった実装だったりします。 詳しくはpepを確認してください。 www.python.org

ちなみに dataclass入れるよりattrsをPythonで標準でいれちゃえば? という声もあったみたいですがそれはやめたみたいです。(guidoがstdlibに入れるのに反対している、この辺を参照。)

github.com 「attrsはいいもんだが、これがファイナルアンサーじゃないしいいところだけ取り出そうぜ」という感じっぽい。

attrsのdataclassとの使い分け

attrsを使うべき

  • 今Python3.5 or 3.6を使っている. 2
    • 特に現在namedtupleを使っているが、3.7にupgradeするタイミングでdataclassの使用を考えている
  • dataclassで slotsなどをつかいたい(for Python3.7以降)
@dataclasses.dataclass
class B:
   __slots__ = ('b',) # __slots__ 追加
   b: int

例えばこんな感じでdataclassでも __slots__ が使えることを前回紹介した。が、 __slots__ に設定するところとプロパティを両方書かないといけないため 実行時にエラーになることが結構おおい。

これをattrsをつかうと以下のようにかける

# slots=Trueをつけるとslotsが有効に
@attr.s(auto_attribs=True, slots=True)
class AttrsMessage:
    sender: str
    recipient: str
    body: str

個人的にはdataclassを使いたくなったら無条件でattrsを使うのをおすすめしたいと思っています。 ただプロジェクトの考え方でなるべく使うライブラリを増やしたくない場合はdataclassでも問題はないかもしれません。 が、複数人で開発するときではミスをするばあいが多いため入れておいたほうが便利だと思います。

余談 __slots__ の効用

Pympler を使ってPython3.8での __slots__ を使ったとき使わないときのメモリ使用量を比べてみました。 使い方についてはこちらが詳しいです。 Asizeof モジュールの使い方 | Xoxzoの公式ブログ

# use NamedTuple
Message = NamedTuple("Message", [("sender", str), ("recipient", str), ("body", str)])

if sys.version_info.minor >=7:
    import dataclasses
    @dataclasses.dataclass
    class DataClassMessage:
        sender: str
        recipient: str
        body: str


    @dataclasses.dataclass
    class SlotMessage:
        __slots__ = ["sender", "recipient", "body"]
        sender: str
        recipient: str
        body: str


@attr.s(auto_attribs=True, slots=True, weakref_slot=False)
class AttrsMessage:
    sender: str
    recipient: str
    body: str

if __name__ == "__main__":
    pos = Message(
        sender="sender@exmaple.com",
        recipient="recipient.example.com",
        body="Hello, World!",
    )
    if sys.version_info.minor >=7:
        simple = DataClassMessage(
            sender="sender@exmaple.com",
            recipient="recipient.example.com",
            body="Hello, World!",
        )
        slotted = SlotMessage(
            sender="sender@exmaple.com",
            recipient="recipient.example.com",
            body="Hello, World!",
        )
        print(
            "Dataclass %d, Slotted dataclass %d"
            % asizeof.asizesof(simple, slotted)
        )
    attrs = AttrsMessage(
        sender="sender@exmaple.com",
        recipient="recipient.example.com",
        body="Hello, World!",
    )
Dataclass 536, Slotted dataclass 56
NamedTuple 272,  Attrs 56

こんな感じで3propertiesのclassでも __slots__ のありなしで 10倍程度メモリ使用量が違ったりします。 特に実行回数が多い関数の返り値などでdataclassを使用する場合は使用メモリのためにも使っておくことが無難です。

まとめ

本記事では以下の説明をしました。

  • attrsのつかいどころ
    • 今Python3.5 or 3.6を使っている。今後dataclassを使いたい
    • dataclassで __slots__ などを使いたい場合にボイラープレートコードを避けたい
  • __slots__ の効果

  1. Scalaのcase class, Kotlinのdata class的なものだと思ってもらうと良いと思います(プロパティはデフォルトではimmutableではないですが)

  2. https://devguide.python.org/#status-of-python-branches Python3.5/3.6もまだEOLでない