CODE Polyglot / よく使う書き方 / Python

Python のよく使う書き方(7本)

開発でよく書くコードの型と小さなツールの、Python の版をまとめました。標準の機能だけで書いていて、毎朝、実際に動かして答えを確かめています。

01. スネークケースとキャメルケースを変換する

user_id と userId を行き来させます。データベースの列名と JSON のキー、定数と変数のように、名前の書き方をそろえるときに使います。HTTPServer のように大文字が続く名前で、よく見かける書き方はほとんどの言語で崩れます。 ほかの言語・正解の例・解説 →

case_convert.py3.14.7 で 14 / 14 件の出力が正解と一致

# スネークケース(user_id)とキャメルケース(userId)を、指定した向きに変換する
import re
import sys

NG = "NG: 半角の英数字と _ だけにしてください"
mode, s = sys.argv[1], sys.argv[2]

# \w は「なまえ」のようなひらがなにも合う(Python の文字列は Unicode が基本)。使ってよい文字を [A-Za-z0-9_] と書く
if not re.fullmatch(r"[A-Za-z0-9_]*", s):
    print(NG)
elif mode == "snake":
    # 切れ目は2種類。小文字か数字の次の大文字(userId)と、大文字の続きの最後で次が小文字のもの(HTTP|Server)
    # 先読み・後読みで「位置」だけに合わせると、文字を食わないので2つの切れ目が重なっても取りこぼさない
    s = re.sub(r"(?<=[a-z0-9])(?=[A-Z])", "_", s)
    s = re.sub(r"(?<=[A-Z])(?=[A-Z][a-z])", "_", s)
    print(s.lower())
else:
    # split("_") は「__」のところで空の部分を作る。空の部分は捨てる
    parts = [p for p in s.split("_") if p]
    # capitalize() は先頭を大文字・残りを小文字にする(title() は数字のあとも大文字にするので使わない)
    print("".join([parts[0].lower()] + [p.capitalize() for p in parts[1:]]) if parts else "")

02. HTML の特殊文字をエスケープする

利用者が入力した文字を HTML に埋め込む前に、< > & " ' を &lt; などに置き換えます。標準の関数は言語ごとに ' の書き方が違い、この決まりの形とそのまま一致したのは Ruby だけでした。 ほかの言語・正解の例・解説 →

escape.py3.14.7 で 8 / 8 件の出力が正解と一致

# 受け取った文字列の & < > " ' を、HTML の書き方(&amp; &lt; &gt; &quot; &#39;)に変える
import sys

s = sys.argv[1]

# html.escape は ' を &#x27; と書く(この回の &#39; と違う)ので、自分で置き換える。
# translate は1文字ずつ1回だけ置き換えるので、& を先に変える順番を気にしなくてよい
TABLE = str.maketrans({"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"})
print(s.translate(TABLE))

03. 順番を保って重複を消す

同じ値の2回目以降を消し、初めて出た順に残します。タグの一覧や、選んだ項目の一覧でよく使います。集合(Set)に入れて戻す書き方では、言語によって順番が変わったり、実行するたびに変わったりします。 ほかの言語・正解の例・解説 →

unique.py3.14.7 で 9 / 9 件の出力が正解と一致

# 受け取った文字列から、2回目以降に出たものを消し、初めて出た順に出す
import sys

items = sys.argv[1:]
# dict は入れた順を覚えている(3.7 から言語の決まり)。set は順を覚えないので、list(set(...)) は並びが変わる
out = list(dict.fromkeys(items))
for s in out:
    print("(空)" if s == "" else s)
print(f"{len(out)}件({len(items) - len(out)}件を消した)")

04. 日付と時刻を ISO 8601 で読み書きする

API やログでよく使う 2026-10-30T09:00:00+09:00 の形を読み、UTC と日本時間で書き直します。標準のパーサーにそのまま渡すと、2月30日を3月2日として読んだり、タイムゾーンの無い日時を動かす場所の時間帯で読んだりします。 ほかの言語・正解の例・解説 →

iso8601.py3.14.7 で 15 / 15 件の出力が正解と一致

# ISO 8601 の日時を受け取って、UTC と日本時間で書き出す
import re
import sys
from datetime import datetime, timedelta, timezone

# datetime.fromisoformat は 3.11 から「20261030T090000」や「+0900」も受け付けるので、形は先に正規表現で決める。
# \d は全角の数字にも当たるので [0-9] と書く
ISO = re.compile(r"([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2})(\.[0-9]{1,9})?(Z|[+-][0-9]{2}:[0-9]{2})?")

m = ISO.fullmatch(sys.argv[1])
if not m:
    print("NG: 日時の形ではありません")
    sys.exit()
y, mo, d, h, mi, se, _frac, tz = m.groups()
# datetime は 2月30日や25時を ValueError にする
try:
    local = datetime(int(y), int(mo), int(d), int(h), int(mi), int(se))
except ValueError:
    print("NG: 日時の形ではありません")
    sys.exit()
# タイムゾーンの無い datetime を astimezone() に渡すと、動かす場所の時間帯で読まれてしまう
if tz is None:
    print("NG: タイムゾーン(Z か +09:00 など)を付けてください")
    sys.exit()
if tz == "Z":
    offset = timedelta(0)
else:
    oh, om = int(tz[1:3]), int(tz[4:6])
    if oh > 23 or om > 59:
        print("NG: 日時の形ではありません")
        sys.exit()
    offset = (1 if tz[0] == "+" else -1) * timedelta(hours=oh, minutes=om)

# timezone(timedelta) は時差だけのタイムゾーン。tzdata を見ないので、どこで動かしても同じになる
t = local.replace(tzinfo=timezone(offset))
utc = t.astimezone(timezone.utc)
jst = t.astimezone(timezone(timedelta(hours=9)))
print(f"UTC {utc:%Y-%m-%dT%H:%M:%S}Z")
print(f"日本時間 {jst:%Y年%m月%d日 %H:%M:%S}")

05. アクセスログを集計する

Web サーバーのアクセスログを標準入力から読み、ステータスごとの件数と、応答の遅いリクエストの上位3件を出します。空白で区切って何番目かを取る書き方は手軽ですが、壊れた行を数えたり、応答秒を文字のまま並べたりします。 ほかの言語・正解の例・解説 →

access_log.py3.14.7 で 8 / 8 件の出力が正解と一致

# 標準入力のアクセスログを読んで、件数・ステータスごとの件数・遅い順の上位3件を出す
import re
import sys

# 日時やHTTPの版には空白が入るので、空白で区切って何番目と数えず、行の形そのものを正規表現で確かめる
LOG = re.compile(r'\S+ \S+ \S+ \[[^\]]+\] "(\S+) (\S+) [^"]+" ([0-9]{3}) ([0-9]+|-) ([0-9]+\.[0-9]+)')

rows = []
bad = 0
# 既定の sys.stdin は \r\n を \n にそろえて読むが、newline="" やバイトで読むと \r が行に残る。どちらでも困らないよう \r を取る
for line in sys.stdin.read().split("\n"):
    line = line.rstrip("\r")
    if line == "":
        continue
    # match は先頭だけ合えば通る。行の終わりまで形どおりか確かめるには fullmatch
    m = LOG.fullmatch(line)
    if m is None:
        bad += 1
        continue
    method, path, status, _, sec = m.groups()
    rows.append((sec, method, path, status))

print(f"件数 {len(rows)}")
if bad:
    print(f"読めない行 {bad}")

counts = {}
for _, _, _, status in rows:
    counts[status] = counts.get(status, 0) + 1
print("ステータス")
# ステータスは3桁そろいなので、文字のまま並べても数の小さい順になる
for status in sorted(counts):
    print(f"{status} {counts[status]}")

print("遅い順")
# 応答秒は文字のまま並べると "10.5" < "9.8" になるので、数にして比べる。
# sorted は安定(同じ値なら元の順)。reverse=True でも同じ秒どうしの順は崩れない
for sec, method, path, _ in sorted(rows, key=lambda r: float(r[0]), reverse=True)[:3]:
    print(f"{sec} {method} {path}")

06. URL 用の文字列(slug)を作る

記事のタイトルから「hello-world」のような URL の一部を作ります。ブログや商品ページのアドレスを、見て意味が分かる形にするときに使います。よく見かける書き方は、日本語のタイトルをそのまま URL に入れたり、空の slug を黙って返したりします。 ほかの言語・正解の例・解説 →

slug.py3.14.7 で 14 / 14 件の出力が正解と一致

# 文字列を URL に使える形(slug)に変える
import re
import string
import sys

MAX = 32
s = sys.argv[1]

# lower() は Unicode の決まりで変えるので、İ が「i」+点の2文字になる。A〜Z だけを変える表を作る
s = s.translate(str.maketrans(string.ascii_uppercase, string.ascii_lowercase))
# Python の \w は「日本語」にも合う。残す文字を [a-z0-9] と書いて、それ以外を続いている分まとめる
s = re.sub(r"[^a-z0-9]+", "-", s).strip("-")
if len(s) > MAX:
    s = s[:MAX].rstrip("-")
print(s or "NG: URL に使える文字がありません")

07. URL エンコードする

検索語や日本語の名前を、URL のクエリの値(`?q=...`)に入れられる形に変えます。よく見かける書き方は、空白を「+」にしたり、`!` や `~` を変えずに残したりして、受け取る側で別の文字列になります。 ほかの言語・正解の例・解説 →

url_encode.py3.14.7 で 15 / 15 件の出力が正解と一致

# 文字列を URL のクエリの値に入れられる形(パーセントエンコード)に変える
import sys
from urllib.parse import quote

# safe="" を付けないと「/」が残る(既定が safe="/" なのでパスに使う想定)。quote_plus は空白を「+」にする
print(quote(sys.argv[1], safe=""))

ほかの言語