Pythonにおける関数の色々な引数（位置引数、省略可能引数、可変長引数、キーワード引数） def func(arg, *args, **kwargs)

# 前置き
今回は小ネタです。
これもまた、職場で使い方を知らない人が多いので、書き連ねておこうと思いました。
特に可変長引数とキーワード引数。他のメジャーな言語には無いからだろう・・・
まぁ、検索すればいくらでも出てくるんだけども・・・

# Pythonにおけるユーザー定義関数の作り方
Python3.X系でやっていきます。基本はこれ。

```python
## 関数の定義 ※'引数'は変数名
def func('引数'):
    # process
    return ret

if __name__ == '__main__':
    # 関数の呼び出し ※'引数'は実際には変数であったり値
    x = func('引数') 
```
今回はここでの引数の渡し方というか受け取り方。
Pythonはこんな受け取り方ができるという話。

# 位置引数
位置引数はそのまま、位置（順番）通りに受け取る方法。
他のプログラミング言語でもこれは不変。
<details><summary>サンプルコードと説明</summary><div>

```python:code
def func(arg1, arg2, arg3):
    return (arg1 * 3.0) + (arg2 * 2.0) + (arg3 * 1.0)

if __name__ == '__main__':
    y = func(4, 5, 6)
    print(y)
```
```python:output
28.0
```
呼び出し側で、それぞれ4,5,6を順に渡している。
なので、受け取り側では位置（順番）通りに渡される。
arg1 = 4、arg2 = 5、arg3 = 6
なので、今回の関数の処理内容は4*3+5*2+6*1=28となる。
</div></details>

# 省略可能引数
何のことはない、呼び出し時に指定しなくてもいい引数を定義しておくこと。
<details><summary>サンプルコードと説明</summary><div>

```python:code
def func(arg=1):
    print(arg)

if __name__ == '__main__':
    func()
    func(99)
```

```python:output
1
99
```
引数argは定義時に省略して呼び出された時、どのように扱うかデフォルト値を決めておく。
「arg=1」としておくことで、省略された時、argは1が入った状態で関数内の処理が始まる、

一回目の呼び出しでは引数を省略しているのでデフォルト値である1が出力される。
二回目は省略していないので、渡した99が出力される。
</div></details>


# 可変長引数
可変長引数はその名の通り、不特定多数のn個の引数を受け取る方法。
引数名に*(アスタリスク)を一つ付ける。関数内で取り扱う時はこのアスタリスクは要らない。
ちょっと他の言語では見たことがない。

<details><summary>サンプルコードと説明</summary><div>

```python:code
def func(*args):
    for i, v in enumerate(args):
        print('args[%d] = %s' % (i, v))

if __name__ == '__main__':
    func((1, 2), 3, 4, (5, 6), 7, 8)
```
```python:output
args[0] = (1, 2)
args[1] = 3
args[2] = 4
args[3] = (5, 6)
args[4] = 7
args[5] = 8
```

関数定義ではargs一つしか用意していないが、呼び出し側では4つの引数を渡す。
呼び出し側の渡し方もちょっとトリッキーにした。
最初の(1, 2)はこれで一つのTuple、(5, 6)も同じ。
それ以外は1つの数値で1つの引数となっているので、outputを見ればわかるように6つの引数となる。

受け取り側ではargsに複数の引数が一つのTupleとなって渡される。
Tupleはiterableなので、forループで一つずる標準出力。
Tupleなので関数内ではargs[i]で任意の位置にある引数値を参照できる。

当然だが、下記のように二つは作れない。
どこからが一つ目の可変長でどこからが二つ目の可変長かわからないから。

```python
def func(*args1, *args2):
    print(args1, args2)
```
</div></details>

# キーワード引数
キーワード引数は可変長引数でもある、可変長引数と違う点はキーワード付きであるところ。
・・・・・
言葉での説明が難しい。
可変長引数はTupleであるのに対し、キーワード引数はDictionary。
<details><summary>サンプルコードと説明</summary><div>

```python:code
def func(**kwargs):
    print(kwargs['arg1'], kwargs['args'])
    # print(kwargs)
    
if __name__ == '__main__':
    func(arg1=1, args=(2, 3, 4), arg2=5)
```

```python:output
1 (2, 3, 4)
```
キーワード引数では呼び出し側は「キーワード=値」という形で渡す。
今回のfuncではkwargsがキーワード引数で関数内では「arg1」と「args」について、標準出力している。
呼び出し側ではarg2も指定しているが、関数内で取り扱ってないので、何もされてない。

渡されてはいるので、コメントアウトしているprint(kwargs)では下記のようにDictionaryが出力される。

```python:
{'arg1': 1, 'args': (2, 3, 4), 'arg2': 5}
```
ちゃんとarg2=5を受け取っている。

ちなみに可変長引数と同じく、二つは作れない。
どっちのキーワード引数かわからないから。
</div></details>

# 組み合わせて使う
これらを組み合わせて使う時はキーワード引数以外は位置が重要になるので、注意が必要。

<details><summary>可変長引数とキーワード引数の組み合わせ</summary><div>
これはよくある組み合わせ。

```python:code
def func(*args, **kwargs):
    print('args = ', args)
    print('kwargs = ', kwargs)
    
if __name__ == '__main__':
    func(1, 2, 3, 4, arg1=5, arg2=6)
```

呼び出し時に前半にキーワードなしの値を4つ、これらは可変長引数argsに渡される。
後半にキーワード付き引数はkwargsにわたされる。
制約としてこの組み合わせを使う時は先に可変長引数、後にキーワード引数でないとダメ。

```python:code
def func(**kwargs, *args):
    ~~~~~
```
これはinvalid syntaxとなる。
</div></details>

<details><summary>位置引数と可変長引数の組み合わせ</summary><div>
位置引数は可変長引数より前に書いてあればそれが位置引数になる。
```python:code
def func(arg1, arg2, *args):
    print('arg1 = ', arg1)
    print('arg2 = ', arg2)
    print('args = ', args)
    
if __name__ == '__main__':
    func(1, 2, 3, 4, 5)
```

```python:output
arg1 =  1
arg2 =  2
args =  (3, 4, 5)
```
あまり難しくはない。
最初の2つは位置引数なので、1と2がそれぞれarg1とarg2。
後は可変長引数にTupleでまとめられる、

</div></details>

<details><summary>位置引数とキーワード引数の組み合わせ</summary><div>
これもあまり気にはならない。
位置引数をすべて先に定義し、キーワード引数は最後。

```python:code
def func(arg1, arg2, **kwargs):
    print('arg1 = ', arg1)
    print('arg2 = ', arg2)
    print('kargs = ', kwargs)
    
if __name__ == '__main__':
    func(1, 2, kwarg1=2, kwarg2=3)
```

```python:output
arg1 =  1
arg2 =  2
kargs =  {'kwarg1': 2, 'kwarg2': 3}
```
</div></details>

<details><summary>省略可能引数とキーワード引数の組み合わせ</summary><div>
これも単純。
キーワード引数を最後に定義する。

```python:code
def func(arg=1, **kwargs):
    print('arg = ', arg)
    print('kargs = ', kwargs)
    
if __name__ == '__main__':
    func(kwarg1=2, kwarg2=3)
    func(99, kwarg1=100, kwarg2=101)
```

```python:output
arg =  1
kargs =  {'kwarg1': 2, 'kwarg2': 3}
arg =  99
kargs =  {'kwarg1': 100, 'kwarg2': 101}
```
呼び出し時は位置引数とキーワード引数の組み合わせの時と同じ感じ。
ただ、位置引数部分について、省略可能になっている。
当然省略した時はデフォルト値。
</div></details>

<details><summary>省略可能引数と可変長引数の組み合わせ</summary><div>
これは注意が必要。
引数の順序は可変長引数が最初、その次以降が省略可能引数とすることで使える。

```python:code
def func(*args, arg=1):
    print('arg = ', arg)
    print('args = ', args)
    
if __name__ == '__main__':
    func(1, 2, 3, 4, arg=5)
    func(5, 6, 7, 8)
```

```python:output
arg =  5
args =  (1, 2, 3, 4)
arg =  1
args =  (5, 6, 7, 8)
```
さらに呼び出し時は省略可能引数はキーワード引数のように引数名を指定しないといけない。
呼び出し時に省略した場合はデフォルト値の1。
</div></details>

# オブジェクト指向に関係する点
他の言語の経験者なら、「ん？関数のオーバーロードは？」と思ったはず。
Pythonではこういった形で不特定多数の引数を渡すことができる。
さらに変数のデータ型も動的型付けであるため、他のオブジェクト指向プログラミング言語にある
**オーバーロード（多重定義）がPythonにはない。**

今回の引数の渡し方を駆使して一つの関数の中で処理内容を変えることで、オーバーロードっぽい事はできる。
ただし、関数名は同じで引数の数や型違いで作られる本当のオーバーロードはできない。

<details><summary>他の言語で慣れたやり方で無理やりやろうとしてみると・・・</summary><div>

```python:overload
def func(arg):
    return arg

def func(arg1, arg2):
    return arg1, arg2

if __name__ == '__main__':
    ret = func(1)
```

```python:output
TypeError: func() missing 1 required positional argument: 'arg2'
```
他の言語ではできる引数の数を変えたオーバーロードだが、Pythonではこれはエラーとなる。
が、インタプリタであるPythonは二つ目に書いたfuncが一つ目を上書きするので、
**エラーの内容は「arg2が指定されてない」である。注意。**
</div></details>