5. 関数を作って処理をまとめよう

プログラミング言語に用意された関数を使えるようになりましたね。 プログラミングでは、関数を自分で作れます ! 「関数を定義する」とも言います。

この章では、関数の作り方を紹介します。 対話モードに入力しながら読み進めてください。

5.1. 関数を作ってみよう

5.1.1. 決まった挨拶を表示する関数

「皆さん、ごきげんよう」と挨拶を表示する関数を作ってみましょう。

決まった挨拶を表示する関数

箱の名前

aisatsu1

箱は何をするか

「皆さん、ごきげんよう」と表示する

箱に入れるもの

なし

箱から出てくるもの

なし

関数は次のように書きます:

>>> def 関数の名前():  # ((defの後に半角スペースが1つ入ります))  
...     関数にまとめる文(の並び)  # ((先頭に半角スペース4つを忘れずに!))

処理系が関数の呼び出しを処理する中で、関数にまとめた文が処理され ます。

ここで作る関数の名前は aisatsu1 とすでに決めています。 「皆さん、ごきげんよう」と表示したいので、print 関数を使った式の出番ですね。

5.1.2. 決まった挨拶を表示する関数を作る

では、aisatsu1 関数を作ります。 まず対話モードに def で始まる行を入力しましょう。

>>> def aisatsu1():  

: (コロン)まで入力したらEnterキーを1回押します。

>>> def aisatsu1():  
...

2行目に入力できるようになりました。 print 関数を使った式を書く行です。 すぐに書き始めるのではなく、半角スペース4つを入れてから 書いてください。

>>> def aisatsu1():  
...     print("皆さん、ごきげんよう")

2行目を入力した後、Enterキーを1回押すと、続く行が入力できるようになります。

>>> def aisatsu1():  
...     print("皆さん、ごきげんよう")
...

aisatsu1 関数にまとめる処理は1行だけなので、何も入力せずにEnterキーを1回押してください。

>>> def aisatsu1():
...     print("皆さん、ごきげんよう")
...
>>>

>>> で始まる行が再び現れました。 これは、aisatsu1 関数が作られたことを意味します。

作った aisatsu1 関数を呼び出してみましょう。 箱に入れるものはないので、呼び出すときはカッコ () を付けるだけです。

>>> aisatsu1()
皆さん、ごきげんよう

aisatsu1 関数にまとめた式 print("皆さん、ごきげんよう") が評価されていますね。

../_images/4-17-function_no_parameters.drawio.png

決まった処理をする箱(何も入れずに呼び出せる)

5.1.3. 関数を作るときのポイント

関数を作るときは、2つのポイントがあります。

  1. def で始める行

  2. 処理の行は先頭に半角スペース4つを入れる

1つ目は、def で始める行 def aisatsu1(): です。

  • aisatsu1 という名前の関数を作ることを表します。

  • 関数に入れるものがないことは、空のカッコ () で表します。

  • 閉じたカッコの後のコロン : は忘れやすいので気を付けてください。

def は、definition(定義)の短縮形です。

コロンを忘れると、処理系は SyntaxError を出します。 対話モードの履歴をさかのぼり、コロンを追加して解消できます。

2つ目は、関数にまとめる文の先頭に半角スペースを入れて 字下げ したことです。 字下げは インデント とも呼ばれます。 関数にまとめる文が複数あるときは、どの文も同じだけインデントします。

def の行だけでは、関数にまとめる処理が定義されていません。 インデントした行が関数の処理 になります。 aisatsu1 関数の処理は、インデントした行の文(式) print("皆さん、ごきげんよう") です。

aisatsu1 関数を呼び出すと、文 print("皆さん、ごきげんよう") が実行され、この式が評価されます。 関数呼び出しが処理系に評価されるときに、インデントした行の文が実行されるようにまとめた わけです。

関数定義は文の1種です。 式自体や代入の文と異なり、関数定義という文の中に 別の文を含められる のが特徴です。 別の文を含められることをコロンで示しています。 別の文はインデントを使って含めます。

5.2. 引数を渡せる関数を作ってみよう

前節で作った関数 aisatsu1 は、引数を渡せない関数でした。

次のように書くと、引数を渡せる関数 を定義できます:

>>> def 関数の名前(変数1, 変数2, ...):  # ((カッコの中に書きます))  
...     関数にまとめる文(の並び)  # ((変数を使った処理が書けます))

渡したい引数の数だけカッコの中に 変数 を書きます。 複数の引数を渡したい場合、カッコの中の変数はカンマ , で区切ります。

関数にまとめた処理には、カッコの中の変数が使えます。 関数呼び出しを評価するとき、この変数に 引数が代入 されます。

1つの引数、複数の引数の順で例を見ていきましょう。

5.2.1. 引数に指定した人への挨拶を表示する関数

指定した人への挨拶を表示する関数

箱の名前

aisatsu2

箱は何をするか

「〇〇さん、ごきげんよう」と挨拶を表示する

箱に入れるもの

文字列(「〇〇」の部分に入る人の名前)

箱から出てくるもの

なし

この関数に入れるものは1つですね。 関数のカッコの中の変数は namae にします。

「〇〇さん、ごきげんよう」の表示は、4 章プログラムの入力と出力を扱おう」の練習問題で扱いましたね。 f を付けて始めた文字列については、「プログラムの入力と出力を扱おう」の発展(TODO:Xページ参照)を確認してください。

>>> def aisatsu2(namae):
...     print(f"{namae}さん、ごきげんよう")
...
>>>

作った aisatsu2 関数を呼び出してみましょう。 本書の筆者の名前を渡します。

>>> aisatsu2("susumuis")
susumuisさん、ごきげんよう
>>> aisatsu2("nao_y")
nao_yさん、ごきげんよう

渡した引数が namae に代入されて、print(f"{namae}さん、ごきげんよう") が評価されていますね。 この仕組みを使って、関数の処理自体は 変数 を使って 抽象的 に書き、引数 として渡した 具体的な値 について処理するわけです。

ぜひ引数として自分の名前を渡して aisatsu2 関数を呼び出してみてください。

../_images/4-16-create-aisatsu-function.drawio.png

関数の定義と、できる関数との対応

5.2.2. 引数に指定した姓と名を元に、挨拶を表示する関数

関数に入れるものを1つから2つに増やした例です。

姓と名を指定した人への挨拶を表示する関数

箱の名前

aisatsu3

箱は何をするか

「〇〇さん、ごきげんよう」と挨拶を表示する。「〇〇さん」は、「姓 名さん」とする

箱に入れるもの

文字列2つ(1つ目が姓、2つ目が名)

箱から出てくるもの

なし

「〇〇さん」の部分は、姓と名の文字列を半角スペースでつなぐことにします。

>>> sei, mei = "田中", "香織"
>>> sei + " " + mei
'田中 香織'

半角スペースは、見やすくするために入れました。

ここまでで確認したことを元にすると、aisatsu3 関数は次のようになります。

>>> def aisatsu3(sei, mei):
...     namae = sei + " " + mei
...     print(f"{namae}さん、ごきげんよう")
...
>>>

複数行に渡る処理は、すべての行を同じだけインデント します。

呼び出してみましょう。 カンマで区切った変数と引数は、同じ位置にあるものどうしが対応 して代入されます。

>>> aisatsu3("田中", "香織")
田中 香織さん、ごきげんよう

aisatsu3("田中", "香織") と呼び出すと、

  • 最初の引数 "田中" が変数 sei に代入され、

  • 2番めの引数 "香織" が変数 mei に代入されて、

関数の処理が実行されます。

複数渡した引数には順番がある ことがポイントです。 aisatsu3("香織", "田中") と呼び出すと、sei に代入されるのは "香織" になります。

[コラム] インデントに関するエラー

関数を作るときのインデントは、初めのうちは間違えやすい箇所です。 インデントがなかったり、揃っていなかったりすると、処理系は IndentationError を出します。 見かけたら インデントを忘れていないか、インデントが揃っているか、確認しましょう。

(1)インデントを忘れてしまった場合

>>> def aisatsu2(namae):  
... print(f"{namae}さん、ごきげんよう")
  File "<stdin>", line 2
    print(f"{namae}さん、ごきげんよう")
        ^
IndentationError: expected an indented block

(2)インデントが少ない行がある場合

>>> def aisatsu3(sei, mei):  
...     namae = sei + " " + mei
...   print(f"{namae}さん、ごきげんよう")  # ((インデントが少ない行))
  File "<stdin>", line 3
    print(f"{namae}さん、ごきげんよう")
                              ^
IndentationError: unindent does not match any outer indentation level

(3)インデントが多い行がある場合

>>> def aisatsu3(sei, mei):  
...     namae = sei + " " + mei
...       print(f"{namae}さん、ごきげんよう")  # ((インデントが多い行))
  File "<stdin>", line 3
    print(f"{namae}さん、ごきげんよう")
    ^
IndentationError: unexpected indent

[コラム] 関数のインデント、タブか半角スペースか

インデントについて「半角スペースを4つ入れましょう」と伝えました。 実は、半角スペースの数は何個でもかまいません。 インデントに使われる半角スペースの数が揃っていれば、処理系は IndentationError を出しません。

また、Tab(タブ)キーを使ってもインデントできます。 ですが、Pythonを書くプログラマーの間では、インデントに 半角スペース を使うのが慣習です。

本書のおすすめは、インデントに半角スペース4つを使う ことです。 この数は、Pythonを書くプログラマーの間で一般的です。

5.3. 返り値のある関数を作ってみよう

ここまでに作った関数には返り値はありませんでした。 次のように return 文(return で始まる文)を使うと、返り値のある関数 を定義できます:

>>> def 関数の名前(変数1, 変数2, ...):  
...     関数にまとめる文(の並び)
...     return 式  # ((returnの後に半角スペースが1つ入ります))

return 文の 式を評価した値 が、関数の返り値となります。

5.3.1. 引数に指定した人への挨拶文を返す関数

返り値のある関数を試しに作ってみましょう。

指定した人への挨拶文を返す関数

箱の名前

aisatsu_bun

箱は何をするか

「〇〇さん、ごきげんよう」という挨拶文を返す

箱に入れるもの

文字列(「〇〇」の部分に入る人の名前)

箱から出てくるもの

文字列(挨拶文)

aisatsu_bun 関数は次のようになります。

>>> def aisatsu_bun(namae):
...     return f"{namae}さん、ごきげんよう"
...
>>>

本書の筆者の名前を渡して呼び出してみます。

>>> aisatsu_bun("susumuis")
'susumuisさん、ごきげんよう'

返り値は、式 f"{namae}さん、ごきげんよう" を評価した値になっていますね。 渡した "susumuis"namae に代入されています。

aisatsu_bun 関数の返り値の文字列を print 関数に渡すことで、挨拶を表示できますね。

../_images/4-18-function_returned_value.drawio.png

返り値のある関数は return を指定して作る

[発展] 関数の返り値は複数あってもいい

return 文に続く式は、カンマで区切って複数指定 できます。 このとき、関数は複数の値を返します。

>>> def 関数の名前(変数1, 変数2, ...):  
...     関数にまとめる文(の並び)
...     return 式1, 式2, ...

挨拶文とその長さを返す関数を作ってみましょう。

>>> def aisatsu_bun_kai(namae):
...     bun = f"{namae}さん、ごきげんよう"
...     return bun, len(bun)
...
>>>

この関数の返り値の代入は、2 章値を変数で扱おう」のコラムで取り上げた「複数の変数に同時に代入」の書き方を使います。

>>> aisatsu, mojisuu = aisatsu_bun_kai("nao_y")
>>> aisatsu
'nao_yさん、ごきげんよう'
>>> mojisuu
14

5.4. なぜ処理をまとめるのか

引数を渡せる関数や、返り値のある関数の作り方が分かりましたね。 皆さんが知っている関数を組合せて、自分だけの関数を作れるようになっています!

プログラマーは、処理のまとまりで関数を作り、それを使ってプログラムを書き ます。 なぜ関数を作って処理をまとめるのかを考えてみましょう。

5.4.1. プログラムに階層構造を作る

関数を使って処理をまとめる理由は、プログラムに 階層構造 を持たせるためです。

2 章値を変数で扱おう」では、途中の結果を変数に代入しながら計算しましたね。 複雑な計算をするプログラムでは、変数に代入しながら書き続けると、100行、1000行を超えることもあるでしょう。 100行、1000行のプログラムは、何も工夫しないと、読んだり書いたりする プログラマーにとってどんな処理なのかが分かりにくく なります。

そこで、処理を抽象度ごとに 階層構造 を持たせた箇条書きにして考えます。

  • 処理A

    • 文 a-1

    • 文 a-2

    • :

  • 処理B

  • 処理C

抽象的 に見れば、処理A、処理B、処理Cを順番にやっていると 概要をつかめます。 このとき、処理のそれぞれに立ち入る必要はありません。 この処理A、B、Cが関数に相当します。 関数の名前から大枠を掴んだあとで、各処理を構成する文を具体的に見ていけばよくなります。

階層構造がなければ、プログラマーはすべてのプログラムを読んで頭の中で階層構造を組み立てなければなりません(それも毎回です!)。 関数を使って階層構造を作れば、プログラムの中で 必要な部分だけを読んで把握 できます。

うまく階層構造が付けられた関数たちは、その中の一部を別の用途でも 再利用 できます。 関数にまとめることで、同じ処理を繰り返し書かずに済むわけです。 熟練したプログラマーほど、再利用のしやすさにこだわって関数を作ります。

[発展] 関数の中でのみ使える変数

aisatsu2 関数に渡した値は、関数の処理でのみ有効な変数 namae に代入されます。 aisatsu2("nikkie") という呼び出しは、変数 namae"nikkie" を代入し、関数の続く処理を実行します。 言い換えると、aisatsu2 関数の外側に同名の namae 変数があったとしても、 関数の内側の変数 namae と外側の namae区別される ということです。

>>> namae = "nikkie"
>>> namae
'nikkie'
>>> def aisatsu2(namae):
...     print(f"{namae}さん、ごきげんよう")
...
>>> aisatsu2("susumuis")
susumuisさん、ごきげんよう
>>> namae
'nikkie'

関数の内側の変数 namae"susumuis" が代入されて処理が実行されましたが、 関数の外側の変数 namae が指す値は変わっていませんね。

同じ名前の変数 namae であっても、関数の中で使われているかいないかで別々のものとして扱います。 この仕組みは、「変数の スコープ」と呼ばれ、プログラミング言語における重要な概念です。

[発展] 引数の2通りの見方

引数には、「実際の引数」と「形式的な引数」があります。 プログラミングの用語で、それぞれ、「実引数(じつひきすう)」「仮引数(かりひきすう)」と呼びます。

実際の引数 (実引数)は、関数を呼び出す立場 からの見方です。 実際の引数は 具体的な値 です(例: 4"こんにちは" など)。 実際の引数として指定した変数や式は、まず評価されて具体的な値になってから関数に渡ります。 本文ではこちらの意味で引数という語を使ってきました。

形式的な引数 (仮引数)は、関数を定義する立場 からの見方です。 形式的な引数は、カッコの中に書いた 変数 で、関数の中でのみ有効です。 関数の処理を書く上で、形式的にでも 渡される値を表す変数 があると書きやすいのです。 形式的な引数には、関数呼び出しで渡された実際の引数が代入されます(代入されるまでは何も値を指していません)。 形式的な引数が複数あるときは、同じだけ実際の引数を渡す必要があり、渡した順に対応して代入されます。

以下は、形式的な引数を namae として aisatsu2 関数を定義し、 実際の引数として変数 namae を渡す例です(指す値 "nikkie" が渡されます)。

>>> def aisatsu2(namae):
...     print(f"{namae}さん、ごきげんよう")
...
>>> namae = "nikkie"
>>> aisatsu2(namae)
nikkieさん、ごきげんよう

形式的な引数と実際の引数が同じ名前だからうまく動いているのではありません。 aisatsu2(namae) という呼び出しで、実際の引数が形式的な引数に代入される のでうまく動いています。 実際の引数に指定する変数は何でもよく、 hissha のように変えられます。 aisatsu2(hissha) という呼び出しで、問題なく動きます。

[発展] 実際の引数には2種類ある

aisatsu3 関数の呼び出しで実際の引数を指定する方法は、実はいくつかあります。

>>> aisatsu3("田中", "香織")
田中 香織さん、ごきげんよう
>>> aisatsu3("田中", mei="香織")
田中 香織さん、ごきげんよう
>>> aisatsu3(sei="田中", mei="香織")
田中 香織さん、ごきげんよう

実際の引数には2種類あります。

  • 位置引数:実際の引数だけを指定した場合

  • キーワード引数形式的な引数=実際の引数 と指定した場合

指定の仕方は異なりますが、渡される値は同じですから、関数の動きは変わりません

注意点として、キーワード引数を位置引数の前に使うことはできません(続く例のように処理系はエラーを出します)。 位置引数 → キーワード引数の順 という決まりがあります。

>>> aisatsu3(sei="田中", "香織")  
  File "<stdin>", line 1
SyntaxError: positional argument follows keyword argument