オブジェクト指向プログラミング

オブジェクト指向プログラミング

プログラミング言語は、その設計思想によって大きく2種類のタイプに分けられます。1つは関数型言語とよばれるものであり、Haskell や Scala などがその代表例です。もう1つは Java や Ruby, PHP などに代表されるオブジェクト指向言語です。
 
Python はオブジェクト指向言語に分類されます。実際には関数型も巧みに組合わせて処理するので「関数型 + オブジェクト指向言語」と呼んだほうが正確かもしれませんが、極めてオブジェクト指向の強い言語であることは確かです。

object は英語で「物、物品、物体」という抽象的な意味をもつ単語です。テーブル、机、書棚、パソコン、ボール、シャープペン … これらはすべて「モノ」の範疇に含まれますが、その機能や用途はそれぞれ異なっています。たとえばテレビには「電波を受信して画面に表示する」、「チャンネルを変える」、「見たい番組を検索する」といった機能があり、エアコンには「室温を設定する」、「除湿する」、「風向を変える」という機能が備わっています。テレビとエアコンでは、当然見た目や寸法も違います。もちろん、このような現実世界のたとえを使ってオブジェクト指向の概念を正確に説明することはできませんが、初学者のうちは大まかなイメージを掴んで、「習うより慣れろ」ぐらいの気持ちでプログラミングしたほうがいいと思います。ある程度慣れてきたところで「オブジェクト指向とは何ぞや?」と本格的な勉強にとりかかりましょう。実際、コードを書いているうちに、少しずつオブジェクト指向についての理解が深まってくるものです。

話を戻しましょう。クラスはオブジェクトの種類を表しています。「テレビ」とか「エアコン」のような分類です。クラス変数やインスタンス変数は個々のオブジェクトの細かな違いを表します。テレビにたとえるなら商品名や色、寸法などに相当します。メソッドはオブジェクトのもつ固有の機能です。リモコンのボタンを押したらチャンネルが変わる、というようなイメージです。
 
たとえば、”apple” という文字列は「自身と他の文字列を連結させた別の文字列を生成する」という join()メソッドを備えています。”orannge” という文字列も同様のメソッドを備えています。”apple” と “orange” は異なるオブジェクトですが、どちらも strクラス(文字列型)に属しているからです。同様に数値の 1 と 2 は intクラス(整数型)に、1.0 と 2.0 は floatクラス(浮動小数点数型)に属しています。あるオブジェクトがどのクラスに分類されるのかは、type関数を用いて簡単に調べることができるので色々と試してみてください。

このように、同じクラスのオブジェクトは見た目(インスタンス変数やクラス変数)は個々で微妙に異なっていても同じ機能を備えています。strクラスのオブジェクトであれば連結機能を備えているし、intクラスや floatクラスのオブジェクトは互いに加えたり引いたりする演算機能を備えています。int や float, str などは組み込みクラスですが、モジュールをインポートすることで、組み込み以外のクラスのオブジェクトを作り出すことができるようになります。また、ユーザー自身が新しいクラスを定義することも可能です。それでは、実際に簡単なコードを書いて、オブジェクト指向プログラミングの扉を開けてみましょう。

クラスとオブジェクト

Pythonでは、あらゆるものがオブジェクトです。整数 100 はオブジェクトであり、文字列 “Python” や真偽値の True や False もオブジェクトです。すべてのオブジェクトは何かの クラス に属しています。type()関数を使うと、あるオブジェクトがどのクラスに分類されているかを調べることができます。

# In[1]

# 100のクラス
print(type(100))

# "Python" のクラス
print(type("Python"))

# Trueのクラス
print(type(True))
# <class 'int'>
# <class 'str'>
# <class 'bool'>

サンプルコードを実行すると、100 は intクラス、”Python” は strクラス、True は boolクラスに属していることがわかります。

インスタンス

クラスはオブジェクトの性質を定義するものです。オブジェクトがプログラミングに欠かせない部品であるとすれば、クラスは部品の設計図です。設計図(クラス)から部品(オブジェクト)を製造することをクラスの インスタンス化 と表現します。
 
すべてのオブジェクトは、必ずあるクラスのインスタンスとして生成されます。intクラスから 100 や 200 というインスタンスが作られ、strクラスから “apple” や “orange” というインスタンスが作られます。インスタンスオブジェクト は同じ意味ですが、「あるクラスから作られたオブジェクト」であることを強調したいときに、インスタンスという言葉を使います。

Pythonには、intクラスや floatクラス, strクラスなど、あらかじめ用意されている組み込みクラスがあります。これらのクラスから生成するインスタンス(オブジェクト)は頻繁に活用するので、特別な手続きなしで使えるようになっています。
 
モジュールをインポートすると、組み込み以外のクラスを使うことができるようになります。たとえば、fractionsモジュールをインポートすると、Fraction というクラスが追加されます。Fractionクラスからは分数(有理数)というインスタンスを作ることができます。

クラスの定義

クラスは自分で定義することもできます。
新しいクラスを定義するときは、

class クラス名:
  スイート

という形式で記述します。一般的にクラス名は関数やメソッド、あるいは組み込みのクラスと区別するために、クラス名の先頭は大文字にするという慣例があります。クラス名のあとに改行し、半角 4 文字でインデントして、スイートとよばれるにメソッドやクラス変数、インスタンス変数などを記述します。
 
以下のサンプルコードでは「長方形の性質をもつオブジェクト」を生み出すための Rectangles クラスを定義しています。

# In[1]

# Rectanglesクラスの定義
class Rectangles:
    def __init__(self, a, b):
        self.width = a
        self.length = b
    
    def area(self):
        return self.width * self.length

Rectangles がクラス名です。続く行に def 文を使ってクラス内部に関数が定義されています。これがメソッドの正体です。
 
__init__() はクラスのインスタンスが生成されるときに自動的に実行されるメソッドです。Rectangles クラスはオブジェクト自身がもつ変数 (インスタンス変数) width と length に数値 a, b を代入するようになっています。これはインスタンスを生成するときにユーザーが引数として渡す値です。実際に1つのオブジェクトを生成してみましょう。コンストラクタは Rectangles(a, b) です。

# In[2]

# Rectanglesクラスのインスタンスを生成
rec = Rectangles(10, 20)

これで、インスタンス変数 width と length にそれぞれ 10, 20 という値をもつ Rectangles オブジェクトが生成されました。格納された値は属性値として参照できます:

# In[3]

# 長方形の二辺の長さを取得
print(rec.width)
print(rec.length)
# 10
# 20

インスタンス生成時に、引数に様々な値が渡すことができるので、個々のオブジェクトのもつ属性値は互いに異なっています。縦横の長さが (10, 20) の長方形もあれば、(15, 30) や (50, 80) の長方形もあります。(50, 50) という正方形も長方形の一種です。確かにどれも少しずつ異なっているのですが、「二辺の長さをもつ」という長方形の性質は維持されています。
 
この例からわかるように、クラスはオブジェクトの大まかな特徴を設計しますが、細部については実際にオブジェクトが生成された時点で確定するようになっています。
 
Python Rectangles(長方形)クラスのインスタンス
 
def __init__() には、a, b の前に self という引数が定義されていますね。クラスメソッドの第 1 引数には必ず self を指定するという決まりがあります。インスタンスが生成されると、self にインスタンス自身が渡されて、self.[変数] という形式でインスタンス変数が値を受け取れる仕組みになっています。
 
慣れないうちは理解しにくいかもしれませんが、あまり深く考えずに「こう書く決まりになっているのだ」ぐらいに覚えるようにしてください。プログラミングに慣れてくると (不思議なことに) こうした仕組みを自然と理解できるようになります。
 
Rectangles クラスには、もうひとつ、area() というメソッドが定義されています。area()メソッドは内部で self.width と self.length を掛け合わせて長方形の面積を計算します。先ほど作成したオブジェクトの面積を取得してみましょう。

# In[4]

print(rec.area())
# 200

Python はすべてがオブジェクトです。あらゆるオブジェクトは、何かのクラスから作られるインスタンスです。すると、
「すべてがオブジェクトであるというならば、クラスはどうなるの?」
という疑問が生じるかもしれません。実はクラスもやはり typeクラスから生成されるオブジェクトなのです (type()関数の引数にクラス名を指定すると <class ‘type’> と表示されます) 。クラスを生み出すクラスのことをメタクラスとよびます。クラスがオブジェクトであることを明示したいときには、敢えてクラスオブジェクトとよぶことがあります。

 

オブジェクト指向の学び方

プログラミング入門者にとって、オブジェクト指向プログラミングは、おそらく最初の関門となります。他のプログラミングの参考書などを読んでいても、オブジェクト指向に踏み込むと急に内容が難しくなるような気がします。数学にたとえると、それまで三角比を学んでいたのに、次の単元で急に微分積分が現れた、みたいな感じです(別に数学にたとえる必要もありませんけど)。

それまでは順調について来られた人も、ここで急に高い壁に阻まれたような気持ちになって戸惑うのかもしれません。逆に教える側としても「クラスとオブジェクトの概念」はひとつの関門なのです。世間に数多いるベテランプログラマーさんたちも、「オブジェクト指向の概念をいかに上手く伝えるか」ということにはけっこう苦心しているようです。クラスを設計図、オブジェクトを部品にたとえるのが一般的ですが、
「ダメだね。そんな教え方ではオブジェクト指向の概念は正確に伝わらないよ」
と主張する人もいます。確かにその人たちの主張も一理あるんですよね。コンピュータ内部の話ですから、クラスはクラス、オブジェクトはオブジェクトであって、他にたとえようがないのです。とはいえ、最初にオブジェクト指向を学ぶ段階であまり正確さにこだわり過ぎても前になかなか進めないと思うのです。
 
私としては、最初のうちは大まかなことを理解して、とにかくコードを書くという経験を積みながら徐々に理解してゆくというスタイルのほうがいいと考えています。だから当サイトの記事でも設計図と部品という一般的な方法で説明したのです(ただの言い訳?)。
 
オブジェクト指向はとても深い概念なので、
「クラスを生み出すクラスとは何ぞや?」
というような事にまで踏み込めんで学ぶときりがないのですが、クラスを設計して目的に応じた振る舞いをするオブジェクトを作るという、普通の使い方をするのであれば、オブジェクト指向は決して難しいものではありません。組み込み型など、もとからあるクラスに修正を加えるという形で新しいクラスを設計することもできます。オブジェクト指向のテクニックを究めるとプログラミングの幅が大きく広がります。自分でクラスを設計し、オーダーメイドの部品(オブジェクト)を使って大規模なプログラムを効率的に組み立てることができるからです。上級者はゲッターやセッター、プロパティなどを使いこなし、より高度な設計を行ないます。

要はコードを書くことです。プログラミングの習得に最適な学習法は、ひたすら書いて実行し、エラーメッセージでお叱りを受け続けることです。エラーメッセージはプログラミングの最良の教師です。「コードのここが良くないよ」と的確に指摘してくれます。クラスとオブジェクトのことを理解したいと思うのなら、サンプルコードを(コピペせずに)すべて自分の手で打ち込んで動作を確認します。そのあと色々なクラスを自身で設計してみましょう。思ったようにいかないことも多々ありますけれど、何度エラーを出されても、目的のコードが完成するまではしつこく食らいつく気持ちが大切です。あれこれ試行錯誤しているうちに少しずつ「クラス」というものの正体が見えてくるようになると思います。
 
私自身のプログラミングの習得過程を思い返すと、慣れないうちは様々に絡み合う専門用語が理解を妨げることもあるかもしれません。理解したつもりでも、思い違いをしていたりすることもけっこうあります。用語の意味を正確に把握するというのは、プログラミングに限らず、何かを学ぶうえで避けて通れない道です。ちょっと記憶や理解が曖昧だと思える用語に関しては、前に戻って、もう一度しっかり読み直すという労力は惜しまないほうがいいでしょう。急いで先のことを学ぶよりも、とにかくしっかり土台を固めるほうが、将来大きく伸びると思います。何事も「急がば回れ」の精神で、ゆっくりと着実に理解を進めてください。