前回は、文字列が正規表現のリストにある要素のいずれかとマッチするかどうかを照会するプログラムを通して、関数が必要となる状況について説明しました。今回は、前回のプログラムに関数を導入して、わかりやすく書き直してみます。

作業ディレクトリは前回と同様に「C:\Users\user\Documents\pychat\function」とします。

関数実行

関数を利用する際は、「関数定義」で関数を作成し、「関数実行」で実行します。

では先に、関数実行から見ていきましょう。と言っても、本連載の読者の皆さんは、すでに関数実行をしたことがあるはずです。

第8回で紹介したinput関数を思い出してください。

user_input = input(">")

このプログラムは、実はinput関数を関数実行しているのです。

この関数実行では、まずinput関数が引数として受け取った文字列「>」をコマンドプロンプトに表示し、ユーザーの入力を待ち受けます。そして、ユーザーが入力を完了すると、input関数が戻り値として返すオブジェクトを「user_input」という変数に束縛するわけです

このように、関数実行を行う際は、引数を渡して関数を呼び出し、その実行結果を戻り値として受け取ります。

なお、関数に渡す引数の数は1つとは限らず、関数によって決まっています。例えば、re.search関数は「re.search(pattern, text_fuji)」のように2つの引数を受け取ります。

以上をまとめると、関数実行の記述形式は次のようになります。

関数名(引数1, 引数2, ...)

※ 「引数」「戻り値」は、第5回でメソッドについて解説した際にも登場しました。関数においてもメソッドと同じく、関数に渡すオブジェクトを引数、処理の結果となるオブジェクトを戻り値と呼びます。

関数定義

input関数やre.search関数はPythonであらかじめ用意されている関数ですが、関数定義によって、任意の関数を自分で作ることもできます。

関数定義のイメージをつかむために、文字列が正規表現リストの要素のいずれかにマッチするかどうかを確認する関数「check_match」を作ってみましょう。プログラム名は「check_match.py」とします。

# check_match.py - 関数定義と実行をするプログラム
import re

# Part1: 関数定義
def check_match(patterns, text):
    matched = False  # マッチしたかを記録する変数
    for pattern in patterns:
        if re.search(pattern, text):
            matched = True
            break
    return matched

# Part2: 関数実行
patterns = ["富士山.*高さ", "東京.*区.*いくつ"]
text_fuji = "ねぇ、富士山の高さは?"
matched_fuji = check_match(patterns, text_fuji)

# 関数実行の結果を表示
print(matched_fuji)

プログラム中のコメント「Part1」以下の部分で「check_match」という関数を定義しています。このように、関数定義では「def」に続いて関数名を書き、続く括弧のなかに「仮引数」を、そして「:(コロン)」を挟んで続くインデントされた行に関数で実行する処理を記述します。

def 関数名(仮引数1, 仮引数2, ...):
    処理

さてここで、仮引数という新しい用語が出てきました。関数実行では、関数に引数を渡していたことを思い出してください。仮引数とは、関数実行時に関数に渡した引数を関数定義側で受け取る変数のことです。

上記のcheck_match関数に当てはめて考えてみましょう。関数定義では、仮引数の1つ目として正規表現リストを受け取る変数patternsを、2つ目として文字列を受け取る変数textを宣言しています。

続いて、関数実行する「Part2」以下の部分を見てみましょう。「Part2」以下の部分では、check_match関数に次の2つのオブジェクトを引数として渡して実行しています。

  • 変数patternsが束縛する正規表現リスト:[“富士山.高さ”, “東京.区.*いくつ”]
  • 変数text_fujiが束縛する文字列:“ねぇ、富士山の高さは?”

その結果、関数定義の仮引数には引数に対応するオブジェクトが束縛されることになります。

  • 仮引数patterns:[“富士山.高さ”, “東京.区.*いくつ”]
  • 仮引数text:“ねぇ、富士山の高さは?”

このように、仮引数に束縛されるオブジェクトは関数実行するごとに変わるため、「仮」という名前がつけられているのです。

では、Part1に戻りましょう。仮引数を宣言したら、続くインデントされた行で仮引数で宣言した変数を使って関数の処理を記述します。

check_match関数の処理では、仮引数で宣言した2つの変数patternsとtextを使い、textが束縛している文字列が変数patterns内のいずれかの要素(パターン)にマッチするかどうかを判定。その結果を表すブール値を変数matchedに代入しています。

そして、処理の最後の行の「return」に注目してください。return文では、「return」に続けて関数が返す値を書きます。これが、関数の戻り値となるのです。

check_match関数の場合は、変数matchedをreturnしているので、文字列が正規表現にマッチしていればTrue、そうでなければFalseが返されます。

実行すると、前回作成したプログラム「match.py」と同様の出力結果が得られるはずです。

$ python check_match.py
True

さて、それではこのcheckmatch関数を使って「東京の区っていくつあるの?」がマッチするかどうかを確認するプログラムを追加してみましょう。「checkmatch_multi.py」というファイル名で次のようなプログラムを作成します。

# check_match_multi.py - 関数定義と2つのマッチ判定を実行するプログラム
import re

# Part1: 関数定義
def check_match(patterns, text):
    matched = False  # マッチしたかを記録する変数
    for pattern in patterns:
        if re.search(pattern, text):
            matched = True
            break
    return matched

# Part2: 関数実行
patterns = ["富士山.*高さ", "東京.*区.*いくつ"]
text_fuji = "ねぇ、富士山の高さは?"
matched_fuji = check_match(patterns, text_fuji)
print(matched_fuji)

# 次のプログラムを追加
text_tokyo = "東京の区っていくつあるの?"
matched_tokyo = check_match(patterns, text_tokyo)
print(matched_tokyo)

前回、関数を使わずに作成したプログラム「match_multi.py」と比較してみると、かなり理解しやすいプログラムになったことが実感できるのではないでしょうか。

match_multi.pyでは、マッチの判定処理をチェックしたい文字列ごとに毎回記述していたので似たような処理が並んでしまい、「読みにくい」「判定処理の修正やチェックする文字列の追加が困難」といった問題がありました。

一方、今回は、マッチの判定処理をcheck_match関数にまとめたため、全体がスッキリし、チェックする文字列を追加したいときは関数実行を増やすだけで対応できるようになりました。

checkmatchmulti.pyを実行すると、期待通りの結果が得られます!

$ python check_match_multi.py
True
True

* * *

高度な処理を実装しようとすると、プログラムは次第に複雑になっていきます。プログラムが複雑になればなるほど、プログラム全体を把握したり、後から修正したりすることが困難になるのは想像に難くないでしょう。実際、自分で書いたプログラムでも、2週間も経つと何が書いてあるかわからなくなることも多いものです。今回紹介したように、重複する処理を関数にまとめて再利用することで、こうした問題をかなり緩和することができます。

明日の自分のためにも、関数を適切に使ってプログラムの可読性やメンテナンス性をぐんと向上させましょう!

著者紹介


株式会社NTTドコモ
R&Dイノベーション本部 サービスイノベーション部
阿部憲幸

2015年京都大学大学院理学研究科数学・数理解析専攻修了。 同年、NECに入社。 2016年から国立研究開発法人情報通信研究機構出向。 2018年より現職。 自然言語処理、特に対話システムの研究開発に従事。 毎日話したくなるAIを夢見て日夜コーディングに励む。
GitHub:https://github.com/noriyukipy