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

辞書

名前で引ける入れ物.データに意味を持たせる.

リストは便利な入れ物でした. しかし,リストからデータを取り出すには番号が要ります. a[0]a[3]. 「0 番目には誰の点数が入っているんだっけ」と, いちいち思い出さなければなりません.

今回習う辞書(dictionary)は, 番号ではなく名前で引ける入れ物です. ten["さくら"] と書けば,さくらさんの点数が出てきます. 紙の辞書で「りんご」を引くと意味が書いてあるのと同じ仕組みです.

番号が名前に変わるだけ,と思うかもしれません. しかしこの変化は大きいのです. データに意味が付くからです. 今回の最後には,入力された言葉に応じて絵を描き分けるプログラムを作ります.

この章でできるようになること
  • リストでは足りない場面を説明できる
  • { } で辞書を作り,キーの関係を説明できる
  • d["key"] で取り出す・書き換える・付け加えることができる
  • inget() で,安全にキーを引ける
  • foritems() で辞書の中身を全部たどれる
  • 辞書とリストを組み合わせて,少し複雑なデータを表せる
  • 辞書を使って,入力された言葉に応じた絵を描くプログラムが書ける
  • listdictsettuple の使い分けがわかり,公式ドキュメントを引ける

18.1 リストでは足りない場面

5 人の点数を持っておきたいとします.リストならこう書けます.

リストで点数を持つ
ten = [78, 92, 65, 88, 71]
print(ten[2])       # → 65

65 は誰の点数でしょうか.分かりません. 「2 番目はさくらさん」という約束を,プログラムの外側で 覚えておかなければならないのです.これは危険です. 1 人減らしただけで,全部の番号がずれてしまいます.

18.1.1 2 本のリストで代用してみる

よくある工夫が,名前のリストと点数のリストを 2 本並べるやり方です. 同じ番号どうしが対応している,という約束にします.

twolists.py
# 名前のリストと点数のリストを並べて使う

namae = ["あかり", "けんた", "さくら", "たろう", "なな"]
ten   = [78,       92,      65,       88,      71]

shirabetai = "さくら"

# 名前のリストから場所を探して(=第 17 章の線形探索)
k = -1
for i in range(len(namae)):
    if namae[i] == shirabetai:
        k = i
        break

# その場所の点数を取り出す
if k >= 0:
    print(shirabetai, "さんの点数は", ten[k], "点です")
else:
    print(shirabetai, "さんは名簿にいません")

動きます.しかし,うれしくありません.

  • 点数を 1 つ知るだけで,毎回リストを端から探すことになる.人数が増えるほど遅くなる.
  • 2 本のリストの長さと順番がずれたら終わり.1 人だけ消したりすると,静かに壊れる.
  • そもそも,書くのが面倒.毎回 8 行も書きたくない.

「名前から値を引く」というのは, プログラムを書いているとあまりにもよく出てくる要求です. だから,はじめから道具が用意されています.それが辞書です.

コラム:面倒だと感じたら 「読みのリスト」と「絵のリスト」を 2 本並べるやり方でも, 絵辞書は作れます.ただ,書いていて「面倒だな」と思うはずです. その面倒さを,言語のほうで引き受けてくれるのが辞書という仕組みです. 面倒だと感じることには,たいてい既に解決策があります.

18.2 辞書を作る

辞書は波かっこ { } で作ります. キー: でつなぎ, 組を , で区切って並べます.

辞書の作り方
ten = {"あかり": 78, "けんた": 92, "さくら": 65, "たろう": 88, "なな": 71}
#       ↑キー    ↑値
言葉意味
キー(key)引くときの手がかり.紙の辞書の「見出し語」"さくら"
(value)引いて出てくる中身.紙の辞書の「意味」65
キーと値のひとそろい.辞書は組の集まり"さくら": 65

長い辞書は,行を分けて書くと読みやすくなります. 最後の組のうしろにも , を付けてよいのが便利なところです.

行を分けた書き方
ten = {
    "あかり": 78,
    "けんた": 92,
    "さくら": 65,
    "たろう": 88,
    "なな": 71,
}

18.2.1 取り出す・書き換える・付け加える

取り出すときは,リストと同じ角かっこを使います. 中に書くのが番号ではなくキーになるだけです.

dict_basic.py
ten = {"あかり": 78, "けんた": 92, "さくら": 65}

# 取り出す
print(ten["さくら"])          # → 65

# 書き換える(すでにあるキー)
ten["さくら"] = 70
print(ten["さくら"])          # → 70

# 付け加える(まだ無いキー)
ten["たろう"] = 88
print(ten)

# 何組あるか
print("人数は", len(ten), "人")
代入すると,無ければ増える 代入の意味が,キーの有無で変わります. ten["さくら"] = 70 は, 「さくら」がすでにあれば書き換え,無ければ新規追加です. リストの a[5] = 3 が「5 番目が無ければエラー」だったのと,ここが違います. 便利な反面,キーを打ち間違えると,静かに別の組が増えます. 「さくら」と「さくら 」(末尾に空白)は別のキーです.

空の辞書は {} で作れます. そこへ 1 組ずつ入れていくのが,よく使う書き方です.

空の辞書から作る
d = {}                  # 空の辞書
d["りんご"] = "red"
d["みかん"] = "orange"
print(d)                # → {'りんご': 'red', 'みかん': 'orange'}

18.2.2 無いキーを引くとエラー

辞書に入っていないキーを引くと,プログラムはその場で止まります.

よくあるエラー
ten = {"あかり": 78, "けんた": 92}
print(ten["ゆうき"])
# → KeyError: 'ゆうき'

KeyError は「そんなキーはありません」という意味です. 入力された言葉を辞書で引くようなプログラムでは, ユーザが何を入力するか分からないので,必ず起きます. 次の節で,その備え方を習います.

虫食い虫食い 18-A 色の名前の辞書

色の日本語名から,タートルで使える色の名前を引く辞書を作り, その色で点を打つプログラムです. ____ を埋めてください.

fill17_color.py
from turtle import *

ht()

iro = {"あか": "red", "あお": ____, "きいろ": "gold", "みどり": "seagreen"}

namae = ["あか", "きいろ", "みどり", "あお"]

x = -150
for n in namae:
    pu()
    goto(x, 0)
    pencolor(iro[____])       # 日本語名から色の名前を引く
    dot(70)
    pu()
    goto(x - 22, -70)
    pencolor("black")
    write(____, font=("Arial", 14, "normal"))
    x = x + 100

print("辞書には", ____(iro), "色入っています")

done()

18.3 キーがあるか調べる

18.3.1 in で確かめる

リストのときと同じ in が使えます. ただし辞書の in が調べるのは,キーのほうです.

dict_in.py
ten = {"あかり": 78, "けんた": 92, "さくら": 65}

print("さくら" in ten)       # → True(キーにある)
print("ゆうき" in ten)       # → False
print(65 in ten)             # → False(値は調べない!)

# 値のほうを調べたいときは values() を付ける
print(65 in ten.values())    # → True

# 安全に引く定石
namae = "ゆうき"
if namae in ten:
    print(namae, "さんは", ten[namae], "点")
else:
    print(namae, "さんは名簿にいません")
辞書はなぜ速いのか 第 17 章で,リストの in は中で線形探索をしている,と話しました. 辞書の in は違います. 辞書は,中身がどれだけ増えても,ほぼ一瞬でキーを見つけられる 仕掛け(ハッシュ表といいます)で作られています. 1000 人分でも 100 万人分でも,引く速さはほとんど変わりません. 18.1.1 節の「2 本のリスト」方式との,いちばん大きな違いがこれです.

18.3.2 get() — 無くてもエラーにしない

「無ければこの値ということにしてほしい」という場面はよくあります. get() を使うと 1 行で書けます.

dict_get.py
ten = {"あかり": 78, "けんた": 92, "さくら": 65}

print(ten.get("さくら"))          # → 65(ふつうに取り出せる)
print(ten.get("ゆうき"))          # → None(エラーにならない)
print(ten.get("ゆうき", 0))       # → 0(無いときの値を指定できる)

# 出席回数を数える,というよくある使い方
kiroku = ["あかり", "けんた", "あかり", "さくら", "あかり", "けんた"]

kaisu = {}
for n in kiroku:
    kaisu[n] = kaisu.get(n, 0) + 1    # 無ければ 0 から数え始める

print(kaisu)
書き方キーがあるときキーが無いとき
d[k]値を返すエラーKeyError
d.get(k)値を返すNone を返す
d.get(k, x)値を返すx を返す
k in dTrueFalse

kaisu[n] = kaisu.get(n, 0) + 1 は,ぜひ覚えてほしい 1 行です. 「数える」「集計する」という処理は,ほとんどこの形で書けます.

虫食い虫食い 18-B 文字を数える

文字列の中に,どの文字が何回出てくるかを数えて, 多い順に棒グラフで描くプログラムです. ____ を埋めてください.

ヒント for c in s: と書くと,文字列 s の文字を 1 つずつ取り出せます.文字列も「並んだもの」なので, リストと同じように for で回せるのです.
fill17_count.py
from turtle import *

ht()
speed(0)

s = "abracadabra"

# 数える
kaisu = {}
for c in s:
    kaisu[c] = kaisu.____(c, 0) + 1

print(kaisu)

# 多い順に並べ替える(キーのリストを作って並べ替える)
moji = list(kaisu.keys())
moji.sort(key=lambda c: -kaisu[c])

# 棒グラフにする
x = -120
for c in moji:
    fillcolor("mediumpurple")
    pu()
    goto(x, -80)
    seth(0)
    pd()
    begin_fill()
    fd(40)
    lt(90)
    fd(kaisu[____] * 30)
    lt(90)
    fd(40)
    lt(90)
    fd(kaisu[c] * 30)
    lt(90)
    end_fill()
    pu()
    goto(x + 12, -104)
    write(____, font=("Arial", 16, "normal"))
    x = x + 55

done()

18.4 中身を全部たどる

18.4.1 for で回す

for k in d: と書くと,キーが 1 つずつ取り出されます. 値ではありません.ここは最初にとまどうところです.

dict_for.py
ten = {"あかり": 78, "けんた": 92, "さくら": 65}

print("--- キーだけ ---")
for k in ten:
    print(k)

print("--- キーと値(キーで引く) ---")
for k in ten:
    print(k, "さんは", ten[k], "点")

print("--- キーと値(items) ---")
for k, v in ten.items():
    print(k, "さんは", v, "点")

print("--- 値だけ ---")
for v in ten.values():
    print(v)

print("合計", sum(ten.values()), "点/平均", sum(ten.values()) / len(ten), "点")
書き方取り出されるもの
for k in d:キー(d.keys() と同じ)
for v in d.values():
for k, v in d.items():キーと値の両方
len(d)組の数(要素数)
なぜ変数が 2 つ書けるのか for k, v in d.items():k, v は, 第 17 章に出てきた a, b = b, a と同じ仕掛けです. items() は「キーと値がひと組になったもの」を返すので, それを 2 つの変数に配っているのです.

辞書の中身は,書いた順(入れた順)に取り出されます. 昔の Python では順番がばらばらでしたが,いまは入れた順と決まっています.

18.4.2 辞書からグラフを描く

キーを名前に,値を棒の高さにすれば,そのままグラフになります. 辞書はグラフと相性がよいのです.

dict_graph.py
# 辞書から棒グラフを描く

from turtle import *

ten = {"あかり": 78, "けんた": 92, "さくら": 65, "たろう": 88, "なな": 71}

ht()
speed(0)
pensize(1)
pencolor("dimgray")

x = -175
for namae, ten_su in ten.items():
    fillcolor("steelblue")
    pu()
    goto(x, -120)
    seth(0)
    pd()
    begin_fill()
    fd(48)
    lt(90)
    fd(ten_su * 2)
    lt(90)
    fd(48)
    lt(90)
    fd(ten_su * 2)
    lt(90)
    end_fill()
    pu()
    goto(x + 4, -145)
    pencolor("black")
    write(namae, font=("Arial", 14, "normal"))
    goto(x + 10, -112 + ten_su * 2)
    write(ten_su, font=("Arial", 13, "normal"))
    pencolor("dimgray")
    x = x + 72

print("平均", sum(ten.values()) / len(ten), "点")

done()
あかり78けんた92さくら65たろう88なな71
図 18.1 辞書 ten をそのまま棒グラフにしたもの.

18.4.3 組を消す — del

要らなくなった組は del で消せます.

dict_del.py
ten = {"あかり": 78, "けんた": 92, "さくら": 65}
print(ten, len(ten))

del ten["けんた"]
print(ten, len(ten))

# 無いキーを del するとエラーになるので,先に確かめる
if "ゆうき" in ten:
    del ten["ゆうき"]
else:
    print("ゆうき は元からいません")

# pop なら,無いときの値を決めておける
x = ten.pop("ゆうき", -1)
print("消した値:", x)

ten.clear()          # 全部消す
print(ten, len(ten))
回しながら消さない for で辞書を回している最中に del で組を消してはいけません. RuntimeError: dictionary changed size during iteration というエラーになります. どうしても消したいときは,消すキーを先にリストに集めておき, for を抜けてから消します.

18.5 キーに使えるもの

キーには,文字列のほかにも使えます. 「学生番号 → 点数」「年 → 人口」のように.

dict_key.py
# 学生番号(数)をキーにする
ten = {2301: 78, 2302: 92, 2315: 65}
print(ten[2302])

# 数のキーは「番号」ではないので,とびとびでもよい
print(2315 in ten)      # → True
print(0 in ten)         # → False(リストなら 0 番目は必ずある)

# 位置(x, y)をキーにすることもできる(タプルを使う)
masu = {(0, 0): "白", (1, 0): "黒", (2, 3): "白"}
print(masu[(1, 0)])

# リストはキーにできない
# d = {[1, 2]: "だめ"}   # → TypeError: unhashable type: 'list'
キーに使えるか理由
○ 文字列"さくら"作ったあとで中身が変わらない
○ 数2302, 3.14同上
○ タプル(1, 0)同上(18.9 節)
× リスト[1, 2]あとから中身を変えられてしまうため
× 辞書{"a": 1}同上
なぜリストはキーにできないのか なぜ「あとから変わるもの」はキーにできないのでしょうか. 辞書はキーを手がかりに「置き場所」を決めています(18.3.1 節). もしキーの中身が途中で変わったら, 置いた場所を見失ってしまうのです. 住所を書いた荷札が,勝手に書き換わるようなものです.

なお,値のほうには何でも入れられます. 数,文字列,リスト,辞書,そして関数まで.次の節で見ていきます.

18.6 辞書とリストを組み合わせる

18.6.1 値がリストの辞書

「1 人につき複数の値」を持ちたいときは,値をリストにします

dict_list.py
# 1 人につき 3 回分のテストの点

ten = {
    "あかり": [78, 82, 91],
    "けんた": [92, 88, 85],
    "さくら": [65, 70, 74],
}

print(ten["あかり"])          # → [78, 82, 91]
print(ten["あかり"][2])       # → 91(3 回目の点)

for namae, ten_list in ten.items():
    heikin = sum(ten_list) / len(ten_list)
    print(namae, "さん:", ten_list, "平均", heikin, "点")

# 1 回分を付け足す
ten["さくら"].append(80)
print(ten["さくら"])

ten["あかり"][2] は「辞書を引いて,出てきたリストの 2 番目」という意味です. 左から順に読むと分かりやすくなります.

18.6.2 辞書が並んだリスト

逆に,リストの中に辞書を並べることもあります. 「1 件=1 つの辞書」と考えると,表(エクセルの 1 行)をそのまま表せます.

list_dict.py
# 1 件が 1 つの辞書.よく使う形

hoshi = [
    {"namae": "シリウス",  "akarusa": 25, "iro": "white"},
    {"namae": "ベテルギウス", "akarusa": 18, "iro": "orangered"},
    {"namae": "リゲル",    "akarusa": 20, "iro": "lightskyblue"},
]

for h in hoshi:
    print(h["namae"], "/明るさ", h["akarusa"], "/色", h["iro"])

# 明るい順に並べ替える(第 17 章の sort と組み合わせる)
hoshi.sort(key=lambda h: -h["akarusa"])
print("いちばん明るいのは", hoshi[0]["namae"])
key=lambda の読み方 key=lambda h: -h["akarusa"] は 「明るさにマイナスを付けた値の小さい順に並べる」, つまり明るい順という意味です. lambda は「名前のない小さな関数」を作る書き方で, sort に「何を基準に比べるか」を教えるときによく使います. いまは形として覚えておけば十分です.
虫食い虫食い 18-C 星空を描く

上の hoshi を使って,星空を描きます. 辞書から位置・大きさ・色を取り出して点を打ってください. ____ を埋めてください.

fill17_stars.py
from turtle import *

ht()
bgcolor("midnightblue")

hoshi = [
    {"namae": "シリウス", "x": -150, "y": 60, "size": 26, "iro": "white"},
    {"namae": "ベテルギウス", "x": 40, "y": 110, "size": 20, "iro": "orangered"},
    {"namae": "リゲル", "x": 120, "y": -40, "size": 22, "iro": "lightskyblue"},
    {"namae": "プロキオン", "x": -60, "y": -90, "size": 16, "iro": "lightyellow"},
]

for h in ____:
    pu()
    goto(h["x"], h[____])
    pencolor(h["iro"])
    dot(h[____])
    pu()
    goto(h["x"] - 30, h["y"] - 38)
    pencolor("lightgray")
    write(h["namae"], font=("Arial", 12, "normal"))

done()

18.7 辞書で図形を選ぶ

辞書の値には数や文字列だけでなく,描き方を決める情報も入れられます. 「言葉 → 図形」の対応表を作ってみましょう.

dict_shape.py
# 言葉 → 角の数 の辞書で,図形を描き分ける

from turtle import *

kado = {"さんかく": 3, "しかく": 4, "ごかく": 5, "ろっかく": 6, "はちかく": 8}
iro  = {"さんかく": "tomato", "しかく": "royalblue", "ごかく": "seagreen",
        "ろっかく": "goldenrod", "はちかく": "mediumpurple"}

ht()
speed(0)
pensize(3)

kotoba = "ごかく"

n = kado[kotoba]              # 角の数を引く
pencolor(iro[kotoba])         # 色を引く

pu()
goto(-60, -70)
seth(0)
pd()
for i in range(n):
    fd(120)
    lt(360 / n)               # 第 2 章でやった 360 ÷ n

print(kotoba, "は", n, "角形です")

done()
図 18.2 kotoba = "ごかく" のときの結果.言葉を変えれば図形が変わる.

if を 1 つも書いていないのがポイントです. もし辞書を使わなければ,こう書くことになります.

辞書を使わないと,こうなる
if kotoba == "さんかく":
    n = 3
    pencolor("tomato")
elif kotoba == "しかく":
    n = 4
    pencolor("royalblue")
elif kotoba == "ごかく":
    n = 5
    pencolor("seagreen")
elif ...

図形を 1 つ増やすたびに elif が 3 行ずつ伸びていきます. 辞書なら,表に 1 行足すだけです. 長い elif の列を見たら,辞書にできないか考える. これはプログラミングの定石の 1 つです.

虫食い虫食い 18-D 曜日ごとの色で円を描く

曜日から色を引く辞書と,曜日から半径を引く辞書を使って, 円を 7 つ並べて描きます.____ を埋めてください.

ヒント for k, v in d.items(): を使うと, キーと値を同時に取り出せます(18.4.1 節).
fill17_youbi.py
from turtle import *

ht()
speed(0)
pensize(3)

iro = {
    "月": "silver", "火": "tomato", "水": "royalblue",
    "木": "seagreen", "金": "gold", "土": "steelblue", "日": "crimson",
}
ookisa = {
    "月": 20, "火": 24, "水": 22, "木": 26, "金": 30, "土": 34, "日": 34,
}

x = -180
for youbi, col in iro.____():
    pu()
    goto(x, 0)
    seth(0)
    pd()
    pencolor(____)
    circle(ookisa[____])
    pu()
    goto(x - 6, -50)
    pencolor("black")
    write(youbi, font=("Arial", 15, "normal"))
    x = x + 60

done()

18.8 絵辞書をつくる

いよいよ今回の目玉です. 入力された言葉に応じた絵を描くプログラムを作ります.

18.8.1 絵を描く関数を用意する

まず,果物を描く関数を 4 つと,「見つかりません」の絵を 1 つ用意します. どれも引数なし・画面の中央に描くという約束にそろえておきます. この「そろえておく」ことが,あとで効いてきます.

図 18.3 ringo()
図 18.4 ichigo()
図 18.5 suika()
図 18.6 mikan()
?
図 18.7 notfound()

18.8.2 辞書に関数そのものを入れる

ここで,少し驚くことをします. 辞書の値に,関数そのものを入れます.

関数を値にする
e = {"りんご": ringo, "いちご": ichigo, "すいか": suika, "みかん": mikan}
#               ↑かっこを付けない

ringo() と書くと「関数を呼ぶ」ですが, ringoかっこを付けずに書くと, 「関数そのもの」という値になります. 辞書から取り出したあとでかっこを付ければ,そのとき呼ばれます.

取り出して,呼ぶ
f = e["りんご"]      # 関数そのものを取り出す(まだ描かれない)
f()                  # ここで呼ばれる → りんごが描かれる

e["りんご"]()        # 1 行にまとめてもよい
dict_app.py
# 絵辞書:言葉を入れると,その絵が出てくる

from turtle import *

def ringo():
    color("saddlebrown")
    pensize(8)
    pu()
    goto(0, 40)
    seth(90)
    pd()
    fd(35)
    pensize(2)
    color("crimson")
    pu()
    goto(0, -60)
    seth(0)
    pd()
    begin_fill()
    circle(55)
    end_fill()

def ichigo():
    color("crimson")
    pu()
    goto(-45, 40)
    pd()
    begin_fill()
    goto(45, 40)
    goto(0, -65)
    goto(-45, 40)
    end_fill()
    color("seagreen")
    pu()
    goto(-52, 42)
    pd()
    begin_fill()
    goto(52, 42)
    goto(0, 72)
    goto(-52, 42)
    end_fill()

def mikan():
    color("orange")
    pu()
    goto(0, -60)
    seth(0)
    pd()
    begin_fill()
    circle(58)
    end_fill()
    color("seagreen")
    pu()
    goto(0, 50)
    pd()
    begin_fill()
    goto(42, 76)
    goto(4, 66)
    goto(0, 50)
    end_fill()

def notfound():
    color("gray")
    pu()
    goto(-22, -45)
    pd()
    write("?", font=("Arial", 110, "bold"))

ht()
speed(0)
pensize(2)

# 言葉 → 絵を描く関数 の辞書
e = {"りんご": ringo, "いちご": ichigo, "みかん": mikan}

kotoba = input("調べたい言葉:")

if kotoba in e:
    e[kotoba]()               # 辞書から関数を取り出して,呼ぶ
    print(kotoba, "の絵を描きました")
else:
    notfound()
    print(kotoba, "は辞書にありません")

done()

右コンソールの入力行に いちご と入れてあります. りんごみかん, 辞書に無い言葉(ばなな など)に書き換えて,実行してみてください.

図 18.8 いちご と入力したときの結果.
2 本のリストと比べてみる 同じ絵辞書を2 本のリストと線形探索で書くこともできます (18.1.1 節).辞書を使うと,探す部分がまるごと ine[kotoba] の 2 つに置き換わります. 道具が変わると,プログラムの形そのものが変わるという良い例です.
コラム:文字列の比べ方 文字列どうしは == で中身を比べられます. "りんご" == "りんご"True です.安心して使ってください. (なお,Python には「同じ物体か」を調べる is もありますが, 文字列や数を比べるのに使ってはいけません.)

18.9 自分で調べる

ここまでで,データをまとめて扱う道具が 2 つになりました. Python には,あと 2 つよく使うものがあります. 集合(set)タプル(tuple)です. 4 つまとめて整理しておきましょう.

名前書き方引き方得意なこと
リスト list[1, 2, 3]番号 a[0]順番が大事なもの.同じ値が何度あってもよい
辞書 dict{"a": 1}キー d["a"]名前から値を引くこと.速い
集合 set{1, 2, 3}引けない(有無だけ)重複を消す・入っているか調べる・共通部分を求める
タプル tuple(1, 2)番号 t[0]あとから変えない組.辞書のキーにできる
four_types.py
# リスト:順番がある.重複してよい
a = [3, 1, 4, 1, 5, 9, 2, 6]
print(a[0], len(a))

# 集合:重複が消える.順番は無い
s = set(a)
print(s, len(s))
print(3 in s)

kudamono1 = {"りんご", "みかん", "すいか"}
kudamono2 = {"みかん", "いちご"}
print(kudamono1 & kudamono2)     # 両方にあるもの
print(kudamono1 | kudamono2)     # どちらかにあるもの
print(kudamono1 - kudamono2)     # 1 にだけあるもの

# タプル:あとから変えられない組
t = (100, 200)
x, y = t                          # 一度に 2 つの変数へ
print(x, y)
# t[0] = 5                        # → TypeError(変えられない)

# 辞書:名前で引く
d = {"あかり": 78, "けんた": 92}
print(d["けんた"])
空の {} は辞書 {}空の辞書であって,空の集合ではありません. 空の集合が欲しいときは set() と書きます. Python の文法の中で,ちょっと意地悪なところです.

どれを使うか迷ったら,やりたいことを日本語で言ってみると決まります.

  • 「1 番目,2 番目…と順に並べたい」 → リスト
  • 名前で引きたい」 → 辞書
  • ダブっているものを消したい」「入っているかだけ知りたい」 → 集合
  • 「2 つ(3 つ)をひとまとめにして渡したい」 → タプル
コラム:配列という言葉 ほかの言語には,作るときに入れられる個数を決める入れ物があり, 配列と呼ばれます. Python のリストは,はじめから個数の上限がありません. append すればいくらでも伸びます. 「配列」という言葉を見かけたときは, 「大きさが決まっているリスト」だと思ってください.

18.9.1 公式ドキュメントの読み方

ここまで習ってきた命令は,Python が用意してくれた道具のごく一部です. 「こんなことができる命令はないかな」と思ったら, 公式ドキュメントを見ます.日本語版があります.

場所何が書いてあるか
Python チュートリアル
docs.python.org/ja/3/tutorial/
読み物として最初から読めるもの.リスト・辞書の章がおすすめ
組み込み型
docs.python.org/ja/3/library/stdtypes.html
listdictsetstr に何ができるかの一覧
標準ライブラリ
docs.python.org/ja/3/library/
randommathtime などの道具箱の説明
turtle
docs.python.org/ja/3/library/turtle.html
タートルの命令の一覧(この教材で使えるのはその一部です)

ドキュメントには,独特の書き方の約束があります. たとえば get() の説明はこう書かれています.

公式ドキュメントの書き方
get(key, default=None)
    key が辞書にあれば key に対する値を,なければ default を返します.
  • key必ず書く引数
  • default=None省略できる引数.省略すると None になる,という意味
  • だから d.get(k) とも d.get(k, 0) とも書ける

この読み方が分かると,sorted(a, reverse=True)write(arg, align="center") のような書き方も, 自分でドキュメントから見つけられるようになります.

プログラムに聞いてみる 実行中の Python に直接たずねることもできます. print(dir({})) と書けば辞書にできることの一覧が出ますし, help(dict.get) と書けば説明が読めます (この教材の画面でも動きます.試してみてください).
コラム:API という言葉 プログラムから使える命令の集まりのことを, API(Application Programming Interface)といいます. 「turtle の API」「Python の標準ライブラリ API」のように使います. プログラミングの学習でいちばん大事な力の 1 つは, 「知らない API のドキュメントを読んで,使ってみられること」です. すべてを暗記する必要はありません. 調べ方を覚えるほうが,ずっと役に立ちます.
虫食い虫食い 18-E 重複を消して数える

アンケートの回答から,何種類の答えがあったかそれぞれ何人かを求め,円の大きさで描きます. ____ を埋めてください. 種類を数えるのは集合,人数を数えるのは辞書の仕事です.

fill17_set.py
from turtle import *

ht()
speed(0)
pensize(2)

kotae = ["いぬ", "ねこ", "いぬ", "うさぎ", "ねこ", "いぬ", "ねこ", "いぬ"]

# 何種類あったか(集合を使う)
shurui = ____(kotae)
print("答えは", len(shurui), "種類ありました")

# それぞれ何人か(辞書を使う)
ninzu = {}
for k in kotae:
    ninzu[k] = ninzu.get(k, ____) + 1
print(ninzu)

# 円の大きさで描く
iro = {"いぬ": "sienna", "ねこ": "dimgray", "うさぎ": "hotpink"}
x = -140
for k, n in ninzu.____():
    pu()
    goto(x, 0)
    pencolor(iro[k])
    dot(n * 28)
    pu()
    goto(x - 20, -70)
    pencolor("black")
    write(k, font=("Arial", 14, "normal"))
    goto(x - 12, -95)
    write(str(____) + " 人", font=("Arial", 13, "normal"))
    x = x + 140

done()

18.10 この章で出てきた命令

書き方意味
d = {}空の辞書を作る
d = {"a": 1, "b": 2}キーと値の組を書いて辞書を作る
d[k]キー k の値を取り出す.無ければ KeyError
d[k] = vキー k の値を v にする.無ければ新しく作る
k in dキー k があれば True
d.get(k)値.無ければ None
d.get(k, x)値.無ければ x
len(d)組の数
for k in d:キーを 1 つずつ取り出す
d.keys()キーの一覧
d.values()値の一覧
d.items()キーと値の組の一覧.for k, v in … で使う
del d[k]キー k の組を消す
d.pop(k, x)消しながら値を取り出す.無ければ x
d.clear()全部消す
set(a)リスト a から重複を消した集合を作る
(x, y)タプル.あとから変えられない組
help(dict.get)その命令の説明を表示する

演習

出発点のプログラムを「▶ この演習を開く」で右側に読み込み, それを発展させて作品にしてください. input() を使う演習には,右コンソールの入力行に あらかじめ値を入れてあります.書き換えて試してください.

演習演習 18-1 おうむ返しと,ことば辞典

入力に慣れるための演習です.次の順に作ってください.

  1. 入力された言葉を,そのまま画面の中央に write で表示する(おうむ返し).
  2. ことばの辞典を辞書で作り,入力された言葉の意味を表示する.5 語以上入れること.
  3. 辞書に無い言葉が入力されたら,「その言葉は知りません」と表示する(inget()).
  4. 余裕があれば,知らない言葉が来たときにその場で意味を入力させて辞書に登録し,登録後の辞書の中身を全部表示する.
ヒント 2 回入力させたいときは,input() を 2 回書けば, input() が上から順に 1 行ずつ読み取ります.
演習演習 18-2 作文さん

名詞動詞を集めておいて, でたらめな文を作るプログラムです.

  • 辞書 tango を作り,"めいし""どうし" をキーに,言葉のリストを値として入れる(18.6.1 節).
  • random.choice(リスト) で 1 つ選び,「○○が△△」という文を作って画面に write する.
  • 文を 5 つ作り,縦に並べて表示する.
  • 余裕があれば,"けいようし" を足して「○○な○○が△△」にする.入力された言葉を辞書に登録できるようにする.
ヒント random.choice を使うには,先頭に import random が要ります. リストに言葉を足すのは tango["めいし"].append("ねこ") です.
演習演習 18-3 タイピングゲーム

絵と単語の辞書を使ったタイピングゲームを作ってください.

  1. 「単語 → その絵を描く関数」の辞書を作る(18.8 節).3 語以上.
  2. 問題の絵を 1 つ描き,その名前を入力させる.
  3. 合っていれば「せいかい」,違えば「ざんねん」と表示する.
  4. time.time() で入力にかかった秒数を測り,表示する.
  5. 余裕があれば,3 問続けて出し,正解数と合計時間,1 問あたりの平均時間を表示する.
ヒント time.time() は「いまの時刻(秒)」を返します. 入力の前後で 2 回呼び,引き算すればかかった時間が分かります.
round(x, 2) と書くと,小数第 2 位までに丸められます.
演習演習 18-4 成績表をつくる

「学生番号 → 点数のリスト」という辞書から,成績表を作ってください.

  • 1 人ずつ,3 回のテストの平均点を計算する.
  • 平均点で評価(80 以上は A,70 以上は B,60 以上は C,それ未満は D)を付ける.評価は辞書ではなく if で決める.
  • 平均点を棒グラフで描き,棒の上に評価を write する.
  • 評価ごとの人数を数えて表示する(get() を使った数え方).
  • 余裕があれば,平均点の高い順に並べ替えてから描く(第 17 章の復習).
演習演習 18-5 道具を取り替えて書き直す

同じ目的でも,使う道具を変えるとプログラムの形が変わります(18.9 節). 次の「2 本のリスト版」のプログラムを,3 通りに書き直して 比べてください.

  1. 辞書で書き直す.何行短くなったか数える.
  2. 集合を使って,「登録されている色は何種類か」「"あか" は登録済みか」を求める.
  3. 辞書が並んだリスト[{"namae": …, "code": …}, …])で書き直す.どれがいちばん読みやすかったか,コメントに書く.
ヒント 同じことを違う道具で書いてみるのは, 道具の性格をつかむいちばん確実な方法です.
自由演習 18-6 自分の辞書アプリ

自由制作です.辞書を中心に据えた作品を作ってください. 題材は何でもかまいません.アイデアの例を挙げておきます.

  • 絵辞書の完成版.果物・動物・乗り物など,自分で絵を 5 つ以上描いて登録する.
  • お絵かきコマンド"まる" "しかく" "ほし" など,言葉を入力すると,その図形が画面に足されていく.
  • 県庁所在地クイズ英単語クイズ.「県名 → 都市名」の辞書を作り,出題して正解数を数える.
  • モールス信号.「文字 → 記号」の辞書を作り,入力された言葉を点と線の図で描く.
  • 顔ジェネレータ.「目 → 描き方」「口 → 描き方」の辞書を作り,入力(または random)で組み合わせて顔を作る.
ヒント 先頭にコメントで,題名・氏名・日付・ 「どんな辞書を作ったか(キーは何で,値は何か)」を書いてください.
条件:辞書を2 つ以上使うこと. inget()知らない入力に備えること. for で辞書の中身をたどる場面を1 つ以上入れること.

まとめ

  • リストは番号で引く入れ物,辞書は名前(キー)で引く入れ物.
  • 辞書は {"キー": 値, …} で作り,d[k] で取り出す.d[k] = v は,キーが無ければ新しく作る
  • 無いキーを d[k] で引くと KeyErrorin で確かめるか,get() を使う.
  • kaisu[k] = kaisu.get(k, 0) + 1 は「数える」の定石.
  • for k in d: はキー,for k, v in d.items(): はキーと値の両方を取り出す.
  • 値にはリストも辞書も関数も入れられる.長い elif の列は,たいてい辞書にできる.
  • キーにできるのは,あとから変わらないもの(文字列・数・タプル).
  • listdictsettuple を使い分ける.迷ったら「やりたいことを日本語で言ってみる」.
  • 知らない命令は公式ドキュメントで調べる.暗記より調べ方.

次の回では,Python が最初から用意してくれている道具を見ていきます. 自分で書けば何十行にもなる処理が,呼ぶだけで済んでしまうことがあります. リストや辞書と組み合わせると,できることが一気に広がります.