217
237

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

お題は不問!Qiita Engineer Festa 2024で記事投稿!
Qiita Engineer Festa20242024年7月17日まで開催中!

「Python標準ライブラリのドキュメントでも読むか~」「え、何その裏技」

Last updated at Posted at 2024-07-15

読み飛ばしてください

おはようございます、しなもんです。

この記事は↓の続編的立ち位置です。

前回の記事がありがたいことに爆発的にトレンド入りしました。ありがとうございました。
(あれらの機能を知らなかったのが私だけじゃないことが分かって安心しました)
思ったより反応があり、Twitter(自称X)でもいろいろ意見をいただきました。

というわけで、今回は標準ライブラリのドキュメントを読んでみました。

予想通り、知らない裏技がたくさん出てきました。
Pythonすごい。

functoolsが便利すぎる

partialを使った関数の部分適用

partial関数を使うと、関数の一部の引数を固定した新しい関数を作成できるようです。
コールバック関数を使う際や、関数型プログラミングっぽいコードを書く際に非常に便利なようです。

私は関数型プログラミングには明るくないのですが、
クラスの継承的なことをしなくても、関数で部分適用ができるのは便利ですね。
覚えておきたいです。

from functools import partial

def greet(greeting, name):
    return f"{greeting}, {name}!"

say_hello = partial(greet, "Hello")
say_goodmorning = partial(greet, "Good morning")

print(say_hello("Alice"))  # 出力: Hello, Alice!
print(say_goodmorning("Bob"))  # 出力: Good morning, Bob!

singledispatchによるシンプルな関数オーバーロード

singledispatchデコレータを使うと、引数の型に基づいて異なる実装を持つ関数を簡単に作成できます。

from functools import singledispatch

@singledispatch
def convert(obj):
    return f"Unknown type: {type(obj)}"

@convert.register(str)
def _(text):
    return text.upper()

@convert.register(int)
def _(number):
    return f"0x{number:x}"

@convert.register(list)
def _(lst):
    return [elem * 2 for elem in lst]

print(convert("hello"))  # 出力: HELLO
print(convert(42))  # 出力: 0x2a
print(convert([1, 2, 3]))  # 出力: [2, 4, 6]
print(convert(3.14))  # 出力: Unknown type: <class 'float'>

関数の中で、typeisinstanceで条件分岐すればいいのでは...?と思いましたが、
型ごとの処理が長い場合や、拡張性を求める場合には@singledispatchデコレータのほうが便利なのかな。

itertoolsもいろいろある

groupbyを使ったデータのグルーピング

groupby関数を使うと、イテラブルの連続する要素をグループ化できるらしい。

from itertools import groupby

data = [1, 1, 1, 2, 2, 3, 4, 4, 5, 1, 1]
for key, group in groupby(data):
    print(f"{key}: {list(group)}")

# 出力:
# 1: [1, 1, 1]
# 2: [2, 2]
# 3: [3]
# 4: [4, 4]
# 5: [5]
# 1: [1, 1]

sort()とはまた違った感じのものがあるんですね。
返ってくるのはイテレータなので注意です。
適当にlist()等を使うなどで対応しましょう。

ちなみにpandasに同名の関数があるようなのですが、挙動は異なるので注意です。

データクラス

field関数を使ったカスタムフィールド

field関数を使うと、データクラスのフィールドをカスタマイズできます。
正直言ってもっと早く知りたかった。

ちょっと違うかもですがDjangoのModelが標準ライブラリでできたのか...

from dataclasses import dataclass, field
import random

def generate_id():
    return random.randint(1000, 9999)

@dataclass
class User:
    name: str
    email: str
    id: int = field(default_factory=generate_id)
    active: bool = field(default=True, repr=False)

user = User("Alice", "alice@example.com")
print(user)  # 出力例: User(name='Alice', email='alice@example.com', id=5678)

__post_init__メソッドを活用した初期化後の処理

__post_init__メソッドを使うと、データクラスの初期化後に勝手に追加の処理を行ってくれるらしい。
しかも、__eq__()などの特殊メソッドも勝手に実装してくれるらしい。

from dataclasses import dataclass, field

@dataclass
class Rectangle:
    width: float
    height: float
    area: float = field(init=False)

    def __post_init__(self):
        self.area = self.width * self.height

rect = Rectangle(5, 3)
print(f"Area: {rect.area}")  # 出力: Area: 15.0

少し脱線しますが、どこかでこんなの見たことあるなーて思ったら、
私が運営しているサービスに@nikawamikanがコントリビュートしてたコードでした。
こちらはpydanticでしたが。pydanticいいですよね。

正規表現モジュール(re)にも裏技がある

名前付きグループを使った複雑なパターンマッチング

名前付きグループを使うと、複雑な正規表現パターンをより読みやすく、管理しやすくできるらしい。
なんだと???

import re

pattern = r"(?P<year>\d{4})-(?P<month>\d{2})-(?P<day>\d{2})"
match = re.match(pattern, "2023-05-15")

if match:
    print(f"Year: {match.group('year')}")
    print(f"Month: {match.group('month')}")
    print(f"Day: {match.group('day')}")

# 出力:
# Year: 2023
# Month: 05
# Day: 15

re.VERBOSEフラグを使った読みやすい正規表現

re.VERBOSEフラグを使うと、複雑な正規表現パターンを複数行で書ける上に、コメントも追加できるらしい。

import re

pattern = re.compile(r"""
    ^                   # 行の先頭
    (\d{3})             # 3桁の数字(エリアコード)
    [-\s]?              # ハイフンまたは空白(オプション)
    (\d{3})             # 3桁の数字
    [-\s]?              # ハイフンまたは空白(オプション)
    (\d{4})             # 4桁の数字
    $                   # 行の末尾
""", re.VERBOSE)

phone_numbers = ["123-456-7890", "987 654 3210", "1112223333"]

for number in phone_numbers:
    if pattern.match(number):
        print(f"{number} is valid")
    else:
        print(f"{number} is invalid")

# 出力:
# 123-456-7890 is valid
# 987 654 3210 is valid
# 1112223333 is valid

さいごに

もっと早く知りたかった機能たくさんあったな...。
(多分まだまだ初心者なだけ。)

217
237
0

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
217
237

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?