Pythonメモ

最近はPythonとTypeScriptを交互に使うことが多く、頭の中で両言語がごっちゃになっているので、本記事でPythonについてメモします。
情報の網羅性はありません。
気づいた時点で随時追加します。

# ループ

ループ中でbreak、continueが使える。

## while文

else節は、whileでbreakが実行されなかったときのみ実行される。

```py

while 式:
    スイート
else:
    スイート
```

## for文

while文同様、else節が書ける。

様々な書き方：

```py

for i in range(n):

for i in range(a, b):

for i in range(a, b, step):

for i in list(range(1, 8)) + list(range(9, 13))

for i, ch in enumerate(s):

# 1からカウント
for i, ch in enumerate(s, 1):

for ch in s:

# カウンタ用変数を使わないなら、_を使うと良い。
for _ in range(n):
```


# データ型

## 型を調べるには

type(式)
※ 式には型と値がある。

## NoneType型

- 他言語のNULLに近いのが、NonType型のNone

```py

if x is None:

if x is not None:
```

## bool型

- False（内部的に0）とTrue（内部的に1）。先頭が大文字。
- 偽とみなされるのは、False、0、0.0、None、空文字''、空リスト[]、空タプル()、空辞書{}等。これら以外とTrueは真とみなされる。

## 数値型

- int ： 整数型（桁数に制限なし）
- float ： 浮動小数点型（倍精度）
- お金の計算には、decimalモジュールのDecimal型を使う。

### 数値リテラル

途中の任意の場所に_を入れられる。

```py

10_000_000_000
```

### 算術演算

```py

# 除算演算子（演算は実数で行われる）
x / y

# 切捨て除算演算子（小数部を切り捨てて整数値を生成）
x // y
```

- ++、--はないが、+=、-=等はある。

## 文字列型（str型）

```py

# 文字数
len(s)

# 部分文字列
s[begin:end]

# 反転
s[::-1]
reversed(s)

# 文字列が別の文字列に含まれているか
if pattern [not] in text:
```

- 文字列結合は、+でつなぐよりもjoinメソッドを使った方が高速。

### f文字列

```py

a, b, c = 1, 2, 3
print(f"{a} + {b} + {c} = {a + b + c}")
```

### ヒアドキュメント

ヒアドキュメントは'''または"""で囲み、ソースコードでインデントしたら textwrap.dedent() で余計な空白等を削除できる。

```py

import textwrap

if True:
    s = '''
        SELECT
            *
        FROM
            dual;
    '''

    print("----")
    print(s)
    print("----")
    print(textwrap.dedent(s)[1:-1])
    print("----")
```

実行結果

```

----

        SELECT
            *
        FROM
            dual;
    
----
SELECT
    *
FROM
    dual;
----

```

## バイト列型（bytes型）

バイナリデータ。

```py

b"..."
```

## リスト型（list型）

```py

# リスト表記演算子
[ 式...]

# 反転
list(reversed(x))
```

- Pythonのリストは、連結リスト<font color="Red">ではなく</font>、配列である。

　　以下の本の p.173 に、そう説明されている。

　　新・明解Python入門　柴田望洋／著　ＳＢクリエイティブ　20190524
　　https://www.amazon.co.jp/dp/4815601526

　　※ teratailで関連質問している方がいました。
　　https://teratail.com/questions/113885

### リスト内包表記

```py

# [ 式 for 要素 in イテラブル ]
[ n - 1 for n in range(1, 8) ]

# [ 式 for 要素 in イテラブル if 判定式 ]
[ n for n in range(1, 8) if n % 2 == 0 ]
```

## タプル型（tuple型）

```py

# 式結合演算子（タプル表記演算子）
( 式...)

# 複数の変数へ異なる値を一括代入
x, y, z = 1, 2, 3
```

- イミュータブルなのでリストより低コスト
- ()は省略可

## 辞書型（dict型）

```py

# 辞書表記演算子
{ キー : 値 }

x = {}

# in演算子で調べられるのはキーで、値ではない。
if key in dic:

for key in dic.keys():

for key, value in dic.items():
```
- キーはイミュータブルである必要がある。
- キーが存在しないとき、値を取り出そうとしたら KeyError例外が発生する。

### 辞書内包表記

```py

{ 式 : 式 for 要素 in イテラブル }
```

## 集合型（set型）

```py

# 集合表記演算子
{ 式...}

x = set()

# ソート
sorted(x)
```

### 集合内包表記

```py

{ 式 for 要素 in イテラブル }
```

# 言語について、その他

- 行の終端の\で次の行に継続できる。
- カッコ記号()、[]、{}の中では自由に改行できる。

## 変数

- Pythonの変数は、値を格納する箱ではなく、オブジェクトを参照するもの（オブジェクトに結びつけられた名前）である。

　　　　以下の本の p.114 に、そう説明されている。

　　　　新・明解Python入門　柴田望洋／著　ＳＢクリエイティブ　20190524
　　　　https://www.amazon.co.jp/dp/4815601526

　　※ 関数型言語の名前束縛と同じかな。束縛からの解放がdelか。

- ミュータブルな型（値が変更可能）：リスト、辞書、集合など
- イミュータブルな型（値が変更不能）：数値、文字列、タプルなど

## 三項演算子（条件演算子）

```py

x if y else z
```

## 何も行わない文

```py

pass
```

## 文書化文字列

```py

def foo(n: int, s: str) -> None:
    """sをn回表示

    仮引数:
        n -- 表示する文字列の個数
        s -- 表示する文字列
    返却値:
        無し

    """
    for _ in range(n):
        print(s)


# 関数fooのドキュメントを表示する
help(foo)

# アノテーションを表示
print(foo.__annotations__)

# 文書化文字列を表示
print(foo.__doc__)

```

## global文、nonlocal文

```py

n = 1

def foo():
    global n # この文がないと、次の行のnがローカル変数になる
    n = 2
```

```py

def foo():
    n = 1
    def bar():
        nonlocal n # この文がないと、次の行のnがbar()のローカル変数になる
        n = 2
```

## ラムダ式

文ではなく式として関数を実現する。

lambda 仮引数並び : 返却値

```py

foo = lambda x, y: x + y

print((lambda x, y: x + y)(1, 2))
```

## カリー化

参考サイト：
https://kk6.hateblo.jp/entry/20110905/1315221175

## モジュール自作

```py

if __name__ == "__main__":
    テスト・デバッグ用のコード
```

## モジュール利用

```py

# モジュール名.オブジェクト名でアクセス
import モジュール名

# 別名.オブジェクト名でアクセス
import モジュール名 as 別名

# 単純名でアクセス。多用すべきでない
from モジュール名 import 名前1, 名前2, ...

# 原則として使うべきでない
from モジュール名 import *

# 名前に別名を与える
from モジュール名 import 名前 as 別名
```

- ディレクトリをPYTHONPATHに設定する。

## 例外処理

```py

class MyException(Exception):
    """マイ例外"""
    pass

try:
    raise MyException
except MyException as e:
    print(traceback.format_exc())    # トレースバックを表示
except (例外1, 例外2) as e:    # 複数の例外をタプルとして書ける
    スイート
except Exception as e:    # 事実上の最上位クラス
    スイート
except:    # 全ての例外をキャッチ。ただしExceptionで捕捉すべき。
    スイート
else:    # 省略可能。例外が捕捉されなかった場合に行う正常処理。
    スイート
finally:    # 省略可能。例外発生の有無に関わらず行う後処理。
    スイート

try:
    スイート
finally:
    スイート
```

# Tips

## ファイル処理

テキストファイルを1行ずつ読み書きするサンプル

```py

with open("input.txt", mode="r", encoding="utf-8") as fi, \
     open("output.txt", mode="w", encoding="utf-8") as fo:
    for line in fi:    # fiはイテラブルオブジェクト
        # lineには最後の改行文字も含まれている
        fo.write(line)
```

## sleep

```py

import time

time.sleep(秒)
```

# ロギング

参考サイト：
https://qiita.com/knknkn1162/items/87b1153c212b27bd52b4

以下、コード例：

## モジュール自作側

```py

import logging

logger = logging.getLogger(__name__)

...

# コード中のログ出力したい場所に埋め込む
logger.debug(文字列)
logger.info(文字列)
```

## モジュール利用側

```py

import logging
from pathlib import Path
import os
import datetime


def make_logfile_path(file: str) -> str:
    """
    ログファイル名の作成

    Parameters
    ----------
    file: str
        __file__が渡されることを想定

    Returns
    -------
    anonymous: str
        フルパスのログファイル名
    """
    log_dir = Path.cwd() / "log"

    # ディレクトリがなければ作成
    log_dir.mkdir(parents=True, exist_ok=True)

    return log_dir / os.path.basename(os.path.splitext(file)[0] + datetime.datetime.today().strftime("_%Y%m%d_%H%M%S.log"))


def get_logger(name: str, filepath: str):
    """
    ロガーの取得

    Parameters
    ----------
    name: str
        __name__が渡されることを想定
    filepath: str
        make_logfile_path(__file__)の戻り値が渡されることを想定

    Returns
    -------
    anonymous
        ログをコンソールに送るハンドラ
    anonymous
        ロガー
    """
    # 設定例
    LOG_LEVEL_FILE = logging.DEBUG      # ログファイルには詳細に出力
    LOG_LEVEL_CONSOLE = logging.INFO    # コンソールには簡便に出力

    # フォーマットを指定 (https://docs.python.jp/3/library/logging.html#logrecord-attributes)
    _detail_formatting = "\n%(asctime)s %(levelname)-8s [%(module)s#%(funcName)s %(lineno)d]\n%(message)s"

    logging.basicConfig(
        level=LOG_LEVEL_FILE,
        format=_detail_formatting,
        filename=filepath
    )

    # ログをコンソールに送るハンドラconsoleを作成
    console = logging.StreamHandler()
    console.setLevel(LOG_LEVEL_CONSOLE)
    console_formatter = logging.Formatter(_detail_formatting)
    console.setFormatter(console_formatter)

    # ロガーを取得し、consoleハンドラを追加する
    logger = logging.getLogger(name)
    logger.addHandler(console)
    return console, logger

###################################################################

console, logger = get_logger(__name__, make_logfile_path(__file__))

def main():
    logging.getLogger(使うモジュール名1).addHandler(console)
    logging.getLogger(使うモジュール名2).addHandler(console)

    ...

    # コード中のログ出力したい場所に埋め込む
    logger.debug(文字列)
    logger.info(文字列)
```

# pytest

PyCharmでpytestを使う設定：
https://pleiades.io/help/pycharm/pytest.html

- 以前使っていた各種ファイル

```tests/pytest.ini

[pytest]
#addopts = -rsxX -l --tb=short --strict    -v --runxfail
addopts = -rsxX -l --strict    -v -x --capture=no
xfail_strict = true
```

```tests/conftest.py

import pytest
import time
import sys
import os

sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
from src import common
from tests import tescommon


console, logger = common.get_logger(__name__, tescommon.stub_make_logfile_path("test"))


@pytest.fixture(autouse=True, scope='session')
def session_scope():
    """Report test durations after each session."""
    start = time.time()
    logger.info(f"pytest started : {time.strftime('%d %b %X', time.localtime(start))}")
    yield
    stop = time.time()
    delta = stop - start
    logger.info(f"pytest finished : {time.strftime('%d %b %X', time.localtime(stop))}")
    logger.info("pytest session duration : {:0.3} seconds".format(delta))


@pytest.fixture(autouse=True)
def function_scope():
    """Report test durations after each function."""
    start = time.time()
    yield
    stop = time.time()
    delta = stop - start
    logger.info("pytest function duration : {:0.3} seconds".format(delta))


@pytest.fixture(scope="session")
def pg_server_conn():
    # DB接続 ＆ テスト呼び出し
    with common.connect_pg_server() as conn:
        yield conn


@pytest.fixture(scope="session")
def sqlite_conn():
    if common.is_in_docker_container():
        yield None
    else:
        # DB接続 ＆ テスト呼び出し
        with common.connect_sqlite() as conn:
            yield conn
```