クラスってすごく分かりずらいですよね。
こんにちは、宇宙業界で活躍する機械学習エンジニアを目指しているRingojamです。
Pythonを学びはじめ、クラスで大きくつまずく方多くのではないでしょうか?
僕自身はじめ見た時、全然理解できませんでした。
改めてクラスを深く理解できていないなと感じたため、本気で調べ時間をかけて理解しこの記事にまとめさせていただきました。
この記事の対象者
- そもそもクラスとは?を学びたい方
- Pythonを学習している初心者の方
Pythonのクラスでつまずく駆け出しエンジニアのみなさんにこの学びを共有できたら幸いです。
早速いっしょに学んでいきましょう!
目次
1. まず、クラスなしで書いてみる
「ユーザー管理」を例に考えてみましょう。
ユーザーには 名前・年齢・メールアドレス という情報があり、「挨拶する」「メールを送る」という操作があるとします。
クラスを使わずに書くと、こうなります。
# ユーザーの情報を変数で管理
name = "りんごじゃむ"
age = 24
email = "ringojam@example.com"
# 操作は関数として別で定義
def greet(name):
print(f"こんにちは、{name}さん")
def send_email(email, message):
print(f"{email}に送信: {message}")
greet(name)
send_email(email, "登録ありがとうございます")
一人のユーザーならこれで十分に見えます。
しかし、ユーザーが2人になったら?
# ユーザー1
name1 = "りんごじゃむ"
age1 = 24
email1 = "ringojam@example.com"
# ユーザー2
name2 = "たろう"
age2 = 25
email2 = "taro@example.com"
def greet(name):
print(f"こんにちは、{name}さん")
def send_email(email, message):
print(f"{email}に送信: {message}")
greet(name1)
send_email(email1, "登録ありがとうございます")
greet(name2)
send_email(email2, "登録ありがとうございます")
変数が一気に増えました。ユーザーが10人、100人になったら、変数の数は爆発的に増えていきます。
さらに 「このemailはどのユーザーのもの?」 という問題も生まれます。データと処理がバラバラに存在しているため、管理が難しくなるのです。
2. クラスで書き直してみる
クラスを使うと、同じ処理をこう書けます。
class User:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def greet(self):
print(f"こんにちは、{self.name}さん")
def send_email(self, message):
print(f"{self.email}に送信: {message}")
# ユーザーを作成
user1 = User("りんごじゃむ", 24, "ringojam@example.com")
user2 = User("たろう", 25, "taro@example.com")
user1.greet()
user1.send_email("登録ありがとうございます")
user2.greet()
user2.send_email("登録ありがとうございます")
出力結果:
こんにちは、りんごじゃむさん
ringojam@example.comに送信: 登録ありがとうございます
こんにちは、たろうさん
taro@example.comに送信: 登録ありがとうございます
ユーザーのデータを管理するところすごくすっきりしたのがわかるでしょうか?
# クラスなし
name1 = "りんごじゃむ"
age1 = 24
email1 = "ringojam@example.com"
name2 = "たろう"
age2 = 25
email2 = "taro@example.com"
これが
# クラスあり
user1 = User("りんごじゃむ", 24, "ringojam@example.com")
user2 = User("たろう", 25, "taro@example.com")
こうです。
1人1行で管理できるようになっていますね、これが何万人ものユーザーを管理するともなるとクラスを使った方が断然楽になりそうです。
3. クラスの基本構造を理解する
では今回のユーザー管理の例で用いたクラスのコードからクラスの基本構造について理解しましょう。
class User: # ① クラスの定義
def __init__(self, name, age, email): # ② 初期化メソッド
self.name = name # ③ データをインスタンスに保存
self.age = age
self.email = email
def greet(self): # ④ メソッドの定義
print(f"こんにちは、{self.name}さん")
① class User: — 設計図を作る
class は「設計図」を定義するキーワードです。User はその設計図の名前です。
② __init__ — インスタンスが作られるときに自動で呼ばれる
__init__ は 初期化メソッド と呼ばれる特殊なメソッドです。
この下にあるコードの時、インスタンスというものが作られます。新しくユーザーを追加したということですね。この時__init__が作動します。
user1 = User("りんごじゃむ", 24, "ringojam@example.com")
# ↑ この引数が __init__ に渡される
もちろん別のインスタンス(user2のような)が作られた際にはこれがまた作動します。
③ self.name = name — データをインスタンスに保存する
ここがクラスの一番の肝です。
def __init__(self, name, age, email):
self.name = name # 右辺のnameは引数(一時的な値)
# 左辺のself.nameはインスタンスに保存された値
右辺の name は引数として渡された一時的な値で、__init__ が終わると消えます。
一方、self.name はインスタンス自身に紐づいた値で、インスタンスが生きている間ずっと保持されます。だから後から greet() の中でも self.name にアクセスできるのです。
④ def greet(self): — メソッドの定義
クラスの中で定義された関数を メソッド と呼びます。
これはただの呼び名なので覚えましょう。基本は普通の関数と同じです。クラスの中にある関数がメソッドです。
self という引数が必ず先頭にあることに気づいたでしょうか。これについては次のセクションで詳しく説明します。
4. self の正体
self は 「このメソッドを呼び出したインスタンス自身」 を指します。
ここすごくわかりずらいですよね。ゆっくり理解していきましょう。
user1.greet() と書いたとき、Pythonは内部でこう解釈しています。
user1.greet()
# ↓ Pythonが内部でこう解釈する
User.greet(user1)
つまり、.の左側にあるインスタンスが自動的に self として渡されるのです。
だから greet() を呼び出すときに引数を何も渡さなくても、メソッドの中で self.name にアクセスできるわけです。
複数のインスタンスで確認すると、より明確にわかります。
user1 = User("りんごじゃむ", 24, "ringojam@example.com")
user2 = User("たろう", 25, "taro@example.com")
user1.greet() # selfはuser1 → "こんにちは、りんごじゃむさん"
user2.greet() # selfはuser2 → "こんにちは、たろうさん"
同じ greet メソッドなのに、誰が呼んだかによって結果が変わる。 これが「データと処理がセットになっている」という意味です。
5. 複数インスタンスで真価を発揮する
クラスを使うと、同じ構造のオブジェクトを何個でも簡単に作れます。
users = [
User("りんごじゃむ", 24, "ringojam@example.com"),
User("たろう", 25, "taro@example.com"),
User("はなこ", 22, "hana@example.com"),
]
for user in users:
user.greet()
user.send_email("登録ありがとうございます")
出力結果:
こんにちは、りんごじゃむさん
ringojam@example.comに送信: 登録ありがとうございます
こんにちは、たろうさん
taro@example.comに送信: 登録ありがとうございます
こんにちは、はなこさん
hana@example.comに送信: 登録ありがとうございます
クラスを使わない場合と比べてみましょう。
| クラスなし | クラスあり | |
|---|---|---|
| ユーザーが増えたとき | 変数が name3, email3... と増え続ける |
インスタンスを1行追加するだけ |
| データの管理 | どの変数がどのユーザーのものか追いにくい |
user1.name のように明確 |
| 処理の呼び出し | 毎回引数にデータを渡す必要がある |
user1.greet() と呼ぶだけ |
6. まとめ
クラスの本質的な価値は 「データ」と「そのデータに関する処理」をセットで管理できること です。
クラス = データ(属性)+ 処理(メソッド)をまとめた設計図
インスタンス = その設計図から作られた実体
self = 「このメソッドを呼んだインスタンス自身」
関数 vs クラスの使い分け
クラスは万能ではありません。次のような基準で使い分けると良いでしょう。
- 関数で十分なとき → 1回しか使わない処理、データとの関係が薄い処理
- クラスを使うべきとき → 同じ構造のものが複数登場するとき、データと処理を一緒に管理したいとき
最初はやっかいだけど、慣れてくるとないと困る存在のクラス。クラスの理解は教科書的に習うよりも、クラスが使われているコードをたくさん読んでなれることだと思います。Githubなどで自分が作ってみたいものに近いプロダクトのコードを見つけてコードを観察してみましょう。わからないことがあったらどんどん素直な疑問でもAIに聞いてみてください。理解が深まると思います!
私も早くクラスとお友達になれるようにがんばります!一緒にがんばりましょう!!
参考
この記事で使ったコードをまとめると以下の通りです。
class User:
def __init__(self, name, age, email):
self.name = name
self.age = age
self.email = email
def greet(self):
print(f"こんにちは、{self.name}さん")
def send_email(self, message):
print(f"{self.email}に送信: {message}")
# インスタンスを作成
user1 = User("りんごじゃむ", 24, "ringojam@example.com")
user2 = User("たろう", 25, "taro@example.com")
# メソッドを呼び出す
user1.greet()
user2.greet()