関数をつくる
命令に名前をつけて,自分だけの部品を作ります.
ここまでで,みなさんは fd・lt・if・
for に加えて,たくさんのかめ・アニメーション・ゲームまで
作れるようになりました.これで「どんなものでも作れる」はずです.
ところが実際に作ってみると,プログラムはどんどん長くなり,
自分でも何を書いたのか分からなくなってきたのではないでしょうか.
今回はその悩みに効く道具を習います.関数(かんすう)です.
関数とは,いくつかの命令をひとまとめにして,名前をつけたものです.
一度作ってしまえば,その名前を書くだけで中身がまるごと実行されます.
fd や lt と同じように使える,
自分専用の命令を作るということです.
Python では,これを関数といいます.
def という 3 文字だけ覚えれば作れます.
- 同じコードの繰り返しに気づき,
defで関数にまとめられる - 関数の定義と呼び出しの違いが説明できる
- 関数を呼んだとき,処理がどう動いて戻ってくるかを追える
- 引数で「大きさ」「角の数」「色」を外から渡せる
- 引数が書いた順番で決まることがわかり,
名前=値(キーワード引数)でも渡せる printやlenも関数だと気づき,自分で作る関数との違いが説明できる- 関数の中の変数が外から見えないこと(ローカル変数)がわかる
- 「描き終わったら元の位置・元の向きに戻す」を守った,組み合わせられる関数が書ける
def はすでに 3 回も使っています.
第 10 章の def step():,第 11 章の def draw_board():,
第 12 章の def tick(): や def go_left():.
あのときは「ontimer に渡すための決まり文句」として,
理由を説明しないまま書いてもらいました.
今回は,その正体を明かします.
なぜ名前を付けるのか,かっこの中に並べた名前(引数)は何なのか,
そして global は何をしていたのか.
決まり文句の理由が分かると,これまで書いてきたプログラムの見え方が変わります.
- 同じ処理をひとつにまとめる.同じことを 2 回書いたら,それは関数にする合図です.直すときも 1 か所で済みます.
- 名前をつけて読みやすくする.20 行の命令の列より,
draw_house()の 1 行のほうが「何をしているか」がすぐ分かります. - 部品として組み合わせる.小さな関数を組み合わせて大きな関数を作れば,プログラム全体の複雑さを抑えられます.これを構造化プログラミングといいます.
13.1 同じ絵を何度も描いている
まず,いまの自分のプログラムがどうなっているかを見てみましょう.
次は正方形を 3 つ並べて描くプログラムです.第 6 章で習った for を使って,
1 つぶんはすでに 3 行に縮めてあります.
from turtle import *
pensize(2)
# 左の正方形
pu()
goto(-220, -40)
pd()
for i in range(4):
fd(80)
lt(90)
# 中央の正方形
pu()
goto(-120, -40)
pd()
for i in range(4):
fd(80)
lt(90)
# 右の正方形
pu()
goto(-20, -40)
pd()
for i in range(4):
fd(80)
lt(90)
done()
for i in range(4): から始まる 3 行が,まったく同じ形で 3 回出てきます.
第 2 章でも「同じ 8 行を 3 回書いていて,いかにも無駄です」と書きました.
for で少し短くなりましたが,本質は変わっていません.
- 1 辺を 80 から 60 に変えたくなったら,3 か所直さなければなりません.1 か所直し忘れると,そこだけ大きさの違う変な絵になります.
- 正方形ではなく正六角形にしたくなったら,やはり 3 か所です.
- そもそも,このプログラムをぱっと見て「正方形を 3 つ描いている」と気づけるでしょうか.
fdとltの列を読み解く必要があります.
同じものを 2 回以上書いたら,関数にする. これが今回のいちばん大事な合図です.
13.2 関数をつくる — def
13.2.1 def の書き方
さきほどのプログラムを,関数を使って書き直します. まず「正方形を描く」という関数を作り,それを 3 回呼びます.
from turtle import *
pensize(2)
# ここが「関数の定義」
def square():
for i in range(4):
fd(80)
lt(90)
# ここが「関数の呼び出し」
pu()
goto(-220, -40)
pd()
square()
pu()
goto(-120, -40)
pd()
square()
pu()
goto(-20, -40)
pd()
square()
done()
def は define(定義する)の略です.書き方の決まりは次の 3 つだけです.
defのあとに関数の名前を書き,続けてかっこを書く(今は空のかっこ).- その行の終わりにコロン
:を打つ. - 次の行から,関数の中身を字下げ(インデント)して書く.字下げが戻ったところで関数の定義は終わり.
def 関数の名前():
中身の命令
中身の命令
if,第 6 章の for と同じ決まりです.
「コロンを打ったら,次の行から字下げ」と覚えてください.
字下げは半角スペース 4 個に揃えます.
エディタで Tab キーを押せば 4 個ぶん入ります.
13.2.2 定義と呼び出しは別物
ここがいちばん誤解しやすいところです.
def を書いただけでは,中身は 1 回も実行されません.
def は「こういう命令を作っておいてね」とコンピュータに登録するだけです.
from turtle import *
# 関数を定義しただけ.呼んでいない
def square():
for i in range(4):
fd(80)
lt(90)
done()
実行しても,エラーは出ません.そのかわり何も描かれません.
料理でいえば,レシピを書いただけで台所に立っていない状態です.
最後に square() の 1 行を足してから,もう一度実行してみてください.
| 書き方 | 呼び名 | 意味 |
|---|---|---|
def square(): | 定義(definition) | 「square という命令を作る」.この時点では何も起きない |
square() | 呼び出し(call) | 「square を実行しろ」.ここで中身が動く |
square() と書けば実行されますが,square とかっこ無しで書いても,
エラーにならないのに何も起きません(「関数そのもの」を指しているだけになります).
「動かないのになぜ?」というときは,まずかっこを確かめてください.
13.2.3 呼び出しの流れ — 呼ぶ・実行する・戻ってくる
関数を呼ぶと,プログラムの流れはいったん関数の中へ飛びます. そして関数の中身を最後まで実行したら, 呼んだ場所のすぐ次の行へ戻ってきます.
次のプログラムで,この流れを目で確かめましょう. 「速さ」を「とてもゆっくり」にして実行してください. 正方形を描く関数と,次の場所へ移動する関数を,交互に 3 回呼んでいます.
from turtle import *
pensize(2)
# 正方形を描く
def square():
for i in range(4):
fd(80)
lt(90)
# 次に描く場所まで移動する
def move():
pu()
fd(100)
pd()
pu()
goto(-220, -40)
pd()
for i in range(3):
square()
move()
done()
本文(関数の外)で実際にやっていることは, 「正方形を描く」「移動する」を 3 回繰り返す,ただそれだけです. プログラムを読んだ人に,やりたいことがそのまま伝わります. これが「名前をつけて読みやすくする」という 2 番目の目的です.
正三角形を描く関数 triangle を作り,3 回呼んで並べます.
____ を埋めてください.
def の行の終わりに何を打つか,中身は何をするか,
そして呼ぶときに何を書くかを思い出しましょう.
from turtle import *
pensize(2)
pencolor("seagreen")
# 1 辺 90 の正三角形を描く関数
def triangle()____
for i in range(____):
fd(90)
lt(____)
pu()
goto(-200, -40)
pd()
____
pu()
goto(-60, -40)
pd()
____
done()
13.2.4 関数を書く場所の決まり
関数を書く場所には,2 つだけ決まりがあります.
その 1:関数は,呼び出すより前(上)に書く. 次のプログラムは失敗例です.開いて実行し,エラーを見てください.
from turtle import *
square() # まだ作っていない関数を呼んでいる
def square():
for i in range(4):
fd(80)
lt(90)
done()
3 行目: NameError: name 'square' is not defined と出ます.
「square なんて名前は知りません」という意味です.
プログラムは上から順に読まれるので,3 行目の時点では
def square(): をまだ読んでいません.
関数の定義は,プログラムの上のほうにまとめて書く習慣をつけてください.
その 2:関数の中に関数を書かない.
次のように書くと,square は draw の中だけのものになり,
外から呼ぶことができません.
def draw():
def square(): # 関数の中に関数を書いてしまった
for i in range(4):
fd(80)
lt(90)
draw()
square() # NameError: name 'square' is not defined
字下げが 1 段深いだけで意味がまったく変わります.
def は必ず左端から書く,と決めておけば安全です.
13.3 引数 — 外から値を渡す
13.3.1 大きさが違うと,関数も別々に必要?
square() は便利ですが,1 辺 80 の正方形しか描けません.
大きい正方形と小さい正方形を描きたくなったら,どうするのでしょうか.
素直にやると,こうなります.
from turtle import *
pensize(2)
def big_square():
for i in range(4):
fd(120)
lt(90)
def small_square():
for i in range(4):
fd(40)
lt(90)
big_square()
small_square()
done()
中身はほとんど同じで,数が 120 と 40 で違うだけです.
3 種類の大きさが欲しければ関数は 3 つ,10 種類なら 10 個.
これでは,せっかくまとめた意味がありません.
13.3.2 引数で「大きさ」を渡す
そこで使うのが引数(ひきすう)です. 関数の名前のあとのかっこに名前を書いておくと, 呼ぶ側から値を渡せるようになります.
from turtle import *
pensize(2)
# 1 辺が size の正方形を描く
def square(size):
for i in range(4):
fd(size)
lt(90)
square(120)
square(80)
square(40)
done()
関数はひとつだけになりました.square(120) と呼べば
size の中身が 120 になり,
square(40) と呼べば 40 になります.
size は,関数の中だけで使える変数です
(第 3 章で習った変数と同じものです).
def 関数の名前(仮引数):
中身の命令
関数の名前(実引数)
| 呼び名 | どこに書くか | 何か |
|---|---|---|
| 仮引数(かりひきすう) | def の行のかっこの中 | 受け取った値を入れておく変数の名前.例の size |
| 実引数(じつひきすう) | 呼び出しのかっこの中 | 実際に渡す値.例の 120 や 40 |
TypeError: square() missing 1 required positional argument: 'size'
と出ます.「size という引数が 1 つ足りません」という意味です.
逆に多すぎると takes 1 positional argument but 2 were given になります.
引数の数は,定義と呼び出しでぴったり合わせるのが決まりです.
大きさを指定して星(五芒星)を描く関数を作ります. 星は 144 度ずつ 5 回曲がるのでしたね(第 2 章). ____ を埋めてください.
from turtle import *
pensize(3)
pencolor("gold")
# 1 辺が size の星を描く
def star(____):
for i in range(5):
fd(____)
rt(144)
pu()
goto(-220, 0)
pd()
star(150)
pu()
goto(60, 0)
pd()
star(____)
done()
13.3.3 引数を 2 つ以上とる関数
引数はいくつでも書けます.カンマで区切るだけです. 「角の数」と「1 辺の長さ」の 2 つを渡せば, 正多角形をなんでも描ける関数になります. 第 2 章で習った「曲がる角度は 360 ÷ n」がここで効いてきます.
from turtle import *
pensize(2)
# 角が n 個で,1 辺が size の正多角形を描く
def polygon(n, size):
angle = 360 / n
for i in range(n):
fd(size)
lt(angle)
pu()
goto(-240, -60)
pd()
polygon(3, 100)
pu()
goto(-90, -60)
pd()
polygon(5, 70)
pu()
goto(80, -60)
pd()
polygon(8, 50)
done()
polygon(3, 100) は「角 3 つ・1 辺 100」ですが,
polygon(100, 3) と書くと「角 100 個・1 辺 3」になり,
小さな円のような図形になってしまいます.エラーは出ません.
順番を間違えてもコンピュータは気づいてくれないので,
仮引数の名前を n・size のように分かりやすくしておきましょう.
順番を間違えたとき,エラーになることもあれば,ならないこともあります.
次はメッセージを n 回表示する関数を,
正しい順番と逆の順番で 1 回ずつ呼んだものです.
# msg を n 回表示する
def repeat_msg(msg, n):
for i in range(n):
print(msg)
repeat_msg("こんにちは", 3) # 正しい順番
repeat_msg(3, "こんにちは") # 順番が逆.ここで止まる
こんにちは が 3 回表示されたあと,
3 行目: TypeError: 'str' object cannot be interpreted as an integer
で止まります.
「range に文字を渡されても回数として読めません」という意味です.
repeat_msg(3, "こんにちは") と書いた時点で,
msg には 3 が,n には "こんにちは" が
入ってしまっているのです.
n = 3 という変数を用意して repeat_msg(n, msg) と書いても,
名前が同じだから正しく届く,ということはありません.
この渡し方を位置引数といいます.
この polygon を使うと,第 2 章で描いた「家」もすっきり書けます.
屋根は正三角形,かべは正方形.どちらも polygon ひとつで描けます.
from turtle import *
pensize(2)
# 正多角形を描く
def polygon(n, size):
angle = 360 / n
for i in range(n):
fd(size)
lt(angle)
# 大きさ size の家を描く
def house(size):
polygon(4, size) # かべ
pu()
lt(90)
fd(size)
rt(90)
pd()
polygon(3, size) # 屋根
pu()
rt(90)
fd(size)
lt(90)
pd()
pu()
goto(-240, -120)
pd()
house(120)
pu()
goto(40, -120)
pd()
house(80)
done()
house の中で polygon を呼んでいます.
関数の中から別の関数を呼んでよいのです.
これが 3 番目の目的「部品として組み合わせる」です.13.6 節でくわしく扱います.
13.3.4 色も渡してみる
引数で渡せるのは数だけではありません. 文字(色の名前)も渡せます.型を書かなくてよい Python では, 数でも文字でも同じように書けます.
from turtle import *
pensize(4)
# 角 n 個・1 辺 size・色 col の正多角形を描く
def polygon(n, size, col):
pencolor(col)
angle = 360 / n
for i in range(n):
fd(size)
lt(angle)
pu()
goto(-240, -60)
pd()
polygon(3, 100, "crimson")
pu()
goto(-90, -60)
pd()
polygon(5, 70, "seagreen")
pu()
goto(80, -60)
pd()
polygon(8, 50, "royalblue")
done()
13.3.5 既定値つき引数 — 省略してよい引数
引数が増えてくると,毎回すべて書くのが面倒になります.
= で既定値(きていち)を書いておくと,
その引数は省略できるようになります.
from turtle import *
pensize(3)
# size と col は省略できる
def polygon(n, size=60, col="black"):
pencolor(col)
angle = 360 / n
for i in range(n):
fd(size)
lt(angle)
pu()
goto(-240, -40)
pd()
polygon(5) # size も col も既定値
pu()
goto(-100, -40)
pd()
polygon(6, 50) # col だけ既定値
pu()
goto(60, -40)
pd()
polygon(3, 90, "darkorange") # 全部指定
done()
def polygon(n=5, size): のように
既定値つきの引数を先に書くと,
SyntaxError: non-default argument follows default argument
というエラーになります.
始めの長さ start から少しずつ長くしながら n 回進んで,
らせんを描く関数を作ります.引数は 2 つ,色には既定値をつけます.
____ を埋めてください.
from turtle import *
pensize(2)
ht()
# n 回まわりながららせんを描く.色は省略できる
def spiral(n, start, col=____):
pencolor(col)
length = start
for i in range(____):
fd(length)
lt(91)
length = length + 3
spiral(40, 5)
spiral(28, 5, "crimson")
done()
13.3.6 引数に名前をつけて渡す — キーワード引数
13.3.3 節で見たとおり,実引数は書いた順番で仮引数に入ります.
ところが Python には,順番ではなく
名前を書いて渡す方法も用意されています.
仮引数の名前=値 と書くだけです.これをキーワード引数といいます.
# msg を n 回表示する
def repeat_msg(msg, n):
for i in range(n):
print(msg)
repeat_msg("位置で渡す", 2) # これまでどおり順番で
repeat_msg(msg="名前で渡す", n=2) # 名前をつけて
repeat_msg(n=2, msg="順番を変えても届く") # 名前があれば順番は自由
3 つとも同じように 2 回ずつ表示されます. 名前をつけて渡せば,順番を気にしなくてよくなるのです.
キーワード引数のうれしいところは,もうひとつあります. 既定値つきの引数(13.3.5 節)を飛ばして,後ろだけ指定できることです.
def polygon(n, size=60, col="black"):
...
polygon(3, col="red") # size は既定値の 60 のまま,色だけ赤にする
polygon(3, "red") と書いてしまうと,
"red" が 2 番目の size に入ってしまいます.
col="red" と名前を書けば,確実に col へ届きます.
読む人にとっても「この "red" は色のことだ」と分かる書き方です.
write("あ", font=("Arial", 13, "normal")) の font= がそれです.
write には move・align・font という
既定値つきの引数があり,そのうち font だけを名前で指定していたのでした.
- キーワード引数は,位置で渡す引数より後ろに書きます.
repeat_msg(n=2, "あ")はSyntaxError: positional argument follows keyword argumentです. - 名前は仮引数と同じ綴りでなければなりません.
polygon(3, color="red")のように違う名前を書くと,TypeError: polygon() got an unexpected keyword argument 'color'になります.
13.4 関数の中の変数は外から見えない
13.4.1 ローカル変数
関数の中で作った変数は,その関数の中だけのものです. 関数が終わると消えてしまい,外からは見えません. これをローカル変数(局所変数)といいます.
def square_size():
size = 100 # この関数の中だけの変数
print("関数の中:", size)
square_size()
print("関数の外:", size) # ← ここでエラーになる
関数の中: 100 まで表示されたあと,
6 行目: NameError: name 'size' is not defined で止まります.
size は square_size の中だけの変数なので,
外では「そんな名前は知りません」と言われるのです.
仮引数も同じで,関数の外からは見えません.
13.4.2 関数の中で外の変数に代入すると
では,関数の中から外の変数を書き換えるとどうなるでしょうか. 予想してから実行してみてください.
count = 0
def add_one():
count = count + 1 # ← ここでエラーになる
print("関数の中:", count)
add_one()
print("関数の外:", count)
4 行目: UnboundLocalError というエラーが出ます.
Python は「関数の中で count に代入している」のを見て,
count はこの関数のローカル変数だと判断します.
ところが右辺の count + 1 を計算しようとした時点では
まだ何も入っていないので,「値が入る前に使われた」と怒られるわけです.
13.4.3 どうしても外の変数を書き換えたいとき — global
関数の中から外の変数を書き換えたいときは,
関数の先頭で global 変数名 と宣言します.
「この名前は外のものを指します」という意味です.
count = 0
def add_one():
global count # 外の count を使うと宣言する
count = count + 1
add_one()
add_one()
add_one()
print("呼ばれた回数:", count)
呼ばれた回数: 3 と表示されます.
global は便利ですが,できるだけ使わないでください.
外の変数を勝手に書き換える関数は,どこで何が変わったのか追いかけにくく,
バグの温床になります.
「値を渡したいときは引数」「結果を返したいときは戻り値(第 14 章)」が原則で,
global はどうしても仕方ないときの最後の手段です.
次のプログラムを完成させてください.
draw_all の中から本数を数えて,外の lines に足していきます.
外の変数を書き換えるには何が必要でしょうか.
from turtle import *
ht()
lines = 0
def draw_line(length):
____ lines # 外の lines を書き換える宣言
fd(length)
lt(60)
lines = lines + 1
for i in range(6):
draw_line(____)
print("引いた線の数:", ____)
done()
13.5 描き終わったら元の場所に戻す
第 2 章で,正方形の最後にもう一度 lt(90) を書く理由をこう説明しました.
「描き終わったら,始めた状態に戻す.後の回で『関数』を習うと,この約束が効いてきます」.
その章が今回です.
関数を部品として使うには, その部品を使ったあとにかめがどこを向いているかが分かっていなければなりません. 次は,最後に向きを戻していない三角形の関数です.3 回呼ぶとどうなるでしょうか.
左は 3 回呼ぶたびに向きが 120 度ずつずれ,別の絵になってしまいました.
右は lt(120) がもう 1 回あるだけで,何回呼んでも同じ場所に重なります.
呼ぶ前と呼んだあとで,かめの位置と向きが同じ.
この性質があって初めて,関数は安心して組み合わせられる部品になります.
- その関数は,かめをどこに置いて呼ばれるのか.
- 描き終わったとき,かめは元の位置に戻っているか.
- 描き終わったとき,かめは元の向きを向いているか.
- ペンを上げたなら,最後に下ろしてあるか.
13.5.1 移動する関数も部品にする
「ペンを上げて,向きを変えて,進んで,向きを戻して,ペンを下ろす」という お決まりの 5 行も,関数にしてしまいましょう. これも向きを元に戻すように作ります.
from turtle import *
pensize(2)
ht()
# 今の向きから angle 度だけ左を向いた方向へ dist 進む(線は引かない)
# 向きは元に戻す
def jump(dist, angle):
pu()
lt(angle)
fd(dist)
rt(angle)
pd()
def square(size):
for i in range(4):
fd(size)
lt(90)
jump(240, 180) # 左へ 240
jump(60, -90) # 下へ 60
for i in range(4):
square(60)
jump(90, 0) # 右へ 90
done()
jump を部品にすると,本文がとても短くなる.| 書き方 | 動き |
|---|---|
jump(100, 0) | 今の向きへ 100 進む(右向きなら右へ) |
jump(100, 90) | 今の向きの左へ 100(右向きなら上へ) |
jump(100, -90) | 今の向きの右へ 100(右向きなら下へ) |
jump(100, 180) | 今の向きの後ろへ 100(右向きなら左へ) |
goto(x, y) を使う方法もありますが,
goto は画面の絶対的な場所を指すので,
その関数は「画面のその場所でしか使えない関数」になります.
jump のように今いる場所からの相対的な移動で書いておくと,
どこで呼んでも同じ形が描ける,使い回しの効く部品になります.
次の petal(花びら)は,ひし形を 1 つ描く関数です.
4 辺を描き終わった時点で位置は出発点に戻っていますが,
曲がった角度の合計は 60 + 120 + 60 = 240 度しかなく,
向きが戻っていません.
____ を埋めて,向きも元に戻る関数にしてください.
1 周は 360 度でしたね.
from turtle import *
pensize(2)
pencolor("hotpink")
ht()
# 花びら(ひし形)を 1 枚描く
def petal(size):
fd(size)
lt(60)
fd(size)
lt(120)
fd(size)
lt(60)
fd(size)
____(____) # ← 向きを元に戻す
for i in range(8):
petal(90)
lt(45)
done()
13.6 関数を組み合わせて大きな絵にする
小さな関数を作り,それを呼ぶ関数を作り,さらにそれを呼ぶ関数を作る. こうして部品の階層を組み上げていく作り方を 構造化プログラミングといいます. 複雑さと戦うための,もっとも基本的な技法です.
13.6.1 部品を積み上げる — 円弧・レモン・花
第 2 章で「円は正 360 角形だ」と習いました.
fd(2) と rt(1) を繰り返すと円弧が描けます.
まずこれを arc という部品にします.
from turtle import *
pensize(2)
ht()
# 中心角 angle 度の円弧を描く
def arc(angle):
for i in range(angle):
fd(2)
rt(1)
pu()
goto(-200, 60)
seth(0) # 真右(東)を向かせる
pd()
pencolor("crimson")
arc(90) # 90 度ぶん
pu()
goto(0, 60)
seth(0)
pd()
pencolor("seagreen")
arc(180) # 半円
pu()
goto(180, 60)
seth(0)
pd()
pencolor("royalblue")
arc(360) # ぐるりと 1 周=円
done()
arc(90)・arc(180)・arc(360).seth(0) は「かめを真右(東)向きにする」命令です(set heading の略).
goto は場所を変えるだけで向きは変えません.
arc は描き終わったあと向きが変わったままなので,
次の円弧を同じ向きから描き始めたいときは seth(0) で向きを揃えます.
この面倒さこそ,13.5 節の「元の向きに戻す」約束が大事な理由です.
次に,この arc を使って
レモン(木の葉)の形を描く関数を作ります.
120 度の円弧を 2 つ向かい合わせるだけです.
最後に向きを戻すのを忘れないでください.
from turtle import *
pensize(2)
pencolor("darkorange")
ht()
def arc(angle):
for i in range(angle):
fd(2)
rt(1)
# レモン(木の葉)の形を描く
def lemon():
arc(120)
rt(60)
arc(120)
rt(60) # 向きを元に戻す
lemon()
done()
arc を 2 回呼んでいるだけ.さらに,この lemon を使って花を描きます.
lemon が「位置も向きも元に戻す」部品になっているので,
少しずつ回しながら呼ぶだけで花びらが並びます.
from turtle import *
pensize(2)
ht()
def arc(angle):
for i in range(angle):
fd(2)
rt(1)
def lemon():
arc(120)
rt(60)
arc(120)
rt(60)
# 花びら n 枚の花を描く
def flower(n, col):
pencolor(col)
for i in range(n):
lemon()
lt(360 / n)
flower(6, "hotpink")
done()
flower(6, "hotpink") の 1 行.flower は lemon を呼び,
lemon は arc を呼び,
arc は fd と rt を呼んでいます.
3 段の積み木です.いちばん上の flower(6, "hotpink") だけを読めば,
「ピンクの花を 6 枚の花びらで描く」ことがすぐ分かります.
13.6.2 関数構成図を先に描く
関数が増えてくると,呼び出し関係がこんがらがります. プログラムを書き始める前に, どんな関数を作り,どれがどれを呼ぶかを紙に書いておきましょう. これを関数構成図といいます.
たとえば「窓のついた家」を描くなら,こんな構成になります.
house(size)家を描くwall(size)かべを描くpolygon(n, size)正多角形を描く
roof(size)屋根を描くjump(dist, angle)線を引かずに移動するpolygon(n, size)正多角形を描く
windows(size)窓を並べるjump(dist, angle)線を引かずに移動するpolygon(n, size)正多角形を描く
この図をそのままプログラムにしたものが次です. 上から下へ,大きい部品から小さい部品の順に並べてあります.
from turtle import *
pensize(2)
ht()
# --- いちばん小さい部品 ---
# 線を引かずに移動する(向きは元に戻す)
def jump(dist, angle):
pu()
lt(angle)
fd(dist)
rt(angle)
pd()
# 正多角形を描く
def polygon(n, size):
angle = 360 / n
for i in range(n):
fd(size)
lt(angle)
# --- 家の部品 ---
def wall(size):
polygon(4, size)
def roof(size):
jump(size, 90)
polygon(3, size)
jump(size, -90)
def windows(size):
w = size / 5
jump(w, 0)
jump(w * 2, 90)
polygon(4, w)
jump(w * 2.5, 0)
polygon(4, w)
jump(w * 3.5, 180)
jump(w * 2, -90)
# --- 家ぜんぶ ---
def house(size):
wall(size)
roof(size)
windows(size)
# --- 本文 ---
pencolor("saddlebrown")
jump(240, 180)
jump(120, -90)
house(130)
pencolor("teal")
jump(200, 0)
house(90)
done()
13.6.3 実は fd も rt も関数だった
ここまで来ると,種明かしができます.
第 2 章から使ってきた fd・rt・pencolor も,
実は誰かが def で書いた関数です.
from turtle import * は,
その関数の入った箱を開けて使えるようにする命令でした.
def fd(distance):
...(かめを distance だけ前に進める中身)...
def rt(angle):
...(かめを angle 度だけ右に回す中身)...
ある目的のために作られた関数の集まりをライブラリといいます.
turtle は「タートルグラフィックスのライブラリ」で,
第 14 章で使う math は「数学の計算のライブラリ」です.
みなさんが今日作った polygon や arc も,
立派な「自分のライブラリ」の第一歩です.
13.6.4 もとからある関数 — 組み込み関数
fd や rt は,from turtle import * と
書いて初めて使えるようになる関数でした.
ところが,何も import しなくても最初から使える関数もあります.
これを組み込み関数(くみこみかんすう)といいます.
実は,みなさんは気づかないうちに,ずっと組み込み関数を使ってきました.
print・input(第 9 章),int(第 9 章),
range(第 6 章).
どれも名前のうしろにかっこを書いて呼ぶ形をしています.
そう,これらはすべて関数だったのです.
# どれも import せずに使える組み込み関数
print(len("こんにちは")) # 文字数 → 5
print(max(3, 8, 5)) # いちばん大きい数 → 8
print(min(3, 8, 5)) # いちばん小さい数 → 3
print(abs(-7)) # 絶対値 → 7
print(round(3.14159, 2)) # 四捨五入 → 3.14
print(int("42") + 1) # 文字を数に変える → 43
print(str(2026) + "年") # 数を文字に変える → 2026年
| 書き方 | 何をするか |
|---|---|
print(x) | 画面に表示する.表示するだけで,答えは持ち帰らない |
input() | キーボードから 1 行読む(第 9 章) |
int(s)・float(s) | 文字を整数・小数に変える(第 9 章) |
str(x) | 数を文字に変える |
len(s) | 文字数を数える |
max(…)・min(…) | いちばん大きい数・小さい数 |
abs(x) | 絶対値(符号を取った大きさ) |
round(x, n) | 小数第 n 位で四捨五入する |
range(n) | 0 から n-1 までを順に取り出す(第 6 章) |
type(x) | その値の種類を調べる |
- 組み込み関数.
print・len・intなど.何も書かずに,いきなり使える. - ライブラリの関数.
fd・rtなど.from turtle import *のようにimportしてから使う. - 自分で作った関数.
polygon・arcなど.defで自分が書いたもの.
len("あいう") も polygon(5, 70) も,
書き方の形は少しも変わりません.
表をよく見ると,print だけが少し違います.
len("こんにちは") は 5 という答えを持ち帰ってくるので,
print(len("こんにちは")) のように,そのまま表示できました.
print のほうは画面に出すだけで,何も持ち帰りません.
この「持ち帰る・持ち帰らない」の違いが,次の第 14 章の主役です.
len = 5 と書いた瞬間に,len は 5 という数になり,
本来の len("あいう") が
TypeError: 'int' object is not callable で動かなくなります.
13.7 節の 4 番目の決まりは,このためのものです.
len・max・min は,
ここでは文字や 2〜3 個の数にしか使えず,ありがたみが薄いかもしれません.
第 16 章でリストを習うと,
「100 個の数の中でいちばん大きいもの」を max の 1 行で取り出せるようになり,
これらの関数が一気に主役になります.
13.7 関数の名前の付け方
関数の値打ちは,半分は名前で決まります.
中身が正しくても,名前が a や func1 では,
読む人(=来週の自分)に何も伝わりません.
まず,名前に使える文字の決まりから確認します.
これは第 3 章で習った変数名の決まりとまったく同じです.
英数字と _ が使えて,数字では始められず,
for・if・def のような
Python があらかじめ使っていることばは名前にできません.
その上で,次の 4 つを守ってください.
- 動詞で始める.関数は「何かをする」ものだからです.
draw_house(家を描く),move_to(〜へ移動する),make_color(色を作る).ただしsquare・lemonのように「描くもの」がはっきりしている図形は,名詞だけでも通じます. - 小文字と
_でつなぐ.drawhouseよりdraw_houseのほうが読めます.この書き方をスネークケースといい,Python の標準です. - 中身を言い当てる.
drawだけでは何を描くのか不明です.draw_starのように具体的に書きます. - すでにある名前を避ける.
print・fd・listなどを関数名にすると,元の命令が使えなくなります.
| よくない名前 | よい名前 | 理由 |
|---|---|---|
a() | draw_star() | 何をするのか分からない |
kakukakei(n, s) | polygon(n, size) | ローマ字書きは通じにくい.英語で短く |
drawtwobighouses() | house(size) | 「2 つ」「大きい」は引数と呼ぶ回数で決めること |
Square() | square() | 大文字始まりはクラスに使う(Python の習慣) |
print(x) | show_result(x) | 元からある命令の名前を奪わない |
def polygon(n, size) と書いてあれば,
呼ぶ側は「角の数と大きさを渡すのだな」と分かります.
def polygon(x, y) だと,座標を渡すように見えてしまいます.
13.8 この章で出てきた書き方
| 書き方 | 意味 |
|---|---|
def 名前(): | 引数なしの関数を定義する.次の行から字下げして中身を書く |
名前() | 関数を呼び出す.ここで中身が実行される |
def 名前(a, b): | 引数を 2 つとる関数を定義する(a・b が仮引数) |
名前(10, 20) | 引数を渡して呼び出す(10・20 が実引数) |
def 名前(a, b=5): | b に既定値 5.名前(10) と省略して呼べる |
名前(10, b=7) | キーワード引数.名前を書いて渡すと,順番を気にしなくてよい |
len(s)・max(a, b) | 組み込み関数.import も def も要らず,最初から使える |
global x | 関数の中で,外の変数 x を書き換えると宣言する |
# コメント | 関数の上に「何をする関数か」を 1 行書く習慣をつける |
| 症状 | 原因と直し方 |
|---|---|
SyntaxError: expected ':' | def の行の終わりにコロンが無い |
IndentationError: expected an indented block | def の次の行を字下げしていない |
NameError: name 'xxx' is not defined | 呼び出しより下に def を書いている/綴り違い/関数の中で作った変数を外で使っている |
TypeError: xxx() missing 1 required positional argument | 実引数が足りない.定義のかっこと呼び出しのかっこを見比べる |
TypeError: xxx() takes 1 positional argument but 2 were given | 実引数が多すぎる |
TypeError: 'str' object cannot be interpreted as an integer | 数を渡すはずのところに文字を渡している.引数の順番が逆になっていないか確かめる |
SyntaxError: positional argument follows keyword argument | 名前=値 で渡した引数より後ろに,ふつうの引数を書いている |
TypeError: xxx() got an unexpected keyword argument | 名前=値 の名前の綴りが,仮引数と違っている |
UnboundLocalError | 関数の中で外の変数に代入している.global を書くか,引数で渡す |
| エラーは出ないが何も描かれない | def しただけで呼んでいない/呼び出しのかっこを忘れている |
| 2 回目以降がずれる | 関数が元の位置・向きに戻していない.13.5 節へ |
演習
以下の演習には出発点となるプログラムが入っています. 「▶ この演習を開く」で右側に読み込み,それを発展させて作品にしてください. どの演習も,作った関数を 2 回以上呼ぶことを条件にします. 1 回しか呼ばないなら,関数にした意味が半分しかありません.
- 引数を増やす(大きさ・色・角の数・回数)
- その関数を呼ぶ,もう 1 つ上の関数を作る
forと組み合わせて,たくさん並べる・回しながら重ねる- 第 7 章で習った入れ子の繰り返しで,縦横に並べる
円を描く関数 circle_by_hand() を作り,2 回以上呼ぶプログラムです.
まずそのまま実行して,動作を確かめてください.
そのうえで,大きさを引数で渡せるように直し,
大小の円を並べた模様に発展させてください.
fd(1) の 1 を引数にします.
def circle_by_hand(step): として fd(step) と書けば,
step が大きいほど大きな円になります.
ただし大きくしすぎると画面からはみ出します.step は 2 くらいまでにしましょう.
大きさを指定して家を描く関数 house(size) があります(窓は無くてかまいません).
2 回以上呼んで動くことを確かめたうえで,発展させてください.
- 色も引数で渡せるようにする
forで,だんだん小さくなる家を並べる- 窓・ドア・煙突を描く関数を足して,
houseから呼ぶ
中心角を指定して円弧を描く関数 arc(angle) があります.
arc(360) なら円,arc(180) なら半円,
arc(1) なら点のようになります.
いろいろな中心角で呼んで,形を確かめてください.
そのうえで,半径も引数で渡せるように直し,
円弧を組み合わせた模様に発展させてください.
arc(120)arc(300)arc を部品に使ってレモン型を描く lemon() と,
さらにその lemon を部品に使って蝶を描く butterfly() があります.
どの関数がどの関数を呼んでいるかを確かめてから,発展させてください.
- レモンの大きさを引数で渡せるようにする
- 羽の角度を変えて,別の生き物にしてみる
- 触角や体を描く関数を足して
butterflyから呼ぶ
lemon() をそのまま再利用して花を描く
flower(n, col) があります.
lemon は一文字も書き換えていないことに注目してください.
「元の位置・元の向きに戻す」部品にしてあるから,こうして使い回せるのです.
花びらの数・色・大きさを変えて,花畑に発展させてください.
row 行 col 列のマス目を描く関数
grid(row, col, size) を完成させてください.
出発点のプログラムには square(size) が用意してあります.
第 7 章で習った繰り返しの入れ子を使います.
grid(2, 3, 50)で 2 行 3 列のマス目になることgrid(5, 7, 20)でも正しく描けること- この 2 つを両方呼んで,位置をずらして並べること
r 行目・c 列目のマスの左下は,
原点から見て右へ c * size,下へ r * size の場所です.
goto(x0 + c * size, y0 - r * size) で移動してから
square(size) を呼べば並びます.
最後は自由制作です.ただし,ひとつだけ厳しいルールがあります.
- 1 つの関数は 10 行以内.本文(関数の外)も 10 行以内にすること
- 関数を3 つ以上作ること
- そのうち少なくとも 1 つは,別の関数を呼ぶ関数にすること
- すべての関数に,何をする関数かを 1 行コメントで書くこと
- 図形を描く関数は,元の位置・元の向きに戻すこと
まとめ
- 同じものを 2 回以上書いたら関数にする.まとめる・名前をつける・部品として組み合わせる,の 3 つが目的.
def 名前():で定義し,名前()で呼び出す.定義しただけでは何も起きない.- 呼ぶと関数の中へ飛び,中身を実行し,呼んだ場所の次の行へ戻る.
- 関数は呼び出しより前(上)に書く.関数の中に関数を書かない.
- 引数で外から値を渡す.定義側が仮引数,呼び出し側が実引数.引数は何個でもよく,
=で既定値をつければ省略できる.Python では型を書かない. - 引数は書いた順番(位置)で仮引数に入る.
名前=値と書くキーワード引数なら順番は自由になり,既定値つきの引数を飛ばして指定できる. print・len・intなどは最初から使える組み込み関数.fdはimportして使うライブラリの関数.自分でdefした関数も含め,使い方はどれも同じ.- 関数の中で作った変数はローカル変数で,外からは見えない.外の変数を書き換えるには
globalが要るが,できれば引数と戻り値で済ませる. - 描き終わったら元の位置・元の向きに戻す.これを守った関数だけが,組み合わせられる部品になる.
- 1 つの関数は 10 行以内.長くなったら分ける.
今回の関数は「やってもらう」だけで,結果を受け取ることができませんでした.
次の回では return を習います.
計算した答えを関数から持ち帰らせて,
「入力(引数)と出力(戻り値)のある関数」を作れるようになります.