関数 — 1つの判断、1つの名前
def で関数を書くこと、return と print の違い、ガードで端のケースを扱うこと、ドキュメント文字列。
どうやって学びますか?
解決する問題
2つのグループが平均点を求める必要があります:
marks_a = [72, 45]
marks_b = [90, 33, 61]
print(round(sum(marks_a) / len(marks_a), 2))
print(round(sum(marks_b) / len(marks_b), 2))58.5
61.332行はほぼ同じで、違うのは名前だけです。3つ目のグループがあれば3行目、4つ目があれば4行目が必要になります。
問題はタイピング量ではありません — コピーは安く済みます。問題は1つの判断がいくつもの場所に書かれていることです。小数第2位ではなく第1位で表示するよう頼まれたら、すべての行を見つけて変えなければなりません。1つでも見落とせば、プログラムが2通りの振る舞いをすることになり — エラーも出ません。
空リストはどうでしょう? sum([]) / len([]) はゼロ除算になり、プログラムが止まります。そのチェックを各行に別々に追加することになります。
関数は、1つの判断を1回だけ書き、それに名前を付ける仕組みです。それ以降、その名前を呼べば仕事をしてくれ、変更も1箇所で済みます。
この章を終える頃には
defで関数を定義し、呼べるreturnとprintの違いを説明できる — この章でいちばん大事なこと- 関数を小さく、1つの仕事に限定できる
- ガードを先頭に置いて、端のケースを処理できる
- ドキュメント文字列を書ける
前提条件: 内包表記.
最初の関数
def average(values):
return round(sum(values) / len(values), 2)
print(average([72, 45]))
print(average([90, 33, 61]))58.5
61.33形はこうです:
def— 「ここで関数を定義します」average— 名前、変数名と同じルール(values)— 仮引数、データが入ってくるドア:とインデントされたブロック — 本体、まさにifやforと同じreturn— 結果を返す
average([72, 45]) と書くのは呼び出しです。[72, 45] は実引数、実際に渡される値;values は仮引数、中での名前です。違う2つの言葉で、どちらもエラーメッセージに出てくるので、区別を覚えておく価値があります。
空行2行。 関数定義のあとに空行2行置くのは Python の慣習です。少なくても動きますが、すべての Python コードがこの書き方なので、習慣にしておく価値があります。
return と print は違う
この章でいちばん大事な節であり、初心者がいちばん混乱する場所です。
def with_return(x):
return x * 2
def with_print(x):
print(x * 2)
a = with_return(5)
b = with_print(5)
print(a)
print(b)
print(a + 1)10
10
None
11画面に出た最初の2つの 10 が、関数を同じに見せます。違います。
with_return は値を手渡し返すので、a には 10 が入り、足せます — だから最後の行が 11 になります。
with_print は値を表示しただけで、何も返しません — だから b は None。数字は画面に出たが、プログラムは何も受け取っていません。b + 1 とすれば TypeError になります。
こう覚えてください:print は人に向けて、return はプログラムに向けて。 計算する関数は return を使い、表示は呼び出し元で行います。そうすれば、同じ関数をある日画面用に、別の日にファイル用に使えます。
return がない関数は黙って None を返します — これは第12章と第13章で append と .sort() が返していたのと同じ None です。なぜそうしたか、今なら分かります:それらの仕事は何かを変えることであって、何かを返すことではなかったからです。
return は即座に止める
def check(mark):
if mark >= 40:
return "pass"
return "fail"
print(check(72))
print(check(20))pass
failreturn が実行される瞬間、関数は終わり — そのあとの行は決して実行されません。だからここでは else が要りません:最初の return が起これば、2行目には決して到達しないからです。
この形 — 条件が合えばすぐに返す — は第9章の深い if/else の木を平らにし、コードをずっと読みやすくします。
端のケースを扱う — ガード
この章の冒頭の問題を覚えていますか? 空リスト:
def average(values):
return sum(values) / len(values)
print(average([]))ZeroDivisionError: division by zero直すのは関数のいちばん上です:
def average(values):
if not values:
return 0.0
return round(sum(values) / len(values), 2)
print(average([72, 45]))
print(average([]))58.5
0.0if not values は第8章の真理値のルールそのものです:空リストは偽、だから not で真になります。len(values) == 0 などと書く必要はありません。
これはガードと呼ばれます:異常なケースを先頭で片付け、あとは心配せずに書きます。関数ならこれが安くできます — チェックを1回書けば、すべての呼び出し元が恩恵を受けます。
0.0が正解か? 議論の分かれるところです。「誰も点を持っていない」と「全員がゼロ点」を混同しています。実用的なプログラムではよくNoneを返すか、第24章のraiseで例外を投げます。ここでは単純化のために0.0にしていますが、これが自明の答えではなく、判断の一つであることは知っておいてください。
ドキュメント文字列
関数の最初の行に置いた文字列が、その関数の文書になります:
def average(values):
"""Return the mean of values, or 0.0 when there are none."""
if not values:
return 0.0
return round(sum(values) / len(values), 2)
print(average([1, 2, 3]))
print(average.__doc__)2.0
Return the mean of values, or 0.0 when there are none.コメントと違うのは、これが実行中のプログラムにも残ること — エディタが表示し、help(average) が出力します。
良いドキュメント文字列は何が返るか、端では何が起きるかを書きます。コードはすぐ下にあります。
完全な例
report.py:
# Three small functions, each doing one thing
def average(values):
"""Return the mean of values, or 0.0 when there are none."""
if not values:
return 0.0
return round(sum(values) / len(values), 2)
def grade(mark):
"""Turn a mark into a letter."""
if mark >= 80:
return "A"
if mark >= 70:
return "B"
if mark >= 40:
return "C"
return "F"
def summarise(name, marks):
"""Build one report line for a person."""
mean = average(marks)
return f"{name:<8} {len(marks):>2} papers avg {mean:>6} grade {grade(mean)}"
people = {
"rafi": [72, 88, 61],
"ahmed": [45, 38],
"dia": [],
}
for name, marks in people.items():
print(summarise(name, marks))rafi 3 papers avg 73.67 grade B
ahmed 2 papers avg 41.5 grade C
dia 0 papers avg 0.0 grade F4つ、気づく価値があります。
各関数が1つの仕事をする。 average は平均を出し、grade は文字にし、summarise は1行を作る。どれも表示しない — 表示は一番下のループで、1箇所だけ。このレポートをファイル出力に変えるなら、そこだけ変えればいい。
関数が関数を呼ぶ。 summarise が average と grade を呼ぶ。これが本当のメリットです:大きな仕事が、小さくて頼れる部品から組み立てられます。
grade に else がない。 すべての return が関数を止めるので、条件がフラットなままです。第9章のルールを思い出してください:いちばん厳しい条件を先に — 40 を 80 より前に置けば、全員が C になります。
dia の空リストで壊れなかった。 ガードが 0.0 を返し、grade がそれを F にし、レポート行が正常に出力されました。端のケースが1箇所で、1回だけ処理されました。
うまくいかないとき
NameError: name 'greet' is not defined — でも関数はそこにある def 行が実行される前に呼び出されたからです。Python はファイルを上から下へ読み、名前は def が実行されるまで存在しません。すべての def をファイルの上に置き、呼び出すコードを下に置きましょう。
TypeError: average() missing 1 required positional argument: 'values' 引数が渡されませんでした — average(marks) ではなく average() と書いています。メッセージに足りない仮引数の名前まで出ています。
関数が None を返す return がないか、間違ったインデントにあります — 条件が合わない if の中など。よくある原因:return を書くべきところに print を書いている。
関数の中の変数が外から見えない それは当然です — なぜそうなるかは第21章のテーマです。今のところ:外で必要なものは return してください。
SyntaxError — def 行にコロンがない if や for と同じく、def 行の末尾にはコロンが要ります。
IndentationError: expected an indented block after function definition def のあとには、最低1行のインデントされた行が必要です。
ステップ 4 / 6 — 予想する
理解度をチェック
関数が返す代わりに表示しています。2行は何を表示するでしょう?
def double(x):
print(x * 2)
result = double(5)
print(result)- A10 None
- B10 10
- CNone 10
- D10
return が2つあり else はありません。何が表示されるでしょう?
def check(mark):
if mark >= 40:
return "pass"
return "fail"
print(check(72))- Apass
- Bpass fail
- Cfail
- DNone
関数はファイルにあります。それでも何が起きるでしょう?
print(greet("rafi"))
def greet(name):
return "hello " + name- A`SyntaxError`
- Bhello rafi
- C`TypeError`
- D`NameError` — 関数が `def` 行より前に呼び出された
回答にはアカウントが必要です
ログインして答えを確認
問題は上にあります。頭の中で答えを考えることがいちばん大切です。ログインすると、正解・解説・3 段階のヒントを確認できます。
やってみよう
text_tools.py というファイルを作り、4つの関数を書きましょう。それぞれにドキュメント文字列を付け、どれも何も表示しません:
clean(text)— 前後のスペースを削除し、小文字にするinitials(full_name)—"Rafi Ahmed"を"RA"にするword_count(text)— 単語を数え、空文字列なら0を返すsummarise(text)— 他の3つを呼び、読みやすい1行を返す
それからファイルの一番下で summarise をいくつかの文字列に対して呼び、結果を表示します — それが表示される唯一の場所です。
最後に3つの実験:
word_count("")を呼ぶ。ガードがなかったらどうなるか?summariseのreturnをprintに変え、その結果を変数に入れて表示する。何が見えるか、なぜか?cleanの呼び出しを、そのdefより上に移動し、エラーを読む。
2つ目が、この章でいちばん価値のある3分間です。print と return の違いを自分の目で一度見れば、二度と混同しません。