「オブジェクト指向」という言葉を聞いたことはあるけれど、
・オブジェクト指向がどういうものかわからない…。
・Pythonにおけるオブジェクト指向について基礎から知りたい…。
と考える方は多いのではないでしょうか。そこでこの記事では、
・オブジェクト指向の基礎
・Pythonにおけるオブジェクト指向プログラミングの基礎
についてわかりやすく解説します。
INDEX
Pythonはオブジェクト指向プログラミング言語
Pythonは、オブジェクト指向プログラミング言語の一種です。Python以外のオブジェクト指向プログラミング言語の例としては、JavaやC++、C#、Rubyなどが挙げられます。オブジェクト指向は、近年のソフトウェア開発において重要な考え方です。なぜなら、オブジェクト指向を用いることで開発期間の短縮や保守性が高まるからです。
そもそもPythonがなぜ人気なのか、学習するメリットや将来性などについて知りたい方は、「Python(パイソン)とは?人気プログラミング言語の特徴・活用事例を解説!」をご覧ください。
\文字より動画で学びたいあなたへ/
Udemyで講座を探す >オブジェクト指向とは
オブジェクト指向とは、データの種類ごとに情報をまとめた「オブジェクト」を中心に、オブジェクト間の相互作用を管理する考え方・概念です。プログラミング言語にはさまざまな考え方(プログラミングパラダイム)が存在します。その代表的なものの一つがオブジェクト指向であり、その他にも「手続き型」や「関数型」などが存在します。
オブジェクトは、直訳すると「モノ」であり、オブジェクト指向においてはさまざまな実体の総称です。オブジェクト指向プログラミングでは、設計書に基づいてオブジェクトが大量に生成されます。
Pythonでは文字列(str)や数値(int)などの変数は、どれも設計書から作られたオブジェクトであると覚えておきましょう。また、オブジェクトは属性とメソッドを持っている点が特徴です。
オブジェクト指向の考え方をわかりやすくするために、自動車を例に見てみましょう。自動車はシャーシ・タイヤ・エンジンから作られているものとします。
自動車というオブジェクトの基本構造を変えないまま、トラックにするための構造、スポーツカーにするための構造というように、必要な部分のみを変更するだけで異なる自動車を定義できます。
これらの構造はオブジェクトの「属性」にあたり、「自動車に何をさせるか」という部分をメソッドとして定義します。オブジェクトは属性とメソッドを持ち合わせているものと考えるとよいでしょう。より深く理解するためには、クラスやインスタンスなどについても知っておく必要がありますが、これらは後ほど詳しく解説します。
オブジェクト指向は、綿密な設計が欠かせませんが、大規模な開発でも効率よく構造を整理しながらプログラミングを進められる点がメリットです。この点が、開発期間の短縮や保守性の向上をもたらします。
Pythonのオブジェクト指向と他のプログラミングとの違い
前述のとおり、プログラミング言語の考え方・概念(プログラミングパラダイム)はオブジェクト指向だけではありません。代表的なプログラミングパラダイムをまとめると次のとおりです。
プログラミングパラダイム | 特徴 | 言語の例 |
オブジェクト指向 プログラミング |
関連する情報をまとめ、相互作用を管理。オブジェクトとして一つにまとめて実装する | Python、C#、Pythonなど |
手続き型 プログラミング |
「手続き」と呼ばれる単位でコードを記述。手続き単位に階層的に実装する | COBOL、C言語、BASICなど |
関数型 プログラミング |
予め定義されている「関数」を組み合わせることでコードを記述。既存の関数を組み合わせて新たな関数を定義することも可能 | Haskell、Ocamlなど |
論理型 プログラミング |
「論理式」を組み合わせて結論や答えを導く。AIの記述言語として利用されることが多い | Prolog |
このなかでも、手続き型・関数型はオブジェクト指向と並んで多くのプログラミング言語で利用されています。では、手続き型や関数型とオブジェクト指向とではなにが違うのでしょうか。ここからはそれぞれの違いを見ていきましょう。
手続き型プログラミングとの比較
手続き型は、基本的に処理の順番通りにコードを記述します。シンプルで短いコードであれば、手続き型のほうが手軽に記述できる点が特徴です。
一方で、手続き型はグローバル変数を多用するため、意図せず書き換えてしまいエラーが発生したり、規模が大きい開発の場合は可読性の引くコードになりやすかったりとデメリットも存在します。
以下は、シンプルなコードで手続き型とオブジェクト指向を比較したものです。このサンプルコードは、自動車の種類としてスーパーカーとトラックを定義し、それぞれのシャーシ部分を出力するコードです。
手続き型のサンプルコード(COBOL)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
IDENTIFICATION DIVISION. PROGRAM-ID. Car_Instance. ENVIRONMENT DIVISION. CONFIGURATION SECTION. DATA DIVISION. WORKING-STORAGE SECTION. 01 WORK-AREA. 03 SUPERCAR-GROUP. 05 SUPERCAR-CHASSIS PIC X(5). 05 SUPERCAR-TIRES PIC X(15). 05 SUPERCAR-ENGINE PIC X(3). 03 TRUCK-GROUP. 05 TRUCK-CHASSIS PIC X(12). 05 TRUCK-TIRES PIC X(18). 05 TRUCK-ENGINE PIC X(8). PROCEDURE DIVISION. MAIN SECTION. MOVE "TT-02" TO SUPERCAR-CHASSIS. MOVE "Pirelli-PZEROx4" TO SUPERCAR-TIRES. MOVE "V12" TO SUPERCAR-ENGINE. MOVE "2axes-CHASSI" TO TRUCK-CHASSIS. MOVE "BRIDGESTONE-M888x6" TO TRUCK-TIRES. MOVE "6UZ1-TCS" TO TRUCK-ENGINE. DISPLAY "スーパーカー:"SUPERCAR-CHASSIS. DISPLAY "トラック:"TRUCK-CHASSIS. STOP RUN. |
手続き型の出力結果
1 2 |
スーパーカー:TT-02 トラック:2axes-CHASSI |
オブジェクト指向のサンプルコード(Python)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Car: def __init__(self, chassis, tires, engine): self.chassis = chassis self.tires = tires self.engine = engine def __str__(self): return f"シャーシ={self.chassis}, タイヤ={self.tires}, エンジン={self.engine}" supercar = Car("TT-02", "Pirelli-PZEROx4", "V12") truck = Car("2axes-CHASSI", "BRIDGESTONE-M888x6", "6UZ1-TCS") print("スーパーカー:", supercar.chassis) print("トラック:",truck.chassis) |
オブジェクト指向の出力結果
1 2 |
スーパーカー: TT-02 トラック: 2axes-CHASSI |
シンプルなコードであれば、手続き型は順番に定義し実行されるためわかりやすいです。しかし、コードが長くなったり複雑な処理を実装したりする場合は、より構造が整理されたオブジェクト指向のほうが適しています。
関数型プログラミングとの比較
関数型は、それぞれ定義された関数に引数を送り、その引数に対して処理を施すという記述方法です。手続き型と異なり、グローバル変数( プログラム内のすべての領域で使用できる変数 )を利用しないためエラーの発生を抑えられます。一方で、関数型は処理が細かく分割されるため、コードの再利用性が低い点がデメリットとして挙げられます。
以下に関数型とオブジェクト指向のサンプルコードを記載するため、比較してみましょう。
このサンプルコードは、スーパーカーとトラックの最高速度を定義し、ブースト時の最高速度を出力しています。トラックはブーストなしとして、最高速度は変わらないようになっています。
関数型のサンプルコード(Haskell)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
booster speed boost = speed + (speed * 0.5 * boost) main = do print $ "SuperCar:MaxSpeed(km/h)=300" print $ "Truck:MaxSpeed(km/h)=180" print $ "SuperCar-boost:" print $ booster 300 1 print $ "Truck-boost:" print $ booster 180 0 |
関数型の出力結果
1 2 3 4 5 6 7 |
"SuperCar:MaxSpeed(km/h)=300" "Truck:MaxSpeed(km/h)=180" "SuperCar-boost:" 450.0 "Truck-boost:" 180.0 |
オブジェクト指向のサンプルコード(Python)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
class Car: def __init__(self, speed, boost): self.speed = speed self.boost = boost def booster(self): self.speed = self.speed + (self.speed * 0.5 * self.boost) def __str__(self): return f"最高速度(km/h)={self.speed}" supercar = Car(300,1) truck = Car(180,0) print("スーパーカー:", supercar) print("トラック:",truck) supercar.booster() truck.booster() print("スーパーカー:", supercar) print("トラック:",truck) |
オブジェクト指向の出力結果
1 2 3 4 5 |
スーパーカー: 最高速度(km/h)=300 トラック: 最高速度(km/h)=180 スーパーカー: 最高速度(km/h)=450.0 トラック: 最高速度(km/h)=180.0 |
この例を見ても分かるとおり、関数型はコードを再利用することは難しく、オブジェクト指向では、クラスを定義することでコードの再利用が可能となっています。
Pythonにおけるオブジェクト指向プログラミングの基本
Pythonにおけるオブジェクト指向を理解するためには、関連する用語について理解しておくことが重要です。ここでは、以下の覚えておくべき3つの言葉について簡単に解説します。実際の記述方法については後述しているため、後ほどご確認ください。
- クラス
- インスタンス
- メソッド
クラス
クラスは、オブジェクトの設計図にあたるものです。クラスには生成されるオブジェクトが共通して持つ属性やメソッドを定義します。Pythonで、数値の変数同士の四則演算が実現できるのは、intというクラスでそれらのメソッドが定義されているためです。
インスタンス
インスタンスはオブジェクトと似ていますが、クラスから生成された実体そのものを表します。インスタンスはクラスから生成されるものであり、このことを「インスタンス化」と呼びます。
オブジェクトは、さまざまな実体の総称でありインスタンスもその一部です。オブジェクト指向プログラミングでは、インスタンスを生成することで、はじめてクラスで定義したメソッドを実行できるようになります。
メソッド
メソッドは、クラスが実行できる処理のことです。記述方法や動作は関数と似ていますが、関数とメソッドは次の点が異なります。
・関数:単独で呼び出せる
・メソッド:クラスを通じて呼び出せる
関数は、サブルーチンなどとも呼ばれ、オブジェクト指向以外でも利用されます。クラスを定義する際には、メソッドも記述するということを覚えておきましょう。
Pythonでオブジェクト指向プログラミングを実践!
ここからは、Pythonでオブジェクト指向プログラミングを以下の手順に沿って実践してみましょう。
- クラスの定義
- インスタンス化・メソッドの呼び出し
- カプセル化
- 継承・オーバーライド
- ポリモーフィズム
クラスの定義
クラスには、後に生成するインスタンスの属性や、実行したいメソッドを記述します。クラスは「class」で定義し、インスタンスの初期化や属性の設定は「__init__()」で記述します。また、Pythonではインスタンス自体を指す変数として「self」を用いることを覚えておきましょう。メソッドも「__init__()」と同様に「def」を使って定義します。
1 2 3 4 5 6 7 8 |
class ClassSample: def __init__(self, i, j): self.i = i self.j = j def methodSample(self): return self.i * self.j |
インスタンス化・メソッドの呼び出し
定義したクラスを使用する際には、インスタンス化が必要です。クラス名と引数を指定してインスタンスを作成します。また、作成したインスタンスに「.(ドット)」をつけてメソッド名を記載することで、メソッドを呼び出せます。
1 2 |
instance = ClassSample(10,5) print(instance.methodSample()) |
クラスの定義と併せてインスタンス化・メソッドの呼び出しを行うコードと、その結果は次のとおりです。
1 2 3 4 5 6 7 8 9 10 11 12 |
class ClassSample: def __init__(self, i, j): self.i = i self.j = j def methodSample(self): return self.i * self.j instance = ClassSample(10,5) print(instance.methodSample()) |
出力結果
1 |
50 |
クラスをインスタンス化する際に、10と5を引数として渡しています。その後、呼び出されるメソッドは2つの引数を乗算して返すものであるため、出力結果は「50」となります。
カプセル化
カプセル化とは、クラスの外部からのアクセスを制限することで、クラス内部のデータを保護する仕組みです。カプセル化を用いることで、オブジェクト内部における処理やデータ構造を変更する際も影響を最小限に抑えられます。また、オブジェクトの独立性が高まることで、再利用性が向上します。
Pythonでは、アクセスを制限したい変数やメソッド名の前に「_(アンダースコア)」を付与します。アンダースコアを1つだけ付与した場合、慣習的なプライベート化を意味するものでプログラム的にはアクセスの制限は行えません。
次のサンプルコードは、クラス内の変数にアンダースコアを1つだけ付与しています。この状態では、インスタンス化したあとに直接変数を参照することが可能です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class CapsuleSample: _food = "apple" def foods(self): return "My favarit food is an " + self._food def printFoods(self): print(self.foods()) inst = CapsuleSample() print(inst._food) print(inst.foods()) inst.printFoods() |
出力結果
1 2 3 4 |
apple My favarit food is an apple My favarit food is an apple |
さらに、もう一つアンダースコアを追加すると、次のようなエラーが出て参照することができません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class CapsuleSample: __food = "apple" def foods(self): return "My favarit food is an " + self.__food def printFoods(self): print(self.foods()) inst = CapsuleSample() print(inst.__food) print(inst.foods()) inst.printFoods() |
出力結果
1 2 3 4 5 |
Traceback (most recent call last): File "Main.py", line 12, in <module> print(inst.__food) AttributeError: 'CapsuleSample' object has no attribute '__food' |
クラス内部では、参照可能であるため、クラス内の変数「food」を直接読み込む処理をコメントアウトすれば、メソッドの結果は正常に返ってきます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class CapsuleSample: __food = "apple" def foods(self): return "My favarit food is an " + self.__food def printFoods(self): print(self.foods()) inst = CapsuleSample() #print(inst.__food) # コメントアウト print(inst.foods()) inst.printFoods() |
出力結果
1 2 |
My favarit food is an apple My favarit food is an apple |
これはメソッドにおいても同様の動作になります。メソッド「foods」にアンダースコアを2つ付与した場合、外部から呼び出すことができなくなります。以下のサンプルコードは前述と同様のエラーを回避するため、13行目をコメントアウトしています。注目すべき点は、メソッド「printFoods」は外部呼び出しが可能であり、クラス内部ではメソッド「__foods」を呼び出せている点です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class CapsuleSample: __food = "apple" def __foods(self): return "My favarit food is an " + self.__food def printFoods(self): print(self.__foods()) inst = CapsuleSample() #print(inst.__food) # コメントアウト #print(inst.__foods()) # コメントアウト inst.printFoods() |
出力結果
1 |
My favarit food is an apple |
継承・オーバーライド
継承とは、既存のクラス・メソッドの内容を引き継ぐことを表します。特定のクラスの特徴を持ちつつ、追加で別の特徴を持ったクラスを生成したい場合などにクラスの継承を行います。このとき、継承元のクラスを「親クラス」、継承先のクラスを「子クラス」と呼ぶことを覚えておきましょう。
継承と併せて、オーバーライド(上書き)についても覚えておくべきです。オーバーライドは子クラスで親クラスのメソッドを記載し、その内容を書き換えることを表します。継承の際にはクラスを定義する際の引数に親クラスを指定し、オーバーライドの際には子クラスのなかで親クラスと同じメソッドを定義します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Parent: def __init__(self, i, j): self.i = i self.j = j def or_method(self): print(self.i, self.j) class Child(Parent): def or_method(self): print(self.i * self.j) inst = Child(10, 3) inst.or_method() |
出力結果
1 |
30 |
ポリモーフィズム
ポリモーフィズムは、同じインターフェイスに対して、データ型を併せて異なる挙動をする仕組みです。ポリモーフィズムを用いれば、データ型ごとに別々のメソッドを用意する必要がありません。Pythonでポリモーフィズムを実現する際には、継承を利用するか抽象基底クラスを利用する方法が考えられます。
Pythonにおけるわかりやすい例としては「print()関数」が挙げられるでしょう。print()関数は、文字列(str)型や数値(int)型などを意識することなく、一つの関数で記述できます。
オブジェクト指向プログラミングを学んでより高度な開発に挑戦しよう!
オブジェクト指向プログラミングは、開発期間の短縮や保守性が高まるといったメリットから、大規模な開発で用いられることが多くなっています。Pythonはオブジェクト指向プログラミング言語であり、オブジェクト指向について学びたい場合にもPythonを活用するとよいでしょう。
UdemyではPythonにおけるオブジェクト指向プログラミングが学べる講座を用意しています。効率よくオブジェクト指向プログラミングについて学びたい場合は、以下の講座がおすすめです。
Pythonオブジェクト指向プログラミング入門。難解なオブジェクト指向の概念をアプリを開発しながら徹底解説。
基礎文法を学んだあと何をしたらいいの? もっとキレイにコードを書きたい? ゲームを作りながら楽しくオブジェクト指向プログラミングを学びましょう。基礎的なクラスの文法、デコレータ、インナークラスを組み合わせクリーンコードで実装する方法を習得。
\無料でプレビューをチェック!/
講座を見てみる評価:★★★★★
まだ復習があるところがありますがとても勉強になりました。ゲームという目的で学習していくので具体的な経験ができてよかったです。
評価:★★★★★
大変満足できたセミナーでした。講師の方もとても丁寧で、やさしさが感じられることも高評価です。内容は中級クラスではありますが、基礎的な内容の復習もできており、基礎~応用まで幅広く対応いただいている内容に感謝しております。コードを丁寧かつキレイに表していただけるので、とても勉強になりました。
オブジェクト指向について理解を深めれば、Pythonを使ってより高度な開発が実現できるようになります。
最新情報・キャンペーン情報発信中