Python 3のunittestを使い始めるためのメモ

Pythonunittestをちゃんと使ったことがなかったので、使い方について調べてまとめました。Windows 10のAnaconda Python 3.8.5で動作確認しています。

もっとも小さな例

被テストコードとテストコードとを同一のスクリプトに記述した小さな例を以下に示します。実際はこんなことはほぼないと思いますが。

import unittest


def add(x, y):
    return x + y


class AddTest(unittest.TestCase):

    def test_add(self):
        self.assertEqual(add(1, 2), 3)


def main():
    unittest.main()


if __name__ == '__main__':
    main()

ここで、被テストコードが関数add()、テストコードがAddTestです。

テストコードは以下の作法にのっとって作成される必要があります。

  • unittest.TestCaseクラスを継承したサブクラスを作成(上記のAddTestクラス)
  • サブクラスの中に、testで始まるメソッドを作成
    • メソッド名がtestで始まらないと実行されないので注意
  • testで始まるメソッドの中で、self.assertEqual()self.assertTrue()などを呼び出してテストを記述

このスクリプトmain.pyとして保存しpython main.pyと実行すると、テストが無事走ります。

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

Pythonパッケージに対するテスト

もう少しリアルな例として、あるPythonパッケージに対してテストを行う例を考えます。

ディレクトリ構造

以下のディレクトリ構造でコードが配置されているものとします。

root_dir/
    ├─comp_prog_lib/
    │  │  __init__.py
    │  │
    │  └─math/
    │          prime.py
    │          __init__.py
    │
    └─tests/
        │  __init__.py
        │
        └─math_tests/
                test_prime.py
                __init__.py

root_dirは、例えばGitのリポジトリに相当するような上位ディレクトリであるものとします。

comp_prog_libは、被テスト対象であるPythonパッケージです。ここでは競技プログラミングのためのライブラリという意味でcomp_prog_libと名付けています。comp_prog_libの下には、競技プログラミングのためのモジュールがある階層構造とともに配置されているものとします。

testsは、comp_prog_libをテストするためのコードです。テストコードは普通ライブラリとは分離して配置したくなるはずなので、comp_prog_libと並べて配置します。

prime.pyのコードを以下に示します。整数n素数かどうかを判定する関数が実装されています。

"""
素数に関する関数を提供
"""


def is_prime(n: int):
    """nが素数か否かを判定"""
    if n <= 1:
        return False
    i = 2
    while i * i <= n:
        if n % i == 0:
            return False
        i += 1
    return True

test_prime.pyのコードを以下に示します。整数1から16までの素数判定が正しく行えているかをチェックするテストが実装されています。

"""
prime.pyのテストスクリプト
"""
import unittest

from comp_prog_lib.math.prime import is_prime


class IsPrimeTest(unittest.TestCase):

    def test_is_prime(self):
        primes = set([2, 3, 5, 7, 11, 13])
        for i in range(1, 17):
            if i in primes:
                self.assertTrue(is_prime(i))
            else:
                self.assertFalse(is_prime(i))

__init__.pyはすべてパッケージを構成するための特殊ファイルで、中身は空です。

Test Discoveryによるテスト実行

テストを実行するには、root_dir/cdした状態で、

$ python -m unittest discover tests

と実行します。すると、testsディレクトリ以下に存在する、ファイル名がtestで始まるテストスクリプトが自動で収集され実行されます。これはTest Discoveryと呼ばれる仕組みです。実行結果は以下です。

.
----------------------------------------------------------------------
Ran 1 test in 0.000s

OK

なお、tests/以下のディレクトリ構造には注意が必要そうです。最初、上記でmath_tests/と示していたディレクトリ名をmath/にしていたのですが、この状態でテスト実行コマンドを実行すると、

E
======================================================================
ERROR: math.test_prime (unittest.loader._FailedTest)
----------------------------------------------------------------------
ImportError: Failed to import test module: math.test_prime
Traceback (most recent call last):
  File "(環境名)\lib\unittest\loader.py", line 436, in _find_test_path
    module = self._get_module_from_name(name)
  File "(環境名)\lib\unittest\loader.py", line 377, in _get_module_from_name
    __import__(name)
ModuleNotFoundError: No module named 'math.test_prime'; 'math' is not a package


----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (errors=1)

というかなりわかりにくいエラーが出て失敗しました。tests/mathcomp_prog_lib/mathと名前衝突していたのがよくなかったようで、tests/mathtests/math_testsと名前を変えることでエラーが消えました(参考)。正直、理屈はよくわかっていません。

ディレクトリ構造についての補足

comp_prog_lib/パッケージのテストがうまくいった理由について説明します。Pythonは、カレントディレクトリが暗黙的にsys.pathに加えられる仕様を持ちます。なのでroot_dir/cdした状態では、comp_prog_libがimport可能な状態になります。なのでtest_prime.pyfrom comp_prog_lib.math.prime import is_primeというimport文が正しく動きます。

これは場合によっては害になることもありえます。例えば、comp_prog_libに相当する部分をpip installなどでインストール可能なように設定していた場合、ローカルのcomp_prog_libをテストしているのかインストール済のcomp_prog_libをテストしているのかが不明瞭になります。この問題を避けるため、Pytestのドキュメント では、comp_prog_libsrcディレクトリの下に移動させるベストプラクティスを推奨しています。これはunittestでも通じる話だと思います。

参考URL

matplotlibでグラフを保存するときのテンプレート

サーバにてmatplotlibを使ってグラフを作成し、ファイルに保存することがよくあるのですが、実装するたびにググりまくって非効率なので、私が高確率で使う機能をテンプレ化しました。

さっそくコードを以下に示します。

import matplotlib
import matplotlib.pyplot as plt
import numpy as np


# ウィンドウを開けない環境でもグラフを作成する
# https://stackoverflow.com/questions/39305810/matplotlib-use-required-before-other-imports-clashes-with-pep8-ignore-or-fix
matplotlib.pyplot.switch_backend('Agg')


def plot_test():
    # プロットしたいデータ系列を2つ作成
    x1 = np.linspace(0, 3, 50)
    y1 = np.sin(x1)

    x2 = np.linspace(0, 4, 70)
    y2 = np.cos(x2)

    # 描画先を作成
    # figsize=(8, 6)により、画像保存時のサイズが800x600になる
    # (figsizeを指定しないとデフォルトで640x480)
    fig, ax = plt.subplots(figsize=(8, 6))

    # 折れ線グラフを2系列プロット
    ax.plot(x1, y1, label='sin curve', marker='o', markersize=3)
    ax.plot(x2, y2, label='cos curve', marker='o', markersize=3)

    # グラフタイトル、X軸タイトル、Y軸タイトルを設定
    ax.set_title('sin and cos curves')
    ax.set_xlabel('x axis')
    ax.set_ylabel('y axis')

    # グリッド線を表示
    ax.grid(axis='both')

    # 凡例を表示
    ax.legend()

    # X軸、Y軸の表示範囲を手動で調整(オプション)
    ax.set_xlim(-0.1, 4.1)
    ax.set_ylim(-1.1, 1.1)

    # グラフの周囲の余計な空白を除去
    fig.tight_layout()

    # ファイルに保存
    fig.savefig('plot.png') 

    # 後始末
    # これがないと、グラフを大量に作成したとき
    # "RuntimeWarning: More than 20 figures have been opened."というメッセージが出る
    # https://stackoverflow.com/questions/45933886/python-plt-close-or-clear-figure-does-not-work
    plt.close(fig)


if __name__ == '__main__':
    plot_test()
        

出力されるplot.pngは以下です。

f:id:minus9d:20210302231718p:plain

大体の説明はコード中に書いてしまいました。以下は補足です。

matplotlib.pyplot.switch_backend('Agg')

上の1行は、サーバのようにウィンドウを開けないような環境でmatplotlibを使うときの定型文です(意味はよくわかってません)。以前は

import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt

という定型文を書いていたのですが、 python - matplotlib.use required before other imports clashes with pep8. Ignore or fix? - Stack Overflow によると、この書き方でよいようです(注:まだ試せていません)。

参考URL

std::vectorのresizeとassignの違い (C++)

恥ずかしながらC++vectorassign(n, val)という関数を知らなかったのでメモです。

よく似た関数に、resize(n, val)という関数があります。以下のコードで挙動を比較してみます。

#include <iostream>
#include <vector>

void print_vector(const std::vector<int> &vec) {
    for (auto e: vec) {
        std::cout << e << ", ";
    }
    std::cout << std::endl;
}

int main(){
    std::vector<int> vec1{1, 2, 3};
    vec1.resize(10, 777);
    print_vector(vec1);

    std::vector<int> vec2{1, 2, 3};
    vec2.assign(10, 777);
    print_vector(vec2);
    
    return 0;
}

結果は以下です。

1, 2, 3, 777, 777, 777, 777, 777, 777, 777, 
777, 777, 777, 777, 777, 777, 777, 777, 777, 777, 

resize(n, val)assign(n, val)も、vectorの長さをnに変える点では同じです。 違いは、resize(n, val)ではvectorの延長された部分のみ値がvalになるのに対し、 assign(n, val)ではvectorのすべての部分で値がvalになることです。

もしvectorオブジェクトのサイズが0の場合はresize(n, val)assign(n, val)も結果は同じになりますが、 assign(n, val)を使うほうがより意図を明確に表せます。

Wandboxへのリンクで実際に実行できます。

参考URL

PowerShellのTab補完をbashのようにする

Windows PowerShell (この記事の執筆時点でバージョンは5.1) でTabキーを押したときの補完方法をbashのようにする方法について調べました。

デフォルトでの補完方法

まず、デフォルトでのWindows PowerShellの補完方法について説明します。例として、20210101.txt, 20210103.txtという2つのファイルがあるディレクトリでWindows PowerShellのシェルを開いている場合を考えます。

Tabによる補完

1つ目の補完はTabを使う方法です。Windows PowerShell2を押したあとにTabを押すと

> .\20210101.txt

と表示され、さらにもう一度Tabを押すと

> .\20210103.txt

が表示されます。これは伝統的なコマンドプロンプトの補完方法と同じです。

Ctrl + Spaceによる補完

2つ目の補完方法はCtrl + Spaceを使う方法です。Windows PowerShell2を押したあとにCtrl + Spaceを押すと、2つのファイル名の共通prefixである2021010の部分までが確定して、かつ、1個目の候補である20210101.txtの残り部分 (1.txt) が選択済である状態になります(言葉で説明しづらいので実際に試してみてください)。

Windows PowerShellの補完方法がどうなっているかを調べるにはGet-PSReadLineKeyHandlerというコマンドレットを使います。デフォルト設定では以下でした。

コンプリート機能
========

Key        Function            Description
---        --------            -----------
Ctrl+Space MenuComplete        入力候補が 1 つだけ存在する場合は、その候補で入力を補完します。それ以外の場合は、入力候
                               補のメニューから項目を選択して入力を補完します。
Tab        TabCompleteNext     次の入力候補を使用して入力を補完します
Shift+Tab  TabCompletePrevious 前の入力候補を使用して入力を補完します

Tabによる補完方法を変える

デフォルトのTabの補完方法は耐え難いものがあるので、Tabbashzshのような補完ができるように変更します。その方法が PowerShellでbash-like, zsh-likeなタブ補完 - Qiita に書いてありました。

bash風の補完

> Set-PSReadLineKeyHandler -Key Tab -Function Complete

zsh風の補完(デフォルトでCtrl+Spaceによりなされる補完と同じ)

> Set-PSReadLineKeyHandler -Key Tab -Function MenuComplete

ただし、この設定はWindows Powershellのウィンドウを閉じると消えてしまいます。恒久的にこの設定を有効にしたければ、プロファイル(.bash_profile的な設定ファイル)を編集する必要があります。プロファイルは何個も種類があって正直よくわかってないですが、私は$PROFILE.CurrentUserAllHosts

Set-PSReadLineKeyHandler -Key Tab -Function Complete

を設定しておきました。

PowerShellからAnaconda Pythonの仮想環境を使えるようにしたときのメモ

PowerShellにてAnaconda Pythonの仮想環境をactivateしようとしてactivate (仮想環境名)というコマンドを実行しても何も起こりません。これを修正しようとしたときにやったことのメモです。注:私はAnaconda PythonにもPowerShellにも精通していないため、これが正しい方法かはわかりません。

condaのバージョンを最新にする

  • conda --versionでバージョンを調べると4.5.11と古かったので、以下の方法で4.9.2にupdate

Windows PowerShell から Anaconda 環境を起動できるようにするための初期化コマンドを実行

> conda init powershell
no change     C:\ProgramData\Anaconda3\Scripts\conda.exe
no change     C:\ProgramData\Anaconda3\Scripts\conda-env.exe
no change     C:\ProgramData\Anaconda3\Scripts\conda-script.py
no change     C:\ProgramData\Anaconda3\Scripts\conda-env-script.py
no change     C:\ProgramData\Anaconda3\condabin\conda.bat
no change     C:\ProgramData\Anaconda3\Library\bin\conda.bat
no change     C:\ProgramData\Anaconda3\condabin\_conda_activate.bat
no change     C:\ProgramData\Anaconda3\condabin\rename_tmp.bat
no change     C:\ProgramData\Anaconda3\condabin\conda_auto_activate.bat
no change     C:\ProgramData\Anaconda3\condabin\conda_hook.bat
no change     C:\ProgramData\Anaconda3\Scripts\activate.bat
no change     C:\ProgramData\Anaconda3\condabin\activate.bat
no change     C:\ProgramData\Anaconda3\condabin\deactivate.bat
modified      C:\ProgramData\Anaconda3\Scripts\activate
modified      C:\ProgramData\Anaconda3\Scripts\deactivate
modified      C:\ProgramData\Anaconda3\etc\profile.d\conda.sh
modified      C:\ProgramData\Anaconda3\etc\fish\conf.d\conda.fish
no change     C:\ProgramData\Anaconda3\shell\condabin\Conda.psm1
modified      C:\ProgramData\Anaconda3\shell\condabin\conda-hook.ps1
no change     C:\ProgramData\Anaconda3\Lib\site-packages\xontrib\conda.xsh
modified      C:\ProgramData\Anaconda3\etc\profile.d\conda.csh
modified      C:\Users\(ユーザ名)\Documents\PowerShell\profile.ps1
modified      C:\Users\(ユーザ名)\Documents\WindowsPowerShell\profile.ps1

==> For changes to take effect, close and re-open your current shell. <==

改変されたprofile.ps1を確認

このコマンドによって改変されたC:\Users\(ユーザ名)\Documents\PowerShell\profile.ps1およびC:\Users\(ユーザ名)\Documents\WindowsPowerShell\profile.ps1を見に行くと、いずれにも以下のコードが付け足されていることを確認しました。(profile.ps1が2つあるのは、たぶんWindows 10に組み込みで入っているWindows PowerShellに加えて、私がPowerShell Coreを別途インストールしてあることが原因です。)

#region conda initialize
# !! Contents within this block are managed by 'conda init' !!
(& "C:\ProgramData\Anaconda3\Scripts\conda.exe" "shell.powershell" "hook") | Out-String | Invoke-Expression
#endregion

スクリプト実行ポリシーを変更

次に、さきほどの指示通り別のWindows PowerShellを起動すると、以下のエラーが出ました。

. : このシステムではスクリプトの実行が無効になっているため、ファイル C:\Users\(ユーザ名)\Documents\WindowsPowerShell\profile.ps1
 を読み込むことができません。詳細については、「about_Execution_Policies」(https://go.microsoft.com/fwlink/?LinkID=13517
0) を参照してください。
発生場所 行:1 文字:3
+ . 'C:\Users\(ユーザ名)\Documents\WindowsPowerShell\profile.ps1'
+   ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : セキュリティ エラー: (: ) []、PSSecurityException
    + FullyQualifiedErrorId : UnauthorizedAccess

これは、私のWindows Powershellスクリプト実行ポリシーがRestrictedのままだったことが原因でした。

> Get-ExecutionPolicy
Restricted

そこで、管理者権限付きのWIndows PowerShellを起動して、以下のコマンドを実行し、スクリプト実行ポリシーを変更しました(注:システム全体に影響を与えます)。

> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned

そして、改めてまた別のWIndows PowerShellを起動することで、無事Anaconda Pythonを使えるPowerShell環境ができたようです。conda activateによる環境の変更も実現できています。

Windows PowerShell
Copyright (C) Microsoft Corporation. All rights reserved.

新しいクロスプラットフォームの PowerShell をお試しください https://aka.ms/pscore6

パーソナル プロファイルとシステム プロファイルの読み込みにかかった時間は 1083 ミリ秒です。
(base) PS C:\Users\(ユーザ名)> conda activate sandbox
(sandbox) PS C:\Users\(ユーザ名)>

同様に、PowerShell CoreからもAnaconda Pythonが使えることを確認しました。

ただ、profile.ps1のコードが増えた影響か、Windows PowerShellを起動するたび毎回約1秒かかるようになってしまいました。これが嫌な場合、Anaconda がやっと PowerShell に公式対応した - Qiita を真似して、condainiという名前の関数を手動で呼んだときだけAnaconda Pythonが使えるように変更するのがよさそうです(私は未テスト)。以下にコードを引用します。

#region conda initialize
Function condaini{
  (& "c:\Users\user\Anaconda3\Scripts\conda.exe" "shell.powershell" "hook") | Out-String | Invoke-Expression
}
#endregion

参考URLs

Visual Studio Codeのショートカットキー集

普段使わなくて覚えられないものを使いこなせるようになるようメモ書き。Windowsで動作確認しています。Ctrl / Shift / Altのコンビネーションが複雑で覚えるのが大変です…

カーソル移動

  • Ctrl + Enter: カーソルがどこにあっても、カーソルの下に空白行を作ってカーソルを移動
  • Ctrl + Shift + Enter: カーソルがどこにあっても、カーソルの上に空白行を作ってカーソルを移動
  • Ctrl + Shift + \: 対応する括弧にカーソルを移動

範囲選択

  • 行番号の部分をドラッグ: 行単位で選択
  • Ctrl + L: カーソルのある行を選択。繰り返し押すと選択範囲が下に伸びる
  • Alt + Shift + → or ←: 単語やカッコなどの単位で選択範囲を広げる
  • Ctrl + Shift + Alt + ↑ or ↓: 矩形選択
    • 「マウスのホイールでドラッグ」、「Shift + Altを押しながらマウスの左ボタンでドラッグ」でもOK
  • Altを押しながらクリック: マルチカーソル

編集

  • 何も選択していない状態でCtrl + C: カーソルのある行をコピー
  • 何も選択していない状態でCtrl + X: カーソルのある行を切り取り
  • Alt + ↑ or ↓: カーソルのある行 or 選択中の行を1行移動
  • Ctrl + Shift + K: カーソルのある行を削除(Ctrl + Xとは違い、クリップボードに削除された行の情報は入らない)
  • Shift + Alt + ↑ or ↓: カーソルのある行をコピーして、上または下に挿入
  • Alt + ↑ or ↓: カーソルのある行を上または下に移動

表示

  • Ctrl + Shift + [ or ]: カーソルのある行のコードを折りたたんだり戻したり
  • Ctrl + K Ctrl + 0: すべてのコードを折りたたむ
  • Ctrl + K Ctrl + J: すべてのコードの折りたたみを解除する

定義

  • F12: 定義へ移動
  • Alt + ←: 元の場所へ戻る
  • Alt + F12: Peek Definition (定義を小窓で表示)
  • Ctrl + K, F12: 定義を別ウィンドウで並べて表示

リファクタリング

  • F2: カーソル位置の単語をリネーム。プロジェクト内の複数ファイルにまたがって行われる。コメントアウト部は無視されるようだ。
  • Ctrl + F2: カーソル位置の単語を全選択状態にする。コメントアウト部も選択対象となる。
  • Ctrl + Shift + L: 選択部分と同じ文字列を全選択状態にする
  • Shift + Alt + F: ソースコード全体をフォーマット
  • Ctrl + K, Ctrl + F: 選択部分をフォーマット

参考文献

Python 3のsqlite3モジュールでSQLiteの練習

Python 3の標準ライブラリであるsqlite3を使って、SQLite と呼ばれるデータベースを触ってみるメモです。

基本

テーブルを作成

以下のコードでは、都道府県のデータを格納するprefecturesという名前のテーブルを定義します。このテーブルは、name(都道府県名), capital(都道府県庁所在地), population(人口), area(面積)という4つのカラムを持ちます。

import sqlite3

# データベースの保存先
database_store_path = './example.db'

# 最初にデータベースを表す Connection オブジェクトを作る
conn = sqlite3.connect(database_store_path)

# カーソルオブジェクトをつくる
c = conn.cursor()

# executeによりSQLコマンドを実行
# ここでは、'prefectures'という名前のtableを生成するSQLコマンドを実行
# ここでtext, integer, realというのは「カラム型」を意味する
c.execute('''CREATE TABLE prefectures
             (name text, capital text, population integer, area real)''')

データを挿入

次に、prefecturesテーブルにいくつかの都道府県のデータを挿入していきます。以下では、c.execute()を使ってデータを1個ずつ挿入する方法と、c.executemany()を使ってデータ列を一度に挿入する方法を示しています。

人口、面積のデータは英語版Wikipediaを参照しました。

# executeによりSQLコマンドを実行
# ここでは、'stocks'という名前のtableに、1個分のデータを挿入するSQLコマンドを実行
c.execute("INSERT INTO prefectures VALUES ('Kanagawa','Yokohama',9058094,2415.83)")
c.execute("INSERT INTO prefectures VALUES ('Tokyo','Tokyo',13929280,2194.07)")

# '?'というプレースホルダーを使って1個分のデータを挿入することもできる
c.execute("INSERT INTO prefectures VALUES (?,?,?,?)", ('Chiba', 'Chiba', 6278060, 5157.61))

# '?'というプレースホルダーを使って複数のデータを挿入することもできる
prefecture_list = [('Tochigi', 'Utsunomiya', 1943886, 6408.09),
                   ('Ibaraki', 'Mito', 2871199, 6097.19)]
c.executemany("INSERT INTO prefectures VALUES (?,?,?,?)", prefecture_list)

後始末

以下のコードによりDBを更新して保存します。

# 変更を保存 (commit)
conn.commit()

# 後始末
conn.close()

データの読み込み

さきほどexample.dbに保存したテーブルに格納されたデータを読み込んでみます。データの読み込み方は、SQLite入門 によると以下の3種類あります。実際は1番目と2番目を使いそうな気がします。

# 一度保存したDBを開く
conn = sqlite3.connect(database_store_path)
c = conn.cursor()

# 1. カーソルをイテレータ (iterator) として扱う
c.execute('SELECT * FROM prefectures')
for row in c:
    print(row)
print()
 
# 2. fetchallで結果リストを取得する
c.execute('SELECT * FROM prefectures')
for row in c.fetchall():
    print(row)
print()
 
# 3. fetchoneで1件ずつ取得する
c.execute('SELECT * FROM prefectures')
print(c.fetchone())  # 1番目のレコード
print(c.fetchone())  # 2番目のレコード
print(c.fetchone())  # 3番目のレコード
print(c.fetchone())  # 4番目のレコード
print(c.fetchone())  # 5番目のレコード
print(c.fetchone())  # 6番目のレコードは存在しないのでNoneが返る
print()

# 後始末
conn.close()

出力結果は以下のとおりです。

('Kanagawa', 'Yokohama', 9058094, 2415.83)
('Tokyo', 'Tokyo', 13929280, 2194.07)
('Chiba', 'Chiba', 6278060, 5157.61)
('Tochigi', 'Utsunomiya', 1943886, 6408.09)
('Ibaraki', 'Mito', 2871199, 6097.19)

('Kanagawa', 'Yokohama', 9058094, 2415.83)
('Tokyo', 'Tokyo', 13929280, 2194.07)
('Chiba', 'Chiba', 6278060, 5157.61)
('Tochigi', 'Utsunomiya', 1943886, 6408.09)
('Ibaraki', 'Mito', 2871199, 6097.19)

('Kanagawa', 'Yokohama', 9058094, 2415.83)
('Tokyo', 'Tokyo', 13929280, 2194.07)
('Chiba', 'Chiba', 6278060, 5157.61)
('Tochigi', 'Utsunomiya', 1943886, 6408.09)
('Ibaraki', 'Mito', 2871199, 6097.19)
None

応用

特定のカラムだけを取り出す

4つのカラムのうち、面積と人口のカラムのみを取り出すにはSELECTを以下のように使います。

# 一度保存したDBを開く
conn = sqlite3.connect(database_store_path)
c = conn.cursor()

# 特定の列だけを取り出す
c.execute('SELECT area,population FROM prefectures')
for row in c:
    print(row)

# 後始末
conn.close()

結果は以下です。

(2415.83, 9058094)
(2194.07, 13929280)
(5157.61, 6278060)
(6408.09, 1943886)
(6097.19, 2871199)

これ以降、DBを開いてカーソルを取得する部分と後始末の部分は省略します。変数cにはカーソルが入っていると思ってください。

特定の条件にあったデータだけを取り出す

人口が500万以上のデータだけを取り出すにはSELECTWHEREで条件を加えます。

# 人口が500万以上の行だけを取り出す
c.execute('SELECT * FROM prefectures WHERE population > 5000000')
for row in c:
    print(row)

結果は以下です。

('Kanagawa', 'Yokohama', 9058094, 2415.83)
('Tokyo', 'Tokyo', 13929280, 2194.07)
('Chiba', 'Chiba', 6278060, 5157.61)

データを特定の条件で並び替える

例えば人口が多い順にデータを並び替えて取り出すにはSELECTORDER BYで条件を加えます。最後のDESCというのが降順を意味していて、これをASCにするか省略するかすると昇順になります。

# 人口が多い順に取り出す
c.execute('SELECT * FROM prefectures ORDER BY population DESC')
for row in c:
    print(row)

結果は以下です。

('Tokyo', 'Tokyo', 13929280, 2194.07)
('Kanagawa', 'Yokohama', 9058094, 2415.83)
('Chiba', 'Chiba', 6278060, 5157.61)
('Ibaraki', 'Mito', 2871199, 6097.19)
('Tochigi', 'Utsunomiya', 1943886, 6408.09)

カラムを追加する

人口密度を表すカラムを追加してみます。SQL文でデータを追加・更新・削除する方法 (2/2)を参考にしました。

# 人口密度を表すカラムを追加
c.execute('ALTER TABLE prefectures ADD population_density float')
# 人口密度を挿入
c.execute('UPDATE prefectures SET population_density=population / area')

# 結果を表示
c.execute('SELECT * FROM prefectures')
for row in c:
    print(row)

結果は以下です。

('Kanagawa', 'Yokohama', 9058094, 2415.83, 3749.474921662534)
('Tokyo', 'Tokyo', 13929280, 2194.07, 6348.603280661054)
('Chiba', 'Chiba', 6278060, 5157.61, 1217.242094691146)
('Tochigi', 'Utsunomiya', 1943886, 6408.09, 303.3487357387302)
('Ibaraki', 'Mito', 2871199, 6097.19, 470.9052858775928)

素性が不明なデータベースを探索

データベースファイルが与えられたときに中身を探索する方法について記します。ちなみに、db.pyでデータベース探索 にあるように、db.pyというツールを使えばもっとかんたんにできるようですが、ここでは練習としてsqlite3モジュールのみで探索してみます。

まずはデータベースに格納されているテーブル一覧を取得する方法です。sqlite_masterに格納されている情報を使います。

# テーブル一覧を取得
# c.f. https://www.kite.com/python/answers/how-to-list-tables-using-sqlite3-in-python
c.execute("SELECT name FROM sqlite_master WHERE type='table'")
print(c.fetchall())

結果は以下です。

[('prefectures',)]

これでprefecturesという名前のテーブルがあることがわかりました。

次に、prefecturesテーブルのカラム名を取得します。やり方が複数ありそうで自信がありませんが、以下のコードで取得できました。

# テーブルのカラム名を取得
# https://stackoverflow.com/questions/947215/how-to-get-a-list-of-column-names-on-sqlite3-database")
c.execute("SELECT name FROM PRAGMA_TABLE_INFO('prefectures')")
print(c.fetchall())

結果は以下です。

[('name',), ('capital',), ('population',), ('area',), ('population_density',)]