Python関数型プログラミング入門 – map・filter・reduceから始める実践ガイド

 

関数型プログラミングとは?基本概念を5分で理解

**関数型プログラミング(Functional Programming)**は、関数を中心としたプログラミングパラダイムです。データの変更(副作用)を避け、関数の組み合わせで問題を解決します。

オブジェクト指向との違い

手法 考え方 特徴
オブジェクト指向 データ + メソッド 状態を変更する
関数型 関数 + データ 新しい値を生成する
# オブジェクト指向的
class Calculator:
    def __init__(self):
        self.result = 0
    def add(self, x):
        self.result += x

# 関数型的
def add(x, y):
    return x + y

Python関数型プログラミングの基本関数

1. map関数 – データの変換

# 全ての要素を2倍にする
numbers = [1, 2, 3, 4, 5]
doubled = list(map(lambda x: x * 2, numbers))
print(doubled)  # [2, 4, 6, 8, 10]

2. filter関数 – データの絞り込み

# 偶数のみを抽出
numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
evens = list(filter(lambda x: x % 2 == 0, numbers))
print(evens)  # [2, 4, 6, 8, 10]

3. reduce関数 – データの集約

from functools import reduce

# 全ての要素の合計を計算
numbers = [1, 2, 3, 4, 5]
total = reduce(lambda x, y: x + y, numbers)
print(total)  # 15

lambda式(無名関数)の活用

基本的な使い方

# 通常の関数定義
def square(x):
    return x ** 2

# lambda式で同じ処理
square = lambda x: x ** 2
print(square(5))  # 25

実践的な活用例

# 辞書のソート
students = [{"name": "太郎", "score": 85}, {"name": "花子", "score": 92}]
sorted_students = sorted(students, key=lambda x: x["score"])
print(sorted_students)  # スコア順でソート

高階関数の実装

関数を返す関数

def multiplier(n):
    return lambda x: x * n

double = multiplier(2)
triple = multiplier(3)
print(double(5))  # 10
print(triple(5))  # 15

関数を引数に取る関数

def apply_operation(numbers, operation):
    return [operation(x) for x in numbers]

numbers = [1, 2, 3, 4, 5]
squared = apply_operation(numbers, lambda x: x**2)
print(squared)  # [1, 4, 9, 16, 25]

デコレータを使った関数型アプローチ

基本的なデコレータ

def timing_decorator(func):
    import time
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        print(f"実行時間: {time.time() - start:.4f}秒")
        return result
    return wrapper

@timing_decorator
def slow_function():
    import time
    time.sleep(1)
    return "完了"

純粋関数(Pure Function)の実践

純粋関数の特徴

# ✅ 純粋関数:同じ入力で同じ出力、副作用なし
def add(x, y):
    return x + y

# ❌ 非純粋関数:外部状態に依存
global_var = 10
def impure_add(x):
    return x + global_var

副作用を避ける方法

# ❌ 元のリストを変更
def bad_double(numbers):
    for i in range(len(numbers)):
        numbers[i] *= 2
    return numbers

# ✅ 新しいリストを作成
def good_double(numbers):
    return [x * 2 for x in numbers]

実践的な関数型パターン

1. パイプライン処理

from functools import reduce

def pipe(*functions):
    return lambda x: reduce(lambda acc, f: f(acc), functions, x)

# 数値を2倍して、3を足して、文字列化
pipeline = pipe(
    lambda x: x * 2,
    lambda x: x + 3,
    str
)
result = pipeline(5)  # "13"

2. カリー化(Currying)

def curry_add(x):
    return lambda y: lambda z: x + y + z

add_5 = curry_add(5)
add_5_and_3 = add_5(3)
result = add_5_and_3(2)  # 10

3. 部分適用

from functools import partial

def multiply(x, y, z):
    return x * y * z

# yを2に固定した関数を作成
double_multiply = partial(multiply, y=2)
result = double_multiply(3, 4)  # 24

関数型 vs 命令型の比較

データ処理の例

# 命令型スタイル
def process_data_imperative(data):
    result = []
    for item in data:
        if item > 0:
            result.append(item * 2)
    return result

# 関数型スタイル
def process_data_functional(data):
    return list(map(lambda x: x * 2, filter(lambda x: x > 0, data)))

# さらに読みやすく
def process_data_readable(data):
    positive = filter(lambda x: x > 0, data)
    doubled = map(lambda x: x * 2, positive)
    return list(doubled)

イミュータブル(不変)データの活用

namedtupleの使用

from collections import namedtuple

Point = namedtuple('Point', ['x', 'y'])
p1 = Point(1, 2)
p2 = p1._replace(x=3)  # 新しいインスタンスを作成
print(p1)  # Point(x=1, y=2)
print(p2)  # Point(x=3, y=2)

dataclassesでイミュータブルクラス

from dataclasses import dataclass

@dataclass(frozen=True)
class ImmutableUser:
    name: str
    age: int

user = ImmutableUser("太郎", 25)
# user.age = 26  # エラー:変更不可

パフォーマンスと最適化

ジェネレータとの組み合わせ

# メモリ効率的な処理
def process_large_data(data):
    filtered = filter(lambda x: x > 100, data)
    doubled = map(lambda x: x * 2, filtered)
    return doubled  # ジェネレータを返す

# 必要な時だけ計算される
large_numbers = range(1000000)
processed = process_large_data(large_numbers)
first_10 = list(itertools.islice(processed, 10))

よくある間違いと対処法

1. lambda式の過度な使用

# ❌ 読みにくい
result = list(filter(lambda x: x['score'] > 80 and x['age'] < 30, students))

# ✅ 読みやすい
def is_young_high_scorer(student):
    return student['score'] > 80 and student['age'] < 30

result = list(filter(is_young_high_scorer, students))

2. 副作用の混入

# ❌ 副作用あり
def bad_process(items, results):
    for item in items:
        results.append(item * 2)  # 外部状態を変更

# ✅ 純粋関数
def good_process(items):
    return [item * 2 for item in items]

実務での活用例

1. データ変換パイプライン

# JSONデータの処理
users = [{"name": "太郎", "age": 25}, {"name": "花子", "age": 30}]
adult_names = list(map(
    lambda user: user["name"],
    filter(lambda user: user["age"] >= 20, users)
))

2. 設定値の適用

# 設定を関数として管理
def create_processor(config):
    return lambda data: data * config["multiplier"] + config["offset"]

config = {"multiplier": 2, "offset": 10}
processor = create_processor(config)
result = processor(5)  # 20

3. バリデーション処理

def validate_all(*validators):
    return lambda data: all(validator(data) for validator in validators)

is_valid_user = validate_all(
    lambda user: len(user.get("name", "")) > 0,
    lambda user: user.get("age", 0) > 0,
    lambda user: "@" in user.get("email", "")
)

まとめ:関数型プログラミングの利点

メリット

  • コードの再利用性が高い
  • テストが容易
  • 並列処理に適している
  • バグが発生しにくい

適用場面

  • データ変換処理
  • 設定値の管理
  • フィルタリング・集計処理
  • パイプライン処理

関数型プログラミングをマスターすることで、より保守性が高く、理解しやすいPythonコードが書けるようになります。まずはmapfilterreduceから始めて、徐々に高階関数やイミュータブルデータの概念を取り入れていきましょう。

 

 

■プロンプトだけでオリジナルアプリを開発・公開してみた!!

■AI時代の第一歩!「AI駆動開発コース」はじめました!

テックジム東京本校で先行開始。

■テックジム東京本校

「武田塾」のプログラミング版といえば「テックジム」。
講義動画なし、教科書なし。「進捗管理とコーチング」で効率学習。
より早く、より安く、しかも対面型のプログラミングスクールです。

<短期講習>5日で5万円の「Pythonミニキャンプ」開催中。

<月1開催>放送作家による映像ディレクター養成講座

<オンライン無料>ゼロから始めるPython爆速講座