クラスとオブジェクト、クラスの定義、インスタンス変数、__init__メソッド、インスタンスメソッドなど、クラスの基礎知識を紹介する。
この記事は会員限定です。会員登録(無料)すると全てご覧いただけます。
* 本稿は2019年7月26日に公開された記事を、Python 3.12.0で動作確認したものです(確認日:2023年11月10日)。
前回はPythonの演算子についてまとめた。今回からは数回にわたって、Pythonのクラスについて取り上げていく。
本連載では、これまでにさまざまなオブジェクトを扱ってきた。それらのオブジェクトはいずれも特定の型に属するものだった。整数「1」ならその型は「int型」であり、文字列'Hello'であれば、その型は「str型」だった。そして、Pythonではそうした型をプログラマーが独自に作り出すこともできる。そのために使うのが、「クラス」と呼ばれる機構だ。実際には、Pythonが組み込みで提供しているさまざまな型はクラスである。それは以下のコードを試してみれば分かるだろう。
print(type('a'))
このコードを実行すると「<class 'str'>」と表示される。
この「class」というのが、文字列'a'の型は「strクラス」であることを示している。言い換えると、「文字列'a'はstrクラスのオブジェクト」となる。では、オブジェクトとクラスの違いは何だろう。
第25回でも述べた通り、「オブジェクト」とは「プログラムの実行時に作成されて、コンピュータのメモリ上に存在する何かしらのデータ」のことだ。これに対して、「クラス」とは「オブジェクトを作成するための設計図」といえる。オブジェクトがどんな値を持ち、どんなメソッドを持ち、どんな演算が可能かを詳細に記したものが「クラス」である。
オブジェクトとは「あるクラスの設計図から作り出され、メモリ上に存在する実体」であることを強調するために「インスタンス」(instance)と呼ぶこともある。「instance」とは「実例」といった意味だが、プログラミングの世界における「インスタンス」とは「何かの概念を具体的に表したもの」のことだ。
例えば、int型は「整数という概念を記述した型(クラス)」であり、整数値「1」は「整数という概念を基にコンピュータのメモリ上に作り出された具体的な値(インスタンス)」である。2次元座標なら、X軸の値とY軸の値、原点からの距離といった要素があり、それらの概念をコードで表したものがクラス、そこから「X=1.0」「Y=1.0」などの具体的な値を持ち、メモリ上に存在するものがインスタンスといえる。
そして、既に述べたように、Pythonでは「クラス」機構を用いて、プログラマーはさまざまな型を自由に定義して、そのインスタンスを作成できる。以下では簡単なクラスを作りながら、そのあらましを見ていこう。
Pythonではクラスは「class文」を用いて定義する。
class クラス名:
クラス定義の本体(クラスが持つ属性を定義する)
「クラス名」という名前を持つ「クラスオブジェクト」を作成する。
以下では例として、2次元座標を表すクラスを定義していこう。
なお、クラスの名前についてはPythonのコーディングスタイルガイドの「Class Names」で「CapWords規約に従った名前にする」ことが基本的には推奨されている。「CapWords」規約とは、その規約名を見ると分かる通り、「単語の先頭は大文字で始めて、名前が複数の単語でつづられるときには、それらをアンダースコアなどを使わずにつなげる」命名法のことだ。例えば、本フォーラムを表すクラスを作るのであれば「DeepInsider」という名前になる。
このように先頭を大文字、他を小文字、アンダースコアは使わずに単語をつなげるような名前にすることで、変数や関数の名前(全て小文字、複数単語はアンダースコアでつなぐ)や定数の名前(全て大文字)と一目でその違いが分かる。
ここでは、クラス名は「CapWords」規約に従い、シンプルに「Point」と単語を1つだけとする。
では、初めてのクラス「Point」を定義してみよう。そのコードは次のようになる。
class Point:
pass
ここではクラス定義の本体が「pass」という見慣れないコードだけになっている。これは「pass文」というもので、「何もしない」文だ。ここでは取りあえず、Pointクラスを定義するだけで、その本体の説明を後回しにしたいので、pass文を使い、何もしないクラスを定義した。
このクラスに固有のものは何もないが、取りあえずPointクラスができた。そこで、このクラスからインスタンスを作ってみよう(これを「インスタンスの生成」と呼ぶことがある)。これには、「クラス名()」という関数を呼び出す。つまり、「Point()」関数呼び出しを行うことで、そのインスタンスが得られる。
point1 = Point()
print(type(point1))
先ほどのクラス定義に続けて、上のコードを実行すると、その結果は次のようになる。
この出力結果には、先ほどの文字列の型を調べたときの出力である「<class 'str'>」とは異なり、「<class '__main__.Point'>」と「__main__」が付いている。「__main__」というのは、本連載のように何らかの形でPythonの対話環境を利用している場合に、そのコードを実行する環境(スコープ)を表すもので、ここでは対話環境(__main__環境)上で定義されたクラス(Point)であることを意味している。
ところで、先ほど「このクラスに固有のものは何もないが」と述べたが、実はクラスを定義するだけで、Pythonのオブジェクトが持つ基本的な機能が備わるようになっている。これを調べるにはdir関数が使える(dir関数については第14回「関数のローカル変数とスコープ」の「ローカル名前空間」でも触れた)。
dir([obj])
objを省略したときには、現在のローカル引数で定義されている名前を含んだリストを戻り値とする。objを指定したときには、そのobjが持つ「属性」のリストを戻り値とする。
パラメーター | 説明 |
---|---|
obj | それが持つ属性を知りたいオブジェクト(省略可能) |
dir関数のパラメーター |
この関数は、オブジェクトを引数に渡すと、そのオブジェクトが持つ「属性」(メソッドなど)を調べてくれる。これを使って、Pointクラスのインスタンスである「point1」オブジェクトがどんな属性を持っているかを見てみよう。
print(dir(point1))
このコードを実行すると、次のようになる。
ご覧の通り、Pointクラスの定義は「pass文」しかなかったのに、2つのアンダースコア「__」で始まる幾つもの属性が表示された。これらがどこからきたものかというと、Pythonが「全てのオブジェクトの基盤」となるクラスとして用意している「objectクラス」からだ。
上で見た方法でクラスを定義すると、このobjectクラスを基にして新規にクラスが作成され、Pythonのオブジェクトが持つべき基本的な属性が自動的にobjectクラスから新しいクラスへと受け渡されるようになっているのだ。先ほどの、クラス定義の構文は実際には以下のコードを省略した表記となる。
class Point(object):
pass
クラスは、何らかのクラスを基に新しく作成できるが、そのときには「クラス名」に続けてかっこ「()」の中に「基となるクラス」を列挙していく。このかっこと基となるクラス名を省略すると、それはobjectクラスを基にしたものとして扱われるようになっているということだ。
このように、特定のクラスを基にして、新しくクラスを定義することを「継承」と呼ぶ。継承を使うことで、よく似た特性を持つ(共通部分がある)、しかし細部が異なるデータを表すクラスを効率的に定義できるようになる。継承については後続の回でさらに詳しく取り上げよう。
話を戻して、次にPointクラスに固有の属性について考えてみよう。まず次の2つが必要なことはハッキリとしている。
これら2つは、値を保存しておくので、Pointクラスの個々のインスタンスが持つ変数のようなものだ。このことから、これらを「インスタンス変数」と呼ぶ。
また、これ以外に次のような処理が可能だとうれしいかもしれない。
これらはインスタンスが持つ個々の値を利用して計算処理を行うので「インスタンスメソッド」と呼ぶ。
座標を表すデータにはこれらの変数やメソッドを持つことを、設計図としてPythonのコードを使って記述したのがPointクラスの定義であり、それを基に具体的なX座標やY座標の値を持ったものがPointクラスのインスタンスとなる。なお、クラスが持つ属性のことを「メンバ」と呼ぶこともある。
以下では、これらについて考えていこう。
既に述べた通り、X座標とY座標を表す属性は、Pointクラスのインスタンスごとに異なる値を保存する変数(インスタンス変数)のようなものだ。先ほどの定義したクラスとインスタンスであれば、次のようにしてX座標とY座標を表すインスタンス変数にその値を代入できる。インスタンス変数の名前はPythonのコーディングスタイルガイドの「Method Names and Instance Variables」で「小文字を使って、読みやすくなるのなら単語をアンダースコアで区切る」ことが推奨されているので、ここでは「x」と「y」という名前にしよう。
point1.x = 1.0
point1.y = 1.0
これでPointクラスのインスタンスである「point1」が「(1.0, 1.0)」という座標を表すようになった。本当に属性が追加されたかを確認してみよう。
print(dir(point1))
このコードを実行すると、次のようになる。
実行結果を見ると分かるように、リストの末尾に「x」と「y」の2つの属性が追加された。ここで、Pointクラスのインスタンスをもう1つ定義しよう。
point2 = Point()
point2オブジェクトを生成したのはいいが、これにはまだ属性xとyがない。そのため、上と同様に、インスタンスの属性にわざわざ代入をしないと、2点間の距離を調べるといったことができない。そうではなく、インスタンスを生成するタイミングで、それらの値を設定できると便利だ。そして、実際にそうすることが可能である。これには「__init__」という名前の「メソッド」を定義する。
Copyright© Digital Advantage Corp. All Rights Reserved.