コンテンツにスキップ

第14回:まとめ

前回は Web スクレイピングで、インターネット上のページから欲しい情報を取り出した。これで、プログラミング言語 I の復習から始まった本科目の内容は一通り出そろった。最終回の今回は、この半期に学んだことをかるく振り返り、最後に総まとめの課題に取り組む。

この半期で学んだことを、あとから見返せるように順に振り返る。各テーマの「代表的な書き方」と「つまずきやすい点」を思い出しながら読んでほしい。

リスト・タプルと基本型(第 2 回)

複数の値をまとめて扱う入れ物として、リスト[]、あとから中身を変更できる)と タプル()、一度作ったら変更できない)を学んだ。

scores = [80, 55, 91]     # リスト:追加・変更できる
scores.append(62)         # → [80, 55, 91, 62]
point = (135, 240)        # タプル:座標などの「変えない組」に向く
  • リストは append(追加)・scores[0](番号で取り出し)・len(scores)(個数)が基本
  • 「変更してよいか」でリストとタプルを使い分ける
  • int(整数)・float(小数)・str(文字列)・bool(真偽)といった基本型と、int() / str() による型変換もここで確認した

ユーザ定義関数(第 3・4 回)

同じ処理を何度も書かずに済むよう、処理をひとまとめにして名前を付けるのが 関数def で定義し、引数で値を受け取り、return戻り値を返す。

def add(a, b):        # a, b が引数
    return a + b      # 計算結果を返す

goukei = add(80, 62)  # goukei に 142 が入る
  • return した値は、呼び出した側で受け取って使える
  • return を書かない関数は None を返す(画面表示の print と、値を返す return は別物)
  • 引数には初期値を付けられる(def f(x, n=1):

高階関数とラムダ式(第 5 回)

関数そのものを引数として渡す関数を高階関数という。渡す相手として、その場で使い捨ての短い関数を書けるのが ラムダ式

def apply_list(func, items):
    return [func(x) for x in items]

print(apply_list(lambda x: x * 2, [1, 2, 3]))   # → [2, 4, 6]
  • 書き方は lambda 引数: 戻り値def を使わず 1 行で書ける
  • sorted(データ, key=lambda x: x[1]) のように、並べ替えの基準を関数で渡す使い方が代表的

クラス・メソッド・継承(第 6〜8 回)

データと処理をひとまとめにする設計図が クラス__init__ で初期値を用意し(第 6 回)、クラスの中に書く関数=メソッドで動きを持たせる(第 7 回)。第 1 引数の self は「自分自身」を指す。

class Character:
    def __init__(self, name, hp):
        self.name = name          # インスタンスごとのデータ
        self.hp = hp
    def show(self):               # メソッド
        print(self.name, 'HP:', self.hp)

hero = Character('勇者', 100)      # インスタンスを作る
hero.show()                        # 勇者 HP: 100

継承(第 8 回)は、既存のクラスを土台に新しいクラスを作るしくみ。共通部分を親クラスにまとめ、違う部分だけ子クラスで書く。

class Hero(Character):             # Character を継承
    def attack(self):             # Hero だけの動きを追加
        print(self.name, 'の攻撃!')
  • インスタンス.メソッド() の形で呼ぶ。メソッドの中では自分のデータを self.〇〇 で参照する
  • 子クラスで同じ名前のメソッドを書くと、親の動きを上書きできる

例外処理(第 9 回)

エラーが起きてもプログラムを止めずに対処するのが try / except。想定できる失敗を種類ごとに受け止める。

try:
    n = int(input('数を入力: '))
    print(10 / n)
except ValueError:
    print('数字を入れてください')     # 数に変換できないとき
except ZeroDivisionError:
    print('0 では割れません')         # 0 で割ったとき
  • try の中でエラーが出ると、対応する except に飛ぶ
  • finally に書いた処理は、エラーの有無にかかわらず必ず実行される(後片付けに使う)

ファイル操作(第 10 回)

プログラムを閉じると変数の中身は消える。残したいデータはファイルに保存し、次回読み込むwith open(...) as f: で開くと、閉じ忘れがない。

# 書き込み(セーブ):改行 \n は自分で付ける
with open('save.txt', 'w', encoding='UTF-8') as f:
    f.write('勇者' + '\n')
    f.write('100' + '\n')

# 読み込み(ロード):1 行ずつ処理する
with open('save.txt', 'r', encoding='UTF-8') as f:
    for line in f:
        print(line.strip())      # strip() で行末の改行を落とす
  • モードは 'w'(書き込み・上書き)と 'r'(読み込み)
  • 読み込んだ 1 行には末尾に改行が付くので、strip() で落とすことが多い
  • 今回の課題の seiseki_data.txt も、この「1 行ずつ読む」処理が土台になる

データの集計とグラフ描画(第 11 回)

集めた数値は、表にするだけでなくグラフにすると傾向が見える。外部ライブラリ matplotlib を使う。

import matplotlib.pyplot as plt
plt.rcParams['font.family'] = 'Yu Gothic'   # 日本語が化けない対策(描画より前に書く)
plt.bar(['月', '火', '水'], [3, 7, 2])       # 棒グラフ
plt.show()
  • plt.bar(ラベル, 値) が棒グラフ、plt.plot(値のリスト) が折れ線グラフ
  • plt.show() で表示、plt.savefig('graph.png') でファイルに保存
  • plt.xlabel(...)plt.legend()(凡例)で見た目を整えられる

画像処理(第 12 回)

写真も、プログラムなら大量にまとめて加工できる。外部ライブラリ OpenCVimport cv2)を使う。

import cv2
img  = cv2.imread('block.jpg')                     # 読み込み
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)       # 白黒に変換
cv2.imwrite('gray.jpg', gray)                      # 保存
  • cv2.imread で読み込み、cv2.imwrite で保存
  • cv2.resize(img, (幅, 高さ)) でサイズ変更、cv2.cvtColor(..., cv2.COLOR_BGR2GRAY) で白黒化、cv2.Canny(img, 50, 100) で輪郭抽出
  • 画面に出すときは cv2.imshow のあと cv2.waitKey(0) を忘れない

Web スクレイピング(第 13 回)

インターネット上のページから欲しい情報を取り出す。ページの取得は requests、中身の解析は BeautifulSoup

import requests
from bs4 import BeautifulSoup

result = requests.get('https://www.shoeisha.co.jp/book/ranking')
soup = BeautifulSoup(result.text, 'html.parser')

for sec in soup.select('section'):        # 該当タグをすべて回す
    if sec.select_one('h2'):              # 先頭の1つを取り出す
        print(sec.select_one('h2').text)  # .text で中の文字列だけ
  • select('タグ名') は該当要素をすべて(リスト)、select_one('タグ名')先頭の 1 つを返す
  • .text を付けるとタグを外して「中の文字列だけ」になる
  • 欲しい情報が「どのタグの中にあるか」を見つけるのがコツ

総まとめの課題について

最終回の課題は、ファイルからデータを読み込み、条件で選び出して集計するという、この半期の土台になった処理の総復習である。seiseki_data.txt を題材にする。上のリンクからダウンロードし、プログラムと同じフォルダに置くこと。

seiseki_data.txt は、氏名と 4 科目(国語・算数・理科・社会)の科目名と点数が、順に改行で並んだデータになっている。

羽柴 秀吉
国語
24
算数
80
理科
88
社会
23
田中 龍之介
国語
15
算数
55

つまり 1 人分は 9 行(氏名 → 「国語」→ 点数 → 「算数」→ 点数 → 「理科」→ 点数 → 「社会」→ 点数)で、これがくり返される。

人数分のリストは作らない

基本・応用ともに、全員のデータをリストに溜めてはいけない。一人分を読んで処理したら次へ進む、という書き方にすること(1 人分だけ変数に持てば十分)。

授業内演習

seiseki_data.txt を読み込み、実行例のとおり、先頭 1 人分の氏名と 4 科目の点数を表示するプログラムを作成しなさい(課題に進む前のウォームアップ)。

実行例:

氏名: 羽柴 秀吉
国語: 24
算数: 80
理科: 88
社会: 23

確認の観点:

  • with open('seiseki_data.txt', 'r', encoding='UTF-8') as f: で開く
  • 1 人分は 9 行f.readline() で 1 行ずつ読むか、for で読みながら数える
  • 点数は文字列で読まれる。数として使うときは int() で変換する(今回は表示だけなのでそのままでもよい)

基本課題

seiseki_data.txt を読み込んで、算数が 85 点以上、かつ国語が 85 点以上の人だけを抽出し、実行例のとおり、該当する氏名合計人数を、画面とファイルの両方に出力するプログラムを作成しなさい。

なお、人数分のリストは作成しないプログラムとすること(一人分のデータを処理したら次に進めばよいので、全員のデータをリストにとっておく必要はない)。

実行例:

算数と国語が85点以上の生徒の氏名
藤村 まなみ
楠 三郎
シャア アズナブル
小幡 信輔
高田 美月
山岡 竜次
亀田 理紗
松沢 完爾
末吉 遥
阪口 俊二
田中 和久
合計人数: 11

ヒント:

  • 1 人分は 9 行。「氏名 → 国語 → 点数 → 算数 → 点数 → 理科 → 点数 → 社会 → 点数」の順で並んでいる
  • 点数の比較は数として行う。読み込んだ点数は int() で数に変換してから比べる
  • 条件(算数 ≥ 85 かつ 国語 ≥ 85)を満たしたら、氏名を表示し、数える用の変数を 1 増やす
  • 最後に合計人数を出す
  • 「画面とファイルの両方」に出す。ファイルへは、画面に print するのと同じ内容を f.write() で書けばよい(print した文字列をそのまま書き込む)

応用課題

seiseki_data.txt において、このデータには理系タイプ文系タイプが存在するだろうか? それを調べるプログラムを作成しなさい。

「考え方や工夫した点」で、得られた結論と、なぜそのように考えたのかを説明すること。基本課題と同様、人数分のリストは作成しないプログラムとすること。

考え方のヒント(ここからは自分なりに):

  • まず「理系タイプ」「文系タイプ」を自分で定義してみよう。たとえば「理科・算数が高く、国語・社会が低い人=理系タイプ」「その逆=文系タイプ」など、基準は自分で決めてよい
  • 決めた基準に当てはまる人が何人いるかを数えれば、「存在するか」を判断できる
  • あるいは、科目どうしの関係を見る手もある(例:算数が高い人は理科も高い傾向があるか? 国語が高い人は社会も高い傾向があるか?)。傾向があれば「タイプが存在する」根拠になる
  • どんな基準・観点を選んだとしても、なぜそう考えたのかを「工夫した点」に必ず書くこと。結論そのものより、考え方の筋道が評価される