この記事では、Pythonのクラス入門編として「クラスとは何か」に焦点を当てて解説します。実際にコードに触れながら説明していきますのでクラスに対する理解を深めていきましょう。
具体例を通してクラスを理解しよう
クラスについて学習する前に、実際にクラスの実装を段階に沿って行なっていきます。
内容としては「BobとLisaの2人の平均点を出力する」というものです。実行した結果は同じものが出力される点が重要です。
1. 単に変数に値を格納して実行するだけの場合
man1 = "Bob"
man1_japanese = 60
man1_english = 90
man1_average = (man1_japanese + man1_english) / 2
female1 = "Lisa"
female1_japanese = 100
female1_english = 70
female1_average = (female1_japanese + female1_english) / 2
print(man1, "の平均点は", man1_average, "です。")
print(female1, "の平均点は", female1_average, "です。")
# 出力結果
# Bob の平均点は 75.0 です。
# Lisa の平均点は 85.0 です。
見て分かると思いますが、BobとLisaの平均点を出すまでのコードはほぼ似たような内容です。平均点を出す人が増えるごとに毎回これを行うのは非現実的です。このような似たコードを使いたい時に役立つのが関数です。
2. 上記の内容を関数に実装した場合
# 平均点を出す関数
def average(name, japanese, english):
average_point = (japanese + english) / 2
print(name, "の平均点は", average_point, "です。")
average("Bob", 60, 90)
average("Lisa", 100, 70)
# 出力結果
# Bob の平均点は 75.0 です。
# Lisa の平均点は 85.0 です。
関数を使う事でだいぶスッキリしました。名前と点数の項目だけ実装すれば良いことになり、人を増やした拡張性も高くなりました。 そして、ここからクラスを使用してみます。コード内にコメントを入れますので、なんとなく雰囲気だけ掴めればOKです。
3. クラスを使用した場合
# ①クラスを定義する
class Average():
# ②クラス内に処理を実装する
def __init__(self, name, japanese, english):
self.__name = name
self.__japanese = japanese
self.__english = english
self.__average = self.__calculate_average()
def __calculate_average(self):
return (self.__japanese + self.__english) / 2
def get_name(self):
return self.__name
def get_average(self):
return self.__average
# ③man1, female1という変数にクラスを実装
# インスタンスの作成とデータの設定を一度に行う
man1 = Average("Bob", 60, 90)
female1 = Average("Lisa", 100, 70)
# ④関数の結果を出力
print(man1.get_name(), "の平均点は", man1.get_average(), "です。")
print(female1.get_name(), "の平均点は", female1.get_average(), "です。")
# 出力結果
# Bob の平均点は 75.0 です。
# Lisa の平均点は 85.0 です。
ここでは、クラスの概念の1つである、「カプセル化」という処理を行なっています。クラス内のメソッドにinitやselfといった特殊な構文があると思いますが、これらはPythonのクラス設計において重要な役割を果たします。initはクラスの初期化メソッド(コンストラクタ)で、インスタンスが生成されるときに自動的に呼ばれます。selfはインスタンス自体を参照するための変数で、クラス内の属性や他のメソッドを参照するために使います。
コードが関数の時より長くなっていて逆に分かりづらいと思うかもしれません。つまり、何でもかんでもクラスを使えば良いというわけではなく、行いたい処理にクラスを使用する必要があるか、を理解することが重要です。
ここまでを踏まえて、今回のカプセル化も合わせて「クラスを使用するメリット・使い所」について説明していきます。
カプセル化とは?
カプセル化とは、オブジェクト指向プログラミングの基本的な原則の一つであり、データとそのデータに対する操作を一緒にまとめることを指します。カプセル化の主な目的は、クラスの内部データを外部から直接変更することを防ぎ、データの整合性とセキュリティを維持することです。
Pythonでは、カプセル化は主にアンダースコア(_)を使用して実現します。一つのアンダースコア(例:_name)は、通常、その変数やメソッドが「内部使用」または「非公開」であることを示す慣習的なシグナルです。しかし、これは厳格なルールではなく、Pythonのコードから直接アクセスすることは可能です。
対照的に、二つのアンダースコア(__)を接頭辞として使用すると、その変数やメソッドは「名前修飾(name mangling)」と呼ばれるプロセスを通じて、クラスの外部から直接アクセスを困難にします。これにより、データが不適切に操作されることを防ぎます。
先述したコードを例に取ります。
# ①クラスを定義する
class Average():
# ②クラス内に処理を実装する
def __init__(self, name, japanese, english):
self.__name = name
self.__japanese = japanese
self.__english = english
self.__average = self.__calculate_average()
def __calculate_average(self):
return (self.__japanese + self.__english) / 2
def get_name(self):
return self.__name
def get_average(self):
return self.__average
このコードでは、name
、japanese
、english
、average
という属性とcalculate_average
というメソッドがダブルアンダースコア(__)で始まる名前になっています。これはこれらの属性とメソッドがプライベートであり、クラスの外部から直接アクセスできないことを意味します。
その代わりに、get_name
とget_average
という公開メソッドを提供しています。これらのメソッドを通じて、name
とaverage
の値を取得することができます。このように、属性に対する直接的なアクセスを制限し、メソッドを通じてのみアクセスを許可することでカプセル化を実現しています。
クラスを使用するメリット・使い所
不正なバグを引き起こさせない
「具体例を通してクラスを理解しよう」では「カプセル化」と呼ばれる処理を行いました。再度実装した内容を見ていきましょう。
クラス内に、属性(変数)やメソッド(関数)を記述しています(②の部分)。これにより、属性の値を設定したり、メソッドを呼び出す際(④の処理)は、クラスから作成したインスタンス(③で作成したman1, female1)を必ず経由しなければなりません。
このような仕組みがあると何が良いのでしょうか。それは、クラスの外部から不用意にデータを書き換えられたりすることが防がれ、データに関するバグの発生を抑制することができます。つまり、カプセル化の処理を行うことにより、「データを保護することができる」というメリットが生じます。
共通処理をまとめたい時
関数でいいじゃんと思うかもしれませんが、クラスを使用するともっと大きな枠で処理を共通化する事ができます。
例えば人間クラスというものに人間として共通する処理を定義し、その人間クラスの関数を引き継いで男性クラス、女性クラスを作成する事が可能です。その男性クラスは「人間の共通部分 + 男性として共通する処理」、女性クラスは「人間の共通部分 + 女性として共通する処理」を定義します。そして、男性クラス・女性クラスから実体としての個々の男性・女性を作成する、という事ができます。こういった大元のクラスから共通した機能を引き継いで新しいクラスを作成することを継承と呼びます。
継承による利点は、コードの追加・修正といったメンテナンスがしやすくなったり、コードの可読性が上がり、既存のコードを使いまわす事ができるメリットがあります。
同じ処理でも実体によって使い分けられる
例えば、動物は鳴きますけど動物それぞれでは鳴き声は違います。犬なら「わんわん」と鳴き、牛なら「もーもー」、豚なら「ぶーぶー」といった感じです。
クラスを使うと、大元の「鳴く」という処理は同じだけど、そこから派生した先では違う「鳴き声」を実装したい、という事ができます。こういった呼び出した側は同じ処理でも、命令を受け取った側でそれぞれ異なる動作をすることをポリモーフィズムと言います。
今回紹介した「継承」、「カプセル化」、「ポリモーフィズム」は、クラスを使用することで受けられる大きなメリットであり、重要な概念です。これらの概念を使いこなすためにも、まずは「クラスとは何か」をしっかり理解する必要があります。
Python公式ドキュメントを確認する
Python公式ドキュメントに、クラスに関して説明がされています。 (参照先:Pythonクラスについて)
クラスは、データと機能を束ねる手段を提供します。新しいクラスを作成すると、新しいタイプのオブジェクトが作成され、そのタイプの新しいインスタンスを作成することができるようになります。各クラスインスタンスは、その状態を保持するための属性を持つことができます。また、クラスのインスタンスは、その状態を変更するためのメソッド(クラスによって定義される)を持つことができます。
今回は上記の引用した文が理解できることをゴールとします。以後文中に引用した文がある場合が出てきますが、Python公式ドキュメントからの引用文です。
引用した文は数行と短いですが、これらを理解するには書かれている以上のことを理解する必要があります。今回は実際にPythonでクラスの定義やクラスに関するコーディングを行いながら説明していきます。
クラスの定義方法
クラス定義: 構文ルール
# <>内は自身で実装する
class <クラス名称>:
<クラスの実装内容>
構文ルールに則った簡単なクラス定義
class TestClass:
print("Hello World!!")
上記のように、class
に続けてクラス名、クラスの実装内容を記述するだけでクラス定義ができます。Pythonのクラス名はCapWords 方式(アッパーキャメルケース)で記述するという命名規則がありますので、クラス名は単語の先頭を大文字にして名付ける点に注意しましょう。(Python コーディング規約 PEP 8: クラスの名前)
インスタンスの作成(インスタンス化)と実行
インスタンスの作成は、クラスからオブジェクトを生成することです。クラスは「設計書」のようなもので、クラスという設計書を基にして何か(オブジェクト)を作成していきます。
クラスは、データと機能を束ねる手段を提供します。
上記のように、クラスはあくまで手段を提供するだけで、クラスを定義しただけではまだ実際に何も作られていないということです。インスタンスを作成は以下のように行います。
class TestClass:
print("Hello World!!")
# インスタンスの作成
test1 = TestClass()
test2 = TestClass()
# インスタンスの実行
test1
test2
# 実行結果
# Hello World!!
# Hello World!!
上記のように、「インスタンス = クラス名()
」とするだけでインスタンスの作成が簡単にできます。また、同じクラスから複数のインスタンスを作成することも可能です。
オブジェクトの種類
Pythonはオブジェクト指向のプログラミング言語です。オブジェクト指向とは、データやそれを扱う処理(ソースコード)をまとめて「オブジェクト(物)」として扱う考え方のことです。
オブジェクトはさまざまな実体の総称を指し、インスタンスはクラスを元に作成したオブジェクトの実体のことを指します。以下の図のようにオブジェクト指向言語では、インスタンスもオブジェクトの一部として扱うので、オブジェクトとインスタンスは同じものを指しています。
クラスには、クラスオブジェクトと、インスタンスオブジェクトが存在します。前の節で、test1 = TestClass()
でtest1
というインスタンスを作成しました。インスタンスの作成元のTestClass
にはクラスオブジェクトが代入されていて、test1
にはインスタンスオブジェクトが代入されています。
クラスオブジェクト
クラスオブジェクトは、クラス定義を抜けると生成されるオブジェクトのことです。これは以下の文のことを意味します。
新しいクラスを作成すると、新しいタイプのオブジェクトが作成され、そのタイプの新しいインスタンスを作成することができるようになります
インスタンスオブジェクト
インスタンスオブジェクトは、クラスオブジェクトからインスタンス化されたものを指します。
メソッド
メソッドは簡単に言うと、クラス内に定義された関数のことをメソッドと呼びます。関数とメソッドの違いは以下のようになります。
# 関数
def test_class_function():
print("Hello World!!")
class TestClass:
# メソッド
def test_class_method():
print("Hello World!!")
関数をクラスの中で定義するとメソッドと呼ばれますが、通常の関数とメソッドで異なる点が1点あります。それは、メソッドは必ず1つ以上の引数を持つということです。(参照: Python3.11.0 Method Objects)なので、上記のメソッドの例は引数に何も指定していないので、厳密なメソッドの定義から外れています。
メソッドを定義する際には、必ずself
という引数を指定する必要があります。self
は必ずしもself
でなければいけないというわけではないのですが、Pythonエンジニアの慣例としてself
がデファクトスタンダードになっています。他の人にも理解できるように、基本的にはself
と記述するようにしましょう。
引数にself
を指定した、正しいメソッドの定義が以下になります。
class TestClass:
# 正しいメソッドの定義
def test_class_method(self):
print("Hello World!!")
# インスタンス化
instance = TestClass()
# メソッドの呼び出し
instance.test_class_method()
# 実行結果
# Hello World!!
selfとは何か
なぜメソッドにself
として引数を指定する必要があるのかは理由があります。その理由を、以下のようにクラスの中に2つのメソッドを定義した例から説明していきます。
class TestClass:
# ①test_method1: 引数messageの値を表示する
def test_method1(self, message):
print(message)
# ②test_method2: test_method1を呼び出す
def test_method2(self):
self.test_method1("Hello self!!")
# インスタンス化
instance = TestClass()
# メソッドの呼び出し
instance.test_method2()
# 実行結果
# Hello self!!
①のtest_method1
は第1引数にself
、第2引数にmessage
を指定し、そのmessage
を出力する処理を定義しています。②のtest_method2
は、先ほど定義したtest_method1
を呼び出すメソッドを定義しています。self.test_method1("Hello self!!")
の部分の実行する処理に注目してください。
メソッドの呼び出しには必ず「インスタンス名 .メソッド名()
」という形でインスタンスを指定する必要があります。インスタンス化するのはクラスを基にして行うので、クラス内ではまだインスタンスが作成されていません。そこで、クラス内で別のメソッドを呼び出したい時に仮のインスタンスとして役割を担うのがself
です。つまり、self
はそのクラス自身を表しているということです。
self.test_method1("Hello self!!")
を分かりやすく説明すると、TestClass(self
)の(.
)test_method1
の引数に"Hello self!!"
を指定して呼び出す、ということです。
コンストラクタ
- コンストラクタの構文ルール
# <>内は自身で実装する
def __init__(self, <引数1>, <引数1>, <引数n>)
<実行する処理>
コンストラクタはメソッドの1種で、__init__
(init
の前後に_
(アンダーバー)が2つ)という名前で固定で、1個以上の引数を持つことができます。よく初期化処理が記述されます。
コンストラクタが実行された際の流れを以下のコードで説明します。
class TestClass:
# ①実行されると変数に値が代入される
def __init__(self):
self.year = 2022
self.greeting = "Hello Constructor!!"
# ②変数の値を出力するメソッド(変数の値はコンストラクタに記述されている)
def print_constructor(self):
print(self.year)
print(self.greeting)
# ③以下の処理の時にコンストラクタが実行される
instance = TestClass()
instance.print_constructor()
# 実行結果
# 2022
# Hello Constructor!!
インスタンス化されたinstance
で②のメソッドを実行すると、コンストラクタで代入した値が出力されました。コンストラクタは、③でinstance
をインスタンス化した際に実行されます。コンストラクタは、通常のメソッドのように自分で呼び出す必要はなく、インスタンスが呼び出された時点(③の処理)で自動的に、かつ1度だけ実行されます。これがコンストラクタの概念です。
クラス変数とインスタンス変数
クラスの中で利用できる変数は、大きく2種類に分けられます。1つはクラス変数で、もう1つがインスタンス変数です。
クラス変数
クラス変数とは、クラス内で定義する変数のことです。クラス内で定義した変数の値は、全てのインスタンスで共通の値を参照します。
class TestClass:
morning = "Good Morning!!" # クラス変数
def greeting(self):
print(self.morning)
instance1 = TestClass() # インスタンス1
instance1.greeting()
instance2 = TestClass() # インスタンス2
instance2.greeting()
# 実行結果: Good Morning!!
# 実行結果: Good Morning!!
上記のmorning
がクラス変数に当たります。異なるインスタンス(インスタンス1およびインスタンス2)でも同じ結果が出力されます。
インスタンス変数
インスタンス変数とは、メソッド内で定義する変数のことです。インスタンス変数はインスタンスごとに異なる値を指定することができます。
class TestClass:
# コンストラクタ
def __init__(self, greeting):
# ①インスタンス変数の初期化
self.greeting = greeting
# ②
instance1 = TestClass("Hello!!")
print(instance1.greeting)
# ③
instance2 = TestClass("Good Evening!!")
print(instance2.greeting)
# 実行結果: Hello!!
# 実行結果: Good Evening!!
インスタンス変数はメソッドの直下でself
の値として定義します。上記の例で言うと、コンストラクタ内のself.greeting
という変数のことをインスタンス変数と呼びます。
今回定義したTestClass
のコンストラクタは、①でgreeting
という引数を取り、その引数で変数であるself.greeting
を初期化しています。②と③でインスタンス化させる際に、greetingに渡す値を変える事で、それぞれのインスタンスが持つ値を変えることが可能です。
属性
オブジェクトが持つ値のことを属性と言います。オブジェクトが持つ値とは、上記で説明したクラス変数とインスタンス変数のことです。具体的に言うと、クラスの中にある関数や変数などのことを指します。これが以下の文のことを指します。
各クラスインスタンスは、その状態を保持するための属性を持つことができます。
インスタンスはメソッドを利用し、属性を変更することができます。その例を以下に挙げます。
class Human:
pass
human = Human()
human.name = "Mike"
human.age = 20
human.height = 180
print("名前:", human.name, "年齢:", human.age, "身長:", human.height, "cm")
# 実行結果
# 名前: Mike 年齢: 20 身長: 180 cm
今回はイメージのためにクラスの中身はpass
にしています。インスタンス化したhuman
の状態が段々と増えていくのが分かります。これが以下の文のことを意味します。
クラスのインスタンスは、その状態を変更するためのメソッド(クラスによって定義される)を持つことができます。
まとめ
今回はPythonの「クラスとは何か」をPython公式ドキュメントと合わせて説明しました。
基礎を押さえておくことは「カプセル化」、「継承」、「ポリモーフィズム」などの概念を学ぶ上でとても重要です。クラスをするメリットを活かすためにも、「クラスとは何か」をしっかり押さえておきましょう。
【番外編】USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
プログラミング塾に半年通えば、一人前になれると思っているあなた。それ、勘違いですよ。「なぜ間違いなの?」「正しい勉強法とは何なの?」ITを学び始める全ての人に知って欲しい。そう思って書きました。是非読んでみてください。
「フリーランスエンジニア」
近年やっと世間に浸透した言葉だ。ひと昔まえ、終身雇用は当たり前で、大企業に就職することは一種のステータスだった。しかし、そんな時代も終わり「優秀な人材は転職する」ことが当たり前の時代となる。フリーランスエンジニアに高価値が付く現在、ネットを見ると「未経験でも年収400万以上」などと書いてある。これに釣られて、多くの人がフリーランスになろうとITの世界に入ってきている。私もその中の1人だ。数年前、USBも知らない状態からITの世界に没入し、そこから約2年間、毎日勉学を行なった。他人の何十倍も努力した。そして、企業研修やIT塾で数多くの受講生の指導経験も得た。そこで私は、伸びるエンジニアとそうでないエンジニアをたくさん見てきた。そして、稼げるエンジニア、稼げないエンジニアを見てきた。
「成功する人とそうでない人の違いは何か?」
私が出した答えは、「量産型エンジニアか否か」である。今のエンジニア市場には、量産型エンジニアが溢れている!!ここでの量産型エンジニアの定義は以下の通りである。
比較的簡単に学習可能なWebフレームワーク(WordPress, Rails)やPython等の知識はあるが、ITの基本概念を理解していないため、単調な作業しかこなすことができないエンジニアのこと。
多くの人がフリーランスエンジニアを目指す時代に中途半端な知識や技術力でこの世界に飛び込むと返って過酷な労働条件で働くことになる。そこで、エンジニアを目指すあなたがどう学習していくべきかを私の経験を交えて書こうと思った。続きはこちらから、、、、
エンベーダー編集部
エンベーダーは、ITスクールRareTECHのインフラ学習教材として誕生しました。 「遊びながらインフラエンジニアへ」をコンセプトに、インフラへの学習ハードルを下げるツールとして運営されています。
関連記事
2020.02.25
完全未経験からエンジニアを目指す爆速勉強法
USBも知らなかった私が独学でプログラミングを勉強してGAFAに入社するまでの話
- キャリア・学習法
- エンジニア
2022.11.13
【初心者入門】JavaのList(配列)について
こちらの記事では、プログラミング言語Javaの配列操作について解説します。
- プログラミング
2022.11.30
【初心者入門】Python printの使い方について
こちらの記事では、Pythonのprintの使い方について解説します。
- プログラミング
2024.06.17
Googleスプレッドシートでデータを口座ごとに分けて合計金額を表示する方法
この記事では、Googleスプレッドシートを使用してデータを口座ごとに分け、各シートの最後の行に月間および年間の合計金額を表示する方法を説明します。スクリプトを使用して自動化することで、手作業でのミスを防ぎ、効率的に作業を進めることができます。
- プログラミング
- gas