sankantsuのブログ

技術メモ・競プロなど

Python: 引数に関する規則のまとめ

概要

Python における関数宣言や関数呼び出しにおける引数の規則についてまとめる。

Introduction

Python では、関数呼び出しの引数に関する規則がやや複雑である。

たとえば、可変長の引数を許すような書き方として

def func(*args,**kwargs):
    ...

をよく見かけるが、他の言語ではあまり見慣れない書き方に思われる。

さらに、やや複雑な例として、

def foo(a,b,/,c=10,*d,e,f=20,**g):
    pass

というような関数宣言があったときに、

foo(1,x=2,*(3,4,5,6),**{"e":7,"y":8})

のような呼び出しを行ったら、aからfまでの各引数の値が何になるかすぐにわかるだろうか? このような書き方について理解が曖昧であると感じるならぜひ最後まで読んでみてほしい。

上の問題の答えは以下である。

a = 1
b = 3
c = 4
d = (5, 6)
e = 7
f = 20
g = {'x': 2, 'y': 8}

Parameter と Argument

Python に限った話ではないが、「引数」には2種類の意味がある。 仮引数(parameter)と実引数(argument)である。

parameter というのは関数定義の中で使われる引数に対する名前のことである。 実際の呼び出しごとに値が変わるというような意味で parameter という語が使われているのではないかと思われる。

一方 argument というのは、関数呼び出しの式の中で実際に渡される値を言う。

これらの区別は重要であるので、以下の説明の中でも意識的に使い分ける。

関数呼び出し (function call)

6. Expressions — Python 3.11.4 documentation

Glossary — Python 3.11.4 documentation

Positional argument と Keyword argument

関数呼び出しにおける argument の渡し方について、

  • positional argument
  • keyword argument

の2種類がある。

def f(x,y):
    pass

f(1,2)     # positional argument
f(x=1,y=2) # keyword argument

positional argument は、対応する parameter を明示的に指定せず、宣言の順番から対応を決定するような渡し方である。

keyword argument は、対応する parameter を明示的に記述する渡し方である。

keyword argument は、positional argument よりも前に書くことはできない。 したがって、以下はエラーとなる。

f(x=1,2) # SyntaxError: positional argument follows keyword argument

Argument unpacking

関数に複数の値を渡すとき通常はカンマで区切って順番に書くが、 複数の引数を iterable や辞書でまとめて渡すこともできる。 これを argument unpacking といい、次の2種類がある。

  • positional argument unpacking
  • keyword argument unpacking

positional argument unpacking は、複数の positional argument を iterable に詰めてまとめて渡すもので、*arg のように * を前に書いて表す。 このとき、argはリストやタプルのような iterable である必要があり、arg からひとつずつ順番に値を取り出して順に positional argument として使う。

つまり、以下の2つの呼び出しは等価である。

f(1,2)
f(*(1,2)) # positional argument unpacking

keyword argument unpacking は、複数の keyword argument を辞書に詰めてまとめて渡すもので、**argのように * を前に書いて表す。 辞書のキーは string になっている必要があり、キーの値と parameter の名前が対応付けられる。

つまり、以下の2つの呼び出しは等価である。

f(x=1,y=2)
f(**{"x":1,"y":2}) # keyword argument unpacking

順番に関する細かい補足

(後の関数呼び出しの詳細を読んでからのほうがわかりやすいです。)

あまりやることはないとは思うが、positional argument unpacking は keyword argument より後に置くことができる。 ただし、keyword argument より positional argument unpacking のほうが先に処理される。 したがって、次のようなことが起こる。

def f(x,y):
    pass

f(y=1,*(2,)) # OK: x=2, y=1
f(x=1,*(2,)) # TypeError: f() got multiple values for argument 'x'

2つ目の呼び出しのエラーは、positional argument unpacking によって最初の positional parameter x2 が渡され、その後に keyword argument として x=1 が渡されたことで x に対する argument が重複していることにより起こっている。

また、keyword argument unpacking より後に positional argument unpacking を置くことはできない。

f(**{"y":2},*(1,)) # SyntaxError: iterable argument unpacking follows keyword argument unpacking

関数定義 (function definition) と parameter の宣言

8. Compound statements — Python 3.11.4 documentation

Glossary — Python 3.11.4 documentation

関数定義で使われる parameter には、以下の種類がある。

  • positional-or-keyword parameter
  • positional-only parameter
  • keyword-only parameter
  • var-positional parameter (*args)
  • var-keyword parameter (**kwargs)

以下、それぞれの種類について説明する。

positional-or-keyword parameter

def f(x,y):
    ...

のような普通の宣言で定義される parameter はこの種類である。

def f(x,y):
    pass

f(1,2)     # OK: x=1, y=2
f(x=1,y=2) # OK: x=1, y=2
f(y=2,x=1) # OK: x=1, y=2

positional-only parameter (Python 3.8 以降)

def f(x,y,/):
    ...

のように/で区切って parameter の宣言をすると、 / 以前の parameter は positional-only-parameter となる。

positional-only parameter に対しては、positional argument としてのみ引数を渡すことができて、 keyword argument として渡すことはできない。

def f(x,/):
    pass

f(1)   # OK: x=1
f(x=1) # TypeError: f() got some positional-only arguments passed as keyword arguments: 'x'

keyword-only parameter

def f(x,y,*,kw1,kw2):
    ...

のように、parameter を*で区切って宣言すると、 *以降の parameter は keyword-only parameter になる。

keyword-only parameter に対しては、keyword argument としてのみ引数を渡すことができて、 positional argument として渡すことはできない。

def f(*,x):
    pass

f(x=1) # OK: x=1
f(1)   # TypeError: f() takes 0 positional arguments but 1 was given

var-positional parameter

def f(*args):
    ...

のように parameter を*identifierの形で宣言すると、var-positional parameter になる。

var-positional parameter は、関数に対して余分に渡された positional argument をすべて吸収してタプルとして受け取る。 もし余分な positional argument がなければ空のタプルが渡される。

def f(x,*args):
    pass

f(1,2,3) # OK: x=1, args=(2,3)
f(1)     # OK: x=1, args=()

var-positional parameter より後に宣言された parameter は keyword-only である。

def f(*args,kw): pass # kw is keyword-only parameter

var-keyword parameter

def f(**kwargs):
    ...

のように、parameter を**identifierの形で宣言すると、var-keyword parameter になる。

var-keyword parameter は、関数に対して余分に渡された keyword argument をすべて吸収して辞書として受け取る。 余分な keyword argument がない場合は空辞書が渡される。

def f(**kwargs):
    pass

f(x=1,y=2) # OK: kwargs={'x': 1, 'y': 2}
f()        # OK: kwargs={}

Parameter のデフォルト値

var-positional, var-keyword 以外の種類の parameter については、 デフォルト値 (default value) を与えることができる。

default value がある場合、関数呼び出しの際に明示的に引数が渡されなければ default value が使われる。

def f(x=1):
    pass

f(100) # OK: x=100
f()    # OK: x=1 (default value)

デフォルト値をもつ parameter より後に、デフォルト値をもたない positional parameter を宣言することはできない。

def f(x=1,y): pass # SyntaxError: non-default argument follows default argument

デフォルト値は関数定義の際に一度だけ計算され、以降のすべての呼び出しで共有される。 したがって、mutable な値をデフォルト値とすることは十分に注意が必要である(基本的に推奨されない)。

関数呼び出しに関する詳細

ここでは、関数呼び出し時の parameter に対する値の割り当てに関する規則についてより詳細に説明する。

まず前提として、python では関数呼び出しに際してすべての parameter になんらかの値が割り当てられる必要がある。 明示的な方法あるいはデフォルト値によって値が割り当てられない限りエラーとなる。 また、var-positional, var-keyword paramter が存在しない状況で余分な引数が発生すればそれもエラーになる。

関数呼び出しにおける parameter への値の割り当ては、具体的には次の順で行われる。

  1. (positional argument unpacking を含む) 各 positional argument を、(*付きでない) positional parameter に前から順に割り当てる。positional argument の数が positional parameter よりも多く、かつ var-positional parameter (*identifier) が存在する場合には余った positional argument をタプルに詰めて var-positional parameter に割り当てる。
  2. (keyword argument unpacking を含む) 各 keyword argument について名前が一致する parameter を探して値を割り当てる。1 ですでに値が割り当てられていればエラーとなる。keyword argument に対して名前の一致する parameter がなく、かつ var-keyword parameter (**identifier) が存在する場合には対応するエントリを var-keyword parameter の値となる辞書に追加する。
  3. 1,2 で値が割り当てられていない parameter が存在し、かつその parameter に対してデフォルト値が存在すればそれを値として使う。値が明示的に割り当てられず、デフォルト値もない場合にはエラー。

最後に、問題として出した例を振り返ってみよう。

def foo(a,b,/,c=10,*d,e,f=20,**g):
    pass

foo(1,x=2,*(3,4,5,6),**{"e":7,"y":8})

parameter について整理すると、

a: positional-only parameter
b: positional-only parameter
c: positional-or-keyword parameter (with default value)
d: var-positional parameter
e: keyword-only parameter
f: keyword-only parameter (with default value)
g: var-keyword parameter

(*の付かない) positional parameter は a,b,c の3つである。

また argument については、argument unpacking した上で

positional argument: 1,3,4,5,6
keyword argument: x=2,e=7,y=8

関数呼び出しにおける parameter への値の割り当ては、

  1. positional argument について、先頭の positional parameter から順に a=1,b=3,c=4 を割り当てる。 余った5,6は var-positional parameter d に対して d=(5,6)のようにタプルで割り当てる。
  2. keyword argument について、
    • e は parameter として宣言されているので、e=7を割り当てる。
    • x,y は parameter としては宣言されていないので、var-keyword parameter g に対してg={'x': 2, 'y': 8}のように辞書で割り当てる。
  3. f は 1,2 で値が割り当てられていないが、デフォルト値20があるのでこれを使う。

以上で、全ての parameter について値が割り当てられた。 まとめると、以下のようになる。

a = 1
b = 3
c = 4
d = (5, 6)
e = 7
f = 20
g = {'x': 2, 'y': 8}

まとめ

この記事では、Python における関数宣言や関数呼び出しにおける引数の規則についてまとめた。

  • parameter, argument の区別
  • positional, keyword の区別
  • 複数の引数をまとめて渡す unpacking
  • parameter に対するデフォルト値

などいろいろあってなかなかややこしい仕様だが、引数の割り当てに関する規則を正確に理解しておくと混乱することが減るだろう。