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

2025年07月10日(木)5・6時限

1 準備・案内

今回の演習の実装例 (解答例) は こちら を参照してください。

1.1 イベントの案内

いずれのイベントも、申し込めば誰でも参加できるものです。

しかし、これらの企業に「サマーインターンシップ」や「就職」で関わろうとする場合は、倍率の高い選考を突破しなければなりません。ワークショップやハッカソンなどのイベントを通じて、現役エンジニアとつながり、顔や名前を覚えてもらうことは、今後の進路を切り拓くうえで大きな強みになります。

一方で、こうした機会を逃し続けてしまうと、いざ挑戦したいと思ったときに、実績も準備もなく大きく出遅れてしまいます。今のうちから積極的に参加することを強く勧めます。

1.2 次回、小テストを実施します。

2 課題05 (自由課題)

現在までの Pythonプログラミング学習 (約4カ月) の集大成 として GoogleColab で実行可能なプログラム (Pythonを主体とするもの) を作成して提出してください。いわゆる 自由課題 となります。

注意

「課題05」の直接的な評価は「10点満点」ですが、科目として成績評価するときには課題01~04よりも ウエイト (重み) は大きくします。特に、ここまでの課題に未提出があったような学生は 挽回のチャンス なので、提出に不備が無いように細心の注意を払い、十分な時間と熱量を費やして計画的に取り組んでください。

2.1 課題05の提出ファイルの作成手順 (🚨必読・重要🚨)

img

2.2 注意事項

現在までに自分で作成してきたアプリやウェブサービスなどの成果物について記述してください。公開しているURLあれば記載してください。

GitHub等でソースコードを公開していたらそのURIを教えてください。

ポートフォリオとしての質を考えた場合、コメントの有無や内容、可読性、保守性、再利用性などもポイントとなります。プログラミングの経験のある学生は、その点も意識してください。

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

今後、授業のなかで練習時間は確保しませんが、以下の手順で Pythonの仮想環境の新規構築がスムーズにできる ようになっておいてください。指示があったときは、以下の作業が (ライブラリのダウンロードとインストールの待ち時間を除いて) 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. プログラムの実行と動作確認
    • 通常プログラムなら .vscode/lanch.json の作成(Python Debuggerの設定)して、F5 押下でコンソールに実行結果が表示されることを確認する (第09回講義資料を参照)。
    • ノートブックプログラムなら、ランタイムの設定をして、出力セルに実行結果が表示されることを確認する (第09回講義資料)。
    • ライブラリを import するための文を書いたときに ModuleNotFoundError が発生しないこと の確認

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

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

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

また、プログラムのなかでインポートしているライブラリ (モジュール) と 同じ名前のPythonファイル名をつけないでください

例えば、プログラムのなかに import random という文を含む場合、そのプログラムファイルに random.py という名前を付けたり、同フォルダに random.py というファイルが存在すると 実行時エラー が発生します。

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

注意

.venv は仮想環境を管理用するためのフォルダです。この .venv フォルダ内に test01.pytest02.ipynb など、自分で作成したプログラムファイルを配置しないでください。

▼▼ NGな配置例 ▼▼

📂 PG1-10
└─ 📂 .venv
    ├─ 📂 ... 
    ├─ 📂 ...
    ├─ 📝 test01.py     👈.venvフォルダ内部に配置 NG
    └─ 📝 test02.ipynb  👈.venvフォルダ内部に配置 NG

▼▼ OKな配置例 ▼▼

📂 PG1-10
├─ 📂 .venv
│   ├─ 📂 ... 
│   └─ 📂 ...
├─ 📝 test01.py
└─ 📝 test02.ipynb

4.1 演習1 ( 目標時間: 10分)

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

.venv/Scripts/Activate.ps1
pip install numpy

既に、仮想環境に numpy がインストール済みであれば pip install numpy は省略可能です (再度、実行しても問題はありません)。

仮想環境の明示的な有効化

VSCodeは起動時に、.venv フォルダの存在を検知して、自動的に仮想環境を有効化 します。ただし、タイミングの問題などで、仮想環境が自動的に有効化されない場合もあります。その状態で、pip を実行すると、ライブラリが (仮想環境ではなく) グローバル環境 (共通環境) にインストールされてしまいます。

グローバル環境にライブラリがインストールされると、ライブラリ同士の依存関係の問題で根深いバグや不具合を引き起こす可能性があります。特に複数のライブラリを組み合わせて大規模な開発をするときに起きます。

そのため、上記では .venv/Scripts/Activate.ps1 のコマンドで 明示的に仮想環境を有効化 しています。

なお、現在、有効になっているPython環境は、PowerShellで以下のコマンドを打って確認できます。表示されたパスに、プロジェクトフォルダの .venv が含まれていれば仮想環境が有効化されています。

Get-Command python | Select-Object -ExpandProperty Definition

なお、そもそも「仮想環境とは何なのか?」が分からない人は、以下のプロンプトで生成AIに質問してください。

Pythonプログラム開発では、仮想環境を構築して、それを有効化して作業するように言われます。そもそも「仮想環境」とは何ですか。それを使うことによるメリットはなんですか。使わないと何が問題なのですか。プログラミング初学者の高校2年生が理解できるように分かりやすく説明してください。専門用語の使用はできるだけ避けて、分かりやすいたとえで説明してください。

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

なお、np.arange については 前回講義 で既に解説済みです。

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を使って解決してください。これは、適切な検索キーワードやプロンプトを構築する練習です

🚨 ここから先は GoogleColab または Jupyter を利用してください 🚨

5 型 (Type)

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

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

また、一方で a='ABC' という文を書けば a自動的に「文字列型 (str)」 となります。

(プロンプト例)

プログラミングの文脈で「動的型付け」とはなんですか?私は Arduino (C言語) も、Pythonも、それぞれ約15時間ぐらい学んでいます。そんな私に分かるように解説してください。

型付けの種類

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

(プロンプト例)

プログラミング言語の型システムには「静的型付け」「強い動的型付け」「弱い動的型付け」「型なし」などが存在すると聞きました。それぞれの違いを教えてください。

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

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

(プロンプト例)

Pythonの「組み込み関数」って何ですか?以前に授業で習った気がするのですが、忘れてしまいました。

具体的には、is 演算子と type() の組み合わせで 変数が特定の型であるかどうか を判定できます。ここでは print 関数と組み合わせていますが、実務的なプログラムでは、if文の「条件」としてよく使用されます

例えば「変数 astr型 であるか?」という判定は次のようにできます。

%reset -f
a = 'Hello'
print( type(a) is str ) # True
print( type(a) is int ) # False 

同様に、isinstance() という組み込み関数を利用することもできます。

%reset -f
a = 'Hello'
print( isinstance(a, str) ) # True
print( isinstance(a, int) ) # False 

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

%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 演習2 ( 目標時間: 8分)

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

中級者向け「isinstance」と「istype」による判定の違い

演習2において 第02行目a=True としたときに予想外の結果がでたと思います。そのため、最初のうちは、is と type の組み合わせを利用することをお勧めしします。

%reset -f
a = True

# bool型であるか?
print( type(a) is bool)      # True
print( isinstance(a, bool) ) # True

# int型であるか?
print( type(a) is int )     # False
print( isinstance(a, int) ) # True ?!

両者の違いを理解するためには、中級レベルの理解 (「継承」などのオブジェクト指向プログラミングの基礎理解) が必要です。現時点では、皆さんの理解の範疇を超えると思うので、授業では扱いません。詳しく知りたい人は、生成AIを利用してください。プロンプトの例を示します。

(プロンプト例)

Pythonで「a=True」のとき、「type(a) is int」はFalseになるのに、「isinstance(a, int)」はTrueになるのはなぜですか?

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

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

「メソッド」とは何か

メソッドとは、オブジェクト指向プログラミングの用語になります。現時点では「変数名のあとにドット . を付けて呼びだす関数」ぐらいに考えてください。

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

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

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

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

5.2.1 演習3 ( 目標時間: 10分)

以下のプログラムに追記して、変数 a

にしてください。

ここでは、elif (既に第06回講義で学習済み) を使用することを期待しています。また、if の条件文には、AND条件 and (学習済み)、OR条件 or (学習済み) を使うことができました。例えば if a%3==0 and a%5==0a が「3の倍数」かつ「5の倍数」のとき真になります。

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

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

6 関数 (Function) 初級

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

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

さらに、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 演習4 ( 目標時間: 15分)

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

%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 演習5 ( 目標時間: 10分)

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

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

%reset -f

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

# 
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 条件文 という形式で使用します。ちなみに、第03行目.pop() は、既に第10回講義で学習済みです。

%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() は意図した動作をしません。

AssertionErrorの発生時に任意のメッセージを表示

assert 文で、次のようにカンマにつづけてメッセージ (文字列) を設定しておくと AssertionError が発生したときに、その文字列を出力することができます。

# カンマにつづけて任意の文字列を設定
assert len(arr) == 6, 'arr の長さが 6 以外になってる!どこかおかしい'

AssertionErrorが発生したときの画面出力

img

assertはデバッグ用途

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

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

7.1 演習6 ( 目標時間: 3分)

変数 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 という標準ライブラリが備えられています。

例えば、次のようなクエリに相当する処理 (情報2の第11回講義資料の p.201 を参照) は、、、

-- テーブル「社員表」が存在しているなら削除
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を出力するためには pip install pillow が必要です。

img
%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 = 120 # シミュレーション繰返し回数

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,figsize=(8,3))
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))

plt.tight_layout()
ani = animation.FuncAnimation(fig, frame, interval=100,frames=iter-ss)
ani.save('animation.gif', writer='pillow', fps=10) # アニメーションGIFを出力
plt.close()

IPython.display.HTML(ani.to_jshtml()) # 出力セルにJavaScriptアニメーションを出力