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

# TL;DR

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

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

# はじめに

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

引数・パラメータの分類が[公式ドキュメント](https://docs.python.org/3.6/glossary.html)と異なりますが、構文上の見た目や実用に則した分類の方が分かりやすいと思い意図的に変更しました。

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

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

## 用語の定義

### 引数 (arguments)とは

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

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

### パラメータ (parameters)とは

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

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

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

# Python 3での引数（あるいはその渡し方）

Python 3での引数は 2 x 2 = 4 種類あります。[公式には2種類](https://docs.python.org/3.6/glossary.html#term-argument)ですが、ここでは渡された値が展開されるかどうかによってさらに2種類ずつに分けて解説します。

値の渡し方で2種類：

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

値の展開で2種類：

- 展開しない
- 展開する

## 位置引数

```py3
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)と同様
```

## キーワード引数

```py3
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種類](https://docs.python.org/3.6/glossary.html#term-parameter)ですが、ここではpositional-onlyを省略しデフォルト値の有無を考慮して、6種類として解説します。

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

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

値の扱いで3種類：

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

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

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

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

```py3
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)
```

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

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

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

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

```py3
#               ,-- これ
#              |
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}
```

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

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

# 例

## Python標準ライブラリ

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

```py3
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系には実際にはありません。）

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

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

```py
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種類：

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

# 参考文献

- [Glossary - Python 3.6.3 documentation](https://docs.python.org/3.6/glossary.html)
- [8. Compound statements — Python 3.6.3 documentation](https://docs.python.org/3/reference/compound_stmts.html#function-definitions)
- [PEP 3102 -- Keyword-Only Arguments | Python.org](https://www.python.org/dev/peps/pep-3102/)