【ご挨拶】こんにちは! ぬかさんエンジニアリングです。(2回目‼)
前回の投稿にありがたいことにコメントを頂きまして、じゃんけんプログラムをより簡潔に書ける方法を教えていただきました。
今回はそれらの内容も含めてじゃんけんプログラムを関数化、そしてクラス化というように進化させていく内容になっています!
普段クラスから作っているという方も、順を追ってみていくことで関数とクラスの便利さに気づけるような構成になっていますので最後まで見て頂けると嬉しいです!
LGTMも是非宜しくお願い致します‼
本シリーズ初めての方へ
【趣旨】
Python初心者プログラマーが入門書で学んだ知識を実践的なコーディングを通じて身に着けていくためのお題を提供します。 お題を基に各自コーディングに挑戦していただいた後、この記事でコーディングの過程を答え合わせします。【対象】
Pythonの入門書を読んで理解はしたけど何か目的をもって実践的にコーディングをしたい方。 ProgateでPythonコースをLv5まで勉強したけど応用力が身に着いていないと感じている方。【初心者とは】
この記事シリーズでの初心者は下記の項目を理解済みであることが目安となっています。 [演算子, 標準ライブラリ, 条件分岐, 繰り返し処理, 例外処理, リスト, タプル, セット, 辞書, オブジェクト指向]【利用ライブラリ & Python3 --version】
Colaboratory へようこそ ←ここからリンクへ飛ぶ
・Python 3.7.12
$$$$
それではさっそく本題に入っていきましょう
第【02】回 -じゃんけんプログラムをクラス化する-
このシリーズ第二回目の今回は、前回のじゃんけんプログラムを応用してじゃんけんプログラムのクラス化をしていきます!
前回のコードをさらに簡潔化させた上で、前回利用しなかったユーザー定義関数とクラスを用いることでその便利さを皆さんと一緒に体感していきたいと思います。
【お題】じゃんけんプログラムを関数とオブジェクト指向を使ってクラス化してみよう!
janken()
# 出力
-------------------------------------------------------
じゃんけんの選択肢:{0: 'グー', 1: 'チョキ', 2: 'パー'}
-------------------------------------------------------
あなたの出す手を入力してください(整数:0, 1, 2) > 1
コンピュータの手:パー
あなたの手:チョキ
<><><><><><><><><>
勝敗結果:勝ち
<><><><><><><><><>
1.前回【01】で作った各機能をそれぞれ関数化してください。
2.Computerクラス
を作成してじゃんけんの手を選んでください。
3.Humanクラス
を作成してじゃんけんの手を選んでください。
4.Judgeクラス
を作成して勝負を判定してください。
5.上記の出力が得られるようなjankenメソッド
を作成して実行してください。
〔お助けヒント〕
**ヒント1**
クラスの中身を書くには、前回投稿分の[解答](https://qiita.com/Nuka-san-Engineering/items/d28d82df21fcdab49f07#%E8%A7%A3%E7%AD%94)を参考にして下さい。**ヒント2**
クラスの中身は1で関数化した内容をそのまま転用する形で作れます。**ヒント3**
関数化する際に、引数と返り値の存在によるコードの変更が必要であることに気を付けてください。**ヒント4**
クラス化する際に、初期化メソッドやインスタンス変数、インスタンスメソッドの存在によるコードの変更が必要であることに気を付けてください。【解答】
解答は以下の通りです。
※この解答はあくまで私の回答です。各々の方法でお題が解けていればそれで全然かまいません。むしろ、もっと簡潔な方法があればご教示頂けると助かります
import random
# じゃんけんの手の辞書を定義
d = {1:"グー",2:"チョキ",3:"パー"}
hand_key = tuple(d.keys())
# コンピュータクラス(コンピュータのじゃんけんの手を返り値で返す)
class Computer:
def pon(self, hand_key):
return random.choice(hand_key)
# ヒューマンクラス(プレイヤーのじゃんけんの手を返り値で返す)
class Human:
def pon(self, hand_key):
while True:
try:
human_hand = int(input("あなたの出す手を入力してください(整数:1, 2, 3) > "))
if human_hand in hand_key:
return human_hand
except:
print("整数1, 2, 3を入力をして下さい")
# ジャッジクラス(選手の出した手を記録、勝敗を判定)
class Judge:
def __init__(self, computer_hand, human_hand):
self.computer_hand = computer_hand
self.human_hand = human_hand
def judge(self):
result = False
if self.human_hand == self.computer_hand:
return "引き分け"
else:
if self.human_hand == 1 and self.computer_hand == 2:
result = True
elif self.human_hand == 2 and self.computer_hand == 3:
result = True
elif self.human_hand == 3 and self.computer_hand == 1:
result = True
if isinstance(result, bool):
return "勝ち" if result else "負け"
# じゃんけんの実施&出力
def janken():
print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55)
computer1 = Computer()
computer1_hand = computer1.pon(hand_key)
human1 = Human()
human1_hand = human1.pon(hand_key)
print(f"コンピュータの手:{d[computer1_hand]}")
print(f"あなたの手:{d[human1_hand]}")
hands = Judge(computer1_hand, human1_hand)
result = hands.judge()
print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6)
# じゃんけんメソッドを実行
janken()
# 出力
-------------------------------------------------------
じゃんけんの選択肢:{1: 'グー', 2: 'チョキ', 3: 'パー'}
-------------------------------------------------------
あなたの出す手を入力してください(整数:1, 2, 3) > 2
コンピュータの手:パー
あなたの手:チョキ
<><><><><><><><><>
勝敗結果:勝ち
<><><><><><><><><>
【解説】
※本解説の中で使われる用語に関する補足事項
メソッド == 関数
初期化メソッド == コンストラクタ
インスタンス変数 == メンバ変数
〔クラスの概念〕
プログラミングで使う便利なお道具箱の枠組みは図の通り5つの入れ子構造になっており、大きい順からライブラリ、パッケージ、モジュール、クラス、メソッドとなっています。
プログラミングを勉強しているとどの言葉も何となく聞く機会が有るかと思いますが、実際はこのような構造になっています。
モジュールは一つ一つのPythonファイルのことで、その中にクラスやメソッドが入っていると考えるとイメージがしやすいかと思います。
他の人が作ってくれたお道具箱の中から必要な部分だけ自分のプログラムに取り込むことでコーディングを効率化できたり、自分が作ったお道具箱をオープンソースで使ってもらえたりと、このお道具箱を通してエンジニア同士の相互扶助が行われています。
では、そんなお道具箱の中でクラスの立場はどうなのかと言うと、なにやら変数とメソッドをまとめている存在みたいです。これがどんな意味を表しているのかはオブジェクト指向を学べば分かりますが、一言で言うと他の人が見ても分かりやすくなるように属性や行動をまとめた「モノ」です。もう少し詳しくクラスを説明するならば、ある「モノ」の「属性」を変数という形で、「モノ」がする「行動」をメソッドという形でまとめている「モノ」の設計資料です。
上記の説明と英語の知識で**「クラス」=「モノ」=「オブジェクト」だと分かれば、オブジェクト指向はクラスを使ってコーディングすることを目的とした考えであることも分かります。
オブジェクト指向を使えばコードを「モノ」ごとに仕分けしてまとめて分かりやすくできるので、大人数を要するプロジェクトでも共通認識を持ちやすく現在幅広く使われています。
(オブジェクト指向の目的である分かりやすさのために「カプセル化」「疎結合」「ポリモーフィズム(多態性)」**という考え方に基づいた方法を意識してコーディングする必要がありますが、詳しくは次回以降機会があれば書いてみます。気になる方はQiitaにも詳しい記事がありますのでご自分で調べてみてください。)
※ここからはクラス内のメソッドを「インスタンスメソッド」、クラス内の変数を「インスタンス変数」と表記して一般的なメソッドと変数と区別します。
話はクラスに戻りますが、その設計資料であるクラスから実物を作ることをインスタンスを作成すると言い、作成したインスタンスがする「行動」をインスタンスメソッドを使って実行します。
1つのクラスから複数のインスタンスを作ることも可能で、各インスタンスごとに「属性」だけをカスタマイズすることによって似たようなインスタンスを簡単に作ることができます。
よく、車についての設計資料から色々な車種を作り出せることに例えられたりします。
今回のじゃんけんプログラムで例えると、じゃんけんプレイヤーの設定資料であるHuman
クラスからインスタンスhuman1
を作成し、じゃんけんの手を出すpon
インスタンスメソッドを実行します。
human1の属性として「名前」の変数を設定して「たけし」というデータを入れてあげると、human1は「たけし」というHumanとしてプログラム的にもプログラマー的にも認識することができます。human2, human3を作ってにもそれぞれ名前を付けてあげると、同じHumanクラスという設計資料から複数の人間を作り出したことになります。
ちなみに、インスタンスは英語に直訳すると「実例」という意味になります。クラスという設計資料から作った実例がインスタンスだと思えば謎の存在ではなくなるはずです。
まとめると、クラスとはモジュールの中にあるもので、ある「モノ」の「属性」と「モノ」がする「行動」をまとめている設計資料的存在と言うことになります。
今回は分かりやすさに重点を置いて説明したので厳密性には少々欠けていると思います。今後別の記事でオブジェクト指向についてしっかり書きたいと考えていますのでまたの機会に。
〔作り方〕
オブジェクトの仕様(クラスの中身)にはインスタンス変数やインスタンスメソッドを入れるのが一般的です。
前回はあえてユーザー定義関数を使わずに各機能をコーディングしていきました。それは、今回こうして必要な時にユーザー定義関数を使ってクラスの中身を作って頂き、同時にクラスと関数の便利さを体感して頂きたかったからです。
コード自体はそこまで前回と変わることはありません。しかし、クラスや関数にすることで得られるメリットはたくさんあります。そんなお得な関数を一緒に作っていきましょう。
それではさっそく作り方をご紹介していきます。
①各機能の処理をユーザー定義関数に変換
import random
# じゃんけんの手の辞書を定義
d = {1:"グー",2:"チョキ",3:"パー"}
my_hand_key = tuple(d.keys())
# コンピュータの手アルゴリズム
def computer_hand_func():
return random.choice(my_hand_key)
# 入力(自分の手)アルゴリズム
def my_hand_func():
while True:
try:
my_hand = int(input("あなたの出す手を入力してください(整数:1, 2, 3) > "))
if my_hand in my_hand_key:
return my_hand
except:
print("整数1, 2, 3を入力をして下さい")
# 勝敗判定アルゴリズム
def judge(computer_hand, my_hand):
result = False
if my_hand == computer_hand:
return "引き分け"
else:
if my_hand == 1 and computer_hand == 2:
result = True
elif my_hand == 2 and computer_hand == 3:
result = True
elif my_hand == 3 and computer_hand == 1:
result = True
if isinstance(result, bool):
return "勝ち" if result else "負け"
def janken():
#出力
print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55)
computer_hand = computer_hand_func()
my_hand = my_hand_func()
result = judge(computer_hand, my_hand)
print(f"コンピュータの手:{d[computer_hand]}")
print(f"あなたの手:{d[my_hand]}")
print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6)
# janken関数を呼び出す
janken()
パッと見てどうでしょう。あまり変化したように見えないんじゃないでしょうか。その通りでほとんどコードは変わっていません。
基本的に、各機能の処理の最終的な出力を変数に代入して定義するのではなく、return
で返り値として返す方法に変更しているだけです。
しかし、これのみの変更でありながら関数にすることで得られるメリットはたくさんあります。
⑴毎回コードを書かずとも、関数名()
と書くだけで同じ処理ができるところ
一度関数を設定してしまえば同じ処理を短い関数名で呼べるので圧倒的な時間の短縮にもなりますし、他の人も使いやすくなります。
例えると、ピカソに絵を描いてほしい時に毎回本名で**「パブロ・ディエゴ・ホセ・フランシスコ・デ・パウラ・ホアン・ネポムセーノ・マリーア・デ・ロス・レメディオス・クリスピン・クリスピアーノ・デ・ラ・サンディシマ・トリニダード・ルイス・イ・ピカソ」って呼ぶのがだるすぎるから「ピカソ」**だけで呼べるようにしちゃおうぜっ!ということです。覚えやすいし使いやすいです。
⑵処理の具体的な処理内容を理解していなくとも、引数のルールと返り値(出力結果)の意味さえ分かっていれば簡単に使えてしまうところ
組み込み関数(Pythonで最初から使える関数)の処理の具体的な処理内容ってPython教材でもあまり載っていませんよね?それよりはどういう引数を入れたらどういう返り値が帰ってきてどういう時に使えるかの方が詳しく載っていますよね。つまり、関数の具体的な処理内容は関数の使い手にっとってそれほど気にするポイントではないということです。気にしなくてもしっかり動いてくれる関数くんすごい。
⑶規模の大きいプログラムにおいて、各所で何の機能が動いているのか分かりやすく可読性向上になるところ
プログラムの規模が大きくなるとコードがずらあーーっと数千数万並んでいて何が何だか分からなくなってしまうなんてことがあってもおかしくありません。そんな中からいちいち「何行目から何行目は○○の処理」「何行目から何行目は△△の処理」と見ていてはキリがありません。だからこそ、コードを切り分けて各処理ごとに関数でまとめることで分かりやすくするのです。特に大規模なプログラムはチームで作るので、他人が見てもわかりやすくするために関数にまとめるのは大切です。
関数のメリットは他にも細かくあるとは思いますがざっくりとこんなものです。これらはクラスにも適応できる考え方です。クラスは「インスタンス」を作成して属性となる変数やメソッドをまとめて簡潔に呼び出せるようになっているので⑴が適応されますし、規模が大きいときにまとまりがあって可読性が上がるので⑶も適応されます。⑵は、関数よりはクラスの方が中身を理解していないと使えません。
とにかくまとめて読みやすさをアップしましょう。
関数のメリットを熱くご紹介した上で、今回のじゃんけんプログラムを見てみましょう。
ちゃんと関数の恩恵を受けていますよ!何故ならjanken()
とコードを書いて実行するだけでいつでもどこでもじゃんけんが出来るようになったじゃないですか!実感が湧かない人は以下の「関数化による変更点の詳細情報」を見ながら実際に手を動かして感動してみてください!
**関数化による変更点の詳細情報**
◆importの場所について変更
# 変更前
# じゃんけんの手の辞書を定義
d = {1:"グー",2:"チョキ",3:"パー"}
my_hand_key = tuple(d.keys())
# コンピュータの手アルゴリズム
import random #<<<<<<<<<<
def computer_hand_func():
return random.choice(my_hand_key)
# 変更後
import random #<<<<<<<<<<
# じゃんけんの手の辞書を定義
d = {1:"グー",2:"チョキ",3:"パー"}
my_hand_key = tuple(d.keys())
# コンピュータの手アルゴリズム
def computer_hand_func():
return random.choice(my_hand_key)
基本的にクラスやメソッドの中にモジュールやライブラリはimportしません。
特別な理由がない限りはそうしてください。
◆コンピュータの手アルゴリズムを一部変更
# 変更前
# コンピュータの手アルゴリズム
def computer_hand_func():
return random.randint(1, 3) #<<<<<<<<<<
# 変更後
# コンピュータの手アルゴリズム
def computer_hand_func():
return random.choice(my_hand_key) #<<<<<<<<<<
辞書キーの値の変更、追加に対して自動的に対応できるようにrandint()
ではなくchoice()
に変更します。choice()
は引数としてシーケンスを受け取り、そのシーケンスの中からランダムに要素を返します。
シーケンスが変われば自動的にコンピュータの手も変更されるためプログラムの頑健性が上がります。
補足:シーケンスとは
リストやタプルなどのオブジェクトは、コレクションの一種で、他のオブジェクトを登録し、集約できるオブジェクトです。
シーケンスとは、コレクションのうちで、集約する要素が一定の順序で並んでいて、その順序(インデックス)を使ってその要素を指定できる種類のオブジェクトのことを指します。
ざっくりまとめるとインデックスで要素を指定できるリストやタプルのこと
python Japan シーケンス←参考資料
◆入力アルゴリズムを一部変更
# 変更前
# 入力(自分の手)アルゴリズム
def my_hand_func():
my_hand = 999 #<<<<<<<<<<
while my_hand not in my_hand_key: #<<<<<<<<<<
my_hand = input("あなたの出す手を入力してください(整数:1, 2, 3) > ")
try:
my_hand = int(my_hand)
except:
print("整数1, 2, 3を入力をして下さい")
# 変更後
# 入力(自分の手)アルゴリズム
def my_hand_func():
while True:
try:
my_hand = int(input("あなたの出す手を入力してください(整数:1, 2, 3) > "))
if my_hand in my_hand_key:
return my_hand
except:
print("整数1, 2, 3を入力をして下さい")
正しく入力出来ればじゃんけんの手を返り値で返してwhile処理を抜けだし、間違った入力で例外処理されれば正しい入力を促す文章を出力した後while文を繰り返す。
try文中には間違った入力では絶対エラーになる様に例外の余地が無い文を書いてください。
◆出力部分をjanken関数として定義する。
# 変更前
# 出力
print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55)
computer_hand = computer_hand_func() #<<<<<<<<<<
my_hand = my_hand_func() #<<<<<<<<<<
result = judge(computer_hand, my_hand)
print(f"コンピュータの手:{d[computer_hand]}")
print(f"あなたの手:{d[my_hand]}")
print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6)
# 変更後
def janken():
#出力
print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55)
computer_hand = computer_hand_func() #<<<<<<<<<<
my_hand = my_hand_func() #<<<<<<<<<<
result = judge(computer_hand, my_hand)
print(f"コンピュータの手:{d[computer_hand]}")
print(f"あなたの手:{d[my_hand]}")
print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6)
jankenメソッドを定義すれば、janken()を入力するだけで「じゃんけんプログラム」が実行されます。
関数名と関数を代入する変数名が同じだと、グローバル変数を関数内で使ってしまうことになりErrorになるので、関数名を一部変更する。(変数名を変更してもよい。異なっていれは大丈夫)
以上が関数化に際する詳細な変更点になります。
②じゃんけんプログラムを「人間」「コンピュータ」「判定員」の3つのクラスに仕分けして、jankenメソッドでじゃんけんを実行
import random
# じゃんけんの手の辞書を定義
d = {1:"グー",2:"チョキ",3:"パー"}
hand_key = tuple(d.keys())
# コンピュータクラス(コンピュータのじゃんけんの手を返り値で返す)
class Computer:
def pon(self, hand_key):
return random.choice(hand_key)
# ヒューマンクラス(プレイヤーのじゃんけんの手を返り値で返す)
class Human:
def pon(self, hand_key):
while True:
try:
human_hand = int(input("あなたの出す手を入力してください(整数:1, 2, 3) > "))
if human_hand in hand_key:
return human_hand
except:
print("整数1, 2, 3を入力をして下さい")
# ジャッジクラス(選手の出した手を記録、勝敗を判定)
class Judge:
def __init__(self, computer_hand, human_hand):
self.computer_hand = computer_hand
self.human_hand = human_hand
def judge(self):
result = False
if self.human_hand == self.computer_hand:
return "引き分け"
else:
if self.human_hand == 1 and self.computer_hand == 2:
result = True
elif self.human_hand == 2 and self.computer_hand == 3:
result = True
elif self.human_hand == 3 and self.computer_hand == 1:
result = True
if isinstance(result, bool):
return "勝ち" if result else "負け"
# じゃんけんの実施&出力
def janken():
print("-"*55 + f"\nじゃんけんの選択肢:{d}\n" + "-"*55)
computer1 = Computer()
computer1_hand = computer1.pon(hand_key)
human1 = Human()
human1_hand = human1.pon(hand_key)
print(f"コンピュータの手:{d[computer1_hand]}")
print(f"あなたの手:{d[human1_hand]}")
hands = Judge(computer1_hand, human1_hand)
result = hands.judge()
print("<>"* 6 + f"\n勝敗結果:{result}\n" + "<>"* 6)
# じゃんけんメソッドを実行
janken()
⑴じゃんけんの手の辞書をクラスの外で定義
じゃんけんの手の辞書d = { 0:"グー", 1:"チョキ", 2:"パー"}
はpon
インスタンスメソッドの引数として利用します。
詳細は後ほど。
⑵コンピュータクラスを作成
このクラスの「モノ」はコンピュータです。
「属性」は今回はありません。(下記の応用編で属性として名前を使います。)
「行動」は"じゃんけんの手を選択して出す"という内容をpon
インスタンスメソッドにまとめています。
⑶ヒューマンクラスを作成
このクラスの「モノ」はヒューマンです。
「属性」は今回はありません。(下記の応用編で属性として名前を使います。)「行動」は"じゃんけんの手を選択して出す"という内容をpon
インスタンスメソッドにまとめています。
⑷ジャッジクラスを作成
このクラスの「モノ」はジャッジ(判定員)です。
「属性」は"コンピュータのじゃんけんの手"と"ヒューマンのじゃんけんの手"です。
「行動」は"⑵と⑶のじゃんけんの手から⑶の勝敗を判定する"という内容をjudge
インスタンスメソッドにまとめています。
⑸クラスを使ってじゃんけんを実施するjanken
メソッドを作成
このメソッドは"じゃんけんを実施して出力する"という内容をまとめています。
Computer
クラスからcomputer1
インスタンス、Human
クラスからhuman1
インスタンスを作成し、両者のpon
インスタンスメソッドを実行します。
ジャッジクラスのインスタンス作成時に⑵と⑶のpon
インスタンスメソッドを実行して得た返り値を引数として受け取り、前者をself.computer_hand
に、後者をself.human_hand
に代入しています。これで⑵と⑶のじゃんけんの手はジャッジクラスにインスタンス変数として保持されます。
Judge
クラスのjudge
インスタンスメソッドを両者のインスタンス変数を使って実行し、結果を返り値として返して出力します。
以上でクラス化は完了です。
②’ 補足情報
⑴特殊メソッド__init__
とは
def __init__(self, 引数):
は、インスタンス作成時に自動的に実行される特殊メソッドで、"初期化メソッド"と言われます。ちなみにinit
には"最初の"と言う意味があります。
⑵インスタンス変数の使い方
インスタンス作成時にクラス名()の()から引数を受け取ることができます。受け取った引数をself.引数名
に代入することでインスタンス変数として扱われ、クラス内ならインスタンスメソッド間を跨いでどこでも使うことが出来るようになります。ここまでの一連の流れをインスタンス変数の初期化と言います。
また、クラスの外側でもインスタンス名.インスタンス変数名
と書けば参照と上書きが可能です。
クラスを使ったじゃんけんプログラミングの作り方の解説は以上です。
クラスの分かりやすさは実感できたでしょうか。Pythonでプログラミングをするのであればクラスを無視することは出来ませんので皆さん各自復習や応用を行ってみてください。
【終わりに】
今回は、前回作ったじゃんけんプログラムを関数に変換した後クラス化するというものでした。いかがっだったでしょうか。
オブジェクト指向に慣れている方は最初からクラスを作ればいいじゃないという発想に至ったかもしれません。
ただ、オブジェクト指向は勉強したけどいまいちメソッドやクラスについて何が便利か分からなかったという方には、順を追って理解してもらういい機会になったんじゃないでしょうか。
もしもコーディングの部分で間違いなどありましたらコメントでご指摘いただけると幸いです。
また、その他質問などございましたらコメントをお願い致します。
次回は、今回作ったJankenクラスを使った応用編として「大人数でじゃんけんプログラム」を作りたいと思います。下記リンクからどうぞ!
→※ただいま作成中です!お待ちください!
この記事が良かったと感じたらLGTMを宜しくお願いします!それではまた次回!