modalsoul’s blog

これは“失敗”と呼べるかもしれないが、ぼくは“学習体験”と呼びたい

Pythonのnamedtupleを使ってScalaのcase class感覚でvalue objectを作る

複数の値をまとめて扱いたい時ありますよね。value object欲しいなって時

そんなとき、Scalaだとcase classを使いますね

Case Classes | Scala Documentation

case class Human(height:Int, weight:Int)

こんな感じで、1行だけで複数の値をまとめて扱うことができます。

付随して、コンパニオンオブジェクトやメソッド群が追加されるので

applyメソッドが実装され、newを省略できたり

scala> val taro = Human(170, 60)
taro: Human = Human(170,60)

unapplyメソッドが実装され、extractorパターンによるパターンマッチができたり

scala> taro match {
     | case Human(h,w) => w/(h*h/10000.0)
     | }
res42: Double = 20.761245674740483

プロパティはimmutable

scala> taro.height = 100
<console>:12: error: reassignment to val
       taro.height = 100

equalsメソッドが適切に実装され、同じプロパティを持つインスタンス同士を同値と判定できたり

scala> taro == Human(170,60)
res43: Boolean = true

と、いろいろ便利です


で、Pythonで複数の値をまとめていい感じに扱うには?

同僚に教えてもらったnamedtupleが便利そうです

8.3. collections — コンテナデータ型 — Python 3.6.3 ドキュメント

case classと同じく、1行でパパっと定義できて

>>> Human = namedtuple('Human', ('height', 'weight'))

サクッと、インスタンスが作れます

>>> taro = Human(height=170, weight=60)
>>> taro
Human(height=170, weight=60)

__str__メソッドを定義しなくとも、オブジェクトの中身を確認できてますね

オブジェクトの属性へアクセスできるし

>>> taro.height
170

属性はimmutable

>>> taro.height = 100
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

そしてunpacking assignmentで、値を取り出せる

>>> for h,w in [taro]:
...     print(w/(h*h/10000))
...
20.761245674740483

同じ属性を持つインスタンス同士の比較もOK

>>> taro == Human(height=170, weight=60)
True

ぱっと見、クラスを定義してるように見えなくて、ナニコレ?ってなるけど、わかるととても便利なやつ