LoginSignup
19
19

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-02-21

随時更新

文法

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

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

ワンライナーちっくなの

環境変数の利用

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

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

リスト生成

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

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

文字列操作

文字列置換

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

Python3.6以上なら

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

処理

スクリプト化

#!/usr/bin/env python

def main():
    print('hoge')

if __name__ == '__main__': main()

日付系

JSTで1日前

from datetime import datetime, timedelta, timezone

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

datetimeオブジェクトの内容を変更
(例:時分秒を0にする)

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

フォーマット

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

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

datetimeを文字列化

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

timestamp用フォーマット

# 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日)にする場合

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)

ファイル操作

ディレクトリの削除(エラーは無視)

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

ファイル一覧取得

import glob
file_list = glob.glob("/hoge_dir/*")
# -> file_list = [hoge.txt, hoo.jpg]
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アドレスチェック

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

import re

re.match(check_str, check_target)

boto3

boto3のサンプル

S3

AWS Lambda

LambdaでPythonを使う際のTips

eventパラメータチェック

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

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

SNSのMessage部分を取る方法

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

環境変数の利用

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

import os

BUCKET_NAME = os.environ['BUCKET_NAME']

関数系

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

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

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

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

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

settings.ini
[dev]
target_names = ["seri", "wb"]
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 .

flake8の定義ファイル

コードチェック用

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

blackの定義ファイル

コードフォーマッタ

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

VSCode

venv環境を認識させる

.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

テストのスキップ

import unittest

class SampleTest(unittest.TestCase):

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

コードサンプル

サンプル1: 文字列チェック

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

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固定になった。

# -*- 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でコーディングしてた際に詰まったところやナレッジのまとめ。

19
19
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
19
19