1 連絡
1.1 2I実験の授業評価アンケート
昨日、回答していない学生は、このタイミング回答をお願いいたします。
1.2 作品共有1
- 2月20日が期限であった課題8
の共有リンクです。
- 共有リンク (学内のみ有効-OneDrive)
- ポートフォリオ側の不備は 一律で4点減
とします。ただし、制作時間と説明文書、スクリーンショットについては
README.mdでの記載でも可とします。- 制作時期の明記がない (「2025年2月」に相当するもの)
- 制作時間の明記がない (「約XX時間」に相当するもの)
- 説明文章200文字以上がない
- スクリーンショット (もしくは動画) が3枚以上ない
- GitHubリンクがない
- リポジトリの不備は 一律で4点減 とします
README.md、requirements.txt、main.pyがない.venv、.vscodeがある
1.3 作品共有2
- 2024年度の3年生「プログラミング3」の「最終課題
(オリジナルアプリの開発)」の作品共有です。
- 課題3の共有リンク (学内のみ有効-OneDrive)
- こちらは22日から採点開始でアナウンスしているため、現時点 (2/21時点)では、半数ぐらいは「鋭意作成中」の状態です😇
2 例外処理
現在までの講義資料のなかで、何度か 例外処理 という言葉が登場しています (第11回講義、第19回講義、第23回講義)。
「例外処理」とは、簡単にいえば「予期せぬエラーが発生した場合に プログラムがクラッシュすること (=停止・強制終了すること) を防ぎ、エラーを適切に処理してプログラムの実行を継続させる ための仕組み」を提供するものです。
ここでの「予期せぬエラー」とは、例えば、次のようなものを意味します。
- 関数に対して、想定していない型の引数が与えられた。
- 例:
math.sin('八十二')
- 例:
- リストの範囲外のインデックスにアクセスしようとした。
- 例:
arr=['A','B','C']に対してarr[9]を参照した。
- 例:
- ファイルを読み込もうとしたがファイルが破損していた/存在していなかった。
2.1 例外とは
プログラミングの分野において 例外 (Exception、エクセプション) という用語は、我々が日常的に使用する「例外」という言葉とは異なる意味合いで使用されます。プログラミング分野においての「例外」とは プログラムの実行中に予期せぬ事態やエラーやプログラムが正常に実行を続けることができない状況 を意味します。そして、例外処理 (Exception Handling) とは、それら例外に対して講じるべき処理や措置を指します。
具体的には次のように「例外」や「例外処理」という言葉が使用されます。
- A先輩「この部分に例外処理を追加することを検討してみてください。
try-exceptブロックを使用すると予期しないエラー、つまり「例外」が発生してもクラッシュせず、アプリが堅牢性が向上します。例えば、この部分ではValueErrorが発生する可能性がありますよね。」 - A先輩「ここのファイル操作をしている部分で
FileNotFoundErrorの例外をキャッチしていないようですね。ファイルが存在しない場合に備えて、この例外を処理するコードを追加すると、アプリのユーザビリティが向上します。try-exceptで囲んで例外処理することを検討してください。」 - B先輩「なんでこのコードでは
try-exceptで例外処理してないわけ? コードを書く前にまずは頭で考えてみてよ、これじゃあ、ちょっとでも想定外の使い方されたらクラッシュするって! 」 - B先輩「だれ? ここの例外処理を書いたのは? これじゃただエラーを無視してるだけじゃない。適切にログをとるか、ユーザーにフィードバックを返さなきゃ意味がないんだよ。」
- B先輩「ファイルがないときの処理を書いてないってどういうこと?
FileNotFoundErrorをキャッチして、何が起きたのかをちゃんとユーザーに伝えないと。全く、もう一度、基本からやり直しだな!」
2.2 例外処理の記述
例外処理 (Exception Handling) とは、プログラム実行時に発生する「例外(=想定外の事態や、そのままではプログラムが続行不可能な状況)」を 捕捉 (Catch、キャッチ) して、それ対して適切な処理を講じるための枠組みを意味します。
例外処理を活用することで、いわゆる「通常のエラー処理」よりも、柔軟かつ効率的にプログラムの クラッシュを回避すること、結果的として強制終了やフリーズが起きずらい「安全性」と「堅牢性」を備えたアプリの提供が可能となります。
この「例外処理」という機能・仕組みは、Python言語に限らず、C++言語、JavaScript、Java などの多くのモダンなプログラミング言語でサポートされています。一方で、C言語 や Go言語 においては、例外処理はサポートされていません。
Pythonでの例外処理は、次のような try ブロックと
except ブロックから構成されます。try
ブロックには通常の実行コードを記述しておき、実行時に
そのブロック内で何らかの例外
(=つまりエラーや想定外の事態)
が検出・捕捉されると、その時点で処理
(制御フロー) が自動的に except
ブロックに移行する という仕組みが提供されます。
一般に except
ブロックには「エラーに対処する処理
(ユーザーへのメッセージ表示やログ出力)」や「プログラムを安全に終了するための処理」を記述しておきます。これにより「エラーを抱えたままプログラムが続行してクラッシュ
(強制終了)
に至る」というUX的に最悪の状態を回避することができます。UX:
User Experience。
以下に、try - except
ブロックを利用した「例外処理」の例を示します。
import os
import pickle
# 設定ファイルを読込む処理
def load_user_setting():
fn = 'user-setting.pickle'
# 通常のエラー処理 (事前検証)
if not os.path.isfile(fn) :
print(f'設定ファイル {fn} が存在しません。')
return None
# 例外処理 (try-except構文) によるエラー処理
try :
with open(fn,'rb') as file:
us = pickle.load(file)
return us
except Exception as e:
print(f'設定ファイル {fn} の読み込みに失敗しました。')
print(f'詳細 => {type(e)} {e}')
return None
# 設定の読み込み
user_setting = load_user_setting()
print('プログラムの実行フローが最後まで到達しました。')上記プログラムの 第09行目 から
第12行目 は「(例外処理ではない)
通常のエラー対策/処理」となります。指定したファイルパスに「ファイルが存在するか?」どうかを
open 関数の 実行前に検証
して、もし、ファイルが存在しなければメッセージを表示して
None を戻り値として関数を抜けています。
このように通常は、そのまま続行すればエラーが生じる可能性がある要因について
事前チェックをかける
という方法でエラー対策を講じます。例えば、除算においては「除数が
0 以外であること」を検証し、math.sin()
を使用する場合では「引数として与える値が数値型であること」を検証し、もし問題があれば、その処理
(=除算 や math.sin() )
を「実行しない」ことで、つまり「エラーが起こる処理を実行しないこと」でプログラムのクラッシュを回避します。
しかし、全てのエラーを事前に想定・予測して対策を講じること が常に可能であるとは限りません。例えば、上記の 第10行目 ではファイルの存在を確認していますが、その直後に ファイルが削除される可能性、ファイルが破損している可能性、排他的なアクセス制御により ファイルが読み取りが不可能になっている可能性などあります。これらの状況をすべて想定してプログラム上で事前に対処することは事実上不可能となります。
このようなケースに対して try-except
による「例外処理」が有効となります。
例外処理を使用すると try ブロック内でエラー (例外)
が発生した場合、そこでの処理は中断され、制御フローが
except
ブロックに移行して処理が続行されます。具体的には、ファイルが存在しなければ
第16行目 の open
関数で例外が発生し、それが検出・捕捉されることで
第20行目
に制御が移行します。また、ファイルが壊れていれば (適切な
pickle 形式でなければ) 第17行目 の
pickle.load
関数で例外が発生し、それが検出・捕捉されることで
第20行目 に制御が移行します。
いわゆる通常のエラー対策では、エラーが発生する可能性がある処理に先立って、その処理が安全に実行可能かを事前検証することで (つまり、エラーが発生しない条件下でのみ処理を実行することで) プログラムのクラッシュを回避します。一方で、「例外処理」ではエラーの発生自体は許容し、発生したエラーに対して適切な処理を施すことで、プログラムのクラッシュを回避するという「考え方の違い」があります。
実際のプログラム設計においては、事前検証を基本とする通常のエラー対策と、例外処理の 両方を適切に組合わせること で、堅牢性のあるアプリを実現します。
プログラムの途中で「クラッシュが発生してアプリが強制終了 (異常終了) すること」と「エラーの発生に基づき意図的にプログラムの終了処理すること」は、アプリの品質上、大きな違いがあるので十分に注意してください。
演習1: 次のプログラムを
'user-setting.pickle'
が存在しない状況で実行し「プログラムの途中でクラッシュすること」を確認せよ。つまり、プログラムの途中で強制終了が発生して
プログラムの最後まで実行されず「プログラムの実行フローが最後まで到達しました。」というメッセージが出力されないこと
を確認せよ。.
import pickle
def load_user_setting():
fn = 'user-setting.pickle'
with open(fn, 'rb') as file:
us = pickle.load(file)
return us
user_setting = load_user_setting()
print('プログラムの実行フローが最後まで到達しました。')(実行結果の一例)
> python crash.py
Traceback (most recent call last):
File "C:\Users\xxxx\Documents\xxxx\ex.py", line 11, in <module>
user_setting = load_user_setting()
^^^^^^^^^^^^^^^^^^^
File "C:\Users\xxxx\Documents\xxxx\ex.py", line 6, in load_user_setting
with open(fn, 'rb') as file:
^^^^^^^^^^^^^^
FileNotFoundError: [Errno 2] No such file or directory: 'user-setting.pickle'
演習2: exception-handling-01.py
の 第10行目 から 第12行目
をコメントアウトして、同フォルダに
user-setting.pickle
が存在しない状態でプログラムを実行せよ。そして、例外処理により、次のメッセージが表示されること
(つまり try ブロックから except
に実行フローが移行していること)、また、プログラムがクラッシュすることなく最後まで実行されていることを確認せよ。
設定ファイル user-setting.pickle の読み込みに失敗しました。
詳細 => <class ‘FileNotFoundError’> [Errno 2] No such file or directory: ‘user-setting.pickle1’
プログラムが最後まで到達しました。正常終了します。
演習3: exception-handling-01.py
と同フォルダに user-setting.pickle
というファイルが存在する状態
(ただし、ファイルの内容はデタラメな文字列となっている)
でプログラムを実行せよ。そして、例外処理により、次のメッセージが表示されることを確認せよ。
設定ファイル user-setting.pickle の読み込みに失敗しました。
詳細 => <class ’_pickle.UnpicklingError’> pickle data was truncated
プログラムが最後まで到達しました。正常終了します。
2.3 エラー処理の方法
次に示す exception-handling-02.py と
exception-handling-03.py
は、次のように標準入力から「区分番号」に相当する文字列を受け取って、それに基づき「小学生」「中学生」・・・「大学生」の区分を選択するプログラムになっています。
0:小学生 1:中学生 2:高校生 3:高専生 4:大学生
区分番号(0~4)を入力してください => AAA
不正な値が入力されました。やり直してください。
区分番号(0~4)を入力してください => 8
不正な値が入力されました。やり直してください。
区分番号(0~4)を入力してください => 6.9
不正な値が入力されました。やり直してください。
区分番号(0~4)を入力してください => 3
区分「高専生」が選択されました。
exception-handling-02.py
は「事前検証を使って「不正な入力値」に対処しているプログラム」になっています。つまり、第10行目
の x.isdigit()
メソッドで、標準入力から得られた文字列が数値から構成されているかを検証し、それがクリアできれば
第11行目 の int()
関数で整数値に変換し、さらに数値範囲が リスト s_arr
のインデックスとして適切なものかどうかを確認しています。これらの「事前検証」を行なうことによって、標準入力から
AAA や 8、6.8
のような文字列が入力されたときも、プログラムがクラッシュすることなく動作する仕組みを提供しています。
def get_student_type():
s_arr = ['小学生', '中学生', '高校生', '高専生', '大学生']
for i, v in enumerate(s_arr):
print(f'{i}:{v} ', end='')
print()
while True:
x = input(f'区分番号(0~{len(s_arr)-1})を入力してください => ')
if x.isdigit():
n = int(x)
if 0 <= n <= len(s_arr) - 1:
return s_arr[n]
print('\n不正な値が入力されました。やり直してください。')
st = get_student_type()
print(f'\n区分「{st}」が選択されました。')一方で exception-handling-03.py
は「例外処理を使って不正な入力値に対処しているプログラム」となっています。こちらのプログラムでは、標準入力から得られた文字列に対して事前検証はせずに、try
内部の 第10行目 で s_arr[int(x)]
を実行してみて、例外が発生すれば 第12行目
に処理を飛ばすことで、不適切な文字列が入力されてもプログラムがクラッシュしない仕組みを提供しています。
def get_student_type():
s_arr = ['小学生', '中学生', '高校生', '高専生', '大学生']
for i, v in enumerate(s_arr):
print(f'{i}:{v} ', end='')
print()
while True:
x = input(f'区分番号(0~{len(s_arr)-1})を入力してください => ')
try:
return s_arr[int(x)]
except Exception as e:
print('\n不正な値が入力されました。やり直してください。')前述したように「事前検証と例外処理のどちらでエラー対策/処理をするべきか」は状況によって変わります。ただし、基本的には事前検証と例外処理を併用することが一般的には望まれます。
演習1 : 上記の2つのプログラムを実行して、その動作を確認せよ。
演習2 : 上記の例外処理を使用したプログラム
(exception-handling-03.py)
では、一部の不正な値を検出することができない。どのようなケースにおいてその問題が発生するか考え、検証せよ。答え:
「-1」や「-2」のような不正入力に対応することができない。
2.4 例外「KeyboardInterrupt」
Pythonプログラム開発や実行において頻繁に遭遇する例外には
TypeError、ValueError、IndexError、ImportErrorなどがあります。加えて、CLIプログラム
(Command Line Inteface) においてキーボードから Ctrl+C
を入力した際に発生する KeyboardInterrupt
という例外も重要となります。
CLIプログラムにおいて、Ctrl+C
は「コピー」のショートカットではなく、プロセスに割り込み信号(SIGINT)を送信し、処理を中断させるための操作
として機能します。この操作は、プログラムが暴走したり、無限ループに陥った際など、プログラムを強制終了させる必要がある場合
に使用します。
例えば、次のプログラム loop-01.py を実行したとき
(VSCodeの F5 ではなく、ターミナルから
python loop-01.py
コマンドで実行したとき)、このプログラムは Ctrl+C
でターミナルに割り込み信号 (SIGINT: Interrupt Signal)
を送信し、強制終了することができます。
このプログラムを実際に実行し、Ctrl+C
を使って強制終了できることを確認してください。なお、Ctrl+C
は Python プログラムに限らず、その他の
CLIプログラムにおいても同様に機能します。
この Ctrl+C によって発生する
KeyboardInterrupt は、try -
except
で例外処理することができます。例えば、次のように例外処理すれば、強制終了ではなく正常終了させることができます。
import time
print('処理中', end='')
while True:
try:
time.sleep(0.5)
print('.', end='')
except KeyboardInterrupt:
print('処理を中断しますか? Y/[N] => ', end='')
if input() == 'Y':
break
print('プログラムの実行フローが最後まで到達しました。')演習 : loop-02.py
を実行して、その動作について確認せよ。
2.5 例外処理についてさらに理解を深める
ここまで例外処理の概念を掴むための解説をしてきました。しかし、ここで触れた内容は例外処理の一端にしか過ぎません。実務プログラミングでは、ここでは紹介していない
raise や finally
というキーワードを使った例外処理のテクニックが使われます。「Python
例外処理
入門」などキーワード検索すると丁寧な解説記事が見つかるので、それらを参照してください。
3 デバッグ支援ライブラリ icecream
プログラムのデバッグに便利なライブラリとしてicecreamというライブラリを紹介しておきます。このライブラリは、「変数名」と「変数に格納された値」を同時に表示する機能を持ちます。
icecreamは標準の組み込みライブラリに含まれていないので、pip を使ってインストールする必要があります。
pip install icecream
例えば、次のように使用します。
実行結果は、次のようになります。
ic| a: 10
ic| a * b: 400
ic| type(a): <class 'int'>
もし、同様に print 関数を使って a: 10
のように表示するとすれば、print('a: {a}')
のように記述する必要があります。これが、このライブラリを使用すれば
ic(a) のように非常に短い文で記述可能になります。
また、引数を与えずに、次のように
ic()
だけを記述すると、それが実行された行数と時刻が出力されます。例えば、途中で強制終了してしまうプログラムがあったとき「どこまで (何行目まで)
プログラムが正常に動作しているか?」などを確認したい場合に利用できます。
実行結果は、次のようになります (変数 a
の値が、何行目時点でどのような値になっているかが把握できます)。
ic| ic-02.py:5 in <module> at 15:51:11.157
ic| a: 10
ic| ic-02.py:10 in <module> at 15:51:11.191
ic| a: 20
ic| ic-02.py:15 in <module> at 15:51:11.195
ic| a: 100
また ic.disable() を呼び出せば、以降の
ic() を 一括して無効化すること
ができます。プログラムのなかの様々な場所で ic()
を記述しているとき、完成版としてリリースするために、それらを個別にコメントアウトしていくことは非常に面倒なので、ic.disable()
を利用してください。
その他、詳しくは公式リファレンスを参照してください。例えば
ic.configureOutput(prefix='[DEBUG] ') のようにすれば
ic| の部分を
[DEBUG] に置き換えるようなこと
もできます。
演習1 : ic-02.py の
第09行目 に ic.disable()
を記述して、その動作を確認せよ。
演習2 :
演習1に引き続き、第14行目 に
ic.enable() を記述して、その動作を確認せよ。
演習3 : ic-01.py の
第02行目 に
ic.configureOutput(prefix='[DEBUG] ')
を記述して、その動作を確認せよ。
f文字列を使ったデバッグの小技
f文字列では、次のように変数名のあとに「イコール」を付けると、変数名と内容を同時に出力してくれます。
実行結果
x=10
x=10
4 総括
本科目についての「総括」です。
本授業は通年科目として約1年間をかけて約30回の講義・演習 (=90min×30=45hr) を通じて「Pythonプログラミング」について学んできました。
しかしながら、4月の第01回講義で伝えてきたように ジュニアレベルのプログラマ(=ソフトウェアエンジニアのタマゴとして就職ができるレベル)に到達するために必要な学習時間は 1,000~2,000 時間と言われおり、また、プログラミングの「超初心者」が「初心者」になるまでに必要な学習時間は 250 時間と言われています。その意味で、学校で受ける90分×30回の授業の学習時間などは誤差レベルでしかありません。
実質的に意味を持ってくるのは「授業で学んだことを基礎/きっかけとして自発的な「学び」をしている時間」です。事実、皆さんの肌感覚としても 高専祭展示や自由課題などを通してプログラミングしている ときが、授業よりも何倍も「学び」や「成長」が得られる有意義な時間という実感があると思います。
また、学校の授業は様々な制約で「週1回」というペースでしか進みませんが、仮に自発的に「週5回」のペースで勉強を進めていれば、わずか2カ月程度で本授業30回分の学習は可能です。つまり、2年生の前期中間試験の頃 (6月頃) には PySide を使ったプログラムがつくれるレベルに到達可能なのです。事実、本校にも、全国の高専生にも、そのようなペースで学習をガンガンと進めている人が少なからず存在します。
プログラミングをはじめとして情報系分野のエンジニアは、学歴や学位ではなく「経験・実績」が大きく評価される領域です(その経験・実績を示すためのひとつの手段がポートフォリオです)。学校で総合成績の上位をとっていれば、どこでも希望のところに就職できる・・・というわけではありません。
自身の「人生設計」や「キャリア計画」に基づき、興味関心のある分野については、授業など待たず、また授業の枠を超えて貪欲に「学び」を深めていって欲しいと思います。授業は、そのための「チュートリアル」と考えてください。
4.1 ぜひ視聴してほしい動画
第01回講義でも見てもらったら動画です。いま、1年間の学習を終えたいま、改めて視聴してほしいと思います。
4.2 達成目標の確認
本科目のシラバスに示した【科目の達成目標】からの抜粋です。
- Pythonの開発環境および実行環境を構築できる。
- エディタやバージョン管理システムなどの各種開発ツールを効果的に利用できる。
- 関数、条件分岐、繰り返し処理を使用して基本的な構造化プログラミングができる。
- Pythonの多様なライブラリを活用して日常生活や学習に有用な小規模アプリを開発できる。
- エラーや意図せぬ結果が生じたとき、その原因特定と解決に向けて適切なアプローチができる。
さて、上記について「成長」を冷静に客観的に評価してみましょう。
なお、本科目では、プログラミングの授業ですが、CPUやメモリなどのハードウェア、OSとの関係においてプログラムはどのようにして動くのかといったこと や アルゴリズムやデータ構造に関すること については、意図的に扱っていません。これらについては、来年度以降の「アルゴリズムとデータ構造1・2」「コンピュータシステム」「コンピュータアーキテクチャ」「情報理論」などの授業のなかで学習していくことになります。