[Python基礎]Pythonの特殊メソッドを理解してコードを読めるようになろう！

# はじめに

今回はPythonの「**特殊メソッド**」について解説していきます。

Pythonの実務でよく他の人が書いたコードを読むことがあります。

その際結構な頻度で「**特殊メソッド**」が使われています。

慣れていないと何をしているのかわからず時間だけが過ぎていく可能性があります。

それを防ぐためにこの記事で「特殊メソッド」に慣れて、他の人が書いたコードをスラスラ読めるようになりましょう！

「**Pythonの基礎はあらかたマスターした！**」

「**Pythonのクラスの知識を深めたい。**」

「**Pythonの特殊メソッドってなんだろう？**」

「**他の人のコードを読めるようになりたい。**」

このような人に役に立てれば幸いです！

前置きは早々に本題に入っていきましょう。

::: note warn
この記事はPythonの「**クラス**」の理解をしておいた方が読みやすいです。「**クラス**」の理解が怪しいかたは以下を参考に学習してみてください。
:::

https://chaldene.net/python-book

# 本題に入る前に

Pythonの「**特殊メソッド**」はめちゃくちゃたくさんあります。

その中でもよく使われるものを中心に紹介していきます。

もし今回紹介する以外の「**特殊メソッド**」を使いたい方は、公式ドキュメントを参考にしたりググってみてください。

また、この記事では丁寧な解説を心がけているので、同じようなコードが繰り返されます。

しつこく感じる方もいるかもしれませんが、初学者を対象に書いているのでご了承ください。

# __new__

::: note
インスタンス生成。
:::

## 概要

`__new__`は「**インスタンスオブジェクト**」が生成される前に呼び出されます。

「**インスタンスオブジェクト**」とは、クラスを呼び出すことだと思ってもらえれば大丈夫です。

`self`オブジェクトをインスタンス化し、第１引数`cls`にクラスオブジェクトが代入されます。

「**変更できないオブジェクトを変更したい**」場面で使用されます。

では早速みていきましょう。

```python
class Cardene:
    def __new__(cls):
        print("__new__")
        print(f"cls: {cls}")
        return super().__new__(cls)
    
    def __init__(self):
        print("__init__")
        print(f"self: {self}")
        
cardene = Cardene()
```

２行目で定義して、引数に`cls`を取っています。

出力を確認しましょう。

```bash
__new__
cls: <class '__main__.Cardene'>
__init__
self: <__main__.Cardene object at 0x10944cfd0>
```

`__new__`の方はクラス自体で、次章で解説する`__init__`はクラスオブジェクトになっているのが確認できますね。

## インスタンス生成しない

ちなみにインスタンスを生成しないと`__init__`が呼ばれません。

確認してみましょう。

```python
class Cardene:
    def __new__(cls):
        print("__new__")
        print(f"cls: {cls}")
        # return super().__new__(cls)
    
    def __init__(self):
        print("__init__")
        print(f"self: {self}")
        
cardene = Cardene()
```

５行目の部分をコメントアウトしました。

```bash
__new__
cls: <class '__main__.Cardene'>
```

先ほど呼ばれていた`__init__`が呼ばれていませんね。

このように`__new__`でインスタンスを生成していないと`__init__`が呼ばれないので注意しましょう。

（`__new__`を定義しない場合は自動でインスタンスが生成されます。）

## イミュータブルオブジェクトを変更

「**イミュータブル**」とは、「**変更できない**」という意味です。

タプルなど一度定義した後に変更できないオブジェクトのことを指します。

通常クラスに渡された「**イミュータブル**」なオブジェクトは変更することができません。

まずはタプルから確認してみましょう。

```python
numbers = (1, 2, 3)

print(numbers)
# (1, 2, 3)
```

ではこのタプルに変更を加えていきましょう。

```python
numbers = (1, 2, 3)

numbers[1] = 4

print(numbers)
# TypeError: 'tuple' object does not support item assignment
```

想定通りエラーになりましたね。

では`__init__`ないで変更を加えてみましょう。

```python
class Cardene:
    def __init__(self, num_tuple):
        self.num_tuple = num_tuple
        print(num_tuple)
        self.num_tuple[1] = 4
        
cardene = Cardene((1, 2, 3))
```

５行目で先ほど同様4を代入しようとしています。

```bash
TypeError: 'tuple' object does not support item assignment
```

先ほどと同じエラーが出ましたね。

では`__new__`を使って解決していきましょう。

```python
class Cardene(tuple):
    def __new__(cls, num_tuple):
        self = tuple.__new__(cls, (
            num_tuple[0], 4, num_tuple[2]
        ))
        print(self)
    
cardene = Cardene((1, 2, 3))
```

４行目でタプルを再定義しています。

```python
(1, 4, 3)
```

変更できましたね！

「**いやいや再定義してるじゃん！それなら別に`__init__`でもできるじゃん！**」

と思った方は鋭い！

ただこのような感じだったらどうでしょうか？

```python
class Cardene(tuple):
    def __new__(cls, num_tuple):
        self = tuple.__new__(cls, (
            num_tuple[0], 4, num_tuple[2]
        ))
        print(self)
        
    def __init__(self, num_tuple):
        self.num_tuple = num_tuple
        print(num_tuple)
        
    
cardene = Cardene((1, 2, 3))
```

先ほど同様`__new__`を定義して、さらに`__init__`も付け足してみました。

```python
(1, 4, 3)
```

しっかり出力できましたね。

何が言いたいかというと、`__init__`の時点でタプルの中身が変わっていますよね？

そのため実質タプルの中身を変更できたということです。

少し難しいですがこのように使用するので頭の片隅に入れておきましょう。

## 参考

::: note
- https://python.ms/new/#_1-new-と-init-の違い
- https://python.ms/immutable/#_1-簡単に言えば
- https://qiita.com/FGtatsuro/items/49f907a809e53b874b18
:::

# 続き

これより先は以下の記事にまとめています。

https://chaldene.net/python-method

より詳しく**特殊メソッド**について学ぶことができるので、興味がある方はぜひ！
（もちろん無料です！）