8
9

【Python中・上級者向け】Dunderとは?

Last updated at Posted at 2023-03-19

はじめに

Everything is an object

Pythonは全てオブジェクトである。

以下のように数字は int というクラスで
文字列は str というクラスである。

a = 1
b = "abc"
type(a) # <class 'int'>
type(b) # <class 'str'>

Pythonのクラスは全てobjectというクラスから継承している。
object内の関数は全てDunderである。

Dunderとは

__init__ のようにアンダースコア2つに囲まれている関数のことを Dunder(Double underscore) と呼ぶ。
Special methods(特殊メソッド) 、 Magic methods ともいわれる。

Dunderの種類

基本的なメソッド

__init__(self[, ...])

インスタンスの初期化。インスタンスを生成すると実行される関数。
2番目 に実行される。 1番目ではなく、2番目です。

__new__(cls)

インスタンスの生成。インスタンス生成前に実行される関数。
1番目 に実行される。

__new__を使用したときは、return super().__new__(cls)をつけないと、__init__が実行されない。

class Foo():
    def __new__(cls):
        print('foo new')
        return super().__new__(cls)

    def __init__(self):
        print('foo init')
Foo()

class Foo2():
    def __new__(cls):
        print('foo2 new')

    def __init__(self):
        print('foo2 init')
Foo2()
実行結果
foo new
foo init
foo2 new

__del__(self)

ファイナライザ、デストラクタ(正しくない)と呼ばれる。インスタンスが削除されるときに実行される。
__del__()は、Pythonのガベージコレクション機能と一緒に使用され、不要なオブジェクトを削除するために自動的に呼び出される。

__del__()はどのタイミングで呼び出されるかは保証されていない。delによって絶対呼び出されるわけではない。また、__del__()をオーバーライドすることは非推奨。

__repr__(self)

オブジェクトを表す公式の文字列を計算して返す。
repr() を実行することで呼び出される。
主にデバッグ用。

参考 : Pythonのstr( )とrepr( )の使い分け

__str__(self)

オブジェクトを表す(非公式の)表示に適した文字列を計算する。
str(), format(), print() などによって呼ばれる。
__str__ が 定義されていなければ、 __repr__が呼ばれる。

__str__と__repr__
class Person:
    def __init__(self, first_name, family_name):
        self.first_name = first_name
        self.family_name = family_name

    def __str__(self):
        return f"{self.first_name}"

    def __repr__(self):
        return f"{self.first_name} {self.family_name}"


person = Person("Taro", "Yamada")
print(str(person)) # Taro
print(repr(person)) # Taro Yamada

__bytes__(self)

bytes() で呼び出される。
オブジェクトのバイト文字列表現を計算する。

__format__(self, format_spec)

format() によって呼ばれる。
何もフォーマット指定子を使用しない場合は、__str__()が呼び出される。

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __format__(self, format_spec):
        if format_spec == 'short':
            return f"{self.name}"
        elif format_spec == 'long':
            return f"{self.name} ({self.age})"
        else:
            return str(self)

    def __str__(self):
        return f"{self.name}{self.age}歳です。"

person = Person("太郎", 20)

print("{:short}".format(person)) # 太郎
print("{:long}".format(person)) # 太郎 (20)
print("{}".format(person)) # 太郎は20歳です。

__hash__(self)

オブジェクトのハッシュ値を返すときに用いられる。
hash() によって呼び出される。

\_\_eq__をオーバーライドしたときは \_\_hash__もオーバーライドしなければならない。\_\_hash__Noneになってしまう。
参考 : Pythonでインスタンスを比較するために__eq__を実装するときの注意点

class Person:
    def __init__(self, name, age):
        self.name = name
        self.age = age

    def __hash__(self):
        return hash((self.name, self.age))

person1 = Person("Taro", 20)
person2 = Person("Jiro", 15)
person3 = Person("Taro", 20)

print(hash(person1)) # 5733229489180753685
print(hash(person2)) # -1653627169604865911
print(hash(person3)) #  名前と年齢が同じなので、hash値はperson1と同じ

__bool__(self)

bool() によって呼びだされる。
TrueFalse を返さなければならない。

拡張比較メソッド

__lt__(self, other)

オブジェクトを<で比較するときに使用される。

__le__(self, other)

オブジェクトを<=で比較するときに使用される。

__eq__(self, other)

オブジェクトの等価性を比較するためのメソッド。
オブジェクトを==で比較するときに使用される。

__ne__(self, other)

オブジェクトの非等価性を比較するためのメソッド。
オブジェクトを==で比較するときに使用される。

__gt__(self, other)

オブジェクトを>で比較するときに使用される。

__ge__(self, other)

オブジェクトを>=で比較するときに使用される。

コード例

以下のように横の長さ、縦の長さの2つの属性を持つ長方形クラスを例に挙げる。

class Rectangle:
    def __init__(self, width, height):
        self.width = width
        self.height = height

    def __eq__(self, other):
        return self.width == other.width and self.height == other.height

    def __ne__(self, other):
        return not self == other

    def __lt__(self, other):
        return self.width * self.height < other.width * other.height

    def __gt__(self, other):
        return self.width * self.height > other.width * other.height

rect1 = Rectangle(2, 3)
rect2 = Rectangle(3, 4)
rect3 = Rectangle(2, 3)

print(rect1 == rect2) # False
print(rect1 == rect3) # True
print(rect1 != rect2) # True
print(rect1 < rect2) # True
print(rect2 > rect3) # True

比較拡張メソッドまとめ

比較拡張メソッド 比較演算子
__eq__ ==
__ne__ !=
__lt__ <
__le__ <=
__gt__ >
__ge__ >=

functoolstotal_orderingデコレータを使えば、 __lt__(), __le__(), __gt__(), __ge__() の中からどれか1つと、 __eq__() メソッドを定義するだけでいい。(速度は少し遅くなる)
参考 : 公式ドキュメント

属性値にアクセスするメソッド

__getattr__(self, name)

存在しない属性にアクセスしたとき、つまりAttributeErrorが発生するときに呼び出される。

__getattribute__(self, name)

インスタンスのすべての属性にアクセスするときに呼び出される。
AttributeError の発生とは関係なく、class.attr を使ったすべての属性アクセスの時に動作する。
sys.audit()でアクセスイベントをログに記録することができる。

__setattr__(self, name, value)

属性の代入がされたときに呼び出される。sys.audit()でアクセスイベントをログに記録することができる。

__delattr__(self, name)

属性の削除がされたときに呼び出される。sys.audit()でアクセスイベントをログに記録することができる。

__dir__(self)

dir() によって呼び出される。dir() はオブジェクトが持つ属性のリストを返す。

class Person:
    def __init__(self, name):
        self.name = name

p = Person("Taro")
print(dir(p))
実行結果
['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getstate__', '__gt__', '__hash__', 
'__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', 
'__str__', '__subclasshook__', '__weakref__', 'name']

【参考】PEP 562 -- Module getattr and __dir__を試してみた

ディスクリプタ

ディスクリプタとは以下の3つのメソッドを持つクラスである。(より詳細な内容は別の記事にまとめる予定。)

__get__(self, instance, owner=None)

インスタンスの属性取得時に呼び出される。

以下の例では、objのパラメータにはAのインスタンス(a)が入る。
selfにはBのインスタンス(b)が、objectにはAのクラスが入る。

class B:
    def __get__(self, obj, objtype=None):
        print(obj.arg1)
        print(obj.arg2)
        return 'Hello, world!'

class A:
    b = B()
    def __init__(self, arg1, arg2):
        self.arg1 = arg1
        self.arg2 = arg2

a = A("foo", "hoge")
print(a.b)
実行結果
foo
hoge
Hello, world!

__set__(self, instance, value)

オーナークラスのインスタンスの属性を設定する際に呼び出される。

__delete__(self, instance)

オーナークラスのインスタンスの属性を削除する際に呼び出される。

コンテナのエミュレート

Pythonにおいて、コンテナとは、複数の要素を格納するオブジェクトのことを指す。(リスト、タプル、セット、辞書など)

コンテナの動作をエミュレートするとは、Pythonの特殊メソッドを使用して、オブジェクトがコンテナのように振る舞う(要素の追加、削除、検索、スライシングなど)ようにすること。

__len__(self)

len() によって呼び出される。0以上の整数で返す。

__getitem__(self, key)

self[key] によって呼び出される。

__setitem__(self, key, value)

self[key] に値を代入するときに呼び出される。

__delitem__(self, key)

self[key] を削除するときに呼び出される。

__iter__(self)

イテレータオブジェクトを返す。iter() によって呼び出される。
また、 for i in xでは xiter() を呼び出す。
参考 : __iter__()はイテレータを返し、__next__()は次の要素を返す

__next__(self)

next() によって呼び出される。
__next__()を呼び出すと、イテレータは次の値を返す。

参考 : __iter__()はイテレータを返し、__next__()は次の要素を返す

__reversed__(self)

reversed() によって呼び出される。
コンテナ内の全要素を逆順にしたイテレータを返す

__contains__(self, item)

in が使われるときに呼び出される。
帰属テスト演算子を実装するために呼び出される。 item が self 内に存在する場合にTrueを、そうでない場合にはFalseを返す。

その他

数値型をエミュレートする\_\_add__(), \_\_sub__, \_\_and__, \_\_or__など他にも色々特殊メソッドは存在する。以下の公式ドキュメントにまとまっているので、そちらを参考にしてください。

公式ドキュメント(データモデル)

まとめ

メソッド名 説明 呼び出す関数例
__init__(self[, args...]) インスタンス生成直後に呼び出される。 obj = MyClass(arg1, arg2)
__new__(self) インスタンス生成直前に呼び出される。 obj = MyClass()
__del__(self) インスタンスが削除される際に呼び出される。 del obj
__repr__(self) オブジェクトの(公式の)文字列を返す。 repr(obj)
__str__(self) オブジェクトを表示に適した文字列を返す。 str(obj)
__bytes__(self) インスタンスをバイト列に変換するために呼び出される。 bytes(obj)
__hash__(self) インスタンスのハッシュ値を返すために呼び出される。 hash(obj)
__bool__(self) インスタンスを真偽値に変換するために呼び出される。 bool(obj)
__eq__(self, other) 2つのオブジェクトが等しいかどうかを判断する。 obj1 == obj2
__ne__(self, other) 2つのオブジェクトが異なるかどうかを判断する。 obj1 != obj2
__lt__(self, other) selfotherよりも小さい場合にTrueを返す。 obj1 < obj2
__le__(self, other) selfother以下の場合にTrueを返す。 obj1 <= obj2
__gt__(self, other) selfotherよりも大きい場合にTrueを返す。 obj1 > obj2
__ge__(self, other) selfother以上の場合にTrueを返す。 obj1 >= obj2
__get__(self, instance, owner) ディスクリプタによる属性取得時に呼び出される。 obj.attr
__set__(self, instance, value) ディスクリプタが属性の値を設定するときに呼び出される。 obj.attr = value
__delete__(self, instance) ディスクリプタが属性を削除するときに呼び出される。 del obj.attr
__iter__(self) イテレータオブジェクトを返し、forループでオブジェクトを反復処理するためのメソッド。 for item in obj,iter()
__next__(self) イテレータオブジェクトの次の要素を返すためのメソッド。 next(obj)
__call__(self[, args...]) オブジェクトを関数のように呼び出すことができるようにするためのメソッド。 obj(args)
__reversed__(self) オブジェクトを逆順にしたイテレータを返す。 reversed()
__contains__(self, item) オブジェクトの中にitemがあればTrueを返す。 item in obj
__len__(self) オブジェクトの長さを返す。 len(obj)
__getitem__(self, key) インデックスまたはキーに基づいてオブジェクトの要素を取得する。 obj[key]
__setitem__(self, key, value) インデックスまたはキーに基づいてオブジェクトの要素を設定する。 obj[key] = value
__delitem__(self, key) インデックスまたはキーに基づいてオブジェクトの要素を削除する。 del obj[key]
__add__(self, other) +演算子を使用してオブジェクトを追加する。 obj1 + obj2
__sub__(self, other) -演算子を使用してオブジェクトを減算する。 obj1 - obj2
__mul__(self, other) *演算子を使用してオブジェクトを乗算する。 obj1 * obj2
__truediv__(self, other) /演算子を使用してオブジェクを除算する。 obj1 / obj2
__and__(self, other) ビット単位のAND演算を行う。 obj1 & obj2
__or__(self, other) ビット単位のOR演算を行う。 obj1 | obj2
__xor__(self, other) ビット単位のXOR演算を行う。 obj1 ^ obj2
8
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
9