Python 3 エンジニア認定基礎試験対策9 - クラス

# はじめに
Python3エンジニア認定基礎試験の対策として自分用に執筆しています。

:::note warn
この記事はPython・執筆学びたての初心者が執筆しています。間違いがあるかもしれませんので、ぜひ各項目にある**参考文献**も確認してください。また、誤りがあればコメントで教えていただけると幸いです。
:::

# クラス

データと機能を組み合わせる方法を提供する。
新しいオブジェクトの型を作成し、その型を持つ新しいインスタンスを作れる。

## 名前とオブジェクト

同一のオブジェクトに複数の名前を割り当てることができる。別名付けとも。オブジェクトの受け渡しは、実装上はポインタが渡されるだけなのでコストの低い操作。

## Pythonのスコープと名前空間

名前空間とは、名前からオブジェクトへの対応付けのこと。例として、

 - `abs()`などの関数や組込み例外名
 - モジュール内のグローバルな名前
 - 関数を呼び出した時のローカルな名前

異なった名前空間にある名前の間には全く関係がない。つまり、モジュールAとモジュールBで同じ名前であるmaximizeという関数を定義することができる。名前空間は属性間で共有される。

属性はドットに続く名前全てに対して使う。
`z.real`の`real`はオブジェクト`z`の属性。

名前空間が作成されるタイミングや寿命は様々で、組込みの名前が入ったものはPythonインタプリタが起動してからインタプリタが終了するまで。関数のローカルな名前空間なら、関数が呼び出されてから戻る、または例外が送出され関数内で処理されなかったときまで。

スコープとは、ある名前空間が直接アクセスできるプログラムのテキスト上の領域。`spam.egg`ではなく`egg`でアクセスできる場所。

関数内部などで`global`と宣言された名前は、一番外側の変数を参照し、`nonlocal`で関数の一つ外側の変数を参照する。

```Python
>>> def scope_test():
...     def do_local():
...         spam = "local spam"
... 
...     def do_nonlocal():
...         nonlocal spam
...         spam = "nonlocal spam"
... 
...     def do_global():
...         global spam
...         spam = "global spam"
... 
...     spam = "test spam"
...     do_local()
...     print("After local assignment:", spam)
...     do_nonlocal()
...     print("After nonlocal assignment:", spam)
...     do_global()
...     print("After global assignment:", spam)
>>> 
>>> scope_test()
>>> print("In global scope:", spam)

After local assignment: test spam
After nonlocal assignment: nonlocal spam
After global assignment: nonlocal spam
In global scope: global spam
```

## クラス初見

クラス定義の方法

```Python
>>> class ClassName;
...     <statement-1>
...     <statement-N>
```

## クラスオブジェクト

属性参照は、Pythonにおけるすべての属性参照で使われている標準的な構文、`obj.name`を使う。
今回の例だと`MyClass.i`, `MyClass.f`。

```Python
>>> class MyClass:
...     """A simple example class"""
...     i = 12345
... 
...     def f(self):
...         return 'hello world'
```

クラスのインスタンス化には関数のような表記法を使う。今回の例だと`MyClass`クラスの新しいインスタンスをローカル変数の`x`に代入している。

``` Python
>>> x = MyClass()
```

もしクラスが`__init()__`クラスを定義しているなら新しく作ったクラスインスタンスに対して自動で実行される。

```Python
>>> class Complex:
...     def __init__(self, realpart, imagpart):
...         self.r = realpart
...         self.i = imagpart
...
>>> x = Complex(3.0, -4.5)
>>> x.r, x.i
(3.0, -4.5)
```

## メソッドオブジェクト

メソッドを変数に入れることができる。

```Python
>>> MyClass.f()
hello world
>>> x = MyClass.f()
>>> x
hello world
```

メソッドについて、インスタンスオブジェクトが第1引数として渡されている。

## クラスとインスタンス変数

```Python
>>> class Cat:
...     att = 'もふもふ'
...     def __init__(self, name):
...         self.name = name
>>> d = Cat('ルナ')
>>> e = Cat('ムギ')
>>> d.att
もふもふ
>>> e.att
もふもふ
>>> d.name
ルナ
>>> e.name
ムギ
```

`att`はクラス変数であり、そのクラスのすべてのインスタンスで共有される。
`self`がつくとインスタンス変数になる。

## 注意点

`self`引数のメソッド属性を使って、他のメソッドを呼び出すことができる

```Python
>>> class Bag:
...     def __init__(self):
...         self.data = []
... 
...     def add(self, x):
...         self.data.append(x)
... 
...     def addtwice(self, x)
...         self.add(x)
...         self.add(x)
```

## 継承

```Python
>>> class DeriviedClassName(BaseClassName):
...     <statement-1>
...     <statement-N>
```

基底クラス（base class）の名前BaseClassNameは、派生クラス定義の入っているスコープで定義されていなければいけない。

`class DerivedClassName(modname.baseClassName):`みたいに、基底クラスが別モジュールで定義されているときでも使える。

派生クラスは基底クラスのメソッドを上書き（オーバーライド）することができる。

基底クラスのメソッドを直接呼ぶには、
`BaseClassName.methodname(self, arguments)`を実行する。

### 継承に関する組み込み関数
 - `isinstance()`はインスタンスの方が調べられる
 - `issubclass()`はクラスの継承関係が調べられる

## 多重継承

```Python
>>> class DerivedClassName(Base1, Base2, Base3):
...     <statement-1>
...     <statement-N>
```

親クラスから継承される属性の検索は深さ優先で行われる。

## プライベート変数

`_spam`のようにアンダースコアで始まる名前は非publicなAPIとして扱う。

名前マングリングと呼ばれる機構では、アンダースコア2個のもので、`__classname__spam`へと置き返される。サブクラスが内部のメソッド呼び出しを壊さずにメソッドをオーバーライドするのに便利。

## 名前付きのデータ要素

```Python
>>> class Employee:
...     pass
>>>
>>> john = Employee()
>>>
>>> john.name = 'John Doe'
>>> john.dept = 'computer lab'
>>> john.salary = 1000
```

## イテレータ

次の要素に順にアクセスできるインターフェースのこと。`for`文などで次の要素に順にアクセスするのにもつかわれている。

```Python
>>> s = 'abc'
>>> it = iter(s)
>>> next(it)
a
>>> next(it)
b
>>> next(it)
c
>>> next(it)
StopIteration
```

## ジェネレータ

イテレータを作成するためのツール

```Python
>>> def reverse(data):
...     for index in range(len(data)-1, -1, -1):
...         yield data[index]
>>>
>>> for char in reverse('park'):
...     print(char)
k
r
a
@
```

# 参考文献

https://docs.python.org/ja/3.9/tutorial/classes.html