2023-2I プログラミング1 第11回 講義資料

2023年07月06日(木)5・6時限

1 準備

2 課題05 (予告)

現在までのプログラミング学習の集大成として「7月26日 (水) 23:55」を期限とする 自由課題 に取り組んでもらいます。

提出場所などの詳細については次回以降の授業で説明しますが、以下の要件で実施予定なので、取り組みをはじめてください。

3 復習: 実行開発環境の構築

今後、授業のなかで練習時間は確保しませんが、必要に応じて以下の手順で 仮想環境の新規構築がスムーズにできるようになっておいてください。指示があったときは、以下について (ライブラリのダウンロードとインストールの待ち時間を除いて) 5分以内 にできるようになっておいてください。

  1. プロジェクトフォルダの作成と、VSCodeによるオープン。
  2. 仮想環境の作成
    • python -m venv .venv
  3. 仮想環境の有効化
    • .venv/Scripts/Activate.ps1
  4. pipのアップグレード
    • python -m pip install --upgrade pip
  5. 必要パッケージのインストール
    • pip install XXXXX
  6. 動作確認用のプログラムファイルの新規作成
    • 通常プログラムなら xxxx.py
    • ノートブック (Jupyter環境を要インストール) なら xxxx.ipynb
  7. プログラムの動作確認。
    • 「コンソール」や「出力セル」に結果が出力されることを確認
    • ライブラリを import して ModuleNotFoundError が発生しないことを確認

慣れるまでは 週1で練習してください。

4 ファイルネームに関する注意

Jupyter環境 (GoogleColab.環境) 以外で、プログラムファイルを作成する際は、ファイル名に 日本語やスペース (半角も全角も) 、ハイフンなどを絶対に使用しないでください。

また、プログラムのなかでインポートしている ライブラリ (モジュール) と同じ名前のファイル名をつけないでください。例えば、プログラムのなかに import numpy as np という文を含む場合、そのプログラムファイルに numpy.py という名前を付けたり、同フォルダに numpy.py というファイルが存在すると実行時エラー が発生します。

初心者によくあるミスなので注意してください。

4.1 演習①

ローカルに構築した仮想環境 (=新規作成は不要。前回に作成したものをそのまま利用すればよい) で pip install numpy を実行して、numpy をインストールせよ。

次に ex01.py という名前で「Pythonファイル」を新規作成し、以下のプログラムを貼り付けて期待する結果が得られるように np.arange の第2引数を調整せよ (サンプル提示のプログラムでは 5.00 が表示されない)。

なお、np.arange については 第10回講義 で解説ずみである。

import numpy as np   # numpy を np という省略名で使用
for t in np.arange(0.0, 5.0, 0.5):
  print(f'{t:.2f}',end=' ')

期待する結果

0.00 0.50 1.00 1.50 2.00 2.50 3.00 3.50 4.00 4.50 5.00 

(ここからが本題) 上記のファイル名を numpy.py (プログラムのなかでインポートしているライブラリと同じ名前) に変更して、その実行結果を確認せよ。

また、ファイル名を np.py に変更して、その実行結果を確認せよ。

必要に応じて「なぜこのようなことが発生するのか」をウェブ検索あるいはChatGPTを使って解決せよ (適切な検索キーワードやプロンプトを構築する練習です)。

5 型 (Type)

ここまでは、あえて言及を避けてきましたが、Python においても、C/C++言語 (Arduino言語)、Java と同様に 型 (Type) が存在して機能しています。

ただし、Python では、C/C++言語のようにプログラマによる明示的な型付けはせずに、Python実行環境が 動的に型付け をします。つまり a=10 という文を書けば a は自動的に「整数型 (int)」となり、一方で a='ABC' という文を書けば a は自動的に「文字列型 (str)」となります。

型付けの種類

プログラミング言語の型システムには「静的型付け」「強い動的型付け」「弱い動的型付け」「型なし」などが存在します。それぞれ一長一短があります。興味のある学生は調べてみてください。なお、Pythonは「強い動的型付け言語」に属します。

5.1 どうやって型を確認するのか?

type() という組み込み関数を使用してデータ型を確認できます

また、isinstance() という組み込み関数を利用して「変数が特定の型であるかどうか」を判定できます(例えば変数 aint型であるか?を判定することができます) 。同じように is 演算子と type() の組み合わせでも「特定の型であるかどうか」を判定できます。isinstance()isif文の条件としてよく使用されます

次のプログラムを実行して、その結果を確認してください。また、その結果とプログラムを突き合わせて内容を理解してください。

%reset -f
a = 10

# 型を確認する
print(f'変数aの型は {type(a)} です。')
print('---')

# 代表的な型 str int bool float list
print(f'変数aの isinstance(a,str) は {isinstance(a,str)} です。')
print(f'変数aの isinstance(a,int) は {isinstance(a,int)} です。')
print(f'変数aの isinstance(a,bool) は {isinstance(a,bool)} です。')
print(f'変数aの isinstance(a,float) は {isinstance(a,float)} です。')
print(f'変数aの isinstance(a,list) は {isinstance(a,list)} です。')
print('---')

# if文に組み込み① → isinstance() 関数
if isinstance(a,str) :
  print('変数aは、文字列型(str)です')
else:
  print('変数aは、文字列型(str)ではない')

# if文に組み込み② → is 演算子
if type(a) is str :
  print('変数aは、文字列型(str)です')
else:
  print('変数aは、文字列型(str)ではない')

5.1.1 演習②-1

上記プログラムの第02行目の a=10 を次のように書き換え、その実行結果を確認せよ。

5.2 型が違うと何が変わるのか?

型によって、その変数 (オブジェクト) から 呼び出すことができるメソッドが違ってきます。例えば、変数 a文字列型(str) であれば a.upper() のようなメソッドを呼び出せますが、それ以外の 整数型 (int) や 浮動小数点数型 (float) では (自分で確認しましょう🫠) が発生します。

以下のコードを実行して結果を確認してください。その後、a=10 に書き換えて (つまり、変数 a を文字列型から整数型に変えて) 、その実行結果を確認してください。

%reset -f
a='windows'
print( a.upper() )

このプログラムは、次のように改良することができます。実行して結果を確認してください。その後、a=10 に書き換えて、その実行結果を確認してください。

%reset -f
a='windows'
if type(a) is str :   # 変数aの型が文字列ならば
  print( a.upper() )
else :
  print('変数aは文字列型ではないので upper メソッドを持っていません。')

5.2.1 演習②-2

以下のプログラムに追記して、変数 a が文字列型であれば文字列を3回出力する処理 (例えば Wow! が格納されていれば Wow!Wow!Wow! を出力)、整数型または浮動小数点数型であれば5倍した値を出力する処理、リスト型であればそのまま出力、それ以外の型 (例えば 真偽値型(bool) ) であれば Error! を出力する処理にせよ。

%reset -f
a='Wow!'  # この変数aには様々な値が入ることを想定しています。

# 型に応じて処理を変えるプログラムを追記

6 関数 (Fuction) 初級

プログラムにおいて「ある処理を行なうための一連の命令をまとめたもの」を 関数 (Function) と呼びます。関数を利用することで、そのプログラムは読みやすくなり、また、修正や変更も容易になります

これまでに print()input() などを何度も使用してきましたが、これらはPythonが最初から提供している 組み込み関数 というものになります。また、math.sin()math.radians() は、特定のライブラリをインポートすることで使用可能になる関数です。

さらに、Pythonをはじめとして各種プログラムでは、プログラマ自身で関数を定義して、それを呼び出して使用することができます

例えば「税抜き価格を引数として与えると、税込み価格 (端数切捨て) を計算して返す関数」は、次のように記述できます。実際に実行して、その結果を確認してください。ここでは食料品を対象に「税率8%」を考えています。

%reset -f

# 自作関数 add_tax_to_price を定義
def add_tax_to_price(price):
  x = price * 1.08
  x = int(x)
  return x # x を戻り値として呼び出し側に返す

p1 = 73
p2 = add_tax_to_price(p1)
print(f'ガリガリ君 {p1}円(税込み{p2}円)') 

p1 = 12
p2 = add_tax_to_price(p1)
print(f'うまい棒 {p1}円(税込み{p2}円)') 

ここで defDefine (日本語で「定義」の意味) の略語になります。

6.1 演習③-1

上記のプログラムについて

%reset -f
# 関数呼び出しを先に記述する
p1 = 73
print(f'ガリガリ君 {p1}円(税込み{add_tax_to_price(p1)}円)') 

# 関数定義をあとに記述する
def add_tax_to_price(price):
  return int(price * 1.08)

6.2 演習③-2

引数として、0 から 5 までの 整数値 を与えると、それに対応して「☆☆☆☆☆」「★☆☆☆☆」・・・「★★★★☆」「★★★★★」という文字列を返してくる関数 star を定義せよ。また、その動作確認をせよ。

なお、引数として0未満の整数値が渡されたときは「☆☆☆☆☆」、6以上の整数値が渡されたときは「★★★★★」を戻り値とすること。また、整数値以外の引数を受け取ったときは「ERROR」という文字列を戻り値とすること。

%reset -f

# 目的の処理をする関数に書き換える
def star(n):
  return 'AAAAA'

# 
print(star(4))     # => ★★★★☆
print(star(-4))    # => ☆☆☆☆☆
print(star(10))    # => ★★★★★

print(star('5'))   # => ERROR
print(star(['5'])) # => ERROR
print(star('1.5')) # => ERROR

6.3 複数の引数を持つ関数

関数では、複数の引数を受け取ることもできます。例えば、次に示すものは 2点の座標 x1, y1, x2, y2 を与えたとき、その距離を計算する関数の記述例です。実際に実行して、その結果を確認してください。

%reset -f

# 距離を計算する関数
def get_distance(x1,y1,x2,y2):
  return ( (x2-x1)**2 + (y2-y1)**2 )**0.5

p1_x, p1_y = 10, 10
p2_x, p2_y = 15, 25
d = get_distance( p1_x, p1_y, p2_x, p2_y )
print(f'距離は {d:.1f}')

7 アサート文

アサート (assert) は主にデバッグやエラーチェックを目的に使用する機能です。例えば、次のように assert 条件文 という形式で使用します。

%reset -f
arr = [0,1,0,1,0,1,0] # 長さ7のリストを初期化
arr.pop()             # リスト末尾の要素を取り除く

# arr の長さが 6 であることを「念のために確認したい」
assert len(arr) == 6

# (以降、継続処理)
print('後続処理')

assert は、それに続く条件式が 真 (True) であることを「確認」する目的として使用します。

もし、条件式が 偽(False) となる場合、プログラムはエラーメッセージを生成して強制停止します。一方で、真 (True) となる場合は何も出力せずに処理を継続します。

上記プログラムの場合、リスト arrpop() メソッドによって「長さ 6 になっていること」を念のために確認するために第06行目に assert 文を書いています。

ここで、第02行目を arr=[0,1,0,1,0,1,0,1] # 長さ7のリストを初期化 に書き換えて再度実行してみてください。今度は (自分で確認しましょう🫠) が発生します。

つまり、assertは、プログラマに対してプログラムが意図せぬ状態 / 想定していない状態なっていること を通知してくれる機能を提供します。

このようなことは、ifprint、さらにプログラムを停止する sys.exit() を組み合わせても可能ですが、 assert と比較すると手間になります。また、Jupyter環境 (Colab.環境) では sys.exit() は意図した動作をしません。

assertはデバッグ用途

assert は、主にデバッグのために使用する機能であることを忘れないでください。製品レベルのエラー処理には、後の講義で学習する 例外処理print による文字列出力を組み合わせて使用します。

例外処理では、予期しないエラーが発生した場合でも、プログラムの処理を継続させることが可能となる仕組みを提供します。

7.1 演習④

変数 aリスト型であることを「確認」するためのアサート文を指定の位置に記述せよ。また、その動作確認をせよ (a=10a='10' に書き換えて assert が機能するか確認せよ) 。

%reset -f
a = [10,20]

# 以下に変数aがリスト型であることを確認するアサート文を記述


print(f'リストの先頭要素は {a[0]} です。')

8 SQLiteとの連携

情報2の授業のなかで RDB や SQL について触れたので「Python と SQLite が簡単に連携できること」を紹介しておきます。詳しいことは、今後の授業のなかで触れていきます。

Pythonには、SQLite3データベースに接続するための sqlite3 という標準ライブラリが備えられています。

例えば、次のようなクエリに相当する処理は、

-- テーブル「社員表」が存在しているなら削除
DROP TABLE IF EXISTS 社員表;

-- ①フィールド「社員番号」「名前」「部署」「住所」を持ったテーブル「社員表」を作成
CREATE TABLE 社員表(社員番号,名前,部署,住所);

-- ②テーブル「社員表」にレコードを挿入
INSERT INTO 社員表 VALUES(2009001,'田中一郎','営業部','神奈川県伊勢原市');
INSERT INTO 社員表 VALUES(2009002,'山本二郎','開発部','神奈川県横浜市');
INSERT INTO 社員表 VALUES(2009003,'佐藤三郎','開発部','神奈川県横浜市');
INSERT INTO 社員表 VALUES(2009004,'ウチのシロ','帰宅部','神奈川県犬山市');

-- ③テーブル「社員表」から全項目(*)を選択して表示
SELECT * FROM 社員表;

以下のPythonプログラムによって記述することができます。実際に実行して確認してみてください。

%reset -f
import sqlite3

# DBとの接続の開始
conn = sqlite3.connect('test.db')
cur = conn.cursor() # DBのカーソル(Cursor)取得

# DBの作成
cur.execute("DROP TABLE IF EXISTS 社員表")
cur.execute("CREATE TABLE 社員表(社員番号,名前,部署,住所)")
cur.execute("INSERT INTO 社員表 VALUES(2009001,'田中一郎','営業部','神奈川県伊勢原市')")
cur.execute("INSERT INTO 社員表 VALUES(2009002,'山本二郎','開発部','神奈川県横浜市')")
cur.execute("INSERT INTO 社員表 VALUES(2009003,'佐藤三郎','開発部','神奈川県横浜市')")
cur.execute("INSERT INTO 社員表 VALUES(2009004,'ウチのシロ','帰宅部','神奈川県犬山市')")
conn.commit() # 処理を実行

# データの更新
# cur.execute("UPDATE 社員表 SET 名前='ウチのミケ', 住所='神奈川県猫山市' WHERE 社員番号=2009004")
# conn.commit()

cur.execute('SELECT * FROM 社員表')

table = list(cur)
for record in table:
  print(*record,sep='|') # タプル(=リストのようなもの) のアンパック

# print(table[0][1]) # => 田中一郎
# print(table[3][2]) # => 帰宅部

# DBとの接続を切る
conn.close()

このプログラムでは、DBの新規作成を行なっていますが、通常は、データベースの新規作成などは他のソフトウェアでおこないます。プログラムでは、既存のデータベースに接続して必要なデータを読み込んだり、データの更新処理を行ないます。

9 斜方投射のシミュレーションと可視化

ここまで学習してきたことを組み合わせて「斜方投射」の簡易シミュレーション可視化に挑戦してみましょう。このセクションでは Jupyter環境(Colab.環境)を使用することを想定 します。

img

9.1 確認

9.2 基本プログラム

前回の宿題として、以下のプログラムについては 既に60分を費やして読み込んでいることを前提 とします。

このプログラムでは、斜方投射したときの物体の位置 (X座標とY座標) 0.1秒間隔 で計算して、それをリスト arr_xarr_y に格納し、最後にそれを可視化 (グラフ化) しています。

シミュレーションの時間範囲は、第11行目と第12行のコード iter=80dt=0.1 から計算できるように、時刻 \(0(\mathrm{s})\) から時刻 \(8.0(\mathrm{s})\)(厳密に言えば\(7.9(\mathrm{s})\) となります。

実際にコードを実行して、その結果を確認してください。

%reset -f
import math
import matplotlib.pyplot as plt # 可視化支援ライブラリ

# パラメータ設定
v0 = 20.0    # 初速度(m/s)
theta = 60   # 水平方向から上方にθ(deg)に向けて投射
g = 9.8      # 重力加速度m/s^2

# シミュレーションの準備・設定
dt = 0.1  # 位置を計算する時間間隔
iter = 80 # シミュレーション繰返し回数

theta = math.radians(theta) # deg -> rad 変換

arr_x=[0]*iter # X位置を格納するリストの初期化
arr_y=[0]*iter # Y位置 〃

assert len(arr_x) == len(arr_y)

arr_x[0] = x = 0  # t=0 のX位置
arr_y[0] = y = 0  #  〃 のY位置

vx = v0 * math.cos(theta) # 初速度のX成分
vy = v0 * math.sin(theta) #  〃 のY成分

# シミュレーション
for i in range(1,iter):
  vy = vy - g * dt # Y方向の速度更新
  x = x + vx * dt  # X位置の更新計算
  y = y + vy * dt  # Y位置 〃

  # 地面衝突の反発 (バウンド) の処理
  if y < 0:
    y = 0
    vy = -vy * 0.8  # 反発係数

  arr_x[i] = x # 更新されたX位置をリストに格納
  arr_y[i] = y #   〃    Y位置を  〃

# 可視化(現状でここから先は深く理解する必要はない)
fig,ax = plt.subplots(dpi=120)
ax.scatter(arr_x,arr_y,marker='o',s=20,alpha=0.5)
ax.axhline(0,c='black',lw=0.5)
ax.set_aspect('equal', adjustable='box')
ax.set_ylim(-1,30) # Y軸の表示範囲
plt.show()

9.3 アニメーション

第43行目以降がアニメーション対応にするための変更箇所になります。GIFアニメ画像としても出力可能です、興味がある学生は声をかけてください。

%reset -f
import math
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import IPython

# パラメータ設定
v0 = 20.0    # 初速度(m/s)
theta = 60   # 水平方向から上方にθ(deg)に向けて投射
g = 9.8      # 重力加速度m/s^2

# シミュレーションの準備・設定
dt = 0.1  # 位置を計算する時間間隔
iter = 80 # シミュレーション繰返し回数

theta = math.radians(theta) # deg -> rad 変換

arr_x=[0]*iter # X位置を格納するリストの初期化
arr_y=[0]*iter # Y位置 〃

assert len(arr_x) == len(arr_y)

arr_x[0] = x = 0  # t=0 のX位置
arr_y[0] = y = 0  #  〃 のY位置

vx = v0 * math.cos(theta) # 初速度のX成分
vy = v0 * math.sin(theta) #  〃 のY成分

# シミュレーション
for i in range(1,iter):
  vy = vy - g * dt # Y方向の速度更新
  x = x + vx * dt  # X位置の更新計算
  y = y + vy * dt  # Y位置 〃

  # 地面衝突の反発 (バウンド) の処理
  if y < 0:
    y = 0
    vy = -vy * 0.8  # 反発係数

  arr_x[i] = x # 更新されたX位置をリストに格納
  arr_y[i] = y #   〃    Y位置を  〃

# アニメーション
fig,ax = plt.subplots(dpi=120)
ss = 5
def frame(i):
  ax.clear()
  ax.set_aspect('equal', adjustable='box')
  ax.set_ylim(-1,30)
  ax.set_xlim(0, round(max(arr_x),-1)+5)
  ax.axhline(0,c='black',lw=0.5)
  ax.text(0.01,0.98,f't={i*dt:.2f}',va='top',transform=ax.transAxes)
  for t in range(ss):
    ax.scatter(arr_x[i+t],arr_y[i+t],
               marker='o',s=20,c='tab:blue',alpha=t/(ss+1))

ani = animation.FuncAnimation(fig, frame, interval=100,frames=iter-ss)
plt.close()

IPython.display.HTML(ani.to_jshtml())