object活用ことはじめ 〜dataclassと特殊メソッド〜

Event

Pythonエンジニア勉強会

Presented

2021/04/27 nikkie

❓ Question(チャット💬 お願いします🙏)

  1. dataclass使ったことありますか?

  2. 「特殊メソッド」ピンと来ますか?

はじめに

  • Python界隈では 自己紹介のエイリアス が「お前、誰よ」(ルーツ活用ください!

お前、誰よ

  • 我が名は にっきー @ftnext / @ftnext

  • Python歴3年半 ほど。現在は ユーザベース 所属のデータサイエンティスト

  • Love anime!!(@ 🎺🎷🔥 🌲🌳🐲 🐴🍚🚿 👩‍🎨🐯🐟)

    ペアプロ・TDDでPython書いて、日々楽しく働いています😃

Pythonエンジニア勉強会(この勉強会)

「Python」について、活用ユーザーが集まり最新トレンド共有やLTを行う勉強会イベント

https://tech-street.connpass.com/event/210087/

Pythonの最新トレンドが共有される他の勉強会イベント

LTするので宣伝させてください🙏

ここから本題:object活用ことはじめ 〜dataclassと特殊メソッド〜

  1. dataclass使ったことありますか?

  2. 「特殊メソッド」ピンと来ますか?

→ nikkieはこれらを活用し始められて、Python書くのがすごく 楽しい !😆

おことわり

  • Python活用事例や最新トレンドではありません🙇‍♂️(私も知りたい!)

  • Pythonを活用するために Pythonの裏側を知りましょう

  • この話が「当たり前」という人は、Pythonを活用できていると思います!

  • 動作環境:Python 3.9.4

補注:Python活用事例が聞きたかったという方へ

nikkieの過去のアウトプットから

目次:object活用ことはじめ 〜dataclassと特殊メソッド〜

  • Pythonのobject

  • objectどうしが「等しい」

  • 特殊メソッドでobjectを反復できる

object活用ことはじめ 〜dataclassと特殊メソッド〜

  • Pythonのobject

  • objectどうしが「等しい」

  • 特殊メソッドでobjectを反復できる

objectとは

Python における オブジェクト (object) とは、データ を抽象的に表したものです。

Python 言語リファレンス - 3. データモデル - 3.1 . オブジェクト、値、および型 (強調は引用者による)

大事なことなので、2回

Python プログラムにおける データ は全て、オブジェクトまたはオブジェクト間の関係として表されます。

Python 言語リファレンス - 3. データモデル - 3.1 . オブジェクト、値、および型 (強調は引用者による)

補注:オブジェクト指向とは無関係です

用語集でobjectを引く

状態 (属性 や値) と定義された振る舞い (メソッド) をもつ全てのデータ

https://docs.python.org/ja/3/glossary.html#term-object (強調は引用者による)

用語集でobjectを引く 続き

もしくは、全ての 新スタイルクラス の究極の 基底クラス のこと。


class SomeClass:  # クラスはobjectを継承している
    pass

小まとめ🥟:Pythonのobjectは2重の意味合い

  • 属性とメソッドを持つ データ

  • どんなクラスも object を継承(究極の 基底クラス

👉 ここからはPythonにおけるデータの 振る舞い について話していきます

object活用ことはじめ 〜dataclassと特殊メソッド〜

  • Pythonのobject

  • objectどうしが「等しい」

  • 特殊メソッドでobjectを反復できる

❓ Question(チャット💬 お願いします🙏)

次のデータ(馬🐴の名を表すobject)は等しい?


>>> class RaceHorseName:
...     """競走馬の名前を表す"""
...     def __init__(self, value):
...         self.value = value
>>> rice = RaceHorseName("ライスシャワー")
>>> rice2 = RaceHorseName("ライスシャワー")
>>> rice == rice2  

次のデータ(object)は等しい? - 🙅‍♀️ (ぶっぶー)

馬の名は、等しくない


>>> rice.value == rice2.value  # value属性の値は等しい
True
>>> rice is rice2  # オブジェクトは同一ではない
False
>>> rice == rice2
False

比較演算子 ==

x==yx.__eq__(y) を呼び出します

https://docs.python.org/ja/3/reference/datamodel.html#object.__eq__

__eq__ メソッド?

  • RaceHorseName には実装していない

  • 究極の基底クラス object__eq__ を持つ

  • その実装は True if x is y else NotImplementedドキュメント

is:同一性の比較

x is y は、 x と y が 同じオブジェクトを指す とき、かつそのときに限り真になります。 オブジェクトの同一性は id() 関数を使って判定されます。

https://docs.python.org/ja/3/reference/expressions.html#is (強調は引用者による)


>>> rice is rice  # オブジェクトは同一
True

小まとめ🥟:等しくなかった理由

  • rice == rice2 は、オブジェクトの 同一性 を比較する結果になった

  • ricerice2別々のオブジェクトを指す ので、False

  • (組み込み関数 id で、別々のオブジェクトを指していることを確認できます)

「等しい」は作れる!

  • value 属性の 値が等しい とき、データ(object)は等しく したい(ライスシャワーはライスシャワー)

  • 👉 __eq__ メソッドを RaceHorseName クラスで実装する(object__eq__オーバーライド

__eq__ メソッド オーバーライド


>>> class RaceHorseName:
...     def __init__(self, value):
...         self.value = value
...     def __eq__(self, other):
...         if not isinstance(other, self.__class__):
...             return NotImplemented  # 👉 Appendix
...         return self.value == other.value

__eq__ メソッド オーバーライド


>>> rice = RaceHorseName("ライスシャワー")
>>> rice2 = RaceHorseName("ライスシャワー")
>>> rice == rice2  # value属性が等しいときに、データとして等しくできた
True
>>> rice is rice2
False

「等しい」はもっと簡単に作れる:dataclass


>>> from dataclasses import dataclass
>>> @dataclass
... class RaceHorseName:
...     value: str
>>> rice = RaceHorseName("ライスシャワー")
>>> rice2 = RaceHorseName("ライスシャワー")
>>> rice == rice2
True

@dataclasses.dataclass

  • 「dataclass使ったことありますか?」はこれを指していました

  • クラスに付けるデコレータ(用語集

  • RaceHorseName クラスに __eq__ を作り、object__eq__ をオーバーライド

補注:以下2つのデコレータの機能は同じ (👉 Appendix)


@dataclass
class RaceHorseName1:
    ...

@dataclass()
class RaceHorseName2:
    ...

@dataclasses.dataclasseq 引数

eq: (デフォルトの)真の場合、 __eq__() メソッドが生成されます。このメソッドはクラスの比較を、そのクラスの フィールドからなるタプルを比較 するように行います。 比較する2つのインスタンスのクラスは同一でなければなりません。

https://docs.python.org/ja/3/library/dataclasses.html#dataclasses.dataclass (強調は引用者による)

@dataclasses.dataclass によって

  • RaceHorseName クラスに __eq__メソッドが作られた

  • この __eq__ では、クラスが同じことと (self.value, ) を比較

  • 👉 クラスが同じで、上記タプルが等しい ので、rice == rice2True と評価された

小まとめ🥟:「等しい」の作り方

  • クラスに __eq__ メソッドを実装して、object__eq__ をオーバーライドすればいい

  • @dataclasses.dataclass でクラスをデコレートすると __eq__ メソッドが作られて、少ない記述で済む 🙌

object活用ことはじめ 〜dataclassと特殊メソッド〜

  • Pythonのobject

  • objectどうしが「等しい」

  • 特殊メソッドでobjectを反復できる

特殊メソッド

  • __eq__ などのメソッドのこと(特殊メソッド名一覧

  • 究極の基底クラス object で定義されていて、オーバーライド することで データの振る舞いをカスタマイズ できる

  • マジックメソッド、ダンダーメソッドとも呼ばれる(👉 Appendix)

例:反復できるobjectを作りたい

RaceHorseName クラスのデータをいくつも持たせられるクラス RaceHorseNames


rice = RaceHorseName("ライスシャワー")
bourbon = RaceHorseName("ミホノブルボン")
names = RaceHorseNames([rice, bourbon])
for name in names:
    print(f"{name.value}さん、こんにちは")

反復できるobjectの作り方

  • 今回は Sequence というクラスを継承 して、特殊メソッドをオーバーライドして作成

  • for 文で繰り返したいだけであれば、Iterable(用語集)になればいいので、他の特殊メソッドをオーバーライドしてもできます

用語集より「シーケンス」

リスト、タプル、文字列もシーケンス

整数インデクスによる効率的な要素アクセスを __getitem__() 特殊メソッドを通じてサポートし、長さを返す __len__() メソッドを定義した iterable です

https://docs.python.org/ja/3/glossary.html#term-sequence

反復できるobjectの作り方

  • 以下の特殊メソッドを実装

    • __len__

    • __getitem__

  • オススメ:抽象基底クラスを継承 する

抽象基底クラスを継承して反復できるobjectを作る

  • collections.abc.Sequence を継承する

  • 継承することで、__len____getitem__実装が強制される

  • オススメ理由:実装する特殊メソッドを覚えておくより、継承する抽象基底クラスを覚えておくほうが覚える量が少ない(※個人の見解です)

補注:実装が強制される様子


>>> from collections.abc import Sequence
>>> @dataclass
... class RaceHorseNames(Sequence):
...     names: list[RaceHorseName]  # 👉 Appendix
>>> names = RaceHorseNames([])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: Can't instantiate abstract class RaceHorseNames with abstract methods __getitem__, __len__

__len__

オブジェクトの長さ を 0 以上の整数で返さなければなりません。

https://docs.python.org/ja/3/reference/datamodel.html#object.__len__ (強調は引用者による)

__len__ を実装


>>> @dataclass
... class RaceHorseNames(Sequence):
...     names: list[RaceHorseName]
...     # リストnamesの長さをオブジェクトの長さとする
...     def __len__(self):
...         return len(self.names)

__getitem__

self[key] の値評価 (evaluation) を実現するために呼び出されます。 シーケンスの場合、キーとして整数とスライスオブジェクトを受理 できなければなりません。

https://docs.python.org/ja/3/reference/datamodel.html#object.__getitem__ (強調は引用者による)

__getitem__ を実装


>>> @dataclass
... class RaceHorseNames(Sequence):
...     names: list[RaceHorseName]
...     # -- __len__ は省略 --
...     # namesはリストなので、整数もスライスも受け付けられる
...     def __getitem__(self, key):
...         if isinstance(key, slice):
...             return self.__class__(self.names[key])
...         return self.names[key]

反復できるobjectを実装!🙌


>>> rice = RaceHorseName("ライスシャワー")
>>> bourbon = RaceHorseName("ミホノブルボン")
>>> names = RaceHorseNames([rice, bourbon])
>>> for name in names:
...     print(f"{name.value}さん、こんにちは")
ライスシャワーさん、こんにちは
ミホノブルボンさん、こんにちは

小まとめ🥟:特殊メソッドでobjectを反復できる

  • クラスに __len__、__getitem__ を実装した

  • Sequence を継承 することで、実装が強制される(推奨納言)

  • Pythonのデータ (object) の振る舞いのカスタマイズの一例

まとめ🌯:object活用ことはじめ 〜dataclassと特殊メソッド〜

  • Pythonのobjectはデータであり、究極の基底クラス

  • object が持つ 特殊メソッドをオーバーライド して、データの振る舞いをカスタマイズ 😆

    • @dataclass で特殊メソッド作成

    • 抽象基底クラスを継承して特殊メソッド実装

ご清聴ありがとうございました

Enjoy development with object!

References、Appendix が続きます(よろしければどうぞ!)

References

Appendix:object活用ことはじめ 〜dataclassと特殊メソッド〜

object__eq__

  • 究極の基底クラス object__eq__ の実装

  • True if x is y else NotImplemented と共有しました

同値性と同一性

  • 同値性: 同じ (同一でなくても値として同じになりうる)

  • 同一性: is で比較。同じ オブジェクト (同じ id

このLTでは、__eq__ をオーバーライドして同値性を比較できるようにした

NotImplemented

  • 単一の値しかない型(ref: 標準型の階層

  • NotImplementedError ではないので注意

__eq__NotImplementedreturn

== の評価は False (そうなるための仕組みが NotImplemented


>>> class RaceHorseName:
...     def __init__(self, value):
...         self.value = value
...     def __eq__(self, other):
...         if not isinstance(other, self.__class__):
...             return NotImplemented
...         return self.value == other.value
>>> rice = RaceHorseName("ライスシャワー")
>>> rice == "ライスシャワー"
False

以下2つのデコレータの機能は同じ

  • @dataclass

  • @dataclass()

nikkieは上の方で使うことが多いです

dataclasses のドキュメントより

dataclass() が引数を指定しない単純なデコレータとして使用された場合、ドキュメントに記載されているシグネチャの デフォルト値のとおり に動作します。

https://docs.python.org/ja/3/library/dataclasses.html#dataclasses.dataclass (強調は引用者による)

特殊メソッドに関する用語

  • 特殊メソッド

  • マジックメソッド

  • ダンダーメソッド

用語集 特殊メソッド

  • special method の訳語

  • (LT本編で解説したとおり)

https://docs.python.org/ja/3/glossary.html#term-special-method

用語集 マジックメソッド

special method のくだけた同義語です。

https://docs.python.org/ja/3/glossary.html#term-magic-method

ダンダーメソッド

"ダンダー" メソッド (訳注:dunderはdouble underscoreの略で、メソッド名の前後にアンダースコアが2つ付いているメソッド)

https://docs.python.org/ja/3/library/dataclasses.html#dataclasses.dataclass

Python3.9 / 3.8以前と型ヒント

以下のコードはPython3.8以前では動きません


>>> @dataclass  
... class RaceHorseNames(Sequence):
...     names: list[RaceHorseName]
TypeError: 'type' object is not subscriptable

なぜPython3.8以前は動かない?

Python 3.8でも動かす方法1


>>> from typing import List
>>> @dataclass
... class RaceHorseNames(Sequence):
...     names: List[RaceHorseName]

typing.List はPython3.9でdeprecated

Python 3.8でも動かす方法2 オススメ


>>> from __future__ import annotations
>>> @dataclass  
... class RaceHorseNames(Sequence):
...     names: list[RaceHorseName]

EOF