可変長位置引数(スター引数)
可変長位置引数を用いると関数の呼び出しコードが簡潔になり読みやすくなる。
以下のように初めての人と知り合いで発言を区別するコードを記述する。
exam.py
def hello(message, name):
if not name:
print('nice to meet you')
else:
print(f'{message}! {name}さん')
hello('こんにちわ', 'saku')
hello('こんにちわ', []) # 第二引数を指定しないとTypeErrorが発生する
>>>
こんにちわ! sakuさん
nice to meet you
このコードに可変長位置引数を使ってみる
exam2.py
def hello(message, *name):
if not name:
print('nice to meet you')
else:
print(f'{message}! {name}さん')
hello('こんにちわ', 'saku')
hello('こんにちわ')
>>>
こんにちわ! ('saku',)さん
nice to meet you
このようにすると、nameがわからない場合でも第二引数を指定せず、関数を呼び出すことができ、コードも読みやすく簡潔にできる。
可変長位置引数を使う上での問題①
可変個数の引数を関数に渡す際にタプル変換されてしまう
exam3.py
# ジェネレーター関数
def generator():
for i in range(10):
yield i
def main(*args):
print(args)
G = generator()
print(main(*G))
>>>
(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) # タプルに変換されている
None
このようにジェネレータからのすべての値がタプルに含まれているので、データ量が多い場合メモリを大量に消費してクラッシュを引き起こす可能性がある。
⇒つまり、*argsを関数に渡す場合はあらかじめ入力要素数が少ないとわかっているのが理想的である。
可変長位置引数を使う上での問題②
exam4.py
def family(grandfather, father, *kids):
if not kids:
return (f'祖父: {grandfather}, 父:{father}')
else:
return (f'祖父: {grandfather}, 父: {father}, 子供: {kids}')
print(family('おじいちゃん', 'お父さん', 'たかし', 'カツオ'))
print(family('おじいちゃん', 'お父さん'))
print(family('お父さん', 'たかし'))
>>>
祖父: おじいちゃん, 父: お父さん, 子供: ('たかし', 'カツオ')
祖父: おじいちゃん, 父:お父さん
祖父: お父さん, 父:たかし
可変長位置引数を定義しない場合は、関数がうまく機能するが、可変長位置引数の前に定義されるべき引数を欠損すると問題が発生してしまう。
⇒このバグは例外を吐き出さず実行されてしまうのでたちが悪い。
ポイント
- 可変長位置引数(*args)を使って関数の引数をすっきりさせよう!
- ジェネレータと可変長位置引数を使うとメモリクラッシュの原因足りうる。注意しよう
- 可変長位置引数と使った関数に、引数を追加したり、削除したりすると例外を発生させないバグのもとになってしまう。注意しよう。
- 可変長位置引数を使った関数の引数は、キーワード引数や型ヒントを用いることで安全に拡張できる可能性がある
参照
Effective Python