Python 3の美しい関数引数システム

  • 26
    Like
  • 0
    Comment

TL;DR

Python 3の関数引数システムは複雑だが美しい。

  • 引数は 2 x 2 = 4 種類
  • パラメータは 2 x 3 = 6 種類

はじめに

Python 3の関数引数システムは一見複雑ですが、一度理解すればとてもきれいに設計されていることが分かります。Python 2から3へのバージョンアップで大きく改善され、より柔軟になりました。

引数・パラメータの分類が公式ドキュメントと異なりますが、構文上の見た目や実用に則した分類の方が分かりやすいと思い意図的に変更しました。

書いてたら汚くなりました。フィードバックいただけるとうれしいです。

まず、関数についての用語の定義から行きます。

用語の定義

引数 (arguments)とは

関数に渡される具体的な値。関数内での実際の計算に使用されます。

foo(42, bar="baz")
#   |         |
#   `---------`-- これ

パラメータ (parameters)とは

関数に渡される具体的な値のプレースホルダ。パラメータ名を使うことで関数内で変数として参照できます。

#         ,-----,-- これ
#        |     |
def foo(arg1, arg2="bar"):
    return arg1 + arg2

引数とパラメータはそれぞれ実引数、仮引数と呼ばれることがあります。引数はパラメータの意味でも使われることがあります。ここでは、引数を値のプレースホルダという狭い意味で定義しておきます。引数とパラメータは鶏卵オーラがはんぱないです。以下の説明でも節どうしを行ったり来たりして読むことをおすすめします。

Python 3での引数(あるいはその渡し方)

Python 3での引数は 2 x 2 = 4 種類あります。公式には2種類ですが、ここでは渡された値が展開されるかどうかによってさらに2種類ずつに分けて解説します。

値の渡し方で2種類:

  • 位置 (positional)
  • キーワード (keyword)

値の展開で2種類:

  • 展開しない
  • 展開する

位置引数

foo(42)
foo(42, 2001)

# イテレータ(リスト、集合、ジェネレータ等)の要素を展開して渡す
iterator = [1, 2, 3]
foo(*iterator) # foo(1, 2, 3)と同様
foo(42, 2001, *iterator) # foo(42, 2001, 1, 2, 3)と同様

キーワード引数

foo(answer=42)
foo(answer=42, odyssey=2001)

# 辞書(またはの要素を展開して渡す
dictionary = {answer: 42, odyssey: 2001}
foo(**dictionary) # foo(answer=42, odyssey=2001)と同様
foo(bar="baz", **dictionary) # foo(bar="baz", answer=42, odyssey=2001)と同様

Python 3でのパラメータ(あるいはその宣言の方法)

Python 3でのパラメータは 2 x 3 = 6 種類あります。公式には5種類ですが、ここではpositional-onlyを省略しデフォルト値の有無を考慮して、6種類として解説します。

値の受け取り方で2種類:

  • 位置またはキーワード (positional-or-keyword)
  • キーワードのみ (keyword-only)

値の扱いで3種類:

  • デフォルト値なし
  • デフォルト値あり
  • 可変長 (variadic parameters)

それぞれの場合について以下に解説します。

位置パラメータ (positional-or-keyword parameters)

位置パラメータはその位置によって束縛される引数が変わるパラメータです。
位置パラメータには位置引数だけでなくキーワード引数も渡せます。

def foo(x):
    return x

foo(42) # -> 42

def foo(x, y):
    return x + y

foo(42, 2001) # -> 2043


# デフォルト値なし
def foo(x):
    return x

foo(42)   # -> 42
foo(x=42) # -> 42
foo()     # エラー

# デフォルト値あり
def foo(x=2001):
    return x

foo(42)   # -> 42
foo(x=42) # -> 42
foo()     # -> 2001

# 可変長
def foo(*args):
    return args

foo()         # -> (,) 空のタプル
foo(42)       # -> (42,)
foo(42, 2001) # -> (42, 2001)

可変長パラメータは一つの関数内で一度しか使えません。

# 構文エラー
def foo(*args1, *args2):
    pass

キーワードのみパラメータ (keyword-only parameters)

キーワードのみパラメータは、キーワード引数のみが使えるパラメータです。

#               ,-- これ
#              |
def foo(*args, x):
    return x

foo(x=42) # -> 42

# 可変長位置パラメータを使いたくない場合
def foo(*, x):
    return x

foo(x=42) # -> 42


# デフォルト値なし
def foo(*, x):
    return x

foo(x=42) # -> 42
foo()     # エラー

# デフォルト値あり
def foo(*, x=123):
    pass

foo(x=42) # -> 42
foo()     # -> 123

# 可変長
def foo(**kwargs):
    return kwargs

foo()                        # -> {} 空の辞書
foo(answer=42)               # -> {"answer": 42}
foo(answer=42, odyssey=2011) # -> {"answer": 42, "odyssey": 2011}

可変長パラメータは一つの関数内で一度しか使えません。

# 構文エラー
def foo(**kwargs1, **kwargs2):
    pass

Python標準ライブラリ

math.isclose()関数は2つの浮動小数点数の値が近いかどうかを調べる関数です。
どれくらい近いかを決めるための2つのオプションパラメータがあります。

import math

# math.isclose(a, b, *, rel_tol=1e-09, abs_tol=0.0)
assert math.isclose(10 ** 9, 10 ** 9 + 1)
assert not math.isclose(10 ** 8, 10 ** 8 + 1)

Python 2ではキーワードのみパラメータがないため、関数定義は以下のようになります。
math.isclose()関数はPython 3.5からの機能なのでPython 2系には実際にはありません。)

def isclose(a, b, rel_tol=1e-9, abs_tol=0.0):
    # 実際の処理

この定義ではrel_tolabs_tolを間違えて指定してしまう可能性があります。

isclose(10 ** 9, 10 ** 9 + 1, 0.0, 1e-9)

キーワードのみパラメータを使用することで、ユーザに引数を結びつけるパラメータを明示的に指定させ誤使用を防ぐことができます。

書くのつかれた。

まとめ

Python 3の関数引数システムは複雑ですが、美しい。色々な引数・パラメータを組み合わせることで柔軟な関数呼び出し・定義ができます。

引数は 2 x 2 = 4 種類

値の渡し方で2種類:

  • 位置 (positional)
  • キーワード (keyword)

値の展開で2種類:

  • 展開しない
  • 展開する

パラメータは 2 x 3 = 6 種類

値の受け取り方で2種類:

  • 位置またはキーワード (positional-or-keyword)
  • キーワードのみ (keyword-only)

値の扱いで3種類:

  • デフォルト値なし
  • デフォルト値あり
  • 可変長

参考文献