通常の利用で引数を可変長引数として受け取ることは、あまり推奨されません。その理由をまとめてみます。
可変長引数(*args, **kwargs)とは?
pythonで利用できる、引数をそのままtuple、dictとして受け取れる構文です。これの便利なところは、渡された引数をそのまま次の関数に渡せることで、主にデコレータなどのフィルタ関数に使われます。
def decorator(f):
def _f(*args, **kwargs):
print("before")
result = f(*args, **kwargs)
print("after")
return result
return _f
@decorator
def print_with_colon(a, b):
print(f"{a}:{b}")
print_with_colon("hello", "world")
結果
before
hello:world
after
また、ビルトイン関数であるprint
も、与えられたargsを結合して文字列として出力している点、あまり一般的な使い方ではないものの、一例として挙げておきます。
print("A", "B", "C", "D", "E") # いくつpositional引数を与えても正常に動作する
絶対に使っちゃいけないの?
そうではありません。先述したような汎用的なデコレータなどの引数としては利用するほうが明らかに簡潔です。デコレータは多くの場合、処理を挟むフィルター関数としての役割が主なので、引数の正当性などの責任は、実際に実行する関数に委譲してしまえばよいと考えられます。
しかし、上に挙げたprint関数のような利用方法は少し慎重になるべきです。
似たような、可変長のデータを受け取って結果を返すビルトイン関数では、max()
やmin()
がありますが、これらは可変長引数ではなく、iterable(listやtuple)を引数として受け取っています。print()
が可変長引数として値を受け取っているのもそれなりの理由があるのでしょうが、逆に言えば、理由がなければ使うべきでない、ということをこれから解説します。
なぜ使うべきでないのか
可変長引数は、tuple、dictとして使うことができますから、配列アクセスだったりgetメソッドなどで値を取り出すこともできます。しかし、そのような使い方はすべきではありません。
引数が明示的でない
可読性の面から見て、引数が明示的でないということは避けるべきです。
def add(*args):
return args[0] + args[1]
print(add(1, 2)) # -> 3
これでも動きますが、どんな引数をいくつ受け付けているのかが実装を見ないと全くわかりません。関数は、「名は体を表す」という言葉にもあるとおり、名前と引数名から使い方をある程度類推できることが望ましいと考えられます。
引数が不適切でもエラーにならない
*args
や、**kwargs
といった引数が関数に指定されていると、不適切な引数を設定したとしてもエラーになりません。これは検知しづらいバグを生む可能性があります。
def add(*args):
return args[0] + args[1]
print(add(1, 2, 3, 4, 5)) # -> 3
*args
を使うことによって、引数をいくら渡してもエラーになりません。
あるいは、以下のような使い方も同様の懸念があります。
def add(a, b, *args):
return a + b
print(add(1, 2, 3, 4, 5)) # -> 3
こちらもエラーになりません。
コード補完が効かない
VSCodeやPycharm、その他IDEを利用していれば、コード補完を行えるような機能が実装されているかと思いますが、多くの場合、わざわざコードを解析してまで可変長引数を意味のある引数として伝えてくれたりするわけではありません。
となれば、関数を利用する度にコードを読んだり、ドキュメントを読む必要があります。これは、positional引数や、keyword引数などで意味のあるわかりやすい名前がついている場合と比較すると、大きな負担となります。
まとめ
- 関数名や、引数名・変数名などは意味のあるわかりやすい名前をつけることが望ましいです。
- 意図しない値が渡ってきた場合は、例外を投げるなどしてプログラマに知らせましょう。
インターフェースを公開するということは、コードの正確性と可読性に責任を持つということです。本稿で上げたような書き方は便利ですが、理解しやすく、使いやすい書き方になっているか常に考えながらコードを書いていきたいものですね。