Python

関数型プログラミング

Python

lambda・map・filter・itertools・generator

lambda と高階関数

map・filter・reduce・sorted

functional.py python
from functools import reduce

# lambda(無名関数、シンプルな処理のみに)
double = lambda x: x * 2
add    = lambda x, y: x + y

# map: 各要素に関数を適用
nums = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, nums))  # [2,4,6,8,10]
# 内包表記が読みやすい場合が多い:
doubled = [x * 2 for x in nums]

# filter: 条件を満たす要素を残す
evens = list(filter(lambda x: x % 2 == 0, nums))  # [2,4]
evens = [x for x in nums if x % 2 == 0]           # 内包表記版

# reduce: 累積処理
total   = reduce(lambda acc, x: acc + x, nums, 0)  # 15
product = reduce(lambda acc, x: acc * x, nums, 1)  # 120

# sorted のキー関数
users = [{'name': 'Charlie', 'age': 30}, {'name': 'Alice', 'age': 25}]
sorted_by_age  = sorted(users, key=lambda u: u['age'])
sorted_by_name = sorted(users, key=lambda u: u['name'])

# operator モジュール(lambda の代わり)
from operator import attrgetter, itemgetter
sorted(users, key=itemgetter('age'))         # dictのキー
sorted(objects, key=attrgetter('name'))      # 属性
sorted(objects, key=attrgetter('x', 'y'))    # 複数属性

ジェネレーター

yield・yield from・ジェネレーター式

generators.py python
# ジェネレーター関数(yield で値を返しつつ状態を保持)
def count_up(start: int = 0):
    while True:
        yield start
        start += 1

counter = count_up()
next(counter)  # 0
next(counter)  # 1
next(counter)  # 2

# 有限ジェネレーター
def fibonacci(n: int):
    a, b = 0, 1
    for _ in range(n):
        yield a
        a, b = b, a + b

list(fibonacci(8))  # [0,1,1,2,3,5,8,13]

# yield from(サブジェネレーターに委譲)
def chain(*iterables):
    for it in iterables:
        yield from it

list(chain([1,2], [3,4], [5,6]))  # [1,2,3,4,5,6]

# ジェネレーター式(メモリ効率)
sum_sq = sum(x**2 for x in range(1_000_000))  # リストを生成しない

# 実用パターン:大きなファイルを行ごとに処理
def read_large_file(path: str):
    with open(path) as f:
        yield from f  # 1行ずつ yield

# パイプライン
def grep(pattern, lines):
    import re
    return (line for line in lines if re.search(pattern, line))

def head(lines, n=10):
    return (line for _, line in zip(range(n), lines))

lines = read_large_file('app.log')
errors = grep(r'ERROR', lines)
first_10 = head(errors, 10)
for line in first_10:
    print(line, end='')

itertools

chain・product・groupby・islice・accumulate

itertools_examples.py python
import itertools

# 結合・繰り返し
list(itertools.chain([1,2], [3,4], [5]))     # [1,2,3,4,5]
list(itertools.chain.from_iterable([[1,2],[3,4]]))  # [1,2,3,4]
list(itertools.repeat('x', 3))              # ['x','x','x']
list(itertools.cycle('AB'))                 # A,B,A,B,...(無限)

# スライス
list(itertools.islice(range(100), 5, 15, 2))  # [5,7,9,11,13]

# 組み合わせ
list(itertools.product('AB', repeat=2))     # AA,AB,BA,BB
list(itertools.combinations('ABCD', 2))     # AB,AC,AD,BC,BD,CD
list(itertools.permutations('ABC', 2))      # AB,AC,BA,BC,CA,CB

# 累積
list(itertools.accumulate([1,2,3,4,5]))     # [1,3,6,10,15]
list(itertools.accumulate([1,2,3,4,5], max)) # [1,2,3,4,5]

# グループ化(事前ソートが必要)
data = [('A',1),('A',2),('B',3),('B',4),('C',5)]
for key, group in itertools.groupby(data, key=lambda x: x[0]):
    print(key, list(group))
# A [('A',1),('A',2)]
# B [('B',3),('B',4)]

# フィルタリング
list(itertools.takewhile(lambda x: x < 5, [1,2,3,4,5,6]))  # [1,2,3,4]
list(itertools.dropwhile(lambda x: x < 5, [1,2,3,4,5,6]))  # [5,6]
list(itertools.compress('ABCDE', [1,0,1,0,1]))  # ['A','C','E']

# ペアリング
list(itertools.pairwise([1,2,3,4,5]))  # [(1,2),(2,3),(3,4),(4,5)]

functools

lru_cache・partial・wraps・reduce

functools_examples.py python
import functools

# lru_cache: メモ化(結果をキャッシュ)
@functools.lru_cache(maxsize=128)
def fibonacci(n: int) -> int:
    if n < 2: return n
    return fibonacci(n-1) + fibonacci(n-2)

fibonacci(50)          # 高速(再帰でも O(n))
fibonacci.cache_info() # CacheInfo(hits=48, misses=51, ...)
fibonacci.cache_clear()

# cache(Python 3.9+): maxsize=None の lru_cache
@functools.cache
def expensive(n: int) -> int: ...

# partial: 引数を一部固定した新しい関数を作る
from functools import partial

def power(base: float, exp: float) -> float:
    return base ** exp

square = partial(power, exp=2)
cube   = partial(power, exp=3)
square(5)  # 25
cube(3)    # 27

# よく使われる例
from functools import partial
int_from_hex = partial(int, base=16)
int_from_hex('ff')   # 255
int_from_hex('1a')   # 26

# total_ordering: __eq__ と1つの比較メソッドから全て生成
@functools.total_ordering
class Card:
    def __eq__(self, other): ...
    def __lt__(self, other): ...
    # __le__, __gt__, __ge__ は自動生成