随時更新
文法
型
# タプル
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の階層構造を想定した場合。
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
コンフィグロードをテストする
[dev]
target_names = ["seri", "wb"]
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 .
が書いてあるのは、編集可能な開発モードでパッケージをインストールするよ、っていうこと。
-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の定義ファイル
コードチェック用
[flake8]
[flake8]
ignore = D203, D400, E265, E401, E402, E701
exclude =
.git,
__pycache__,
bin,
lib,
build,
dist
max-line-length = 120
blackの定義ファイル
コードフォーマッタ
[tool.black]
line-length = 120
exclude = '''
(
migrations
| .mypy_cache
| .pytest_cache
| .tox
| venv
)
'''
VSCode
venv環境を認識させる
{
"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でコーディングしてた際に詰まったところやナレッジのまとめ。
- タイムゾーン指定の話
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
- 存在チェックはisとis notを使う
http://monmon.hateblo.jp/entry/20110214/1297710749 - Noneとのチェックはisinstanceを使うみたい
- 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