1
4

More than 3 years have passed since last update.

Python の関数定義・関数適用の記法、決定版

Last updated at Posted at 2021-02-26

ChangeLog (2021-03-20): 位置/キーワード専用引数があるときの発展ルールについて追記した
ChangeLog (2021-03-09): 変数の束縛ルールを追記した
ChangeLog (2021-03-08): 各節のまとめを節の先頭に持ってきた

はじめに

Python の関数定義・関数適用はやや特殊であるが、完全な仕様については意外と書かれていないのでまとめる。
主に 引数リストをどう指定するか に注目して説明する。

対象バージョン

Python 3.9

参考文献

Python 3.9.2 Documentation

関数定義 (仮引数リスト) の仕様

サマリー

変数には 位置専用 (positional-only) 引数、普通の (positional-or-keyword) 引数、キーワード専用 (keyword-only) 引数 に加えて、可変長位置 (var-positonal) 引数可変長キーワード (var-keyword) 引数 の合わせて5種類がある。

4種または5種の変数を入れた場合の例は次のとおり:

def foo(a, b, /, c, d, *, e, f, **kwargs): pass
def bar(a, b, /, c, d, *args, e, f, **kwargs): pass
  • いずれの関数も、a, b は位置引数としてのみ指定できる。
  • いずれの関数も、c, d は位置引数としてもキーワード引数としても指定できる。
  • いずれの関数も、e, f はキーワード引数としてのみ指定できる。
  • (**kwargs があるので) いずれの関数も、余ったキーワード引数は kwargs に格納される。
  • いずれの関数も、最後の引数のあとにコンマをつけてもよい。
  • 関数 bar において、余った位置引数は args に格納される。
  • *args**kwargs はそれぞれ、高々1回出現できる。

構文定義

Python は構文定義がちゃんと書かれているので参考にするとよい。<parameter_list> が仮引数リスト。

funcdef                   ::=  [decorators] "def" funcname "(" [parameter_list] ")"
                               ["->" expression] ":" suite
decorators                ::=  decorator+
decorator                 ::=  "@" assignment_expression NEWLINE
dotted_name               ::=  identifier ("." identifier)*
parameter_list            ::=  defparameter ("," defparameter)* "," "/" ["," [parameter_list_no_posonly]]
                                 | parameter_list_no_posonly
parameter_list_no_posonly ::=  defparameter ("," defparameter)* ["," [parameter_list_starargs]]
                               | parameter_list_starargs
parameter_list_starargs   ::=  "*" [parameter] ("," defparameter)* ["," ["**" parameter [","]]]
                               | "**" parameter [","]
parameter                 ::=  identifier [":" expression]
defparameter              ::=  parameter ["=" expression]
funcname                  ::=  identifier

まず、デフォルト値の指定は忘れて話を進める。

parameter_list

まず、<parameter_list> には2つのパターンがある。/ を引数リストに含む場合とそうでない場合である。
よって、次のような関数定義は正当である。
なお、最近の言語の流行りにしたがい、最後の引数のあとにコンマをつけることが許される。

def foo(a, b, /, <parameter_list_no_posonly>): pass
def bar(a, b, /): pass
def bar(a, b, /,): pass
def baz(<parameter_list_no_posonly>): pass

でも以下はだめ:

def foo(/, <parameter_list_no_posonly>): pass

後述する関数適用 (関数の呼び出し) で説明するが、Python の実引数指定方法には 位置引数キーワード引数 の2種類がある。
Python 3.8 で導入された / により、関数適用時に、/ の前の引数は位置引数としてしか指定できない。

parameter_list_no_posonly

次に、<parameter_list_no_posonly> は前述の「位置引数限定仮引数」以外の引数リスト部分である。これも2つのパターンがある。前半部分にあたる 普通の引数、つまり、「位置引数としてもキーワード引数としても指定できる引数」を含むパターンとそうでないパターンである。
これを合わせると、以下の関数定義はいずれも正当である:

def foo(a, b, /, c, d, <parameter_list_starargs>): pass
def fop(a, b, /, c, d): pass
def bar(a, b, /, <parameter_list_starargs>): pass
def baz(c, d, <parameter_list_starargs>): pass
def qux(<parameter_list_starargs>): pass

parameter_list_starargs

最後に来るのは star args である。これは * を含む可変長引数と、キーワード引数としてのみ指定できる引数を含む。
定義が少し複雑なので、まずは正当な <parameter_list_starargs> の例をあげたほうがわかりやすいだろう。
これも大きく分けて * 引数を含むか否かで2つのパターンがある:

*, x, y
*, **kwargs # 正当ではあるが使われない。この場合単に **kwargs でいい。
*, x, y, **kwargs
*args, **kwargs
*args, x, y, **kwargs
**kwargs

ここで

  • *<識別子> は、余った位置引数が格納される可変長引数である。一般的には *args が使われる。
  • **<識別子> は、余ったキーワード引数が格納される可変長引数である。一般的には **kwargs が使われる。

定義からわかるように、これらはそれぞれ高々1回しか出現できない。さらに、
* もしくは *<識別子> より後ろの引数は 関数適用時に、キーワード引数としてのみ指定できる、というルールがある。

デフォルト値

*args**kwargs を除く」各引数には <識別子>=<値> の形式でデフォルト値を設定することができる。
(指定できないが、*args のデフォルト値は空の tuple、**kwargs のデフォルト値は空の辞書である。)
引数リストのどこかでデフォルト値を設定した場合、そこより後ろの引数には全てデフォルト値を設定する必要がある。

よってこれはOK:

def foo(x, y, z): pass
def bar(x, y=0, z=1): pass
def baz(x, y=0, *, z=1): pass
def qux(x, y=0, *args, z=1, **kwargs): pass

これはNGである:

def foo(x, y=0, z): pass # z にデフォルト値が設定されていない
def bar(x, y=0, **kwargs={}): pass # **kwargs にデフォルト値を設定した

関数適用 (実引数リスト) の仕様

サマリー

実引数には

  • 位置引数: foo(3)
  • キーワード引数: foo(k=3)
  • イテラブルアンパック (iterable unpacking): foo(*a)foo(*[1,2,3])foo(*(1,2,3))
  • 辞書アンパック (dictionary unpacking): foo(**d)foo(**{'k':3})

の4種類がある。実引数の指定はかなり自由であって、仮引数のリストと異なり、複数のアンパックが使える。
以下の2つのタブーだけ避ければよい。

  • 位置引数をキーワード引数 (もしくは辞書アンパック) の右に置いてはいけない。
  • イテラブルアンパックを辞書アンパックの右に置いてはいけない。
foo(1, *a, 2, *b, s=3, *c, t=4, **x, u=5, **y, v=6)     # OK
foo(1, *a, 2, *b, s=3, 7, *c, t=4, **x, u=5, **y, v=6)  # NG: 7 が s=3 の右にきているので
foo(1, *a, 2, *b, s=3, *c, t=4, **x, *d, u=5, **y, v=6) # NG: *d が **x の右にきているので

自信がなければ「位置引数、イテラブルアンパック、キーワード引数、辞書アンパック」の順で並べておくのが無難だし、読みやすい。

位置引数とキーワード引数

関数適用で重要なのは、位置引数とキーワード引数 それぞれの指定方法、そして、それがどう展開されて 仮引数にどう束縛されるか、である。

位置引数は単に実引数を指定したもの、キーワード引数は <仮引数名>=<実引数> の形で指定したものである。
例えば

foo(3, b=2)

において、第1引数は位置引数、第2引数はキーワード引数として指定されている。
以下の説明では省略するが、すべての位置引数にはセイウチ演算子による代入式が使える。よって

foo(x:=3, b=2)

と書くことができる。
プログラムを書く人にとってのキーワード引数の最大の利点は、引数の順番を問わずに指定できる ことである。

構文

まず、関数の呼び出しについても構文定義が与えられているので見ることにする:

call                 ::=  primary "(" [argument_list [","] | comprehension] ")"
argument_list        ::=  positional_arguments ["," starred_and_keywords]
                            ["," keywords_arguments]
                          | starred_and_keywords ["," keywords_arguments]
                          | keywords_arguments
positional_arguments ::=  positional_item ("," positional_item)*
positional_item      ::=  assignment_expression | "*" expression
starred_and_keywords ::=  ("*" expression | keyword_item)
                          ("," "*" expression | "," keyword_item)*
keywords_arguments   ::=  (keyword_item | "**" expression)
                          ("," keyword_item | "," "**" expression)*
keyword_item         ::=  identifier "=" expression

<argument_list>実引数のリスト を表す。雑に書くと、次の3パートで構成されている。

foo(<positional_arguments>, <starred_and_keywords>, <keywords_arguments>)

第1ゾーンの <positional_arguments> では位置引数を指定できる。これは単に式の並びであるが、それに加えて複数の *<式> が指定できる。これは イテラブルアンパック と呼ばれる。これにおいて、<式> はイテラブル (リスト、タプル、文字列など) でなければならなくて、中身が位置引数列にアンパックされて組み込まれる。つまり、次のプログラムの3行目と4行目は等価である。

a = [2, 3]
b = (4,)
foo(1, *a, *b)
foo(1, 2, 3, 4)

位置引数とイテラブルアンパックはミックスできるので、以下の呼び出しは構文として正当である。

foo(*a, 1, *b, 2)

第2ゾーンの <starred_and_keywords> では前述のイテラブルアンパックと、キーワード引数のミックスした並びが許容される。
なのでこのような呼び出しは構文としては正当である。

foo(3, b=1)
foo(3, *a, b=1)
foo(3, *a, 4, b=1) # "3, *a, 4" までが第1ゾーン、"b=1" が第2ゾーン

さらに (Python 3.9 からは) これらも、意外にも正当である。

foo(3, b=1, *a)
foo(3, b=1, *a, c=2)

やってはいけないのは、位置引数をキーワード引数の右に置くこと である。たとえばこれはNG:

foo(b=1, 3) # NG

第3ゾーンの <keywords_arguments> ではキーワード引数と **<式> のミックスした並びが書ける。
**<式>辞書アンパック と呼ばれる。<式> に書けるのはマッピングオブジェクト (辞書など) であり、これがキーワード引数列にアンパックされて組み込まれる。つまり、次のプログラムの2行目と3行目は等価である。

x = {b: 2, c: 3}
foo(a=1, **x, d=4)
foo(a=1, b=2, c=3, d=4)

構文定義により、以下の呼び出しは正当である。

foo(a=1, **x, d=4, **y)

一方で、やってはいけないのは、イテラブルアンパックを辞書アンパックの右に置くこと である。たとえばこれはNG:

foo(**x, *y) # NG

関数を呼んだときの変数束縛

サマリー

関数を呼んだ時には、仮引数が実引数に束縛される。その順番は次のとおり:

  1. イテラブル/辞書アンパックを実引数リスト内で展開する。
  2. 位置引数を仮引数に割り当てる。
  3. キーワード引数を仮引数に割り当てる。
  4. 【実引数が足りないとき】デフォルト値を割り当てる。
  5. 【実引数が余ったとき】可変長仮引数があればそこに割り当てる。

以下、まずは位置専用引数などの専用引数のことは忘れて、変数の束縛について説明する。
(専用引数がある場合については、最後のセクションを参照のこと)

(1) アンパックの展開

イテラブル/辞書アンパックをその出現位置で実引数リストの一部として展開する。
例えば、

a = [2, 3]
b = {'k':5}

foo(1, *a, 4, **b, l=6)

は次の呼び出しと等価である:

foo(1, 2, 3, 4, k=5, l=6)

以下、実引数には位置引数とキーワード引数の2種類しかないもの として話を進める。

(2) 位置引数の割り当て

まず、実引数リストの位置引数を先頭から、仮引数の先頭から順に割り当てていく。
公式ドキュメントでは仮引数に対する「スロット」という言葉を使っている。

例えば、

def foo(a, b, c, d): ...
foo(1, 2, d=3)

で位置変数を割り当てるとこうなる:

仮引数→ a b c d
位置引数 1 2
キーワード引数

(3) キーワード引数の割り当て

次に、実引数リストのキーワード引数を対応する (識別子の) 仮引数に割り当てていく。

def foo(a, b, c, d): ...
foo(1,2, d=3)

キーワード引数を割り当てるとこうなる:

仮引数→ a b c d
位置引数 1 2
キーワード引数 3

この時点で、あるスロットに位置引数とキーワード引数の両方が割り当てられている場合、呼び出しは失敗し、例外が発生する。
たとえばこのような場合である。

def foo(a, b): ...
foo(1, a=2)
仮引数→ a b
位置引数 1
キーワード引数 2

(4) 【実引数が足りないとき】デフォルト値の割り当て

実引数が割り当てられていないスロットがある場合、関数定義でデフォルト値が指定されていればそれが割り当てられる。

def foo(a, b, c=99, d=99): ...
foo(1, 2, d=3)
仮引数→ a b c d
位置引数 1 2
キーワード引数 3
デフォルト値 99

もし、これでも空いているスロットが残る場合、呼び出しは失敗し、例外が発生する。
たとえば次のような場合、c に割り当てるべき値が見つからないのでエラーとなる。

def foo(a, b, c, d=99): ...
foo(1, 2, d=3) # TypeError
仮引数→ a b c d
位置引数 1 2
キーワード引数 3
デフォルト値

(5) 【実引数が余ったとき】可変長仮引数への割り当て

位置引数がスロットより多い場合、仮引数に *args のような可変長位置引数があれば、余った分はそこに タプルとして 格納される。もしそのような仮引数がない場合、呼び出しは失敗する。

def foo(a, b, *args): ...
foo(1, 2, 3, 4)
仮引数→ a b args
位置引数 1 2 (3, 4)

なお、可変長位置引数があるけど実引数が余らなかった場合は、空のタプル () が格納される。


同様に、キーワード引数がスロットより多い場合、仮引数に **kwargs のような可変長位置引数があれば、余った分はそこに 辞書として 格納される。もしそのような仮引数がない場合、呼び出しは失敗する。

def foo(a, b, **kwargs): ...
foo(a=1, b=2, c=3, d=4)
仮引数→ a b kwargs
キーワード引数 1 2 {'c':3, 'd':4}

なお、可変長キーワード引数があるけど実引数が余らなかった場合は、空の辞書 {} が格納される。

ここまで読んだ人は、公式ドキュメントにある次の例が、なぜエラーになるかわかると思う:

def f(a, b): ...
f(a=1, *(2,)) # TypeError

まず *(2,) により、位置引数の割り当てが起きて a のスロットに 2 が割り当てられる。
次にキーワード引数の割り当てにより、a1 が割り当てられる。
よって a に2つの値が割り当てられるので、前述 (2) で説明したように、呼び出しは失敗する。

変数束縛の発展ルール: 位置/キーワード専用引数との組み合わせ

ここまでで仕様の9割方は説明できた。しかし、位置/キーワード引数が存在している場合には、前述した、実引数のスロットへの展開方法に特殊ルールが働く。これを含めた束縛ルールのサマリーは次のようになる (ステップ2および3に変更がある):

  1. イテラブル/辞書アンパックを実引数リスト内で展開する。
  2. 位置引数を キーワード専用引数以外の 仮引数に割り当てる。
  3. キーワード引数を 位置専用引数以外の 仮引数に割り当てる。
  4. 【実引数が足りないとき】デフォルト値を割り当てる。
  5. 【実引数が余ったとき】可変長仮引数があればそこに割り当てる。

(2) 位置引数の割り当て【発展版】

正確には、実引数リストの位置引数を仮引数に割り当てる際に、キーワード専用引数には割り当てない。
例えば、

def foo(a, b, *, c, d): ... # c と d はキーワード専用引数
foo(1, 2, 3, d=4) # TypeError

この関数呼び出しは、c3 が割り当てられないので、c に束縛する値がないことによりエラーになる。

可変長位置引数がある場合には、束縛はこのようになる:

def foo(a, b, *args, c=99, d=99): ... # args は可変長位置引数、c と d はキーワード専用引数
foo(1, 2, 3, 4, d=5) # OK
仮引数→ a b args c d
位置引数 1 2 (3, 4)
キーワード引数 5
デフォルト値 99

c にはデフォルト値を指定しておいたので、すべてのスロットに値が割り当てられている。よって、この関数呼び出しは成功する。

(3) キーワード引数の割り当て【発展版】

正確には、実引数リストのキーワード引数を仮引数に割り当てる際に、位置専用引数には割り当てない。
例えば、以下の関数呼び出しを考える:

def foo(a, b, /, c, d): ... # a と b は位置専用引数
foo(1, b=2, c=3, d=4) # TypeError

b には 2 が割り当てられないのでこの呼び出しは失敗する。

これも同様に、可変長キーワード引数がある場合を考えるとわかりやすい:

def foo(a, b=99, /, c=99, d=99, **kwargs): ... # a と b は位置専用引数、kwargs は可変長キーワード引数
foo(1, b=2, c=3, d=4) # OK
仮引数→ a b c d kwargs
位置引数 1
キーワード引数 3 4 {'b': 2}
デフォルト値 99

実引数で指定している b=2kwargs のほうに束縛される。一方で、仮引数の b にはデフォルト値の 99 が割り当てられる。

b の割り当てに見られるこの機能があると嬉しいときがある。例を次節で説明する。

位置専用引数の使いどころ

たとえば、このような (無意味な) 関数 call_twice を作りたいとする:


f を関数として、

call_twice(f, 1, 2, c=3, d=4)

が次の呼び出しと等価になる:

f(1, 2, c=3, d=4)
f(1, 2, c=3, d=4)

Pandas や Dask を使うときには、partial などを駆使するよりは、こういう引数の指定方法があると嬉しかったりする。
このときに、次のように call_twice を定義すると、後述するように、困る場合がある。

def call_twice(func, *args, **kwargs):
    func(*args, **kwargs) # 変数をパススルーするおなじみのイディオム
    func(*args, **kwargs)

こう定義すると、f がキーワード引数 func を取るときに困る。つまり、

call_twice(f, 1, 2, c=3, d=4, func=add) # TypeError

とは呼べない。なぜかというと、仮引数 func には第1引数の位置引数としてすでに f が割り当てられているので、キーワード引数で func を指定することができない。つまりこういうことである。

仮引数→ func args kwargs
位置引数 f (1, 2)
キーワード引数 add {'c': 3, 'd': 4}
備考 ↑重複

このとき、第1仮引数を位置専用引数として宣言しておく。

def call_twice2(func, /, *args, **kwargs):
    func(*args, **kwargs) # 変数をパススルーするおなじみのイディオム
    func(*args, **kwargs)

こうすることにより、

call_twice2(f, 1, 2, c=3, d=4, func=add) # OK

と呼ぶと func=addkwargs の一部として束縛されることになり、無事に f に渡されることになる。
こうして、call_twice 自体で使う変数と、パススルーする変数を安全に分けることができる。

仮引数→ func args kwargs
位置引数 f (1, 2)
キーワード引数 {'c': 3, 'd': 4, 'func': add}

(終)

1
4
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
4