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

# はじめに

## *Everything is an object*

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

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

```python
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__`が実行されない。

```python
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のガベージコレクション機能と一緒に使用され、不要なオブジェクトを削除するために自動的に呼び出される。

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

### \_\_repr__(self)

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

参考 : [Pythonのstr( )とrepr( )の使い分け](https://gammasoft.jp/blog/use-diffence-str-and-repr-python/)

### \_\_str__(self)

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

```python:__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__()`が呼び出される。

```python
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()** によって呼び出される。

:::note alert
`\_\_eq__`をオーバーライドしたときは `\_\_hash__`もオーバーライドしなければならない。`\_\_hash__`が **None**になってしまう。
参考 : [Pythonでインスタンスを比較するために__eq__を実装するときの注意点](https://qiita.com/The-town/items/84405e872d13098f5fa1#__eq__%E3%83%A1%E3%82%BD%E3%83%83%E3%83%89%E3%82%92%E3%82%AA%E3%83%BC%E3%83%90%E3%83%BC%E3%83%A9%E3%82%A4%E3%83%89%E3%81%97%E3%81%9F%E5%A0%B4%E5%90%88)
:::

```python
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()** によって呼びだされる。
*True* か *False* を返さなければならない。

## 拡張比較メソッド

### \_\_lt__(self, other)

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

### \_\_le__(self, other)

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

### \_\_eq__(self, other)

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

### \_\_ne__(self, other)

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

### \_\_gt__(self, other)

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

### \_\_ge__(self, other)

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

### コード例

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

```python
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__       |    >=      |

:::note info
**functools** の **total_ordering**デコレータを使えば、　\_\_lt__(), \_\_le__(), \_\_gt__(), \_\_ge__() の中からどれか1つと、 \_\_eq__() メソッドを定義するだけでいい。(速度は少し遅くなる)
参考 : [公式ドキュメント](https://docs.python.org/ja/3/library/functools.html#functools.total_ordering)
:::

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

### \_\_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()** はオブジェクトが持つ属性のリストを返す。

```python
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__を試してみた](https://qiita.com/nakkun/items/71c101add09f057b5a0d)

## ディスクリプタ

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

### \_\_get__(self, instance, owner=None)

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

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

```python
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`では **x** の **iter()** を呼び出す。
参考 : [\_\_iter__()はイテレータを返し、\_\_next__()は次の要素を返す](https://commte.net/7366)

### \_\_next__(self)

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

参考 : [\_\_iter__()はイテレータを返し、\_\_next__()は次の要素を返す](https://commte.net/7366)

### \_\_reversed__(self)

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

### \_\_contains__(self, item)

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

## その他

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

[公式ドキュメント(データモデル)](https://docs.python.org/ja/3/reference/datamodel.html)

# まとめ

| メソッド名 | 説明 | 呼び出す関数例 |
| --- | --- | --- |
| `__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)` | `self`が`other`よりも小さい場合に`True`を返す。 | `obj1 < obj2` |
| `__le__(self, other)` | `self`が`other`以下の場合に`True`を返す。 | `obj1 <= obj2` |
| `__gt__(self, other)` | `self`が`other`よりも大きい場合に`True`を返す。 | `obj1 > obj2` |
| `__ge__(self, other)` | `self`が`other`以上の場合に`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` |