ゼロから始めるPython(8)

久しぶりの投稿になります。

ゼロから始めるPythonの8回目。いよいよクラスの登場です。

[7回目はこちら](https://qiita.com/wind0411/items/da3bf021e0f5e91f63a6)

#おさらい
前回は主にファイルの読み書きとエラー・例外について説明しました。
特に後者はしっかり抑えておきましょう。

# 17.クラス
クラスやインスタンスそのものに関する説明は、諸先輩方が語り尽くしていらっしゃるのでここでは触れません。
しかし、Pythonでのクラスはどのようなものかは説明しましょう。

## 17.1. 定義

Pythonでのクラスというのは、基本的なオブジェクト指向でのそれにあたります。
そして、一つでインスタンスにもなり、クラスそのものにもなります。
メソッドの引数によって自分自身のインスタンスを代入していますが、メソッド自体はクラスが持ちます。
詳しくは後述します。

### 17.1.1. クラス定義

まず、クラスの定義を行う場合、次のように記述します。

```python
class class_name:
```

### 17.1.2. コンストラクタ定義

コンストラクタは、**`__init__`**として定義します。
このとき、引数に注目してください。

```python
def __init__(self(,arg1,arg2...))
```

引数`self`は、インスタンス自身の参照を代入します。
インスタンス経由でメソッドが実行されるときに自動的に参照が代入されます。
なお、selfに関して言うと名前自体は何でもいいです。hoge,fugaでも通ります。


### 17.1.3. メソッド定義

基本は通常のメソッド定義と同じですが、引数はインスタンスのときと同様になります。

```python
def method_name(self):
   pass # 処理を記述
```

### 17.1.4. アクセスの制限

Pythonにはアクセス修飾子がサポートされていません。
このため、慣習的に次のように区別します。
prefixに`アンダーバー1つ(_)`が定義される変数やメソッドは、基本的に外部から参照しない。(参照自体は可能）

```python
def _inner_method(self)
```

prefixに`アンダーバー2つ(__)`が定義される変数やメソッドは、外部の参照を受けず、アクセスできない。(やりようによってはアクセス可能）

```python
def __private_method(self)
```

というルールがあります。

なお、違いはPEP8で説明されています。

**実践されている命名方法**
>\_ : “内部でだけ使う” ことを示します。たとえばfrom M import *は、アンダースコアで始まる名前のオブジェクトをインポートしません。 
>\_\_: クラスの属性に名前を付けるときに、名前のマングリング機構を呼び出します (クラスFoobarの__booという名前は_FooBar__booになります。以下も参照してください)

**メソッド名とインスタンス変数**
>公開されていないメソッドやインスタンス変数にだけ、アンダースコアを先頭に付けてください。 
サブクラスと名前が衝突した場合は、Pythonのマングリング機構を呼び出すためにアンダースコアを先頭に二つ付けてください。

### 17.1.5. 継承
Pythonのクラスでも、勿論継承を利用できます。

> class 派生クラス名(基底クラス名):
> ...

当然ですが、基底クラスが参照できなければなりません。

### 17.1.6. オーバーライド
基本的には何ら難しいことはありません。
基底クラスで定義されているメソッドを派生クラスで定義することで上書きできます。
注意が必要なのは、基底クラス側で上書き済みのメソッドを呼び出している場合には上書き済みメソッドで呼び出されることになるかもしれない点です。
上書きではなく機能を拡張する場合には、派生クラス側で基底クラスの対象メソッドを呼び出しましょう。
> 基底クラス名.methodName(self, arguments)

### 17.1.7. 継承関係
> isinstance(対象オブジェクト,型(クラス))

インスタンスの型が一致するかどうかを検証します。
対象オブジェクト.\_\_class\_\_が指定の型やその派生クラスと一致する場合`True`を返します。

>issubclass(対象オブジェクト,型(クラス))

対象のオブジェクトが指定の型(クラス)のサブクラスかどうかを検証します。

### 17.1.8. 多重継承
>class 派生クラス名(基底クラス1, 基底クラス2, 基底クラス3):
>...

Pythonでは多重継承もサポートしています。
私は多重継承に明るくないので詳しくありませんが、
基本的には、深さ優先で左から右に検索されるとのこと。
ただし、必ずしもそうでないので注意が必要です。

##17.2. 名前空間

名前空間はPythonの辞書として実装されています。
重要なことは異なる名前空間にある名前の間には関係がないことです。

2つの別々のモジュールで同一名称の関数を定義しても、定義は混同されません。
ただし、区別する場合にはモジュール名をつけなければなりませんが。
このことは、[過去の記事](https://qiita.com/wind0411/items/2f3845468cd3c0a7f508)で詳しく紹介しています。

##17.3. スコープ

過去の記事で触れていたと思いますが、~~(うろ覚え）~~
Pythonでは次のようなスコープがあります。
・ローカルスコープ
・グローバルスコープ
・ビルトインスコープ

ただし、クラスの場合は注意が必要です。
クラスの変数には**`クラス変数`**と**`インスタンス`**とがあります。
ローカルとグローバルの関係にも似ていますが、
**クラス変数**は、クラスから生成されたインスタンス全てで共有する変数を指し、
**インスタンス変数**は、インスタンス内だけで使用する変数を指します。
このとき、インスタンス側から**クラス変数**を変更できます。
(ただし、バグの原因にもなるので使い所は要検討）

```python:class_scope.py
class examples:
  class_str = 'aaa'

  def __init__(self):
    self.instance_str = 'bbb'
    print(self.class_str) # class変数の参照
    print(self.instance_str)
    examples.class_str = 'ccc' #instance側からclass変数を書き換え
```

```python:console
>>> import class_scope as c
>>> a = c.examples()
aaa
bbb
>>> b = c.examples()
ccc # 書き換わっている
bbb
```

```python
>>> class test_class:
...     def __init__(self):
...         print('hello world')
...
>>> a = test_class() #instance
hello world
>>> test_class.__init__(None) #class
hello world
```

##17.3.1. nonlocal
グローバルスコープの変数はその配下のスコープで参照できますが、
配下のローカルスコープで値を変更した場合でも、スコープを抜ければグローバルスコープで定義した値になります。

```python:
def func():
    a = 1
    def func_a():
        print(a)

    def func_b():
        a = 2
        print(a)

    func_a()
    func_b()
    print(a)

func()

# 1
# 2
# 1

```

使い時はさておき、このとき意図的にローカルスコープで変更した値をグローバルスコープに適用したい場合は、`nonlocal`文によって制御します。

```python:
def func():
    a = 1
    def func_a():
        a = 2
        print(a)

    def func_b():
        global a
        a = 3
        print(a)

        def func_b_1():
            a = 10
            print(a)

        func_b_1()
        print(a)

    def func_c():
        nonlocal a #ここでバインド
        a = 4
        print(a)

    func_a()
    print(a) 
    func_b() 
    print(a)
    func_c()
    print(a) # funcのaに対してfunc_c内のaをバインドした結果

func()

# 2
# 1
# 3
# 10
# 3
# 1
# 4
# 4

```

#まとめ
クラスだけではなくスコープについては意外と挙動が異なることもあり、ちゃんと理解しないとなと思いました。
次回は仮想環境についてまとめたいと思います。