タートルグラフィックスで学ぶ Python のキホン 第 14 章
教材コード
第 14 章

値を返す関数

計算した答えを,関数に持ち帰らせます.

前の章で,関数を作れるようになりました.引数を使えば, 外から関数の中へ値を渡すこともできました.

では逆はどうでしょうか.関数に計算をしてもらって, その答えを受け取りたいことがあります. 「BMI を計算して」「この 2 点の距離を教えて」「次に塗る色を決めて」. こういうとき必要になるのが,今回の return(リターン)です.

return で返す値を戻り値(もどりち)といいます. 教科書によっては「返り値(かえりち)」「リターン値」とも呼びますが, どれも同じもので,英語の return value の訳が定まっていないだけです. この教材では戻り値と呼びます.

この章でできるようになること
  • return で,関数から値を持ち帰らせられる
  • 戻り値を変数に入れる・そのまま使う・print する,が使い分けられる
  • return を実行すると関数がそこで終わることがわかる
  • 戻り値のない関数が None を返していることがわかる
  • 座標・距離・色を返す関数を作って,タートルの絵に使える
  • math モジュールの関数が使える(度とラジアンの違いに気をつけられる)
  • 関数を組み合わせて,プログラム全体を見通しよく組み立てられる

14.1 関数の入力と出力

関数は,箱だと思ってください. 左から材料を入れると,右から製品が出てきます.

関数area(w, h)引数(入力)3 と 4戻り値(出力)12
図 14.1 関数は入力(引数)を受け取り,出力(戻り値)を返す箱.

第 13 章で作った関数は,入口だけがあって出口が無い箱でした. square(80) と呼ぶと絵が描かれますが, こちらに何かが返ってくるわけではありません. 今回はここに出口をつけます.

関数の種類例呼び方
入力も出力もないdef hello():hello()
入力だけ(第 13 章)def square(size):square(80)
出力だけdef pi(): …… return 3.14x = pi()
入力も出力もある(今回の主役)def area(w, h): …… return w * hx = area(3, 4)
もとからある関数も同じ 4 種類 この 4 つの分け方は,自分で作る関数だけのものではありません. 第 13 章 13.6.4 節で見た組み込み関数も,同じように分けられます.
  • print("あ") は入力だけ.画面に出すのが仕事で,こちらには何も返しません.
  • input() は出力だけ.引数は要らず,打ち込まれた文字を返します.第 3 章から s = input() と書いてきたのは,まさに戻り値の受け取りでした.
  • max(3, 8)・len("あいう")・int("42") は入力も出力もある関数です.
int(input()) という第 3 章からのお決まりの書き方も, 「input の戻り値を int の引数にする」という意味だったのです.

14.2 return — 値を持ち帰らせる

14.2.1 return の書き方

関数の中で return と書き,そのうしろに返したい値を書きます. それだけです.

area.py
# 横 w・縦 h の長方形の面積を返す
def area(w, h):
    answer = w * h
    return answer

s = area(3, 4)
print("面積は", s, "です")

print(area(10, 20))

コンソールに 面積は 12 です と 200 が出ます. area(3, 4) という書き方そのものが,12 という値に化ける と考えてください.

戻り値つき関数の形
def 関数の名前(仮引数):
    中身の命令
    return 返したい値

変数 = 関数の名前(実引数)
何を返すかはコメントに書く 「何を返す関数か」は return の行を見れば分かる,という考え方です. そのぶん,関数の上に「何を返す関数か」をコメントで書いておくと親切です.

14.2.2 戻り値の使い道は 4 つ

返ってきた値は「ただの数」です.数を書いてよい場所なら,どこにでも書けます.

use_return.py
# 2 倍にして返す
def double(x):
    return x * 2

# 1. 変数に入れる
a = double(5)
print(a)

# 2. そのまま print に渡す
print(double(5))

# 3. 計算の途中で使う
print(double(5) + double(3) + 1)

# 4. 別の関数(自分自身でもよい)の引数にする
print(double(double(5)))

10・10・17・20 と表示されます. 3 番目は 10 + 6 + 1,4 番目は double(10) ですね.

返したのに使っていない よくある失敗が「返したのに受け取っていない」です.
python
area(3, 4)          # 12 を返しているが,誰も受け取っていない
print("面積は 12 です")
エラーは出ませんが,計算した 12 は捨てられます. s = area(3, 4) のように変数に入れるか, print(area(3, 4)) のようにその場で使うか,必ずどちらかにしてください.
虫食い虫食い 14-A 三角形の面積

底辺 b・高さ h の三角形の面積を返す関数を作ります. ____ を埋めてください. 三角形の面積は「底辺 × 高さ ÷ 2」です.

fill13_area.py
# 底辺 b・高さ h の三角形の面積を返す
def triangle_area(b, h):
    ____ b * h / 2

s = triangle_area(10, 6)
print("面積は", ____, "です")

# 変数に入れずに,そのまま使ってもよい
print(____(8, 5))

14.2.3 return すると,関数はそこで終わる

return には,もうひとつ大事な働きがあります. return を実行した瞬間に,関数はそこで終わります. その先に命令が書いてあっても,実行されません.

return_exit.py
def test():
    print("ここは実行される")
    return 1
    print("ここは実行されない")

x = test()
print("戻ってきた値:", x)

これは欠点ではなく,むしろ便利な性質です. if と組み合わせると,こう書けます.

judge.py
# 点数から合否を返す
def judge(score):
    if score >= 60:
        return "合格"
    else:
        return "不合格"

print(judge(80))
print(judge(45))
print(judge(60))

return "合格" が実行されれば,そこで関数は終わるので, else を書かずに次のようにも書けます.どちらでも構いません.

else を省いた書き方
def judge(score):
    if score >= 60:
        return "合格"
    return "不合格"        # 60 点未満のときだけここに来る
返し忘れに注意 return がどの道を通っても実行されるかを確かめてください. 次の関数は score が 60 未満のとき, return に一度も出会わずに終わってしまいます.
python
def judge(score):
    if score >= 60:
        return "合格"
    # 60 未満のとき,何も返さない
エラーにはなりませんが,judge(30) の値は None(後述)になり, print すると None と表示されて「あれ?」となります.

14.2.4 返す値が無い関数と None

第 13 章で作った関数には return がありませんでした. では square(80) の「値」は何なのでしょうか.確かめてみましょう.

none.py
def hello():
    print("こんにちは")

x = hello()
print("hello() の値:", x)
print(type(x))

None(ノン)と表示されます. None は「値が無い」ということを表す特別な値です. return を書かなかった関数は, Python が自動的に None を返しているのです.

このことを知らないと,こんな失敗をします. 表示する関数を,さらに print で囲んでしまうという失敗です.

print_none.py
# 表示するだけの関数(return が無い)
def show_stars():
    print("*****")

print(show_stars())      # よくある勘違い

***** の次の行に,None が出てしまいます. まず show_stars() が実行されて ***** が表示され, そのあと,返ってきた None を外側の print が表示するからです. 直し方は簡単で,print を外して show_stars() と書くだけです.

print も None を返す print 自身も,画面に出すだけで何も返さない関数です. ですから print(print("あ")) と書くと, あ の次の行に None が出ます. 「None が 1 行よけいに出る」ときは, 戻り値の無い関数を print で囲んでいないかを疑ってください.
None は「値が無い」という値 Python では,何も返さない関数は None を返す というルールになっています. None は「値が無い」ことを表す特別な値で, 0 や ""(空文字)とは別ものです.

14.2.5 戻り値はいくつまで?

引数はいくつでも書けました.戻り値はどうでしょうか. Python では,カンマで区切っていくつでも並べて返せます.

min_max.py
# 小さいほう・大きいほうの順に並べて返す
def min_max(a, b):
    if a < b:
        return a, b
    else:
        return b, a

small, big = min_max(7, 3)
print("小さいほう:", small)
print("大きいほう:", big)

受け取る側も small, big = ... とカンマで並べます. 見た目は 2 つ返しているようですが,正確にいうと 「2 つの値をひとまとめにしたタプルという 1 個の値」を返しています. つまり「戻り値はひとつ」という原則は Python でも変わっていません. 書き方が楽になっているだけです.

ヒント この書き方は,座標のようにセットで意味を持つ値にぴったりです. 14.4 節で return x, y として使います.

14.3 いろいろな種類の値を返す

Python では型を書かないので,返せる値の種類に制限がありません. 数でも,文字でも,真偽値でも,同じ return で返せます. 「相性占い」を作りながら確かめましょう.

14.3.1 数を返す

aisho1.py
# 2 つの名前から相性(0〜99)を計算して返す
def aisho(name1, name2):
    total = 0
    for c in name1 + name2:
        total = total + ord(c)
    return total % 100

print("相性は", aisho("はなこ", "たろう"), "です")
print("相性は", aisho("因島", "花子"), "です")
ord と文字列の for for c in name1 + name2: は, 2 つの名前をつないだ文字列から1 文字ずつ取り出す繰り返しです. ord(c) はその文字に割り当てられている番号を返します (ord("A") は 65). 文字を数に変えて全部足し,% 100 で 0〜99 に収めているわけです. 占いですから,中身に深い意味はありません.

14.3.2 文字を返す

数のままでは味気ないので,判定したことばを返す関数にしてみます.

aisho2.py
def aisho(name1, name2):
    total = 0
    for c in name1 + name2:
        total = total + ord(c)
    return total % 100

# 点数からことばを返す
def judge(name1, name2):
    score = aisho(name1, name2)      # 戻り値を変数に受け取っておく
    if score > 70:
        return "ばっちり"
    elif score > 40:
        return "そこそこ"
    else:
        return "努力しだい"

print("はなこさんとたろうさんの相性は", judge("はなこ", "たろう"))
print("因島さんと花子さんの相性は", judge("因島", "花子"))

judge の中で aisho を呼んでいることに注目してください. 戻り値のある関数どうしも,組み合わせられます.

14.3.3 真偽値(True・False)を返す

第 5 章の if で使った score >= 60 のような式は, True(真)か False(偽)という値になるのでした. これも return で返せます.

aisho3.py
def aisho(name1, name2):
    total = 0
    for c in name1 + name2:
        total = total + ord(c)
    return total % 100

# 相性がよければ True,そうでなければ False を返す
def is_good(name1, name2):
    if aisho(name1, name2) > 60:
        return True
    else:
        return False

# 戻り値を if の条件にそのまま書ける
if is_good("はなこ", "たろう"):
    print("はなこさんとたろうさんは相性ばっちり!")
else:
    print("はなこさんとたろうさんは努力しだい...")

if is_good("因島", "花子"):
    print("因島さんと花子さんは相性ばっちり!")
else:
    print("因島さんと花子さんは努力しだい...")

if の条件には「最後に True か False になるもの」を 書く決まりでした. ですから if is_good("はなこ", "たろう"): のように, 真偽値を返す関数をそのまま条件に書けます.

is_ で始める 真偽値を返す関数には,is_(〜である)や has_(〜を持つ)で 始まる名前をつけるのが習慣です. is_good・is_even(偶数か)・is_hit(当たったか). こうしておくと if is_hit(x, y): が英語の文のように読めます.
== True は要らない is_good(...) は,中身がすでに True か False です. if is_good(...) == True: と書く必要はありません (書いても動きますが,くどい書き方です). さらに,次のように書けば if すら要りません.
python
def is_good(name1, name2):
    return aisho(name1, name2) > 60
虫食い虫食い 14-B 偶数かどうかを返す関数

数が偶数なら True,奇数なら False を返す関数を作り, if の条件に使います. 偶数とは「2 で割った余りが 0」の数でした(第 7 章の剰余). ____ を埋めてください.

fill13_even.py
# n が偶数なら True,奇数なら False を返す
def is_even(n):
    if n % 2 == ____:
        return ____
    else:
        return ____

for i in range(6):
    if ____(i):
        print(i, "は偶数")
    else:
        print(i, "は奇数")

14.4 タートルで使う

戻り値は,絵を描くときにも大活躍します. 「どこに描くか」「どれだけ進むか」「何色にするか」を 計算して返す関数を用意しておくと,本文がとても短くなります.

14.4.1 座標を計算して返す関数

中心から半径 r,角度 deg の場所の座標を返す関数を作ります. 座標は x と y の 2 つなので,14.2.5 節で習った「並べて返す」を使います.

point_on_circle.py
from turtle import *
import math

# 中心から半径 r・角度 deg の位置の座標を返す
def point_on_circle(r, deg):
    x = r * math.cos(math.radians(deg))
    y = r * math.sin(math.radians(deg))
    return x, y

ht()

for i in range(12):
    x, y = point_on_circle(150, i * 30)
    pu()
    goto(x, y)
    pd()
    pencolor("royalblue")
    dot(18)

done()
図 14.2 時計の文字盤のように 12 個の点を打つ.

本文の側は x, y = point_on_circle(150, i * 30) の 1 行だけで, 「半径 150 の円周上の,30 度ずつの場所」を手に入れています. 三角関数の計算は関数の中に押し込めてあるので, 読むときに気にしなくてよくなりました. 面倒な計算を関数に隠すのも,関数の大事な働きです.

同じ point_on_circle を使って,点を線でつないだり, 半径を少しずつ変えたりすると,まったく違う絵になります. 変えたのは呼び方だけで,関数の中身は一文字も直していません.

図 14.3 150 度ずつの点を線でつなぐ
図 14.4 半径を少しずつ大きくしてらせんに

14.4.2 距離を返す関数

2 点の距離は,三平方の定理(ピタゴラスの定理)で求められます. 横の差と縦の差をそれぞれ 2 乗して足し,平方根をとります. Python では ** が「べき乗」, math.sqrt が「平方根」です.

line_length.py
from turtle import *
import math

# (x1, y1) と (x2, y2) の距離を返す
def line_length(x1, y1, x2, y2):
    yoko = x2 - x1
    tate = y2 - y1
    return math.sqrt(yoko ** 2 + tate ** 2)

ht()
pensize(3)
pencolor("royalblue")

pu()
goto(-150, -80)
pd()
goto(120, 110)

nagasa = line_length(-150, -80, 120, 110)
print("線の長さ:", nagasa)

pu()
goto(-60, 40)
pencolor("crimson")
write(round(nagasa, 1), font=("Arial", 22, "bold"))

done()
330.2
図 14.5 引いた線の長さを計算して,その場に書き込む.
round で丸める round(x, 1) は「小数第 1 位までの値に丸める」命令です (ちょうど半分のときは偶数側へ丸めるので,厳密な四捨五入ではありません.第 3 章). round(x) とだけ書くと整数に丸めます. 計算結果をそのまま表示すると 330.15148038438356 のように 長くなるので,表示のときは丸めましょう.

14.4.3 色を決めて返す関数

返すのは数だけではありません.色の名前(文字)を返す関数も作れます.

pick_color.py
from turtle import *

# i 番目に使う色を返す
def pick_color(i):
    if i % 3 == 0:
        return "crimson"
    elif i % 3 == 1:
        return "seagreen"
    else:
        return "royalblue"

pensize(3)
ht()

for i in range(12):
    pencolor(pick_color(i))
    fd(140)
    lt(150)

done()
図 14.6 pencolor(pick_color(i)) の 1 行で色が変わっていく.

pencolor(pick_color(i)) は, pick_color(i) が返した色の名前を, そのまま pencolor の引数にしています. 戻り値をほかの関数の引数にする,14.2.2 節の 4 番目の使い方です.

虫食い虫食い 14-C 大きさを決めて返す関数

i 番目の正方形の 1 辺の長さを返す関数を作り, だんだん小さくなる正方形を描きます. ____ を埋めてください.

fill13_size.py
from turtle import *

pensize(2)
ht()

# i 番目の正方形の 1 辺の長さを返す
def pick_size(i):
    ____ 160 - i * 15

# 1 辺 size の正方形を描く(第 13 章で作った関数)
def square(size):
    for j in range(4):
        fd(size)
        lt(90)

pu()
goto(-80, -80)
pd()

for i in range(8):
    square(____(i))
    lt(10)

done()

14.5 math を使った計算

math.sqrt や math.cos を,ここまで説明なしに使ってきました. これらは math モジュールに入っている関数です. 第 13 章の最後に出てきた「ライブラリ」のひとつで, 数学の計算をする関数が集められています.

math の使い方
import math          # 数学の道具箱を開ける(1 行目に書く)

print(math.sqrt(2))  # math. を付けて呼ぶ
import math と from turtle import * の違い from turtle import * は「箱の中身を全部取り出して, そのままの名前で使えるようにする」書き方でした. import math は「箱ごと持ってくる」書き方で, 使うときに math. を付けます. math.sqrt と書けば「math の中の sqrt」という意味で, どこから来た関数かがひと目で分かります.
書き方意味例
math.sqrt(x)平方根math.sqrt(9) → 3.0
math.pi円周率 π(関数ではなく値)3.141592653589793
math.sin(x)サイン(x はラジアン)
math.cos(x)コサイン(x はラジアン)
math.tan(x)タンジェント(x はラジアン)
math.radians(d)度をラジアンに直すmath.radians(180) → 3.1415…
math.degrees(r)ラジアンを度に直すmath.degrees(math.pi) → 180.0
math.floor(x)切り捨て(小さいほうの整数へ)math.floor(3.7) → 3
math.ceil(x)切り上げ(大きいほうの整数へ)math.ceil(3.2) → 4
math.hypot(a, b)斜辺の長さ(三平方の定理)math.hypot(3, 4) → 5.0
math を付けない関数 abs(x)(絶対値)・round(x, n)(丸め)・ max(a, b)・min(a, b)・len(s) は, math の外にある組み込み関数です (第 13 章 13.6.4 節). import が要らないかわりに,math. も付けません. math.sqrt には math. が要り, abs には要らない.この違いは 「どこの箱に入っている関数か」の違いです.

14.5.1 度とラジアンに気をつける

sin・cos は度では動かない ここが今回いちばんの落とし穴です. math.sin・math.cos・math.tan に渡す角度の単位は 度ではなくラジアンです. タートルの lt(90)・rt(90) は度なので, うっかり同じ感覚で書くと,エラーも出ないまま間違った絵になります.
radians.py
import math

print("math.sin(90)          =", math.sin(90))
print("math.sin(radians(90)) =", math.sin(math.radians(90)))

print("math.cos(0)           =", math.cos(0))
print("math.cos(radians(60)) =", math.cos(math.radians(60)))

print("180 度は", math.radians(180), "ラジアン")
print(math.pi, "ラジアンは", math.degrees(math.pi), "度")

math.sin(90) は 0.894… という, まるで見当違いの値になります.エラーは出ません. 90 度のサインは 1 のはずですから,これはバグです. 三角関数に角度を渡すときは,必ず math.radians() をかぶせる. そう覚えてください.

度ラジアン書き方
0 度0math.radians(0)
30 度π/6 ≒ 0.524math.radians(30)
90 度π/2 ≒ 1.571math.radians(90)
180 度π ≒ 3.142math.radians(180)
360 度2π ≒ 6.283math.radians(360)

14.5.2 sin で波を描く

math.sin を「x の場所での波の高さを返す関数」として使うと, タートルで波が描けます. math.sin が返すのは -1 から 1 の値なので, 80 倍して見える大きさにしています.

wave.py
from turtle import *
import math

# 横位置 x のところの波の高さを返す
def wave_y(x):
    return 80 * math.sin(math.radians(x * 2))

ht()
pensize(3)
pencolor("teal")

pu()
goto(-280, wave_y(-280))
pd()

for i in range(141):
    x = -280 + i * 4
    goto(x, wave_y(x))

done()
図 14.7 サインカーブ.高さを返す関数を 1 つ作っただけ.
やってみよう math.radians(x * 2) の 2 を変えると波の細かさが, 80 を変えると波の高さが変わります. math.cos に変えるとどうなるか, 80 * (math.sin(math.radians(x)) + math.sin(math.radians(x * 3))) のように 2 つ足すとどうなるかも試してみてください.
虫食い虫食い 14-D 星の頂点の座標を返す

星(五芒星)の i 番目の頂点の座標を返す関数を作り, 線でつないで星を描きます. 頂点は 144 度ずつ離れているのでしたね(第 2 章). ____ を埋めてください. math.cos と math.sin に渡す前に,何をかぶせるか忘れずに.

fill13_star.py
from turtle import *
import ____

pensize(3)
pencolor("gold")
ht()

# 星の i 番目の頂点の座標を返す
def star_point(i):
    deg = 90 + i * ____
    x = 160 * math.cos(math.____(deg))
    y = 160 * math.sin(math.____(deg))
    return x, y

x, y = star_point(0)
pu()
goto(x, y)
pd()

for i in range(1, 6):
    x, y = star_point(i)
    goto(x, y)

done()

14.6 関数を組み合わせて組み立てる

第 13 章で,図形を部品に分けて組み立てました. 戻り値があると,計算する部分も部品に分けられます. 「西暦和暦変換プログラム」で見てみましょう.

まず,関数に分けずに書くと,こうなります. 短いプログラムなので,これでも読めます.

関数に分けていない版
print("西暦和暦変換プログラムを開始します")

print("西暦を入力してください")
year = int(input())

if year >= 2019:
    wareki = "令和" + str(year - 2018)
elif year >= 1989:
    wareki = "平成" + str(year - 1988)
elif year >= 1926:
    wareki = "昭和" + str(year - 1925)
else:
    wareki = "不明"

print(year, "年は", wareki, "年です")
print("西暦和暦変換プログラムを終了します")

これを「入力する」「変換する」「表示する」の 3 つに分けます. 変換する部分だけが戻り値を持つところに注目してください.

wareki.py
# 西暦を入力してもらって返す
def input_year():
    print("西暦を入力してください")
    return int(input())

# 西暦を和暦の文字に変えて返す
def to_wareki(year):
    if year >= 2019:
        return "令和" + str(year - 2018)
    elif year >= 1989:
        return "平成" + str(year - 1988)
    elif year >= 1926:
        return "昭和" + str(year - 1925)
    else:
        return "不明"

# 結果を表示する(戻り値は無い)
def show_result(year, wareki):
    print(year, "年は", wareki, "年です")

# 変換をひとまとめにする
def convert_year():
    year = input_year()
    wareki = to_wareki(year)
    show_result(year, wareki)

print("西暦和暦変換プログラムを開始します")
convert_year()
print("西暦和暦変換プログラムを終了します")
入力の打ち方 右コンソールの入力行にあらかじめ 2005 と書いてあります. 実行すると,この行が input() に渡されます. 数を変えて,令和・平成・昭和のそれぞれを試してください.
関数入力(引数)出力(戻り値)役割
input_year()なし西暦(数)人に聞く
to_wareki(year)西暦(数)和暦(文字)計算する
show_result(year, wareki)西暦と和暦なし(None)見せる
convert_year()なしなし3 つを順に呼ぶ
入力・計算・出力を分ける 表を縦に見てください. 「人に聞く」「計算する」「見せる」を別々の関数にするのが, プログラムを見通しよくする定石です.
  • 「計算する」関数は print も input もしません.だからそのままテストできます(14.7 節).
  • 画面への出し方を変えたくなっても,直すのは「見せる」関数だけです.
  • 入力をキーボードからファイルに変えても,「計算する」関数はそのまま使えます.
虫食い虫食い 14-E 関数をつないで使う

身長と体重から BMI を計算して判定するプログラムです. 計算する関数と判定する関数に分かれています. BMI は「体重 ÷ (身長 m × 身長 m)」で求めます. ____ を埋めてください.

fill13_bmi.py
# 身長(cm)・体重(kg) から BMI を計算して返す
def get_bmi(height, weight):
    m = height / 100
    ____ weight / (m * m)

# BMI から判定のことばを返す
def judge_bmi(bmi):
    if bmi < 18.5:
        return "やせぎみ"
    elif bmi < 25:
        return "ふつう"
    else:
        ____ "ふとりぎみ"

bmi = ____(170, 62)
print("BMI は", round(bmi, 1))
print("判定は", ____(bmi))

14.7 関数をテストする

戻り値のある関数には,うれしい性質があります. 正しく動いているかを,機械的に確かめられるのです. これをテストといいます.

例として,累乗(base を exponent 回かけた数)を 計算する関数を作ります.

power1.py
# base を exponent 回かけた数を返す
def power(base, exponent):
    answer = 1
    for i in range(exponent):
        answer = answer * base
    return answer

print("power(2, 2) =", power(2, 2))
print("power(2, 3) =", power(2, 3))
print("power(2, 4) =", power(2, 4))
print("power(3, 3) =", power(3, 3))

毎回キーボードから入力して確かめるのは大変ですし, どのテストをやったか忘れてしまいます. さらに,プログラムを 1 行直しただけでも, どこが動かなくなるかは予測できません. だから,テストはプログラムに書いて自動化するのが正解です. 上のように並べて呼び出して表示するだけでも,立派なテストです.

14.7.1 テストも関数にする

print の行が長くて,書き換えるのが大変です.テストも関数にしましょう.

power2.py
def power(base, exponent):
    answer = 1
    for i in range(exponent):
        answer = answer * base
    return answer

# 1 件ぶんのテストを表示する
def test(base, exp):
    print("power(", base, ",", exp, ") =", power(base, exp))

test(2, 2)
test(2, 3)
test(2, 4)
test(3, 3)

14.7.2 期待値を書いて,合否まで出す

これでも「答えが合っているか」は自分の目で確かめなければなりません. プログラムに答え(期待値)も書いておき, 合っているかどうかまで表示させましょう.

power3.py
def power(base, exponent):
    answer = 1
    for i in range(exponent):
        answer = answer * base
    return answer

# expected は「こうなるはず」という答え
def test(base, exp, expected):
    answer = power(base, exp)
    if answer == expected:
        result = "PASS"
    else:
        result = "FAIL"
    print("power(", base, ",", exp, ") =", answer, " 判定:", result)

test(2, 2, 4)
test(2, 3, 8)
test(2, 4, 16)
test(3, 3, 27)
test(5, 0, 1)        # 0 乗は 1
test(2, 1, 2)

すべて PASS と出れば安心です. このあと power の中身を書き換えたときも, このプログラムを 1 回実行するだけで, 壊れていないかが一瞬で分かります.

端っこを試す テストは「うまくいく場合」だけでなく, 端っこの場合も入れておくと役に立ちます. test(5, 0, 1)(0 乗),test(0, 3, 0)(0 のべき乗), test(2, 1, 2)(1 乗)のように, 「0 のとき」「1 のとき」「いちばん小さいとき」を必ず試してください. バグの多くはここに隠れています.

14.8 この章で出てきた書き方

書き方意味
return 値その値を戻り値として返し,関数はそこで終わる
return a, b2 つ以上の値を並べて返す
x = f(…)戻り値を変数に入れる
print(f(…))戻り値をそのまま表示する
g(f(…))戻り値を別の関数の引数にする
if f(…):真偽値を返す関数を条件に使う
None「値が無い」ことを表す値.return の無い関数が返す
import math数学の関数の入った箱を持ってくる
math.sqrt(x)平方根.math. を付けて呼ぶ
math.radians(d)度をラジアンに直す.sin・cos に渡す前に必ず使う
round(x, 1)小数第 1 位までの値に丸める
ord(c)文字に割り当てられた番号を返す
今回よくある間違い
症状原因と直し方
SyntaxError: 'return' outside function関数の外で return を書いている.字下げを見直す
None と表示されるreturn を書き忘れている/if のある道筋で return に届いていない/表示するだけの関数を print(…) で囲んでいる
TypeError: unsupported operand type(s) for +: 'NoneType' and 'int'None を返す関数の値で計算しようとしている.その関数に return があるか確かめる
NameError: name 'math' is not definedimport math を書き忘れている
NameError: name 'sqrt' is not definedmath. を付け忘れている
計算結果がおかしい(エラーは出ない)sin・cos に度をそのまま渡している.math.radians() をかぶせる
値が変わらない・使われていないf(3) と呼んだだけで,戻り値を受け取っていない
return のうしろが実行されない仕様どおり.return で関数は終わる

演習

以下の演習には出発点となるプログラムが入っています. 「▶ この演習を開く」で右側に読み込み,それを発展させて完成させてください.

「テストする」の意味 どの演習も,関数を 2 回以上呼んでテストすることを条件にします. 「テストする」とは,14.7 節でやったように プログラムの中からその関数を呼び,結果を表示することです. 演習 14-1〜14-5 では,14.7.2 節の期待値つきの test 関数も書き, 要件に書いてある期待値がすべて PASS になることを確かめてください. 演習 14-7 では,期待値を自分で計算して test を書きます.
演習演習 14-1 年齢計算

生まれ年(西暦)から今年の年齢を計算して返す関数 get_age(year) があります. テストされていることを確かめてから,次の要件を満たすように直してください.

  • get_age(year, this_year) に直し,「今年」も引数で受け取る.生まれ年が 1900 年より前か,this_year より後なら,ありえない年なので -1 を返す
  • 期待値つきの test(year, this_year, expected) を作り(14.7.2 節),test(2006, 2026, 20)・test(2026, 2026, 0)・test(1800, 2026, -1)・test(2030, 2026, -1) がすべて PASS になるようにする
  • これもやりましょう:年齢から立場を表すことばを返す関数 get_stage(age) を新しく作る.6 歳未満なら「未就学」,12 歳未満なら「小学生」,15 歳未満なら「中学生」,18 歳未満なら「高校生」,22 歳未満なら「大学生」,それ以上なら「おとな」を返します.get_stage(get_age(2006, 2026)) のように 2 つの関数を組み合わせて呼び,「大学生」と表示されることを確かめます.get_stage(9) は「小学生」です
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. get_age(year, this_year) が今年も引数で受け取り,1900 年より前か this_year より後の生まれ年には -1 を返す
  2. 期待値つきの test(year, this_year, expected) があり,test(2006, 2026, 20)・test(2026, 2026, 0)・test(1800, 2026, -1)・test(2030, 2026, -1) がすべて PASS になる
  3. get_stage(age) が,6・12・15・18・22 歳を境目に 6 つのことばのどれかをreturn で返す(関数の中で print しない)
  4. get_stage(get_age(2006, 2026)) で「大学生」,get_stage(9) で「小学生」と表示される

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • get_stage も期待値つきでテストする(境目の 6・12・15・18・22 歳を試す)
  • キーボードから生まれ年と今年を入力し,年齢と立場を表示する関数を分けて作る
  • 生まれ年と生まれ月から,学年(小 1・中 2・大 3 など)を返す関数を足す

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

演習演習 14-2 税込み価格

税抜き価格から税込み価格(消費税 10 %)を計算して返す関数 get_with_tax(price) があります. テストされていることを確かめてから,次の要件を満たすように直してください.

  • get_with_tax(price, rate) に直し,税率 rate(%の数.10 % なら 10)も引数で受け取る.1 円未満は切り捨てる(int() または math.floor())
  • 期待値つきの test(price, rate, expected) を作り,test(100, 10, 110)・test(100, 8, 108)・test(980, 10, 1078)・test(0, 10, 0) がすべて PASS になるようにする
  • これもやりましょう:ドーナツ屋さんにする.店内で食べる("eatin")か持ち帰る("takeout")かを文字で受け取って税率を返す関数 get_rate(category) を新しく作る."takeout" なら 8,"eatin" なら 10,それ以外の文字なら -1 を返します.200 円のドーナツについて get_with_tax(200, get_rate("takeout"))(216 円)と get_with_tax(200, get_rate("eatin"))(220 円)を表示します
どちらが 8 % か 日本の消費税は,持ち帰りが 8 %,店内で食べると 10 % です. 店内で食べるのは「外食」のあつかいになるので,軽減税率が使えないのです. 同じドーナツでも合計金額が変わります.
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. get_with_tax(price, rate) が税率(%)も引数で受け取り,1 円未満を切り捨てる
  2. 期待値つきの test で,test(100, 10, 110)・test(100, 8, 108)・test(980, 10, 1078)・test(0, 10, 0) がすべて PASS になる
  3. get_rate(category) を新しく作り,"takeout" なら 8,"eatin" なら 10,それ以外なら -1 を return で返す
  4. get_rate の戻り値を get_with_tax に渡して,200 円のドーナツが持ち帰り 216 円・店内 220 円と表示される

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • 税額だけを返す get_tax(price, rate) を作り,get_with_tax から呼ぶ
  • 個数を引数にして合計金額を返す関数を足す
  • get_rate と組み合わせて,レシートの 1 行を表示する関数を作る(-1 のときは「食べ方が分かりません」と出す)

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

演習演習 14-3 勝率と BMI

勝率(%)を返す関数 get_winning_rate(win, lose) と, BMI を返す関数 get_bmi(height, weight) があります. どちらも「数を受け取って数を返す」関数です. テストされていることを確かめてから,次の要件を満たすように直してください.

  • get_winning_rate(win, lose, draw) に直し,引き分けも試合数に数える(勝率 = 勝数 × 100 ÷ 試合数,小数は切り捨て).0 勝 0 敗 0 分のように試合数が 0 のときは,割り算をせずに -1 を返す
  • 期待値つきの test(win, lose, draw, expected) を作り,test(12, 8, 0, 60)・test(3, 7, 0, 30)・test(1, 1, 2, 25)・test(0, 0, 0, -1) がすべて PASS になるようにする
  • これもやりましょう:勝率から調子を表すことばを返す関数 judge_rate(rate) を新しく作る.70 以上なら「絶好調」,40 以上なら「まずまず」,それより小さければ「立て直そう」,-1(まだ試合がない)なら「まだ試合がありません」を返します.judge_rate(get_winning_rate(12, 8, 0)) が「まずまず」になることを表示して確かめます
  • これもやりましょう:身長(cm)から標準体重(BMI が 22 になる体重,kg)を返す関数 get_standard_weight(height) を新しく作る.標準体重 = 22 × 身長(m) × 身長(m) です.round(get_standard_weight(170), 1) を表示して 63.6 になることを確かめます
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. get_winning_rate(win, lose, draw) が引き分けも試合数に数え,試合数 0 のときは割り算をせずに -1 を返す
  2. 期待値つきの test で,test(12, 8, 0, 60)・test(3, 7, 0, 30)・test(1, 1, 2, 25)・test(0, 0, 0, -1) がすべて PASS になる
  3. judge_rate(rate) が 4 つのことばを return で返し,judge_rate(get_winning_rate(12, 8, 0)) で「まずまず」と表示される
  4. get_standard_weight(height) が標準体重を返し,170 cm で 63.6 kg(小数第 1 位まで)と表示される

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • get_bmi と get_standard_weight も期待値つきでテストする
  • BMI から「やせぎみ」「ふつう」「ふとりぎみ」を返す関数を足す(14.6 節)
  • キーボードから入力し,「人に聞く」「計算する」「見せる」の関数に分ける

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

演習演習 14-4 総和

1 から n までを全部足した数を返す関数 get_sum(n) があります. テストされていることを確かめてから,次の要件を満たすように直してください.

  • get_sum(a, b) に直し,a から b までの総和を返す(a と b も足す数に入る)
  • 期待値つきの test(name, answer, expected) を作る.name は表示用の名前,answer は関数を呼んだ結果です(例:test("get_sum(1, 10)", get_sum(1, 10), 55)).get_sum(1, 10) が 55,get_sum(1, 100) が 5050,get_sum(5, 5) が 5 になり,すべて PASS になるようにする
  • これもやりましょう:1 から n までの積(階乗)を返す関数 get_product(n) を新しく作る.同じ test で,get_product(5) が 120,get_product(1) が 1,get_product(0) が 1 になることも確かめます(掛け算の始まりの数を何にすればよいかを考える)
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. get_sum(a, b) が a から b までの総和を return で返す
  2. 期待値つきの test で,get_sum(1, 10) が 55,get_sum(1, 100) が 5050,get_sum(5, 5) が 5 になり,PASS と表示される
  3. 1 から n までの積(階乗)を返す関数 get_product(n) を新しく作っている
  4. get_product(5) が 120,get_product(1) が 1,get_product(0) が 1 になり,PASS と表示される

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • ガウスの公式 n * (n + 1) / 2 で計算する関数を作り,for で 1〜10 の両方の答えが一致するか確かめる
  • 偶数だけ・奇数だけの総和を返す関数を足す(第 7 章の剰余)
  • get_sum(10, 1) のように a が b より大きいときの扱いを決めてテストする

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

演習演習 14-5 ランダムとじゃんけん

最小値と最大値の間のランダムな数を返す関数 make_random(low, high) と, じゃんけんの勝敗を返す関数 janken_judge(hand1, hand2) があります.

手は 0=グー,1=チョキ,2=パーで表し, 結果は player1 から見て 1=勝ち・0=あいこ・ -1=負けを返します. 次の要件を満たすように発展させてください.

  • 期待値つきの test(hand1, hand2, expected) を作り,test(0, 1, 1)・test(0, 2, -1)・test(2, 2, 0)・test(1, 2, 1) がすべて PASS になるようにする
  • 手の数字から「グー」「チョキ」「パー」を返す関数 hand_name(hand) と,勝敗の数字(1・0・-1)から「勝ち」「あいこ」「負け」を返す関数 result_name(result) を新しく作る.print(hand_name(0), "対", hand_name(2), "→", result_name(janken_judge(0, 2))) で「グー 対 パー → 負け」と表示されることを確かめる
  • これもやりましょう:コンピュータと n 回勝負して,自分が勝った回数を返す関数 count_wins(n) を新しく作る.1 回ごとに,自分の手もコンピュータの手も make_random(0, 2) で決め,janken_judge が 1 を返した回数を数えます.関数の中では print せずに回数を return し,呼んだ側で「10 回中 ○ 勝でした」と表示します
ヒント じゃんけんの判定は,hand1 - hand2 の値で場合分けすると楽です. 差が 0 ならあいこ,-1 か 2 なら player1 の勝ち, 1 か -2 なら負けです. random.randint(a, b) は a 以上 b 以下の 整数をランダムに返します.
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. 期待値つきの test で,test(0, 1, 1)・test(0, 2, -1)・test(2, 2, 0)・test(1, 2, 1) がすべて PASS になる
  2. hand_name(hand) が「グー」「チョキ」「パー」を返す
  3. result_name(result) が「勝ち」「あいこ」「負け」を返し,「グー 対 パー → 負け」と表示される
  4. count_wins(n) が make_random(0, 2) で両方の手を決めて n 回勝負し,勝った回数を return で返す.count_wins(10) の結果が表示される

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • キーボードから自分の手を入力して,コンピュータと 1 回勝負する
  • 勝った回数から勝率(%)を返す関数を足し,回数を増やすと何%に近づくか確かめる
  • あいこのときは勝負がつくまで続ける

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

演習演習 14-6 コイン投げのシミュレーション

コイン投げをまねる関数 flip() があります. 2 分の 1 の確率で True(表)を返します. また simulate(n) は flip() を n 回呼び, 表が出た回数を返します. これを使って,次の要件を満たすシミュレーションにしてください.

  • n 回投げて表が出た割合(%)を返す関数 get_rate(n) を新しく作る.中で simulate(n) を呼び,表の回数 × 100 ÷ n を返します
  • get_rate を 10 回・100 回・1000 回・10000 回で呼び,割合を小数第 2 位まで(round(値, 2))表示して,回数を増やすと理論値の 50 %に近づくことを確かめる
  • これもやりましょう:サイコロ版を作る.1〜6 の目を返す関数 roll() と,n 回振って 1 の目が出た割合(%)を返す関数 roll_rate(n) を新しく作り,同じく 10 回・100 回・1000 回・10000 回で表示して,理論値の約 16.67 %(6 分の 1)に近づくことを確かめます
注意 回数を増やしすぎるとブラウザが止まります. 100000 回までにしてください.
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. n 回投げて表が出た割合(%)を返す関数 get_rate(n) を新しく作り,中で simulate(n) を呼ぶ
  2. コインの割合を 10 回・100 回・1000 回・10000 回で小数第 2 位まで表示し,回数を増やすと 50 %に近づくことが確かめられる
  3. 1〜6 の目を返す roll() と,n 回振って 1 の目が出た割合(%)を返すroll_rate(n) を新しく作る
  4. サイコロの割合も 10 回〜10000 回で表示し,約 16.67 %に近づくことが確かめられる

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • 表が何回連続で出たか,いちばん長い連続を返す関数を足す
  • 2 個のサイコロの目の合計が 7 になる割合を調べる
  • 結果をタートルの棒グラフで描く(戻り値を fd の長さに使う)

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

自由演習 14-7 戻り値を使った作品

最後は自由制作です. 出発点の pick_color・point_on_circle とは別に, 戻り値のある関数を自分で 1 つ以上作り,その戻り値を使って絵を描いてください. 作った関数の上には,何を返す関数かを 1 行コメントで書きます. どんな値を返すかは,次の例を参考にしてください.

  • 座標を返す関数(return x, y)
  • 色を返す関数(return "crimson")
  • 大きさ・角度・長さを計算して返す関数
  • 「もう描いてよいか」を判定して返す関数(return True)

これもやりましょう:絵に使う前に,作った関数を 期待値つきの test(14.7.2 節)で確かめます. 引数を 2 通り以上選び,そのときに返るはずの値(期待値)を自分で手計算して書き, すべて PASS になることを確かめてください.

test は演習 14-4 の test(name, answer, expected) の形にすると, どの関数にも使えます.

ヒント 出発点のプログラムは,色を返す関数と 座標を返す関数の 2 つを使って,円周上に点を並べています. 半径・個数・色の決め方を変えるだけでも,まったく違う模様になります. math.sin を使って半径を波打たせると,花のような形になります. 期待値は,if の分かれ目や sin が 0・1 になる角度など, 手で計算しやすい引数を選ぶと書きやすくなります. 小数を返す関数は,round(値, 1) などでそろえてから比べます.
評価の目安(10 段階)

必須(すべて満たして正しく動けば 8)

  1. 出発点の pick_color・point_on_circle とは別に,戻り値のある関数を自分で 1 つ以上作り(座標・色・大きさ・角度・真偽値など),その上に何を返す関数かを 1 行コメントで書く
  2. その戻り値を goto・pencolor・fd・dot・if などに使い,絵が出発点と変わっている
  3. 自分で作った関数を期待値つきの test で 2 通り以上の引数について確かめ,すべて PASS と表示される(期待値は自分で計算した値で,関数の戻り値と == で比べている)

発展(必須に加えて 1 つ以上で 9,学んだ範囲を大きく超える作り込みで 10)

  • 戻り値のある関数を 3 つ以上組み合わせる(返した値を別の関数の引数にする)
  • 半径を角度から計算して返す関数を math.sin で作り,波の数や振れ幅を引数 1 つで変えられるようにする
  • if で返す値が変わる関数について,分かれ目の両側の引数もテストする

必須が 1〜2 つ足りない → 7 / 動かない・未着手が複数 → 6

まとめ

  • return 値 で,関数から値を持ち帰らせる.その値を戻り値という.
  • 戻り値は,変数に入れる・そのまま print する・計算に使う・別の関数の引数にする,のどれでも使える.受け取らなければ捨てられる.
  • return を実行すると,関数はそこで終わる.そのうしろの命令は実行されない.
  • return の無い関数は None を返している.
  • 返せる値の種類に制限は無い.数・文字・真偽値,return a, b で 2 つ以上まとめて返すこともできる.
  • タートルでは,座標・距離・色を計算して返す関数が便利.面倒な計算を関数の中に隠せる.
  • math モジュールには sqrt・sin・cos・pi などがある.sin・cos に渡す角度はラジアンなので,math.radians() を忘れない.
  • 「人に聞く」「計算する」「見せる」を別々の関数にすると,計算する関数をテストできる.期待値を書いて PASS/FAIL を出すのが定石.

次の回では,データをまとめて持つリストを習います. 覚えておきたい数が 5 個でも 100 個でも,変数を 100 個作る必要はなくなります. 第 8 章で 1 匹ずつ名前をつけたかめも,リストに入れてしまえば for で一斉に動かせます. ここまでに作った関数と組み合わせると,書ける量が一気に増えます.

復習:虫食いで確かめる

コードの ____ を埋めてください.実行前に結果を予測し,実行後に問題文の条件と照合しましょう.assert は条件が成り立つかを調べ,成り立たないとエラーになります.

虫食い虫食い・復習 1 面積を返す

長方形の面積を呼び出し側へ返してください.結果が60と表示され,assert が通ることを確認します.

fill_review_lec11_1.py
def area(width, height):
    ____ width ____ height
result = area(10, 6)
print(result)
assert result == 60