Python初心者の備忘録 #05

# はじめに
今回私は最近はやりのchatGPTに興味を持ち、深層学習について学んでみたいと思い立ちました！  
深層学習といえばPythonということなので、最終的にはPythonを使って深層学習ができるとこまでコツコツと学習していくことにしました。  
ただ、勉強するだけではなく少しでもアウトプットをしようということで、備忘録として学習した内容をまとめていこうと思います。  
この記事が少しでも誰かの糧になることを願っております！
**※投稿主の環境はWindowsなのでMacの方は多少違う部分が出てくると思いますが、ご了承ください。**
最初の記事：[Python初心者の備忘録 #01](https://qiita.com/Yushin-Tati/items/961dc47d6163f944f7e9)
前の記事：[Python初心者の備忘録 #04](https://qiita.com/Yushin-Tati/items/83aa101d7673e69d9279)
次の記事：[Python初心者の備忘録 #06 ~DSに使われるライブラリ編01~](https://qiita.com/Yushin-Tati/items/5dcad7518a0f8a3fdbc1)

今回の記事はStyle Guide、ErrorとException、ファイルの入出力、テスト、Databaseついてまとめてあります。
## ■学習に使用している資料
Udemy：[米国AI開発者がゼロから教えるPython入門講座](https://www.udemy.com/course/python-ai/)
## ■Style Guide
プログラミング言語には共通した、またはその言語やプロジェクト内独自のコーディングのルールのが存在する。
Pythonにも例外なく存在しており、「PEP8」というルールに準拠してコーディングすることが推奨されている。
基本的なものや勘違いされやすいものを下記にまとめてある。
```python
# 変数定義
# Correct:
xxx = 1
y = 2
# Wrong:
xxx           = 1
y             = 2

# 無駄に行の最後にスペースを入れない(エディタによっては見えないので注意)
x = 1

# 引数の「=」にはスペース不要
# Correct:
def complex(real, imag=0.0):
    return magic(r=real, i=imag)
# Wrong:
def complex(real, imag = 0.0):
    return magic(r = real, i = imag)

# operatorの周りにスペース一個．もしオペレータのpriorityがある場合は無くす
# Correct:
x = x + 1
x += 1
x = x*2 - 1
a = x*x + y*y
c = (a+1) * (a-1)

# Wrong:
x=x+1
x +=1
x = x * 2 - 1
a = x * x + y * y
c = (a + 1) * (a - 1)

# コンマの後にはスペースを入れる．
# Correct:
range(1, 11)
# Wrong:
range(1,11)
# しかし，そのあとに閉じカッコが来る場合はスペース不要
# Correct:
foo = (0,)
# Wrong:
bar = (0, )

# 要素を並べる時にコンマで終えることが可能．そうすることで簡単に行を追加でき，gitなどのバージョン管理で差分にならない
# ただこれをやる場合は，閉じカッコは次の行にすること
# Correct:
FILES = [
    'setup.cfg',
    'tox.ini',
    ]
# Wrong:
FILES = ['setup.cfg', 'tox.ini',]

# PEPでは一行79文字までを推奨しているが，厳密にやる必要はない
# 高解像度化により80文字は全然フィットするようになってきている．
# (pycharmのデフォルトは120文字)
# 関数名が長くて折り返す場合は，
foo = long_function_name(var_one, var_two,
                         var_three, var_four)
# のように引数のはじまりの縦をあわせるか
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)
# のように引数を全てインデントするか
# ※この場合一行目の引数は書かない．つまり以下はNG
foo = long_function_name(var_one, var_two,
    var_three, var_four)

# 関数定義の際に改行する場合は，二行目を8スペース開けることでその次のブロックとの差がわかるようにする
# correct
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

# ブロックの境目がわからなくなるのはNG
# wrong
def long_function_name(
    var_one, var_two, var_three,
    var_four):
    print(var_one)

# '\'で区切って改行する

print("このように表示する文章がながったりした場合は \
バックスラッシュで区切ると改行できます")
# 演算子の後ではなく，前で改行する．
# Correct:
a = 10000000000000000 \
    + 100000000000000 \
    + 10000 \
    + 10000

# Wrong:
b = 10000000000000000 +\
    100000000000000 +\
    10000 +\
    10000


# 関数間は2行空ける
def func():
    pass


def func2():
    pass


# メソッド間は1行空ける
class MyClass:
    def __init__(self):
        pass

    def method(self):
        pass

# importはファイルの最初に書く
# importは別々の行に書く
# Correct:
import os
import sys

# Wrong:
import sys, os

# ただfromをつける場合は一行にまとめてOK.(でないと大量のimport文を書くことになる)
# Correct:
from subprocess import Popen, PIPE

# 書く順番は
# 1.Standard library
# 2.Third party
# 3.Our library
# 4.Local library
# それぞれ1行空ける

# なるべくabsolute importを使う
import mypkg.sibling
from mypkg import sibling
from mypkg.sibling import example

# 長すぎたらNG
from package.subpackage1.subpackage2.subpackage3.module4 import function

# コメント
# ・コメントは常に最新にする．コメントとコードの中身が異なるなら，コメントは無い方がまし
# ・なるべく英語で書く．絶対に日本語がわからない人が読むことがないなら英語で書く必要はない
# ・書くときは文章で書くのが望ましい
# ・# のあとに半角スペースを入れる
# ・Docstringは基本的に全てのmodule, function, class, methodにつける(non publicな外からアクセスしない関数には不要)
# ・そのコードが「なにをしているか」より「なぜそう書いたか」の方が有益

# 命名規則(naming convention)
# 変数名や関数(メソッド)名: snake_case 例) balance, image_height
# クラス名: CamelCase 例) Person, MyClass
# モジュールやパッケージ名: なるべく短いlower caseで，必要であればsnake_case 例) time, numpy

# アンダースコア
# - _xxx internal use only(non public)の意味
# - xxx_ Pythonで既に使われている単語を使いたいとき．(例:type_
# - __xxx クラスの属性として使うことで名前修飾される
# - __xxx__ magic methodと呼ばれるもので，Pythonが特別に設定しているもの．開発者定義することはない．(overrideすることはある)
# - _: 最近実行した戻り値や，iteration時に使わない変数

# l, I, Oという一文字の変数名は1や0に見間違えるので使わないこと
# Correct:
length = len(letter)
# Wrong:
l = len(letter)

# Constants(宣言後変更しない変数)は大文字のsnakecaseを使う
PI = 3.14
# 変更は可能なので注意(Pythonでは強制できない)
PI = 3

# returnを書くなら全部書く．書かないなら一個も書かない.
# Correct:
def foo(x):
    if x >= 0:
        return math.sqrt(x)
    else:
        return None

def bar(x):
    if x < 0:
        return None
    return math.sqrt(x)

# Wrong:
def foo(x):
    if x >= 0:
        return math.sqrt(x)

def bar(x):
    if x < 0:
        return
    return math.sqrt(x)

# オブジェクトタイプの確認はisinstance()を使う
# Correct:
if isinstance(obj, int):
# Wrong:
if type(obj) is type(1):

# Booleanに比較演算子は使わない
bool_var = True
# Correct:
if bool_var:

# Wrong:
if bool_var == True:

# Type hints
# function定義時にannotationをつけて
def greeting(name: str) -> str:
    return 'Hello ' + name
# のように型のヒントを与えることができる．
# ・一つでもhitをつけたら全てにつける．
# ・typeのチェックをするわけではない
# ・Pythonは動的型付言語であることを意識すること
# ・このtype hintを強制したり，命名規約に入れるべきではない
```
## ■ErrorとException
### ▶構文エラー(SyntaxError)と例外(Exception)
プログラミングのエラーは大別して、構文エラーと例外が存在する。
構文エラー：単純にコーディングミス、(if文で`:`をつけ忘れている、plint()のようにつづりを間違えているなど)
例外：コーディングはあっているが、システム的に間違っている(int型 + str型になっている、0で割る計算をしているなど)
### ▶try exceptで例外を処理する
基本的には例外が発生した時点で、プログラムは止まってしまうのでアプリケーションが正常に終了しない。
try exceptを使用することで例外が発生したときはその時用のプログラムを実行させることができ、途中でプログラムが終了してしまうのを回避することができる。
`except <例外> as <変数名>:`とすることで特定の例外に対しての処理を書くこともでき、`as <変数名>`とすることで例外の内容を<変数名>に格納することができる。
```python
def split_bill(price):
    num = input("何人で割り勘しますか？")
    try:
        each = price / int(num)
    except ZeroDivisionError as e:
        each = price
        print("0で割ることはできません")
        # エラーメッセージをprintすることが可能
        print(e)
    except ValueError:
        each = price
        print("数字を入力してください")
    print(f"一人{int(each)}円です")


if __name__ == "__main__":
    split_bill(10000)
```
### ▶try elseとtry finally
else：例外が発生しなかった際に実行される。
finally：例外の有無に関係なく最後に実行される。
```python
def split_bill(price):
    num = input("何人で割り勘しますか？")
    try:
        each = price / int(num)
        return "try"
    except ZeroDivisionError as e:
        print("0で割ることはできません")
        # エラーメッセージをprintすることが可能
        print(e)
    except ValueError:
        print("数字を入力してください")
    else:
        print(f"一人{int(each)}円です")
    finally:
        print("ご利用ありがとうございます")
        return "finally"


if __name__ == "__main__":
    return_value = split_bill(10000)
    print(return_value)
```
### ▶raise
tryの中で`raise <例外>`とすることで、意図的に<例外>をthrowすることができる。
```python
try:
    # tryの中で特定のErrorをraiseすることで，例外をthrowできる。
    raise ValueError("raise ValueError for test")
except ValueError:
    # 意図的にexceptを実行することができる
    print("Do something")
```
### ▶例外の自作
クラスを定義する際にExceptionクラスを継承することで、例外クラスとして定義することができる。
作成した例外クラスはraiseで呼び出すことができる。
```python
# Exceptionクラスを継承
class MyError(Exception):
    """my error"""

    def __str__(self):
        return "my error occurred"


if __name__ == "__main__":
    response = input("y/n?")
    if response != 'y' and response != 'n':
        raise MyError
```
### ▶tracebackモジュール
try exceptで例外を処理した場合、その例外の内容はprint()しない限り表示されなくなってしまうが、tracebackモジュールを使用することで、try exceptを使用しなかった時のようなログを出力することができるようになる。
tracebackを使用することでどこで例外が発生しているのか、どのような内容の例外なのかがわかるのでよりデバックしやすくなる。
```python
import traceback

def split_bill(price):
    num = input("割り勘する人数を入力してください")
    try:
        each = price / int(num)
    except ZeroDivisionError:
    	# 例外が発生した個所と内容を出力する。
        traceback.print_exc()
        print("0以外の数字を入力してください．")
    else:
        print(f"一人{int(each)}円です")


def check(bill):

    total_bill = sum(bill.values())
    try:
        split_bill(total_bill)
    except ValueError:
        traceback.print_exc()
        print("エラーがでましたやり直してください")


if __name__ == "__main__":
    bill = {'burger': 500, 'pasta':1400, 'fries': 300, 'egg': 200}
    check(bill)
```
## ■ファイル出力(File I/O)
### ▶open関数とwithステートメント
`open(<ファイルパス>)`関数を使用することで、<ファイルパス>に指定したファイルの内容を取り込むことができる。
open()関数はファイル内の内容を1行ずつ取得するので、for文で回すことで順番にファイルの内容を取り扱うことができる。
open()関数で取得した内容はメモリに残り続けるので、必要がなくなったら`.close()`でメモリを解放する必要がある。
`with open(<ファイル名>) as f`とすることで、withスコープ内でopen()関数で取得した内容が完結するようになり、スコープを抜けた際に勝手にメモリを解放してくれるようになるので、`.close()`を省略することができる。
```text:pep8_introduction.txt
Introduction
This document gives coding conventions for the Python code comprising the standard library in the main Python distribution. Please see the companion informational PEP describing style guidelines for the C code in the C implementation of Python [1].

This document and PEP 257 (Docstring Conventions) were adapted from Guido's original Python Style Guide essay, with some additions from Barry's style guide [2].

This style guide evolves over time as additional conventions are identified and past conventions are rendered obsolete by changes in the language itself.

Many projects have their own coding style guidelines. In the event of any conflicts, such project-specific guides take precedence for that project.
```
```python
# built in であるopen()関数を使う
f = open("pep8_introduction.txt")
for line in f:
    print(line, end='')
# open後はclose()
f.close()

# try finallyを使う
try:
    f = open("pep8_Introduction.txt")
    for line in f:
        print(line, end='')
finally:
    f.close()

# 実際にはwith statementを使うのが一般的
with open("pep8_Introduction.txt") as f:
    for line in f:
        print(line, end='')
```
### ▶ファイルの読み込み、書き込み
open()関数の第2引数に`mode=<モード>`を渡すことで、ファイルを読み込み用、書き込み用、両方などの、どの用途で呼び出すのかを指定することができる。(デフォルトは読み込み)
読み込みの際は`.read(), .readline(), .readlines()`などの関数がある。
書き込みは`.write()`もしくはprint()の第2引数に`file=<ファイルobj>`で行うことができる。
・.read()：ファイルの中身をいつのstr型で返却する。
・.readline()：1行ずつ内容を返す。
・.readlines()：1行ずつList型に格納して返す。
```python:read()
# ループで回す
with open("pep8_introduction.txt") as f:
    for line in f:
        print(line)


# .read()でファイルの中身の全てを一つのstringとして返す
    # ※大きなファイルでやらないこと
with open("pep8_introduction.txt") as f:
    print(f.read())

# .readline()で一行ずつ取得しstringとして返す
with open("pep8_introduction.txt") as f:
    line = f.readline()
    row_num = 0
    while line:
        print(f"{row_num}行目", line, end='')
        line = f.readline()
        row_num += 1

# .readlines()で全ての行をlistで返す
with open("pep8_introduction.txt") as f:
    lines = f.readlines()
    print(lines)
```
```python:write()
# ファイルに書き込むにはmode='w'で開く
with open("writing_file.txt", mode='w') as f:
    # ファイルへの書き込みにはf.write()を使う
    # ファイルがなければ作成され，あれば上書きされる
    f.write("You can write contents here \nuse 'backslash n' to break the row")
    f.write("New write() doesn't break row")
    # print(file=)を使って書き込むことも可能
    print("You can add a new row using 'file' parameter", file=f)
    print("new print() method breaks the row for you!", file=f)
    print("new print() method breaks the row for you!", file=f)
    print("new print() method breaks the row for you!", file=f)
    # mode='w'を指定すると，readできないので注意
    # print(f.read())
```
```python:open_mode
# mode=a (append): ファイルの最後尾に内容を追加
with open("writing_file.txt", mode='a') as f:
    f.write("mode=a appends text")

# mode=r+: 読み書きどちらも可能
with open("writing_file.txt", mode='r+') as f:

    f.write("You can write and read with r+ mode!!")
    # .read()で読み込むことも可能，すると，positionが最後尾に移動する(読み込みによりpositionが移動しているから)
    print(f.read())
    f.write("This should be the last sentence")

# mode=w+: 読み書きどちらも可能．Truncate(上書き保存)されることに注意
with open("writing_file.txt", mode='w+') as f:
    # read()も可能だが，truncate(つまりファイルのbyteは0)されているので，読み込むものはない
    print(f.read())
    f.write("You can write and read with w+ mode!!")
    f.write("The content should be truncated")
    # positionが最後になってしまっているので，.seek(0)でpositionを先頭に戻す
    f.seek(0)
    # 書き込んだものをread可能
    print(f.read())

# mode=a+: 読み書きどちらも可能で，positionが最後尾から始まり，truncateされない
with open("writing_file.txt", mode='a+') as f:
    # read()も可能だが，positionが最後尾になっているので読み込むものはない
    print(f.read())
    f.write("\nYou add sentences to the last of the file content with a+ mode!!")
    # positionが最後になってしまっているので，.seek(0)でpositionを先頭に戻す
    f.seek(0)
    # 書き込んだものをread可能
    print(f.read())
```
## ■テスト
### ▶Pythonコードをテストする
プログラミングではコードを書くこと自体はそこまで難しくはないが、作成したシステムが安定して稼働する(エラーが起きない)ようにするのは難しいものとなっている。
そのため、ほとんどのシステムは世の中に公開する前にテストを行っており、このテストがとても重要になってくる。
コードを書く前にテストコードを作成し、最後にテストを実行して問題がないかを確かめるというように実装するのが一般的。
### ▶assert
Pythonでは、簡単なものだと`assert <条件>, <条件がFalseの際に出力する文字列>`とすることで、作成したコードが正常に稼働するかをテストすることができる。
```python
a = 1
b = 1
assert a + b == 2, "a + b is equal to 2!"

# base ** exp
def power(base, exp):
    return base * exp


# 本来は3の2乗を返却するはずだが、実装が誤っているのでテストが失敗する
assert power(3, 2) == 9, "3 ** 2 must be 9"
```
### ▶テストスクリプト
テストコードは通常のコードとは別のファイルに書くのが一般的で、ファイル名も`test_<テストしたいファイル名>`とする。
そうすることで管理が楽になり、後述するが多数のテストをいっぺんに実行することもできる。
テストファイル内では確認したい関数の名前に`test_`を付けた形でテスト関数を実装する。
テストスクリプトではエラーが発生した場合はそのテスト関数までしかスクリプトが走らないので、すべてのテストを通すというのには限界がある。その問題を解消するために後述のテストランナーを使用する。
```python:power.py
def power(base, exp):
    return base ** exp


def times(num1, num2):
    return num1 * num2


def divide(num1, num2):
    if num2 == 0:
        return None
    else:
        return num1 / num2
```
```python:test_power.py
from power import power, times


def test_power():
    base = 2
    exp = 3
    assert power(base, exp) == 8, "this should be 8"


def test_times():
    num1 = 2
    num2 = 3
    assert times(num1, num2) == 6, "this should be 6"


if __name__ == "__main__":
    test_power()
    test_times()
```
### ▶unittest
テストランナーの一つで、importすることで使用することができる。
クラス定義時に`unittest.TestCase`を継承することで、テストクラスにすることができる。
テスト関数を定義する際は`self`を引数に渡してやり、`self.assertEqual`など様々なテスト関数を呼び出すことができる。
テストスクリプトで書いたように、どこかでエラーが発生してもテストが止まらず最後まで実行して結果を出力してくれる。
テストを実行するには`unittest.main()`を最後に記述すればよい。
```python
import unittest
from power import power, times


#テストクラスの宣言
class TestMyMethods(unittest.TestCase):

    def test_power(self):
        base = 2
        exp = 3
        self.assertEqual(power(base, exp), 8)

    def test_times(self):
        num1 = 2
        num2 = 3
        self.assertEqual(times(num1, num2), 6)


if __name__ == "__main__":
    # テストが実行される
    unittest.main()
```
### ▶例外のテスト
テストでは正しく例外を処理してくれるかの確認も必要になるときがある。
その際はwithステートメントと`self.assertRaises(<例外>)`を組み合わせて例外のテストを行うことができる。
また、Pythonでは一つの関数に対するテストは同じテスト関数内でまとめて定義するのが一般的。
```python
import unittest
from power import power


class TestMyMethods(unittest.TestCase):

    def test_power(self):
        base = 2
        exp = 3
        self.assertEqual(power(base, exp), 8)
        # 例外処理のテスト
        with self.assertRaises(TypeError):
            power("3", "2")


if __name__ == "__main__":
    unittest.main()
```
### ▶pytest
別のテストランナーでunittestよりも簡単に実装することができるので、基本的にはこちらのテストランナーを使用するようにする。
Pythonに標準で入っていないので、`pip install pytest`でパッケージをインストールする必要がある。
```python
import pytest

from power import power, times


def test_power():
    base = 2
    exp = 3
    assert power(base, exp) == 8
    with pytest.raises(TypeError):
        power("3", "2")


def test_times():
    num1 = 2
    num2 = 3
    assert times(num1, num2) == 6
```
### ▶テストカバレッジ
カバレッジとはテストを実行する際に重要な考え方となっており、作成したテストがどこまでのコードをカバーできているのかを表すものとなっている。
せっかくテストを作成したとしても、確認箇所が漏れていたら意味がないのですべてのコードに対してテストが実行できているかの確認も重要な点となっている。
pytestにもカバレッジの機能が搭載されており、別途pytest-covというパッケージをインストールする必要があるが、必ず使用する機能となっているので、気になる人は調べてみよう。
## ■Database
### ▶pythonとdatabase(sqlite3)
pythonには簡単にデータベースと接続し、CRUD操作を実行できる仕組みが存在する。
今回はpythonに標準搭載のsqlite3を例にして、接続方法を紹介する。
まず、sqlite3を使用するにはインポートする必要があり、`sqlite3.connect(<接続したいDBファイルパス>)`とすることで簡単にDBに接続することができる。もし、パスに指定したファイルがない場合は新規作成してくれる。
上記で呼び出したDBオブジェクトに対して`.cursor()`とすることで、DBに対して様々な関数を呼び出すことができる。(例：`.execute(<SQL文>)` => DBに対してSQLを実行できる)
最後にDBオブジェクトに`.commit()`とすることで、今まで実行したSQLを確定し、DBに更新結果を保存することができる。
```python
import sqlite3

con = sqlite3.connect("sample.db")
cursor = con.cursor()

create_user_table_query = """
CREATE TABLE IF NOT EXISTS User (
    UserId INTEGER PRIMARY KEY NOT NULL,
    Name TEXT DEFAULT 'anonymous',
    Email TEXT NOT NULL,
    Age INTEGER CHECK(Age > 0)
) 
"""

cursor.execute(create_user_table_query)

insert_user_query = "INSERT INTO User VALUES(1, 'John', 'john@gmail.com', 30)"
cursor.execute(insert_user_query)

insert_user_query = """
INSERT INTO User VALUES(1, 'John', 'john@gmail.com', 30);
INSERT INTO User VALUES(2, 'Emily', 'emily@gmail.com', 28);
INSERT INTO User VALUES(3, 'Taro', 'taro@gmail.com', 25);
"""
# SQL文が複数ある場合は.executescript()を使用する。
cursor.executescript(insert_user_query)

con.commit()
```
### ▶updateとInjection攻撃
update文を実行する際にInjection攻撃に脆弱な書き方は絶対にしないように気を付ける。
SQL文を定義する際は更新する値を`?`で置き換えておき、`.execute()`の際に第2引数に値を設定することでInjection攻撃からDBを保護することができる。
```python
import sqlite3

con = sqlite3.connect("sample.db")
cursor = con.cursor()

# 以下はSQL Injection攻撃に対して脆弱なのでやらないこと
target_name = input("whose 'age' do you want to update?")
new_age = input("Tell me new age")
# 以下はSQL Injection攻撃に対して脆弱なのでやらないこと
# 例えばnew_ageに"50; DROP TABLE User;"とするとテーブルを削除できてしまう
# update_query = f"UPDATE User SET age = {new_age} WHERE name = '{target_name}'"
# cursor.executescript(update_query)

# placeholderである'?'と，execute()の第二引数(タプル)を使う
# そうすることで「;」は終端文字ではなくただの文字列と識別されるので、不正なSQLとしてエラーで返され、実行されなくなる。
update_query = 'UPDATE User SET age = ? WHERE name = ?'
cursor.execute(update_query, (new_age, target_name))
con.commit()
```
### ▶rollback
複数のDBにSQLを実行した際にどこかでエラーが発生した場合、今までの操作をすべて元に戻したいということが発生する。その時に使用するのがrollbackである。
`.rollback()`を呼び出すことで、今までのSQL操作をすべてなかったことにすることができる。
よくtry exceptのexcept内で使用される。
```python
import sqlite3

con = sqlite3.connect("sample.db")
cursor = con.cursor()
create_history_table_query = """
CREATE TABLE IF NOT EXISTS History (
    Name TEXT,
    Age INTEGER
) 
"""

cursor.execute(create_history_table_query)
target_name = input("whose 'age' do you want to update?")
new_age = input("Tell me new age")
update_user_query = 'UPDATE User SET Age = ? WHERE Name = ?'

insert_history_query = 'INSERT INTO History VALUES (?, ?)'
try:
    cursor.execute(insert_history_query, (target_name, new_age))
    cursor.execute(update_user_query, (new_age, target_name))
except sqlite3.Error:
    print("sqlite3 error occurred")
    # エラーの際は操作を取り消す
    con.rollback()
else:
    con.commit()
```
#### 次の記事
[Python初心者の備忘録 #06 ~DSに使われるライブラリ編01~](https://qiita.com/Yushin-Tati/items/5dcad7518a0f8a3fdbc1)