関数の基礎知識

編集

関数とは

編集

関数(function)は、特定の処理をまとめた再利用可能なコードブロックです。入力として引数(parameters/arguments)を受け取り、処理を実行して、必要に応じて結果を戻り値(return value)として返します。

関数を使用する利点

編集
  • コードの再利用:同じ処理を何度も書く必要がなくなり、保守性が向上
  • 抽象化:複雑な処理を関数名で表現でき、コードの可読性が向上
  • モジュール化:プログラムを機能単位に分割でき、開発効率が向上
  • スコープの管理:変数の有効範囲を制御でき、バグの防止に貢献

関数の基本構文

編集

Python での関数定義の基本形式は以下の通りです:

def 関数名(引数1, 引数2, ...):
    # 処理内容
    return 戻り値  # 省略可能

基本的な関数の例

編集
def add_numbers(a, b):
    """2つの数値を受け取り、その和を返す関数"""
    result = a + b
    return result

# 関数の使用例
sum = add_numbers(5, 3)  # sum には 8 が代入される

引数の扱い方

編集

単一引数の例

編集
def double_number(x):
    """数値を2倍にする関数"""
    result = x * 2
    print(f"{x}を2倍したら{result}")
    return result

# 使用例
double_number(5)  # "5を2倍したら10"と出力

複数引数の例

編集
def calculate_rectangle_area(width, height):
    """長方形の面積を計算する関数"""
    area = width * height
    print(f"幅{width}、高さ{height}の長方形の面積は{area}")
    return area

# 使用例
calculate_rectangle_area(3, 4)  # "幅3、高さ4の長方形の面積は12"と出力

戻り値の扱い方

編集

戻り値がある場合

編集
def multiply(x, y):
    """2つの数の積を返す関数"""
    return x * y

result = multiply(6, 7)  # result には 42 が代入される

戻り値がない場合

編集
def greet(name):
    """挨拶を出力する関数(戻り値なし)"""
    print(f"こんにちは、{name}さん!")

greet("田中")  # "こんにちは、田中さん!"と出力

発展的な内容

編集

デフォルト引数

編集

引数にデフォルト値を設定することで、引数を省略して関数を呼び出すことができます。

基本的な使用法

編集
def greet(name, greeting="こんにちは"):
    """挨拶を返す関数(デフォルト引数付き)"""
    return f"{greeting}{name}さん!"

print(greet("田中"))             # "こんにちは、田中さん!"
print(greet("佐藤", "おはよう")) # "おはよう、佐藤さん!"

デフォルト引数の注意点

編集

可変オブジェクトをデフォルト値として使用する際は注意が必要です:

# 危険な例
def add_item(item, lst=[]):    # リストをデフォルト引数として使用
    lst.append(item)
    return lst

print(add_item(1))  # [1]
print(add_item(2))  # [1, 2] 予期せぬ結果

# 推奨される方法
def add_item_safe(item, lst=None):
    if lst is None:
        lst = []
    lst.append(item)
    return lst

可変長引数

編集

*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="田中", age=25, city="東京")
# 出力:
# name: 田中
# age: 25
# city: 東京

キーワード引数

編集

基本的な使用法

編集
def create_profile(name, age, city="東京"):
    return f"名前: {name}, 年齢: {age}, 居住地: {city}"

# 位置引数として渡す
print(create_profile("田中", 25))

# キーワード引数として渡す
print(create_profile(age=25, name="田中", city="大阪"))

キーワード専用引数

編集

Python 3では、キーワード専用引数を定義できます:

def process_data(data, *, sort=False, reverse=False):
    """
    データを処理する関数
    sort, reverse はキーワード引数としてのみ指定可能
    """
    result = data.copy()
    if sort:
        result.sort(reverse=reverse)
    return result

# 正しい使用法
data = [3, 1, 4, 1, 5]
print(process_data(data, sort=True))

# エラーとなる使用法
# print(process_data(data, True))  # TypeError

残余引数

編集

関数の定義で固定された引数とともに、可変長引数を持つことができます。 ここで、残余引数は、可変長引数の後ろに定義される一連の引数です。 残余引数のために、アスタリスク(*)を使用します。

def my_func(a, b, *args):
    print(a)
    print(b)
    for arg in args:
        print(arg)

これで、my_func()関数は少なくともaとbの2つの引数を取り、残りの引数を可変長引数として扱います。

多値返却風な処理

編集

Pythonには、厳密な意味での多値返却はありませんが、タプルやリストを返すことで多値返しに近いことができます。

多値返し風な処理
def addsub(x, y) :
    a, b = x + y, x - y
    return a, b

def mulquoremdiv(x, y) :
    return x * y, x // y, x % y, x / y

a, s = addsub(13, 5)
m, q, r, d = mulquoremdiv(19, 7)
print(f'''\
{a=}, {s=} 
{m=}, {q=}, {r=}, {d=}
''')
実行結果
a=18, s=8 
m=133, q=2, r=5, d=2.7142857142857144

「多値返却風な処理」や「多値返しに近いこと」と歯切れが悪いのは、型アノテーション

def addsub(x: int, y: int) -> int, int: # Error!
    a, b = x + y, x - y
    return a, b

def mulquoremdiv(x: int, y: int) -> int, int, int, float: # Error!
    return x * y, x // y, x % y, x / y

としたいのですが、

from typing import Tuple

def addsub(x: int, y: int) -> Tuple[int, int]:
    a, b = x + y, x - y
    return a, b

def mulquoremdiv(x: int, y: int) -> Tuple[int, int, int, float] :
    return x * y, x // y, x % y, x / y

としなければならず、タプルを返していることを明確に意識する必要があるからです。

デコレーター

編集

デコレーター(decorator)は、関数の内容を書き換えずに修飾するための仕組みです。 関数の引数に関数を指定します。既存の関数の前に@{デコレーター名}を付けることで、その関数を修飾することができます。

形式
def デコレーター名(デコレーターの引数) : 
    def ラッパー関数名(*args, **kwargs) :
        # 前処理
        result = デコレーターの引数(*args, **kwargs)
        # 後処理
        return result
    return ラッパー関数名

@デコレーター名
def 修飾される側の関数(その引数) :
    修飾される側の関数の処理
コード例
def my_decorator(fn) :
    def _wrapper(*args, **kwargs) :
        print(f"my_decorator<prologue>:{fn.__name__=}:{args=}, {kwargs=}")
        result = fn(*args, **kwargs)
        print(f"my_decorator<epilogue>:{result=}")
        return result
    return _wrapper

@my_decorator
def calc(left, right) :
    return left + right

print(f'''\
{calc(3, 5)=}
{calc(right=8, left=9)=}''')
実行結果
my_decorator<prologue>:fn.__name__='calc':args=(3, 5), kwargs={}
my_decorator<epilogue>:result=8
my_decorator<prologue>:fn.__name__='calc':args=(), kwargs={'right': 8, 'left': 9}
my_decorator<epilogue>:result=17
calc(3, 5)=8
calc(right=8, left=9)=17

デコレターの文法は、以下のような糖衣構文です。

@my_decorator
def fn(...):
    ...

# は

def fn(...):
    ...
fn = my_decorator(fn)

#と同じ
デコレーター・パターン

デコレーター・パターンは、オブジェクト指向プログラミングにおいて、同じクラスの他のオブジェクトの動作に影響を与えることなく、個々のオブジェクトに動的に動作を追加することができるデザインパターンです。 デコレーター・パターンは、単一責任原則(Single Responsibility Principle)を遵守するために有用で、独自の関心領域を持つクラス間で機能を分割することができます。 デコレーターの使用は、全く新しいオブジェクトを定義することなく、オブジェクトの動作を拡張することができるため、サブクラス化よりも効率的です。

Pythonのデコレーター構文は、デコレーター・パターンの言語支援と言えます。

関数のドキュメント化

編集

ドックストリング

編集

関数の説明文をドックストリングとして記述することで、コードの可読性が向上します:

def calculate_bmi(weight, height):
    """
    体重と身長からBMIを計算する関数
    
    Args:
        weight (float): 体重(kg)
        height (float): 身長(m)
    
    Returns:
        float: BMI値
    
    Examples:
        >>> calculate_bmi(60, 1.70)
        20.76
    """
    return weight / (height ** 2)

# ドックストリングの参照
help(calculate_bmi)

高度な関数の概念

編集

ラムダ関数

編集

一行で書ける簡単な関数を定義するときに使用します:

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

# 同じ機能のラムダ関数
square_lambda = lambda x: x ** 2

# 使用例
numbers = [1, 2, 3, 4, 5]
squared = list(map(square_lambda, numbers))
print(squared)  # [1, 4, 9, 16, 25]

組込み関数id

編集

組込み関数idを使うとオブジェクトのidを得ることができます。 2つの変数同士のidが同じ場合、2つの変数は同じオブジェクトにバインドされています。 大概の型のオブジェクトのidはメモリー上のアドレスですが、整数のようにそうではない型も少数ながらあります。

idの例
"""
idの例
"""
a = 1
b = 2
c = 3

def f2():
    """
    f2で参照されるa,b,cはグローバル変数
    """
    print(f"f2:{a=}({id(a)=}),{b=}({id(b)=}),{c=}({id(c)=}),")

def func(a) :
    """
    funcで参照されるaは仮引数
    funcで参照されるbはローカル変数
    funcで参照されるcはグローバル変数
    """
    b = a * 2
    print(f"func:{a=}({id(a)=}),{b=}({id(b)=}),{c=}({id(c)=}),")
    f2()

func(111)
print(f"GLOBAL:{a=}({id(a)=}),{b=}({id(b)=}),{c=}({id(c)=}),")
実行結果
func:a=111(id(a)=9792128),b=222(id(b)=9795680),c=3(id(c)=9788672),
f2:a=1(id(a)=9788608),b=2(id(b)=9788640),c=3(id(c)=9788672),
GLOBAL:a=1(id(a)=9788608),b=2(id(b)=9788640),c=3(id(c)=9788672),

戻り値

編集

関数の戻り値(return value;返り値・返却値とも)は、return 文で返します。

関数の戻り値
import inspect

def f(a) :
    """ 引数を戻り値とする関数 """
    return a

print(inspect.getsource(f))
print(f'''\
{f(1)=}
{f(3.1415926536)=}
{f("string")=}
''')

def n(a) :
    """ return文のない関数 """
    pass

print(inspect.getsource(n))
print(f'''\
{n(n)=}
{n(3.1415926536)=}
{n("string")=}
''')

def x(a) :
    """ return文に引数のない関数 """
    return

print(inspect.getsource(x))
print(f'''\
{x(n)=}
{x(3.1415926536)=}
{x("string")=}
''')
関数は多くのケースで何らかの値を返します。
これを戻り値とよびます。
関数は、return文の引数を戻り値とします。
return文に出会わず関数定義の終わりに達した場合は、オブジェクトNoneが返ります。
return文で戻り値を指定しない場合は、オブジェクトNoneが返ります。
def f(a, b) :
    return a + b
この関数は引数 a と b を受けとり、足した結果を返します。
return文による途中終了
編集

return文があると、その行で関数が終了します。

def work() :
    print("a")
    return 0
    print("b")

work()
work()
実行結果
a
a
print("b") が実行されることはありません。

関数のスコープ

編集

スコープとは変数の有効範囲のことで、大きく分けてグローバルスコープとローカルスコープがあります。

グローバルスコープとローカルスコープ

編集
# グローバル変数
global_var = 100

def my_function():
    # ローカル変数
    local_var = 200
    print(f"グローバル変数: {global_var}")  # グローバル変数にアクセス可能
    print(f"ローカル変数: {local_var}")

my_function()
print(global_var)  # アクセス可能
print(local_var)   # エラー:ローカル変数にはアクセス不可

globalキーワード

編集

関数内でグローバル変数を変更する場合は、globalキーワードを使用します:

counter = 0

def increment():
    global counter
    counter += 1
    return counter

print(increment())  # 1
print(increment())  # 2

nonlocal

編集

nonlocalキーワードは、内包関数から、自分を呼び出した関数のローカル変数にアクセスする仕組みです。 global文と違い、新たな変数を作ることはできません。

コード例
def outer() :
    f = 25
    def inner() :    # 関数内関数
        nonlocal f
        f = 13
        return 1
           
    inner()
    print(f)
    
outer()
実行結果
13
nonlocal の行をコメントにすると、結果は 「25」 に変わります。

クロージャ

編集

クロージャ(関数クロージャ)は、外側の変数を格納する関数です。

コード例
def f(z) :
    def _f() :
        nonlocal z
        z += 1
        return z
    return _f
    
q = f(-2)
print(q())
print(q())
print(q())
print(q())
実行結果
-1
0
1
2
_fをクロージャ、fをエンクロージャといいます。

この絵柄に見覚えがある人もいると思います。デコレーターの引数(デコレート対象の関数)はラッパー関数は格納した変数です。