Pythonチュートリアル #7 -クラスについて　前編-

※以下の企画です

https://qiita.com/tatataichi/private/56936a028e625e62af8e

今回は9章をやっていきます。
9章は「クラス」というタイトルで、結構しっかり目にクラスの説明をしてくれていそうな感じ。
全くと行っていいほどクラスを使いこなせている感覚はないので、ここで改めてクラスの概念と使い所を抑えていきたい。
クラスもまともに使いこなせていない奴がエンジニアを名乗るなというツッコミが聞こえてきますが、勘弁してください。もうほんと頑張ります。
それではやっていきますぅ。

# 9章 学習内容まとめ
## オブジェクト型
本書の中に「**新しいクラスを作ることは新しいオブジェクト型を作ること**」と記載されており、学部時代にJava多少かじった程度の僕からすると、「**オブジェクト型ってなんだ**」という疑問が生じた。
これ以上の言及が文書中になかったので、いつものごとくGPT様のお力をお借りした。

### オブジェクト型とは

* **オブジェクト（Object）**: メモリ上に存在するデータの単位で、属性（データ）とメソッド（操作）を持ちます
* **型（Type）**: オブジェクトの種類や性質を定義するもので、オブジェクトがどのようなデータを持ち、どのような操作が可能かを決定します

**属性**は、**クラス内（オブジェクト型の定義内）で用いる変数**のことで、**メソッド**は、**クラス内で定義して活用できる関数**のことと理解。
厳密には違うのかもしれないが、まぁ多分イメージとしてそこまで間違っていなさそう。
ここらへんはJavaやC++と同じ。というか、Pythonのクラス関連はC++を基にしているらしい。

## クラスの書き方
クラスの定義はめちゃくちゃシンプルで

```Python:クラスの定義
>> class ClassName:
>>     <属性とかメソッドとか>
```

これだけ。Javaとかと同じ感じ。
ちなみに、クラス名の命名規則は通例的にアッパーキャメルケースになっている。
割となんでもスネークケースにしてしまっているので、ここらへんはさすがにもう少し意識しよう…

https://qiita.com/2Gken1029/items/c1733e8780e2b527ae00

## クラスオブジェクトの取り扱い
実際に属性やらメソッドやら触ってみる。

```Python:クラスの操作①
>> class TestClass:
>>   """this is a sample class"""
>>   name = "test"
>>   i = 100
>>
>>   def f(self):
>>     return 'hello world'
```

まずは上記のように定義。`name`と`i`が属性で、`f`がメソッド。

```Python:クラスの操作②
>> TestClass.__doc__
this is a sample class


>> TestClass.i
100


>> TestClass.name
test


>> TestClass.i = 200
>> TestClass.i
200
```

ざっくりこんな感じで、取り扱いはめちゃ簡単。`クラス名.属性`を指定するだけ。
メソッドの呼び出しについては`インスタンスの生成`を学ぶ必要がある。

## インスタンス化
### 作り方
**クラスという設計図からオブジェクト（インスタンス）** を具体的に作り出すことを指す。
クラスを基にインスタンスを作って、そのインスタンスを操作して処理していくというイメージ。
ここらへんはJavaで苦労して理解したのでスッと入ってくるが、初めて触れる方にとってはややこしポイントだと思う。
生成方法も、取り扱いも簡単。

```Python:インスタンス化
test = TestClass()
```

おわり。
これで、testがクラスオブジェクト`TestClass`で定義された属性やメソッドを活用できる。
ここで注意なのは、testの属性やメソッドをどんなにいじくっても、元のクラス（`TestClass`）には何の影響もないということ。クラスという設計図を基に、`test`という新しい実体を作っただけなので。

### 初期状態
インスタンスを生成する（＝クラスオブジェクトをコールする　ともいうらしい）際に、初期状態をカスタマイズすることができる。
どういうことかというと…まぁこれは例を見ていただいた方がスッと理解できると思う。
``` python: init
>> class TestClass2:
>>   def __init__(self):
>>     self.name = "test"
>>     self.age = 100
>>     print("init class")
  
>>   def f(self):
>>       return 'hello world'  

>> test2 = TestClass2()
init class
```
こんな感じで`__init__`というメソッドを記述すると、インスタンスを生成した瞬間（`test2 = TestClass2()`）に呼び出されていることがわかる。

`self`というのは、「このインスタンス」という意味を持つ。通常、一つの`Class`に対して、複数のインスタンスが生成されるので、**この`name`は、自分自身のインスタンスの`name`**と表明している感じ。ここは説明が難しく感じるので、自分の中でも実はちゃんと理解できていないのかもしれない…

※ちなみに`self`じゃないキーワードに変えることもできる。`this`とか。だが、慣習的に`self`と表記されることが多いので、よほどな理由がない限りは変えないほうが可読性を下げないかも？

## クラスへンストインスタンス変数
クラスの中で変数を宣言するときはスコープに要注意。
これは恥ずかしながら「たしかに」と思う内容だった。

クラス内でかつ`__init__`外で宣言すればクラス変数、クラス内でかつ`__init__`内で定義すればインスタンス変数となる。
百聞は一見に如かずということでコードを見ていく。

```python:クラス変数とインスタンス変数①
>> class dog:
>>   tricks = []  # <-- これがクラス変数

>>   def __init__(self, name):
>>     self.name = name  # <-- これがインスタンス変数

>> dog_a = dog("taro")
>> dog_b = dog("hachi")
>> print(f"dogs are {dog_a.name} and {dog_b.name}")
dogs are taro and hachi
```

ここまでは問題なし。
※ちなみに上記のように`__init__`に引数を設定すれば、コール時に値をあらかじめセットすることができる。

ここからが2つの変数の取り扱い時の注意。

```python:クラス変数とインスタンス変数②
>> dog_a.tricks.append("jump")
>> dog_b.tricks.append("run")
>> print(dog_a.tricks)
['jump', 'run']
```

なんと、**`dog_b`で登録したはずの`run`が、`dog_a`にまで登録されている！**
これが**クラス変数**の特性。
クラス変数として宣言した変数については、**全インスタンスで共有されてしまう**。

こういうときは、インスタンス間でしか共有されない`インスタンス変数`として定義すべきだ。

```python:クラス変数とインスタンス変数③
>> class dog:
>>   def __init__(self, name):
>>     self.name = name
>>     self.tricks = []
```

# まとめ
実はまだ9章の内容は残っているので、次の投稿でも引き続きクラスの内容を学んでいく。
クラスの概念などは知っていたものの、`__init__`のこととか、変数のスコープとか忘れていることが多数あったので、もっと普段から意識してクラスは使うべきだと感じた。
GPTに頼りすぎて自分がどんどんショボいコーダーになっていると改めて認識した( ◠‿◠ ) 辛い

まだまだ頑張ります！