Python/関数
関数の基礎知識
編集関数とは
編集関数(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をエンクロージャといいます。
この絵柄に見覚えがある人もいると思います。デコレーターの引数(デコレート対象の関数)はラッパー関数は格納した変数です。