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

## 読み飛ばしてください

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

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

https://qiita.com/CinnamonSea2073/items/cec093f5a9298aadcdc1

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

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

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

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

## `functools`が便利すぎる

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

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

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

```python
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!
```

https://qiita.com/tetsuya-zama/items/146d3ee9154e15158f38

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

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

```python
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'>
```

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

## `itertools`もいろいろある

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

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

```python
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](みたいなことhttps://qiita.com/ykoji/items/87cf343b92ebd0656107)が標準ライブラリでできたのか...

```python
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__()`などの特殊メソッドも勝手に実装してくれるらしい。

```python
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
```

https://qiita.com/ttyszk/items/01934dc42cbd4f6665d2

少し脱線しますが、どこかでこんなの見たことあるなーて思ったら、
私が運営しているサービスに[@nikawamikan](https://qiita.com/nikawamikan)がコントリビュートしてた[コード](https://github.com/GenshinStatus/genshin-status-image-builder/blob/main/app/model/assets_model.py)でした。
こちらは`pydantic`でしたが。`pydantic`いいですよね。

## 正規表現モジュール（re）にも裏技がある

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

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

```python
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`フラグを使うと、複雑な正規表現パターンを複数行で書ける上に、コメントも追加できるらしい。

```python
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
```

## さいごに

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