STEP 4

Python 関数 完全ガイド

def文による関数定義から、引数の種類、lambda式、クロージャ、デコレーター、ジェネレーターまで、Pythonの関数に関するすべてを解説します。

📖 中級者向け内容含む🎯 初心者〜中級者

1. 関数の基本(def文)

関数は処理のまとまりに名前をつけて再利用できるようにするものです。def キーワードで定義します。

# 最もシンプルな関数
def greet():
    print("こんにちは!")

greet()  # こんにちは!

# 引数を持つ関数
def greet_name(name):
    print(f"こんにちは、{name}さん!")

greet_name("Alice")  # こんにちは、Aliceさん!

# docstring(関数の説明)
def add(a, b):
    """
    2つの数値を加算して返す。

    Args:
        a: 1つ目の数値
        b: 2つ目の数値

    Returns:
        a と b の合計
    """
    return a + b

print(add(3, 5))        # 8
print(add.__doc__)      # docstringを表示
help(add)               # ヘルプを表示

2. 引数の種類

位置引数・キーワード引数

def profile(name, age, city):
    print(f"{name}, {age}歳, {city}在住")

# 位置引数(順番通りに渡す)
profile("Alice", 25, "Tokyo")

# キーワード引数(名前で渡す)
profile(age=30, city="Osaka", name="Bob")

# 混在(位置引数は先に)
profile("Carol", city="Kyoto", age=28)

デフォルト引数

def greet(name, greeting="こんにちは"):
    print(f"{greeting}、{name}さん!")

greet("Alice")                    # こんにちは、Aliceさん!
greet("Bob", "おはよう")          # おはよう、Bobさん!
greet("Carol", greeting="やあ")   # やあ、Carolさん!

# 注意:デフォルト引数にミュータブルを使ってはいけない!
# 悪い例(バグの元)
def bad_append(item, lst=[]):
    lst.append(item)
    return lst

print(bad_append(1))   # [1]
print(bad_append(2))   # [1, 2] ← 前の呼び出しの状態が残る!

# 正しい書き方
def good_append(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

可変長引数(*args / **kwargs)

# *args - 任意個数の位置引数をタプルで受け取る
def sum_all(*args):
    return sum(args)

print(sum_all(1, 2, 3))        # 6
print(sum_all(1, 2, 3, 4, 5))  # 15

# **kwargs - 任意個数のキーワード引数を辞書で受け取る
def print_info(**kwargs):
    for key, value in kwargs.items():
        print(f"{key}: {value}")

print_info(name="Alice", age=25, city="Tokyo")

# 組み合わせ(順番は: 通常引数, *args, キーワードのみ, **kwargs)
def complex_func(required, *args, keyword_only=None, **kwargs):
    print(f"required: {required}")
    print(f"args: {args}")
    print(f"keyword_only: {keyword_only}")
    print(f"kwargs: {kwargs}")

complex_func("必須", 1, 2, 3, keyword_only="KW", extra="追加")

# アンパックして渡す
numbers = [1, 2, 3]
print(sum_all(*numbers))    # リストをアンパック

params = {"name": "Alice", "age": 25}
print_info(**params)        # 辞書をアンパック

# / と * による引数の種類を強制
def strict(pos_only, /, normal, *, kw_only):
    """
    pos_only: 位置引数のみ(名前で渡せない)
    normal: どちらでも可
    kw_only: キーワード引数のみ(位置で渡せない)
    """
    print(pos_only, normal, kw_only)

strict(1, 2, kw_only=3)
strict(1, normal=2, kw_only=3)

3. 戻り値(return)

# 単一の戻り値
def square(x):
    return x ** 2

result = square(5)   # 25

# 複数の戻り値(実際はタプルを返している)
def min_max(numbers):
    return min(numbers), max(numbers)

lo, hi = min_max([3, 1, 4, 1, 5, 9])
print(lo, hi)   # 1 9

# 条件によって異なる値を返す
def absolute(x):
    if x >= 0:
        return x
    return -x    # return があれば else は不要

# 戻り値なし(None を返す)
def print_greeting(name):
    print(f"Hello, {name}!")
    # return None が暗黙的に実行される

result = print_greeting("Alice")
print(result)  # None

# 早期リターン(ガード節)
def divide(a, b):
    if b == 0:
        return None    # 早期リターン
    return a / b

print(divide(10, 2))   # 5.0
print(divide(10, 0))   # None

4. スコープ(変数の有効範囲)

Pythonのスコープは LEGB ルールに従います:Local → Enclosing → Global → Built-in

x = "global"   # グローバル変数

def outer():
    x = "enclosing"   # enclosing スコープ

    def inner():
        x = "local"   # ローカル変数
        print(x)      # local

    inner()
    print(x)          # enclosing

outer()
print(x)              # global

# global 宣言
count = 0

def increment():
    global count     # グローバル変数を変更する宣言
    count += 1

increment()
print(count)  # 1

# nonlocal 宣言(クロージャで使用)
def make_counter():
    count = 0
    def counter():
        nonlocal count   # 外側スコープの変数を変更
        count += 1
        return count
    return counter

c = make_counter()
print(c())  # 1
print(c())  # 2
print(c())  # 3

5. lambda式(無名関数)

lambdaは名前のない小さな関数を1行で書く記法です。ソートのキー関数などシンプルな処理に使います。

# 基本構文: lambda 引数: 式
square = lambda x: x ** 2
print(square(5))   # 25

add = lambda a, b: a + b
print(add(3, 4))   # 7

# 条件式(三項演算子)を使う
classify = lambda x: "正" if x > 0 else "負" if x < 0 else "ゼロ"
print(classify(5))    # 正
print(classify(-3))   # 負
print(classify(0))    # ゼロ

# sorted のキー関数として使う
students = [("Alice", 85), ("Bob", 92), ("Carol", 78)]

# 点数でソート
sorted_by_score = sorted(students, key=lambda s: s[1], reverse=True)
print(sorted_by_score)  # [('Bob', 92), ('Alice', 85), ('Carol', 78)]

# 辞書のリストをソート
people = [
    {"name": "Alice", "age": 30},
    {"name": "Bob", "age": 25},
    {"name": "Carol", "age": 28}
]
sorted_by_age = sorted(people, key=lambda p: p["age"])

# 複数条件でソート(タプルを返す)
data = [("Alice", 30, "B"), ("Bob", 25, "A"), ("Carol", 30, "A")]
# 年齢昇順、その後グレード昇順
sorted_data = sorted(data, key=lambda x: (x[1], x[2]))

6. 高階関数(map / filter / sorted / functools)

nums = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

# map - 全要素に関数を適用
squares = list(map(lambda x: x**2, nums))
# [1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

# filter - 条件を満たす要素だけ残す
evens = list(filter(lambda x: x % 2 == 0, nums))
# [2, 4, 6, 8, 10]

# 内包表記の方が Pythonic(推奨)
squares = [x**2 for x in nums]
evens = [x for x in nums if x % 2 == 0]

# functools.reduce - 要素を蓄積して1つの値に
from functools import reduce
product = reduce(lambda acc, x: acc * x, nums)  # 全要素の積
# 1*2*3*4*5*6*7*8*9*10 = 3628800

# functools.partial - 引数を部分的に固定した新しい関数を作る
from functools import partial

def power(base, exp):
    return base ** exp

square = partial(power, exp=2)
cube = partial(power, exp=3)
print(square(4))   # 16
print(cube(3))     # 27

# functools.lru_cache - キャッシュで再帰関数を高速化
from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n < 2:
        return n
    return fibonacci(n-1) + fibonacci(n-2)

print(fibonacci(50))  # 瞬時に計算

7. クロージャ

クロージャは、外側のスコープの変数を「記憶」する内側の関数です。状態を持つ関数ファクトリーを作るのに使います。

# カウンターファクトリー
def make_counter(start=0, step=1):
    count = start
    def counter():
        nonlocal count
        current = count
        count += step
        return current
    return counter

c1 = make_counter()
c2 = make_counter(10, 2)

print(c1(), c1(), c1())    # 0 1 2
print(c2(), c2(), c2())    # 10 12 14

# 掛け算ファクトリー
def multiplier(factor):
    def multiply(x):
        return x * factor
    return multiply

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

# ロガーファクトリー
def make_logger(prefix):
    def log(message):
        print(f"[{prefix}] {message}")
    return log

info_log = make_logger("INFO")
error_log = make_logger("ERROR")

info_log("処理開始")     # [INFO] 処理開始
error_log("接続失敗")    # [ERROR] 接続失敗

8. デコレーター

デコレーターは関数を「ラップ」して機能を追加する仕組みです。@ 構文で使います。

import time
import functools

# シンプルなデコレーター(実行時間の計測)
def timer(func):
    @functools.wraps(func)   # 元の関数の情報を保持
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        print(f"{func.__name__} の実行時間: {elapsed:.4f}秒")
        return result
    return wrapper

@timer
def slow_function():
    time.sleep(0.5)
    return "完了"

slow_function()  # slow_function の実行時間: 0.5xxx秒

# 引数を持つデコレーター
def repeat(times):
    def decorator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for _ in range(times):
                result = func(*args, **kwargs)
            return result
        return wrapper
    return decorator

@repeat(3)
def say_hello():
    print("Hello!")

say_hello()   # Hello! が3回出力

# ログデコレーター
def log_calls(func):
    @functools.wraps(func)
    def wrapper(*args, **kwargs):
        args_repr = [repr(a) for a in args]
        kwargs_repr = [f"{k}={v!r}" for k, v in kwargs.items()]
        signature = ", ".join(args_repr + kwargs_repr)
        print(f"呼び出し: {func.__name__}({signature})")
        result = func(*args, **kwargs)
        print(f"戻り値: {result!r}")
        return result
    return wrapper

@log_calls
def add(a, b):
    return a + b

add(3, 5)
# 呼び出し: add(3, 5)
# 戻り値: 8

# クラスベースのデコレーター
class Retry:
    def __init__(self, max_attempts=3):
        self.max_attempts = max_attempts

    def __call__(self, func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            for attempt in range(self.max_attempts):
                try:
                    return func(*args, **kwargs)
                except Exception as e:
                    print(f"試行 {attempt+1}/{self.max_attempts} 失敗: {e}")
            raise RuntimeError("最大試行回数を超えました")
        return wrapper

@Retry(max_attempts=3)
def flaky_function():
    import random
    if random.random() < 0.7:
        raise ValueError("ランダムエラー")
    return "成功"

9. ジェネレーター

ジェネレーターは値を一つずつ生成する特殊な関数です。大量のデータを扱う際にメモリを節約できます。

# yield を使ったジェネレーター関数
def count_up(start, stop):
    current = start
    while current <= stop:
        yield current   # 値を返しつつ一時停止
        current += 1

gen = count_up(1, 5)
print(next(gen))  # 1
print(next(gen))  # 2
for n in count_up(1, 5):
    print(n)       # 1, 2, 3, 4, 5

# フィボナッチ数列ジェネレーター(無限)
def fibonacci():
    a, b = 0, 1
    while True:
        yield a
        a, b = b, a + b

fib = fibonacci()
print([next(fib) for _ in range(10)])
# [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

# ファイルを行ごとに読む(大容量ファイルに有効)
def read_large_file(filepath):
    with open(filepath, "r", encoding="utf-8") as f:
        for line in f:
            yield line.strip()

# yield from - 別のイテラブルに委譲
def chain(*iterables):
    for it in iterables:
        yield from it

for x in chain([1, 2], [3, 4], [5]):
    print(x)   # 1, 2, 3, 4, 5

# send() - 外部から値を送り込む
def accumulator():
    total = 0
    while True:
        value = yield total
        if value is None:
            break
        total += value

acc = accumulator()
next(acc)          # 最初のyieldまで進める
print(acc.send(10))   # 10
print(acc.send(20))   # 30
print(acc.send(30))   # 60

10. クラスと特殊メソッド(概要)

クラスはデータ(属性)と処理(メソッド)をまとめたものです。関数と深く関わる重要な概念です。

class Circle:
    """円を表すクラス"""
    pi = 3.14159   # クラス変数

    def __init__(self, radius):   # コンストラクタ
        self.radius = radius      # インスタンス変数

    def area(self):               # インスタンスメソッド
        return Circle.pi * self.radius ** 2

    def perimeter(self):
        return 2 * Circle.pi * self.radius

    @classmethod
    def from_diameter(cls, diameter):  # クラスメソッド
        return cls(diameter / 2)

    @staticmethod
    def is_valid_radius(r):        # スタティックメソッド
        return r > 0

    def __str__(self):             # str() 変換
        return f"Circle(r={self.radius})"

    def __repr__(self):            # repr() 変換
        return f"Circle({self.radius!r})"

    def __eq__(self, other):       # == 比較
        return self.radius == other.radius

    def __lt__(self, other):       # < 比較
        return self.radius < other.radius

# 使用例
c = Circle(5)
print(c.area())         # 78.53975
print(c.perimeter())    # 31.4159
print(str(c))           # Circle(r=5)
c2 = Circle.from_diameter(10)
print(c == c2)          # True

# データクラス(Python 3.7+)
from dataclasses import dataclass, field

@dataclass
class Point:
    x: float
    y: float
    z: float = 0.0   # デフォルト値

    def distance_from_origin(self):
        return (self.x**2 + self.y**2 + self.z**2) ** 0.5

p = Point(3.0, 4.0)
print(p)                         # Point(x=3.0, y=4.0, z=0.0)
print(p.distance_from_origin())  # 5.0
🎉
関数をマスター!いよいよGUIアプリ開発へ

Pythonの関数・クラスを学びました。いよいよ実際に動くGUIアプリを作ってみましょう!