🎯 この章で学ぶこと

  • リスト:順番のあるデータの列
  • 辞書:キーと値のペア
  • タプルと集合:変更できない列と、重複のない集まり
  • 内包表記によるスマートなデータ加工

4.1 リスト(list)— 順番のあるデータの列

リストは複数の値を順番に並べて持つ入れ物です。角かっこ [ ] で作ります。

users = ["tanaka", "suzuki", "sato"]

print(users[0])       # tanaka(先頭は0番)
print(users[-1])      # sato(マイナスは後ろから)
print(len(users))     # 3(要素数)

users.append("yamada")   # 末尾に追加
users.remove("suzuki")   # 値を指定して削除
print(users)             # ['tanaka', 'sato', 'yamada']

スライス — 範囲の切り出し

>>> nums = [10, 20, 30, 40, 50]
>>> nums[1:3]      # 1番から3番の手前まで
[20, 30]
>>> nums[:2]       # 先頭から2番の手前まで
[10, 20]
>>> nums[2:]       # 2番から最後まで
[30, 40, 50]

並べ替えと集計

response_times = [120, 85, 340, 95, 2100]  # ミリ秒

print(max(response_times))     # 2100 — 最も遅い応答
print(min(response_times))     # 85
print(sum(response_times))     # 2740
print(sorted(response_times))  # [85, 95, 120, 340, 2100]

4.2 辞書(dict)— キーと値のペア

辞書は「名前(キー)で値を引ける」入れ物です。波かっこ { } で作ります。実務データのほとんどは辞書の形をしています。

account = {
    "username": "tanaka",
    "department": "情報システム部",
    "is_admin": False,
    "last_login": "2026-07-07",
}

print(account["username"])        # キーで値を取り出す
account["is_admin"] = True        # 値の変更
account["mfa_enabled"] = True     # 新しいキーの追加

# キーが無いかもしれない時は get() が安全(無ければNoneや既定値)
print(account.get("phone", "未登録"))   # 未登録

辞書のループでは items() でキーと値を同時に取り出せます。

for key, value in account.items():
    print(f"{key}: {value}")

実践 — ユーザーごとの失敗回数を数える

「キーが存在するかどうか」で処理を分けるのは辞書の定番パターンです。

failed_users = ["admin", "root", "admin", "tanaka", "admin"]

counts = {}
for user in failed_users:
    if user in counts:
        counts[user] = counts[user] + 1
    else:
        counts[user] = 1

print(counts)   # {'admin': 3, 'root': 1, 'tanaka': 1}

この「出現回数のカウント」は、ログ解析・不正検知の最重要パターンです。第13章で本格的に活用します。

4.3 タプル(tuple)— 変更できないリスト

タプルは丸かっこ ( ) で作る、後から変更できない並びです。

>>> server = ("192.168.1.10", 443)
>>> server[0]
'192.168.1.10'
>>> server[0] = "10.0.0.1"
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment
✅ 「変更できない」ことは利点

変更されては困るデータ(設定値、座標、IPとポートの組など)をタプルにしておけば、うっかり書き換えるバグをPython自身が防いでくれます。「変更の必要がないものは変更できなくしておく」──これはセキュリティの最小権限の原則と同じ発想です。

4.4 集合(set)— 重複のない集まり

集合は重複を許さない入れ物で、「重複除去」と「比較(差分)」が得意です。

# アクセス元IPの重複を除いて種類数を数える
ips = ["10.0.0.5", "10.0.0.9", "10.0.0.5", "10.0.0.7", "10.0.0.5"]
unique_ips = set(ips)
print(unique_ips)        # {'10.0.0.5', '10.0.0.9', '10.0.0.7'}
print(len(unique_ips))   # 3

# 集合同士の演算
allowed = {"tanaka", "suzuki", "sato"}      # 許可リスト
actual  = {"tanaka", "suzuki", "yamada"}    # 実際にアクセスした人

print(actual - allowed)   # {'yamada'} — 許可されていないのにアクセスした人!
print(allowed - actual)   # {'sato'} — 許可されているが使っていない人
print(allowed & actual)   # {'tanaka', 'suzuki'} — 両方に含まれる人
🛡️ セキュリティの視点 — アカウント棚卸しは集合演算

「人事台帳にいる社員の集合」と「システムにアカウントがある人の集合」の差分を取れば、退職済みなのに残っているアカウント(=攻撃者に悪用されやすい放置アカウント)が一瞬で見つかります。IT部門の定例業務である棚卸し作業は、まさに今日学んだ set の引き算です。Excelで目視確認していた作業が3行のPythonになります。

4.5 内包表記 — ループを1行で書く

「リストから条件に合うものを選んで新しいリストを作る」処理は頻出なので、Pythonには専用の短い書き方があります。

logs = [
    "LOGIN OK user=tanaka",
    "LOGIN FAILED user=admin",
    "LOGIN FAILED user=root",
]

# 通常の書き方
failed = []
for line in logs:
    if "FAILED" in line:
        failed.append(line)

# 内包表記なら1行
failed = [line for line in logs if "FAILED" in line]
print(failed)

読み方は「logs の各 line のうち、FAILED を含むものを集めたリスト」。最初は通常のfor文で書き、慣れてきたら内包表記に書き換える、で構いません。

4.6 どの入れ物を使うべきか

入れ物特徴実務での典型例
list順序あり・変更可・重複可ログの行、処理対象ファイルの一覧
dictキー→値で引けるユーザー情報、設定、集計結果
tuple順序あり・変更不可(IP, ポート) の組、固定の設定値
set重複なし・集合演算ユニークなIPの数、許可リストとの差分

まとめ

練習問題

問題 4-1

リスト ports = [22, 80, 443, 3389, 8080] から、1024未満のポート(ウェルノウンポート)だけを内包表記で取り出してください。

解答を見る
ports = [22, 80, 443, 3389, 8080]
well_known = [p for p in ports if p < 1024]
print(well_known)   # [22, 80, 443]
問題 4-2

人事台帳 employees = {"tanaka", "suzuki", "sato"}、システムのアカウント accounts = {"tanaka", "suzuki", "sato", "yamada", "test_user"} があります。「台帳に存在しないアカウント」を集合演算で求めてください。

解答を見る
employees = {"tanaka", "suzuki", "sato"}
accounts = {"tanaka", "suzuki", "sato", "yamada", "test_user"}

orphans = accounts - employees
print(orphans)   # {'yamada', 'test_user'}

test_user のような「検証用に作って消し忘れたアカウント」は実際のインシデントでも侵入口としてよく悪用されます。

問題 4-3

次のIPアドレスのリストから、辞書を使って「IPごとの出現回数」を数え、最も多く出現したIPを表示してください。
ips = ["10.0.0.5", "10.0.0.9", "10.0.0.5", "10.0.0.7", "10.0.0.5", "10.0.0.9"]

解答を見る
ips = ["10.0.0.5", "10.0.0.9", "10.0.0.5", "10.0.0.7", "10.0.0.5", "10.0.0.9"]

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

print(counts)                    # {'10.0.0.5': 3, '10.0.0.9': 2, '10.0.0.7': 1}
top_ip = max(counts, key=counts.get)
print(f"最多アクセス元: {top_ip} ({counts[top_ip]} 回)")

counts.get(ip, 0) + 1 は「無ければ0、あれば今の値に1を足す」という定番イディオムです。同一IPからの異常な回数のアクセスは攻撃の兆候かもしれません。