__init__やselfに関する備忘録

今回は、プログラミングにおいて、`__init__`や`self`のことを深く理解してない状態だったので、備忘録として残しておきたいと思います。

# 開発環境
MacOS
Python3.7(anaconda)
VSCode

[クラスと self と __init__](https://python.ms/class/#%E3%81%AF%E3%81%97%E3%82%99%E3%82%81%E3%81%AB)
[【Python入門】クラスの基本を１から解説する―完全版](https://it-biz.online/python/class-2/#toc2)
[Python の super() 関数の使い方](https://www.lifewithpython.com/2014/01/python-super-function.html)

# __init__とは
`__init__`とは、インスタンスを生成した際に、1番最初に呼び出される関数になります。

```python

class Human:
    name = 'Jack'
    def __init__(self):
         self.name = 'Bob'

boku = Human()
print(boku.name)  ->  Bob
```

といった具合に、`インスタンス化`が行われた後に、`__init__`が呼び出されるため、元々はnameにはJackが入っていましたが、実行した際は`Bob`が出力されることになります。

## 追記(2020/12/19) @shiracamusさんより
`name='Jack'`のnameはクラス変数であり、`self.name='Bob'`のnameはインスタンス変数になります。
なので、`boku.name`で呼び出す時には、インスタンス変数の中身である`Bob`が呼び出され、`Human.name`で呼び出す時には、クラス変数である`Jack`が呼び出されます。

そして、もしも、`boku.name`で呼び出した際に、インスタンス変数の方の`name`が存在しない場合、クラス変数の`name='Jack'`が呼び出されるという仕組みになっております。


# インスタンス化とは何か
インスタンス化とは、簡単に言えば、型作りのようなものですね。
人間という大雑把な形を作って、`Jack`や`Bob`という個人を作る部分に分かれてくるという具合です。

```python

class Human:
    def __init__(self)
        self.name1 = 'Jack'
        self.name2 = 'Bob'

boku = Human()  <-  ここがインスタンス化
print(boku.name1)  ->  Jack
print(boku.name2)  ->  Bob
```
といった具合に、`boku = Human()`によってインスタンス化が行われます。
`インスタンス名 = クラス名()`といった書き方になります。


# self.変数とは何か
## 追記(2020/12/28)
`self.変数`に関する説明はしていなかったのと、正直自分も、`self.変数`自体はちゃんと理解できていなかったので、備忘録として残しておきたいと思います。

まずは、`self`を使わないバージョンを見ていきましょう!

```python
class Numbers:
  def __init__(self):
    number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]


  def Next(self):
    print(number)

numbers = Numbers().Next()  ==>>  エラー
```
こちらを実行した場合、エラーになってしまいます。
`__init__`の中で定義された**number**と`Next`の中で出力されている**number**は別の物として認識されているのが原因です。

それでは、`self`を使ったバージョンを見ていきましょう!
まずは、`__init__`の方にだけ`self`を付けた場合を見ていきましょう!

```python
class Numbers:
  def __init__(self):
    self.number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  def Next(self):
    print(number)

numbers = Numbers().Next()  ==>>  エラー
```
こちらの方も、`__init__`の`self.number`と`Next`の`number`は別物であるというワケです。

では、両方に`self`を使った場合を見ていきましょう!

```python
class Numbers:
  def __init__(self):
    self.number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

  def Next(self):
    print(self.number)

numbers = Numbers().Next()  ==>>  [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
```
こちらは無事に出力されました。
`self`を使うことによって、メソッドを跨いだとしても、同じ変数であるということが認識されているワケですね。
`self`は**1つのクラスの中**で同じ変数を使い回したい時に活躍してくれます。


# self.変数=変数とは何か
まずは、`self.name`に実名を入れていくだけの処理を見ていきます。

```python

class Human:
    def __init__(self):
      self.name1 = 'Jack'
      self.name2 = 'Bob'

boku = Human()
print(boku.name1)
print(boku.name2)
```

これだと、nameを100人分作る場合は、`変数nameを100個`用意しなければいけない状態になります。

次に、`self.name=name`を使うバージョン

```python

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

boku = Human('Jack')
```
こちらは、`Human('Steve')`や`Human('Bob')`に変更することによって、名前を変更することができます。
こちらの処理の方が、よりシンプルに書けて無駄な処理をしなくて良い状態になっております。

# クラスとメソッドを使う上での注意点
クラスとその中でメソッドを使う時の注意点を個人的に理解したつもりなので、備忘録として残しておきたいと思います。

まずは、クラス内にある別の関数を呼び出すときは、`self.メソッド名`という書き方をします。
この書き方によって、`そのクラスのメソッド`という意味になります。
下の例で見てみると、`self.Next()`は`HumanクラスのNextメソッド`という扱いです。

```python
class Human:
      def __init__(self):
            self.name1 = 'Jack'

            print('私の名前は' + self.name1 + 'です')
            
      def Before(self):
            self.name2 = 'Boku'

            self.Next()    

      def Next(self): 
            self.name1 = 'Mary'
            self.name2 = 'Smith'

            print('私の名前は' + self.name2 + 'です')

human = Human()  ==>>  私の名前はJackです
```
このような出力結果となります。
別に、呼び出したわけでもないのに、実行されるのが、`__init__`になりますね。
`インスタンス生成`をした時点で必要な変数などは定義しておくと便利ですね。

次に、`Beforeクラス`を呼び出す場合の、処理を書いていきたいと思います。

```python
class Human:
      def __init__(self):
            self.name1 = 'Jack'
            self.name2 = 'Boku'

      def Before(self):
            self.name3 = self.name1
            print('私の名前は' + self.name3 + 'です')
            print('私の名前は' + self.name2 + 'です')    

      def Next(self):
            self.name4 = self.name3
            self.name2 = 'Smith'

            print('私の名前は' + self.name4 + 'です')

            self.Before()
            
human = Human().Next()  ==>>  エラー
```

こちらを実行したらエラーになってしまいます。
これを見ると、関数とは、`呼び出されて初めて処理が行われる`というのがわかるのではないでしょうか。

`Beforeメソッド`は、`Nextメソッド`よりも先に定義はされているが、`Nextメソッド`の中で呼び出されて初めて実行されるので、`self.name4=self.name3`の部分では`self.name3`というのはまだ存在しておらず、エラーになるという状態です。

個人的には、
**`self.メソッド名()`が書かれた場所から、そのメソッドの処理が書かれている部分に飛んで、メソッド内の処理が行われる。**
と、勝手に認識しております。
こちらはもっとわかりやすい認識の方法がございましたら、教えていただけるとありがたいです。

下に示しますが、`self.Before()`を`self.name4=self.name3`の前に持ってくれば無事に出力されます。

```python
class Human:
      def __init__(self):
            self.name1 = 'Jack'
            self.name2 = 'Boku'

      def Before(self):
            self.name3 = self.name1
            print('私の名前は' + self.name3 + 'です')
            print('私の名前は' + self.name2 + 'です')    

      def Next(self):
            self.Before()

            self.name4 = self.name3
            self.name2 = 'Smith'

            print('私の名前は' + self.name4 + 'です')
            
human = Human().Next()  ==>>  私の名前はJackです
                              私の名前はBokuです
                              私の名前はJackです
```



# superとは何か
次に、`__init__`の後によく見かける`super`って何ですかいっていう説明をします。

まず、複数のクラスで同じような処理をする場合は、毎度同じ処理を書くわけではありません。

1番最初に、メインのクラスを書いたら、そのクラスを`継承する`ことで、同じ処理の記述を省くことにしています。

```python

class Human:
   def __init__(self, name, sex, weight):
       self.name = name
       self.sex = sex
       self.weight = weight

boku = Human('Jack')
print(boku.name)  ->  Jack

class Bird(Human):
   def __init__(self, name, sex, height):
   super().__init__(name, sex)
   self.height = height
```
このように、`クラス名の後に、継承したい親クラスの名前`を書きます。

そして、`super().__init__(親クラスと同じ処理をする変数)`という記述をすることで、`self.name`や`self.sex`を書く必要がなく、同じ処理を行うことができます。

# ****argsと*****kwargsとは
メソッドを作る時に、よく見かけるのが、`*args`と`**kwargs`だと思います。
これは、引数に決まったものが来ない場合でも使える最強アイテムでして、
> **argsは、複数の値をタプル型で取得可能
> ***kwargsは、辞書型で取得可能

というような感じで、とりあえず、この2つを引数に書いておけば色んな引数をカバーできるんじゃないかなって思っております。

## 追記(2020/12/25)
あやふやな理解のまま、`*args`と`**kwargs`を使っていたのですが、しっかり理解して使わないとエラーの原因がわからないなと思ったので、記述しておきたいと思います。

`*args`と`**kwargs`を仮引数(関数側の引数)として書く場合は、処理を書く部分で`args`と`kwargs`を使って表現する必要があります。

まず、間違った例から紹介したいと思います。

```python
def Try(*args):
    print('僕の名前は'+ name + 'です')

Try('Jack')    ==>>    エラー
```
自分は当初、このように他の変数名を用いても出力はできるのかと思っていたのですが、そうは問屋が卸さないというワケでした。

```python
def Try(*args):
    print('僕の名前は'+ args + 'です')

Try('Jack')   ==>>   僕の名前はJackです
```
こちらのように、しっかり、`仮引数と同じ名前`でアクセスする必要があります。

また、複数の引数を受け取る場合のアクセス方法は以下のようになります。

```python
def Try(*args):
    print('僕の名前は'+ args[0] + 'です')

Try('Jack', 'Bob', 'Mary')    ==>>    僕の名前はJackです
```
このような感じで、`インデックス番号`でアクセスする形になります。

次に、`**kwargs`の例も載せておきます。

```python
def Try(*args, **kwargs):
    print('僕の名前は'+ kwargs['name'] + 'です')

Try('Jack', 'Bob', name='Mary')    ==>>    僕の名前はMaryです
```
`**kwargs`で引数を受け取るときは、`変数名=実引数`という書き方をする必要があります。


# まとめ
理解しなくても処理を行うことはできるかと思いますが、個人的にモヤモヤした状態になってしまうので、今回記事にさせてもらいました。

わかりにくい部分が多いと思いますが、皆さんのお助けになればと思います。