オレオレPythonコードスニペット集

**随時更新**

# 文法

## 型

```py
# タプル
from typing import Tuple
def some_func() -> Tuple[str, bool]:
    pass

# リストと辞書
from typing import Dict, List
somelist: List[Dict] = []
```

# ワンライナーちっくなの

## 環境変数の利用
環境変数に定義してあればその値を利用して、なければデフォルト値を設定する。
サンプルは環境変数dryrunがあればその値をboolのTrue/Falseに変換し、なければFalseを設定する。

```python
import os
from distutils.util import strtobool
dryrun = strtobool(os.environ['dryrun']) if 'dryrun' in os.environ else False
```

## リスト生成
セミコロン区切りのhostsを分解して、値があるものだけでリストを作る。
前後の空白は取り除く。

```py
results = [host.strip() for host in hosts.split(';') if not host.strip() == '']
```

## 文字列操作

文字列置換

```py
'cp conf/env-%(stage)s.conf %(release)s/settings.conf' % {
    'release': env.current_release,
    'stage': env.stage
}
```

Python3.6以上なら

```py
f'cp conf/env-{env.current_release}.conf {env.stage}/settings.conf'
```





# 処理

## スクリプト化

```py
#!/usr/bin/env python

def main():
    print('hoge')

if __name__ == '__main__': main()
```

## 日付系

JSTで1日前

```py
from datetime import datetime, timedelta, timezone

JST = timezone(timedelta(hours=+9), 'JST')
yesterday = datetime.now(JST) + timedelta(days=-1)
```

datetimeオブジェクトの内容を変更
（例：時分秒を0にする）

```py
from datetime import datetime
today = (datetime.today()).replace(hour=0,minute=0,second=0,microsecond=0)
```

### フォーマット

YYYYMMDD形式の文字列をdatetimeへ変換

```py
from datetime import datetime
yyyymmdd_datetime = datetime.strptime("20190321", '%Y%m%d')
```

datetimeを文字列化

```py
date_str = datetime.now(JST).strftime('%Y%m%d')
```

timestamp用フォーマット

```py
# yyyy-MM-ddTHH:mm:ss.SSSSSSZ形式
timestamp = datetime.now(JST).strftime("%Y-%m-%dT%H:%M:%S.%fZ")
```


### 月の操作

datetimeでmonthが操作できないのでdateutil.relativedeltaを使う。
dateutilを使うためにpython-dateutilをインストール

```
$ pip install python-dateutil
```

17ヶ月前の月頭（1日）にする場合

```py
from dateutil.relativedelta import relativedelta
from datetime import datetime, timedelta, timezone

JST = timezone(timedelta(hours=+9), 'JST')

today = datetime.now(JST)
before_seventeenth_month = today + relativedelta(months=-17)
start_date = before_seventeenth_month.replace(day=1)
```


## ファイル操作

ディレクトリの削除（エラーは無視）

```py
import shutil
shutil.rmtree('ディレクトリパス', ignore_errors=True)
```

ファイル一覧取得

https://docs.python.org/ja/3/library/glob.html

```py
import glob
file_list = glob.glob("/hoge_dir/*")
# -> file_list = [hoge.txt, hoo.jpg]
```

```py
from pathlib import Path

def get_target_files(target_dir):
    """
    指定ディレクトリ配下のファイル情報を取得する。
    無ければ空のリストを返す。
    Parameters
    ----------
    target_dir : str
       対象ディレクトリ

    Returns
    -------
    list(PosixPath) :
        対象ディレクトリ配下のファイルパスのリスト
    """
    p = Path(target_dir)
    target_files = list(p.glob("*"))
    return target_files

filepath_list = get_target_files("/hoge_dir")
for filepath in filepath_list:
    print(str(filepath))
    # -> /hoge_dir/hoge.txt
```



## IPアドレスチェック

```py
import ipaddress

allowed_hosts = [
    '127.0.0.1',
    '172.27.0.0/24'
]

def check_host(host_addr):
    for allowed_host in allowed_hosts:
        if is_valid_ip(allowed_host):
            if host_addr == allowed_host:
                return True
        elif is_valid_network(allowed_host):
            if ipaddress.ip_address(host_addr) in ipaddress.ip_network(allowed_host):
                return True
        else:
            raise Exception(f"allowed_hostsに不正な値が設定されている: {allowed_host}")
    return False


def is_valid_ip(ip_addr):
    try:
        ipaddress.ip_address(ip_addr)
        return True
    except ValueError:
        return False


def is_valid_network(ip_addr):
    try:
        if not is_valid_ip(ip_addr):
            ipaddress.ip_network(ip_addr)
            return True
    except ValueError:
        return False
```


## 文字列チェック: match


```py
import re

re.match(check_str, check_target)
```








# boto3
boto3のサンプル

## S3




# AWS Lambda
LambdaでPythonを使う際のTips

## eventパラメータチェック

キーがあればその値、なければデフォルト値を使う。

```py
ignore_folders = []
if 'ignore_folders' in event and type(event['ignore_folders']) is list:
    ignore_folders = event['ignore_folders']
```

SNSのMessage部分を取る方法

```py
event['Records'][0]['Sns']['Message']
```

## 環境変数の利用

getenvを使うと、mypyのOptionalチェックがうざいので、Pythonにスマートキャストが来るまではenvironにしておく。

```py
import os

BUCKET_NAME = os.environ['BUCKET_NAME']
```

# 関数系

## コンフィグファイルローダー

特定箇所にあるコンフィグファイルを読み込む。

configディレクトリまでの階層によって、場所指定は適時変える。
以下はconfig/とsrc/utils/config.pyの階層構造を想定した場合。

https://docs.python.jp/3/library/configparser.html

```python:config.py
import os
import configparser


def get_config(config_file_name):
    """
    configディレクトリ配下のコンフィグファイルをロードして返却する

    Parameters
    ----------
    config_file_name : str
        ファイル名

    Returns
    -------
    SafeConfigParser
        ロードしたコンフィグ情報
    """
    config_path = os.path.join(
        os.path.dirname(__file__), '../../config/' + config_file_name)
    config = configparser.SafeConfigParser()
    config.read(config_path, encoding='utf8')

    return config
```

### コンフィグロードをテストする

```ini:settings.ini
[dev]
target_names = ["seri", "wb"]
```

```py:test_sample.py
import os
import configparser
import json


def test_get_config_settings():
    config_file_name = 'settings.ini'
    config_path = os.path.join(
        os.path.dirname(__file__), '../config/' + config_file_name)
    config = configparser.SafeConfigParser()
    config.read(config_path, encoding='utf8')
    
    actual = json.loads(config['dev']['target_names'])[0]

    expected = 'seri'
    assert actual == expected
```


# 設定系

## requirements.txt
ファイルに`-e .`が書いてあるのは、編集可能な開発モードでパッケージをインストールするよ、っていうこと。

```:requirements.txt
-e .
```

- https://stackoverflow.com/questions/51010251/what-does-e-in-requirements-txt-do
- https://www.lifewithpython.com/2018/07/python-install-package-dev-versions.html#%E3%81%8A%E3%81%BE%E3%81%91-a-editable-mode-%E3%81%A7%E3%81%AE%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%BC%E3%83%AB


## flake8の定義ファイル
コードチェック用

```toml:.flake8
[flake8]
[flake8]
ignore = D203, D400, E265, E401, E402, E701
exclude =
    .git,
    __pycache__,
    bin,
    lib,
    build,
    dist
max-line-length = 120
```

## blackの定義ファイル
コードフォーマッタ

- https://github.com/ambv/black

```toml:pyproject.toml
[tool.black]
line-length = 120
exclude = '''
(
    migrations
    | .mypy_cache
    | .pytest_cache
    | .tox
    | venv
)
'''
```


## VSCode
venv環境を認識させる

```json:.vscode/settings.json
{
    "python.pythonPath": "bin/python3"
}
```

## pythonでのテスト実行

デフォルトだと`test_ほげほげ.py`をテストとして見るので、パターンが違う場合は-pで指定する

```
python -m unittest discover tests
python -m unittest discover -p "*_test.py" tests
```

サブディレクトリまでは見ないので、そういう場合はこんな感じのことをするらしい
https://qiita.com/yoichi22/items/2b488dc0696d9b45fad6

### テストのスキップ

```py
import unittest

class SampleTest(unittest.TestCase):

    @unittest.skip('スキップ理由などのコメント')
    def test_add_number_plus_double(self):
        pass
```



# コードサンプル

## サンプル1: 文字列チェック
指定の文字列が入力文字列で何個登場するかを返却するプログラム。
前後の文字列をつなげた場合に指定の文字列が登場する場合も、カウントする。

```py
import sys


MIN = 1
MAX = 100
CHECKS = "watashihanoone"


def main():
    argvs = sys.argv
    argc = len(argvs)
    if (argc == 2):
        input_data = argvs[1]
        if not (input_data and len(input_data) >= MIN and len(input_data) <= MAX):
            print(f'入力可能文字数は{MIN}以上{MAX}以下です')
            exit(-1)
    else:
        print('パラメータが不正です')
        exit(-1)

    result = 0
    
    if len(input_data) >= len(CHECKS):
        add_position = len(CHECKS) - 1
        result = f'{input_data}{input_data[:add_position]}'.count(CHECKS)

    print(result)
    return result

if __name__ == '__main__': main()

```






# Tips

3系からはファイルの先頭にUTF-8指定を書かなくても、デフォルトでUTF-8固定になった。

```py
# -*- coding:utf-8 -*-
```

## Zen of Python

REPLでimport thisを実行すると表示される

```
>>> import this
The Zen of Python, by Tim Peters

Beautiful is better than ugly.
Explicit is better than implicit.
Simple is better than complex.
Complex is better than complicated.
Flat is better than nested.
Sparse is better than dense.
Readability counts.
Special cases aren't special enough to break the rules.
Although practicality beats purity.
Errors should never pass silently.
Unless explicitly silenced.
In the face of ambiguity, refuse the temptation to guess.
There should be one-- and preferably only one --obvious way to do it.
Although that way may not be obvious at first unless you're Dutch.
Now is better than never.
Although never is often better than *right* now.
If the implementation is hard to explain, it's a bad idea.
If the implementation is easy to explain, it may be a good idea.
Namespaces are one honking great idea -- let's do more of those!
```








----
# ナレッジ（おまけ）

Pythonでコーディングしてた際に詰まったところやナレッジのまとめ。

- タイムゾーン指定の話
https://qiita.com/mattsu6/items/5511c5631e7a54550f7f
- boto3をインストールしようとしたらコケたので、この設定を入れた
https://yuzu441.hateblo.jp/entry/2015/10/15/212314
- importで読み込むパスの話。カレントディレクトリにpipでインストールした場合読み込まれるのはこれが理由っぽい
https://note.nkmk.me/python-import-module-search-path/
- Intellijでプロジェクトを開いているときにpipでインストールしたモジュールをIntellij側に認識させるには、Intellijを再起動する
- Pythonのコーディング規約：PEP8
  - http://pep8-ja.readthedocs.io/ja/latest/
  - https://www.yoheim.net/blog.php?q=20160612
- 存在チェックはisとis notを使う
http://monmon.hateblo.jp/entry/20110214/1297710749
- Noneとのチェックはisinstanceを使うみたい
  - http://python.g.hatena.ne.jp/oneshotlife_tom/20121127/1354009706
  - https://www.lifewithpython.com/2014/06/python-check-object-instance-class-relationship-by-type-isinstance.html
- json.dumpsで変換したのを再度json.dumps内のKeyのValue値にすると、文字列として認識してうまく動かなかった
https://docs.python.jp/3/library/json.html
- 2系にあって3系にないurlencodeの話
https://teratail.com/questions/51894
- 正規表現の使い方
https://uxmilk.jp/41416
- VS CodeのPython VersionはデフォルトだとBashからの読み込み臭い。
変更する場合はウインドウ左下のPython +バージョンのところをクリックすればOK
- ファイル情報の取得系
https://qiita.com/amowwee/items/e63b3610ea750f7dba1b
- IPアドレスのチェック https://teratail.com/questions/101693