
Classes and Class instantiation
とある面接で、「Pythonのクラスとインスタンスについて説明してください」と言われ、うまく答えられませんでした。そのため、基本の理解のために書いたブログです。
*このブログは公式チュートリアル、9. Classesの内容の範囲です。
オブジェクト指向とは何か?
オブジェクト指向とは、クラスを用いたプログラミングのことです。オブジェクト指向は、以下のように表すことができます。
例えば、
であれば、person(モノ)が、何か言う(say_something = アクション)ことを規定したオブジェクト指向となります。
例えば、
であれば、smoke(モノ)が、ある速度で動く(get_velocity = アクション)ことを規定したオブジェクト指向となります。
クラスの基本構造
以下がオブジェクト指向のクラスの基本構造となります。

① クラス名を書いてクラスを定義します。
② 必ずdef __init__(self): でクラスを初期化します。
ここでは第二引数にnameと書き、オブジェクトが初期化される際に引数を渡すようにします。クラスで使う変数は全て、__init__()内で宣言します。この特殊メソッド__init__()をコンストラクタと呼びます。
この場合、nameをself.name = nameとして変数を宣言します。このself.nameのようなオブジェクト自身が持っている変数のことをインスタンス変数と呼びます。
③ クラス内で定義される関数 = メソッドを記述します。
メソッドには、必ずselfを引数として渡します。そうすることでメソッドの中から別のメソッドを呼び出すことができます。この場合、say_something()メソッドの中で、walk()メソッドを呼び出しています。
④ オブジェクトを作成します(呼び出します)。
person = Person()としてオブジェクトを作成しますが、このクラスからオブジェクトを作成する(呼び出す)操作のことを、インスタンス化すると言います。
クラスのインスタンス化について、公式のチュートリアルのリンクは以下です。(9.3.2. Class Objects)
以下が全体のコードです。
person.nameのようにインスタンス.変数名と書くことで変数nameにアクセスできます。同様にインスタンス.メソッド名でメソッドにアクセスします。上記では、walk()メソッドと、say_something()メソッドが呼ばれています。*1
クラスの継承(Inheritance)
クラスの継承とは、継承元のクラスが持つ機能を共通として持たせ、さらに継承した先のクラスで独自の機能を追加する構文(syntax)のことです。共通する機能を親クラスに記述し、個別の機能を子クラスに記述することで、簡便化します。
*親クラス:base class、子クラス:derived class
これについては、"科学技術計算のためのPython入門"の解説が実践的で分かりやすいので、私の方で改変したものを紹介します。*2



① 子クラスの引数に継承するクラスを渡します。
② 子クラスで__init__()メソッドで初期化します。
③ super().__init__()で、親クラスのメソッドを呼び出します。
④ 子クラスで変数を宣言します。
⑤ 子クラスで、eval_forceメソッドを再度定義(オーバーライド)します。親クラスの流体の動きに加えて、HeavySmoke(子クラス)独自の動きを上書きします。オーバーライドされたメソッドが、継承元のメソッドよりも優先されます。
全体のコードは以下になります。
イテレータ(Iterators)
イテレータとは、特殊メソッドの__iter__()と__next__()を実装したオブジェクトのことです。イテレータとは、forループのように何かを繰り返させる(ループさせる)という意味です。
公式チュートリアルの書き方を見てみましょう。
①まず、Reverse クラスを定義します。コメントに、"""Iterator for looping over a sequence backwards."""(シーケンスを逆順にループするためのイテレータ)とあります。
②def __init__(self):でコンストラクタが定義され、インスタンス変数 data と index が初期化されています。data はループする対象のシーケンスであり、index はシーケンスの長さとして初期化されています。
③イテレータの__iter__()の戻り値は、必ずそのイテレータ自身とします。__next__()はループのたびに呼び出されます。
④__next__() メソッドでは、index が 0 になるまでループを行います。index をデクリメントし、対応する位置の要素を返します。ただし、index が 0 になった場合は StopIteration例外を発生させ、ループを終了させています。
ジェネレータ(Generators)
ジェネレータとは、yield文を含むすべての関数のことです。リストやタプルのように、for文で利用できるイテラブルなオブジェクトです。return文のように値を1つ返すのではなく、複数の値を1つずつ作り出す(yieldする)ため、メモリ使用量がリストやタプルに比べて小さいです。*3
ジェネレータの作成方法は2つあります。1つがジェネレータ関数、もう1つがジェネレータ式です。
*ジェネレータ関数:full generator definitions、ジェネレータ式:generator expressions
ジェネレータ関数
以下が、ジェネレータ関数の例です。*4
①値を返したい時(yieldしたい時)に、yield文を使用します。
②ジェネレータはnext()を使うたびに、中断したyield文から処理を再開します。
③1つ目のnext(gen)を実行した後、一度処理が中断されます。そして2つ目のnext(gen)を記述すると、n=2の次のn=1から処理が再開されます。
④2つ目のnext(gen)が実行された後、関数を抜けることで自動でStopIterationが送出されます。
ジェネレータ式
以下が、ジェネレータ式の例です。
①リスト内包表記は[]で書きますが、同じ構文で()を使うのが、ジェネレータ式です。
上のコードでは、要素が1つなので分かりにくいですが、ジェネレータ式の場合、各要素が必要になるまで計算されません。もう少し詳しくジェネレータ式を見てみましょう。
②要素が5つあり、各要素が必要になるまで計算されないので、genを見てみるとジェネレータオブジェクトが返ってきます。
③リスト化すると、最後の要素まで計算されます。
あと2つほど、公式のサンプルを見てみましょう。*5
1つ目は、文字列を逆順にするジェネレータ式です。range(len(data)-1, -1, -1)のrange(len(data)-1, -1)でrange(3, -1)を表すため、インデックス'3, 2, 1, 0'の順で文字を取得します。
2つ目は、2つのシーケンス seq1 と seq2 から要素の組み合わせを生成し、それらの組み合わせをタプルとしてリストに格納したジェネレータ式です。*6
yield from式
最後に、yield from式について見ておきましょう。
PEP 380で、yield from式が追加され、ジェネレータがその操作の一部を別のジェネレータに委譲できるようになりました。
ジェネレータ関数で書かれた3、4行目をジェネレータ式に置き換えて、yield from式を併せたものが以下です。
ジェネレータ関数の短縮形がyield from式で、yield from iterableの形をとります。chain()関数からサブジェネレータの(v for v in iterable)に処理を委譲させています。
まとめ
Pythonのクラスが説明できないとは本当にひどいです。記録ではPythonだけで1,000時間程学習していますが、構文がごっそり抜けています。動画教材を見てやった気になっていました。動画は副教材で、動画:テキスト=3:7 くらいにしないと、理解してないですね。
あとクラスで残っている項目は、以下でしょうか。後日まとめたいと思います。
参考文献
*1:『シリコンバレー一流プログラマーが教える Pythonプロフェッショナル大全』、酒井 潤(2022)、KADOKAWA
*2:"科学技術計算のためのPython入門"、Tetsuya T(2021)、Udemy
*3:『Python基礎&実践プログラミング[プロへのスキルアップ+プロジェクトサンプル]』、Magnus Lie Hetland、武舎 広幸(2020)、インプレス
*4:『Python実践入門 ── 言語の力を引き出し、開発効率を高める』、陶山 嶺(2020)、技術評論社
*5:9. Classes — Python 3.12.2 documentation
*6:Functional Programming HOWTO — Python 3.12.2 documentation, Python Software Foundation.

