Python の勉強 スロットゲーム編 〜その12:関数を定義する〜

プログラミング

今回は “関数” について勉強します。数学にも “関数” は登場しますね。
この 2 つの関数を「あ、似てる」と思う人と、「全然、違うじゃん」と思う人と分かれるようです。ですが理系だから前者だとか、前者の方が理解が早いとか、そういうことはありませんので安心して下さい。ここでは、数学とは違う方向から勉強していこうと思います。

そもそもプログラムって何だろう……私は “手順書” だと思います

これまでに何個かプログラムを作成して実行してきました。そもそも、これは Python にとってどういう意味があるものなのでしょうか?Python はプログラムがあれば、その通りに実行してくれます。何回何回も、プログラムに書かれている通りに正確に実行してくれます。つまりプログラムとは Python にとっての “手順書” のようなものなのです。正しい手順書があれば、Python はいつだって正しく応えてくれます。
(”関数” の話はもう少し待って下さい m(_ _;)m 汗)

分かりやすい手順書を作ろう

初期のスロットゲームの内容を一部抜粋して日本語に書き起こしてみましょう。もし単純に Python がやっていることを書き起こすとこんな風になるはずです。

「1回目」と表示する。
「現在の所持コイン数は○枚です。」と表示する。
「掛コイン数を入力して下さい:」と表示し、コイン数の入力を求める。
リールのリストから任意(ランダム)にマークを1つ選んで記録する…を 3 回繰り返す。
記録を表示する。これを今回の結果とする。
結果が「♠♠♠」の場合は配当倍率を 15 倍、「♥♥♥」の場合は配当倍率を 12 倍、………初めの 2 つが「♠♠」の場合は 5 倍、いずれでもない場合は -1 倍とする。
所持コイン数は、元々の所持コイン数と、掛けコイン数に先程の配当倍率を掛け算した結果を足して計算する。
「2回目」と表示する。
「現在の所持コイン数は○枚です。」と表示する。
「掛コイン数を入力して下さい:」と表示し、コイン数の入力を求める。
リールのリストから任意(ランダム)にマークを1つ選んで記録する…を 3 回繰り返す。
記録を表示する。これを今回の結果とする。
結果が「♠♠♠」の場合は配当倍率を 15 倍、「♥♥♥」の場合は配当倍率を 12 倍、………初めの 2 つが「♠♠」の場合は 5 倍、いずれでもない場合は -1 倍とする。
所持コイン数は、元々の所持コイン数と、掛けコイン数に先程の配当倍率を掛け算した結果を足して計算する。
「最後だよ」と表示する。
……(略)

例え間違っていなかったとしても読みづらいですよね。

  • 全体の流れが分かりづらい
  • 同じことが何度も書かれているようだが……本当に同じかどうか確認しないと分からない

この手順書でも Python さんなら実直に対応してくれます。しかし、手順を修正しようと思った時、新しい手順を追加しようとした時、手にとった資料がこれだとガッカリしてしまいそうです。これを改善するためにやることは2つです。

  • まとまった作業に名前を付ける
  • 作業の詳細を別途、記載する

作業に名前、詳細は別途記載した結果

その結果どうなるかと言うと……

<やること>
メッセージ毎(「1回目」「2回目」「最後だよ」)に以下を実施する
  メッセージを表示する
  掛けコインを確認する(詳細は別記)
  スロットの結果を表示する(詳細は別記)
  配当倍率を確認する(詳細は別記)
  所持コイン数を精算する(詳細は別記)


<詳細>
掛けコインを確認する
必要なもの:所持コインの数
成果物:掛けコイン数
  「現在の所持コイン数は○枚です。」と表示する。
  「掛コイン数を入力して下さい:」と表示し、コイン数の入力を求める。

スロットの結果を表示する
必要なもの:(特になし)
成果物:ランダムに選んだ3枚の絵柄
  リールのリストから任意(ランダム)にマークを1つ選んで記録する…を 3 回繰り返す。
  記録を表示する。

……(略)

となります。こちらの方が断然、読みやすいですね!この別記した作業詳細それぞれを “関数” と呼びます。プログラムで関数を使う理由は、全体把握のしやすさにあると私は思います。もちろんこれだけが理由ではありませんし、プログラムに初めて “関数” という概念を取り入れた人が考えたのは全く別の意図があったかも知れません。しかし長年、エンジニアとしてプログラムに触れてきた私の経験からは、この理解で十分だと思っています。

Python で関数を使う上で注意しないといけないことは、上記のように後から詳細を記載するのではなく、先に詳細を記載しておく必要があるということです。Python はプログラムを上から順番に読み取っていきますので、予め「この関数はこういう内容ですよ」ということを教えておかないと、そんなの知らないよ〜となってしまうのです。

ちなみに改良版の手順書にある<やること>の部分は、スロットゲームのプログラム(001.py)と同じ形をしています。

for message in ['1回目', '2回目', '最後だよ']:
    print(message)
    bets = ask_bets(player_coin)
    marks = display_and_get_result()
    division = get_division(marks)
    player_coin = calculate_coin(player_coin, bets, division)

「def」で “関数” を定義してみる

Python における “関数” は上記手順書の<詳細>に該当します。

  • 関数を定義するには「def」(definition: 定義)を使用する
  • 作業に必要なものは “引数” と呼ばれる
  • 関数定義の範囲はインデント(字下げ)して表現する
  • 成果物(関数を使用した結果)を “戻り値” と呼び「return」を使って表現する
  • “引数”、”戻り値” は必要な場合のみ記載する

これだけ意識すれば、簡単な……でも十分な関数を定義することができます。
具体的な書式は以下の様になります。

名前
必要なもの:A, B
成果物:C
  作業の詳細①
  作業の詳細②
   :
def 名前 ( A, B,… ):
作業の詳細①
作業の詳細②
:
return

ちなみに、関数には引数の有無、戻り値の有無の組み合わせで 4 パターンがあります。先ずは作成した関数を使用する際の書き方と合わせて簡単に確認しましょう。

def with_no_args_no_returns():
    print('This function is defined which having no args and no returns.')

# 引数も戻り値もない場合は、「関数名+()」とする
with_no_args_no_returns()
def with_args_no_returns(hoge):
    print('This function is defined which having args and no returns.')
    print(f'Args: hoge ==> {hoge}')

# 引数を指定する場合は「関数名+(引数)」とする
with_args_no_returns('aiueo')
def with_args_returns(hoge, fuga):
    print('This function is defined which having args and returns.')  
    print(f'Arge: hoge ==> {hoge}, fuga ==> {fuga}')

# 引数が復数ある場合は、定義した順番に設定される
# 関数の戻り値は、変数に代入して保存しておくことが可能
piyo = with_args_returns('ほげ', 'ふが')

# 引数名を使うと、定義と違う順番で指定することも可能
hogera = with_args_returns(fuga='ふがふが', hoge='ほげほげ')

前回までで、ゲーム終了時(所持コイン数が 0 枚以下になった場合)に終了のメッセージを表示するようになっています。今回はこの終了メッセージを表示するための関数を実際に定義してみます。

(修正前)

# ============================================================
# 実際にゲームを実行する
# ============================================================

# プレーヤーの所持コイン数
player_coin = 123

show_start_message()
player_name = ask_player_name()

if player_name in BONUS_PLAYER_AND_COINS:
    player_coin = player_coin + BONUS_PLAYER_AND_COINS[player_name]
elif player_name in SPECIAL_THANKS:
    player_coin = player_coin + SPECIAL_THANKS_COIN

while player_coin > 0:
    bets = ask_bets(player_coin)
    marks = show_and_get_result()
    division = get_division(marks)
    player_coin = calculate_coin(player_coin, bets, division)

print('ゲームオーバーです')
print(f'さようなら {player_name} さん')

(修正後)

def show_gameover_message(player_name):
    print('ゲームオーバーです')
    print(f'さようなら {player_name} さん')

# ============================================================
# 実際にゲームを実行する
# ============================================================

# プレーヤーの所持コイン数
player_coin = 123

show_start_message()
player_name = ask_player_name()

if player_name in BONUS_PLAYER_AND_COINS:
    player_coin = player_coin + BONUS_PLAYER_AND_COINS[player_name]
elif player_name in SPECIAL_THANKS:
    player_coin = player_coin + SPECIAL_THANKS_COIN

while player_coin > 0:
    bets = ask_bets(player_coin)
    marks = show_and_get_result()
    division = get_division(marks)
    player_coin = calculate_coin(player_coin, bets, division)

show_gameover_message(player_name)

関数の名前の付け方について

その2では、変数には分かりやすい名前を付けましょうと書きました。関数名についても分かりやすい名前を付けることは、とても大切なことです。関数の名前に決まりはないのですが、作業A、作業Bみたいな適当名前を使ってしまうと、全体の見通しをよくしたいという意図に反してしまいます。そこで、スロットゲームでは以下の記事を参考にして関数名を決めています。

私は、ふつうの関数の名前についても、ブール値を返す関数と同じように、説明を英語で書く、という方法で解釈しています。

関数の名前の解釈 | ブール値を返すメンバー関数の命名規則

各関数の近くに日本語と英語をまとめて表記したのは、ここでこの説明をしたかったためです。(英語は得意ではないので、もし不自然なものがあったらご指摘いただけると助かります)
今回の関数については「ゲーム終了のメッセージを表示する」ということを記載したかったので「This function is to be called to show a game-over message.」という英文を元に関数名を決めました。

Pydoc、引数、戻り値

さらに関数の概要をメモしておきたい場合もあるでしょう。Python では「def」の次の行にダブルクォート 3 個「"""」を記載し、次の「"""」までの間に概要を記載しておくことができます。

これには、さらに以下 2 つの利点があります。

  • VS Code が読み取り、関数を使う際に内容を表示してくれる
  • pydoc モジュールを使用してドキュメントを生成することができる
    ※こちらは現時点のプログラムの書き方では想定取りに動作しません。別途、プログラムを修正& pydoc を使用する記事を投稿したいと思います。

そのためスロットゲームの関数全てに、このメモを記載してあります。関数を作成する際には、是非、この情報も記載するようにして下さい。構成としては以下の様になっています。

  1. 関数の概要を記載する。
  2. その関数が必要としている情報(上記A、B)がある場合は、「Args:」(Arguments)というタイトルを付けて情報を記載する。”引数” の説明に使います。
  3. その関数から何らかの結果を期待している場合、「Returns:」というタイトルを付けて情報を記載する。”戻り値” の説明になります。

この様に特定の書式に従って記載したメモのことを特に “Pydoc” と呼びます。元々は Java というプログラミング言語で採用されたドキュメント用の機能です。今では大抵のプログラミング言語に移植されていて、Java は「Javadoc」、Python は「Pydoc」、JavaScript は「JSDoc」のように「○○doc」という名前がついています。書き方はそれぞれのプログラミング言語によって異なりますが、いずれも基本的な構成は上記 3 つの項目からなります。詳しい書式については「{プログラミング言語名} ドキュメント 生成」等で検索すると見つかると思いますので、Python 以外の言語についても気になるものがあれば是非、探してみて下さい。

というわけで、先程の関数に情報を追加しましょう。

def show_gameover_message(player_name):
    """ゲーム終了のメッセージを表示する
    (This function is to be called to show a game-over message.)

    Args:
        player_name (string): プレーヤー名前
    """
    print('ゲームオーバーです')
    print(f'さようなら {player_name} さん')

今回、”戻り値” は特に必要ないので「Returns:」は記載しませんでした。
(私のつたない)英語文は必要ないのですが、他と合わせて記載しておきました。

コメント

タイトルとURLをコピーしました