1. ホーム
  2. python

args','**','**kwargs' を理解する。

2022-02-19 18:12:30
<パス

オリジナルテキスト '*'、'*args'、'**'、'**kwargs ' を理解する。

私がPythonを学び始めた頃、args,kwargs,*,**の使い方に戸惑ったことがあります。きっと、このことで迷っている人はたくさんいると思います。私はこの投稿でその混乱を解消しようと思います(そしてできれば混乱が少なくなるように)。

以下の5つのステップで理解していきましょう。

  1. 関数呼び出しで「*」を理解する
  2. * ' を通して関数の定義を理解する。 関数の定義による「args
  3. 関数呼び出しによる'**'の理解
  4. * 関数定義で '* を解く 関数の定義による「kwargs」。
  5. args', 'kwargs' シナリオを説明するためのアプリケーション例とその理由

関数呼び出しで「*」を理解する

3つの位置引数 "fun" を持つ関数を定義します。

>>> def fun(a,b,c):
...     print a,b,c
... 


3つの位置引数でこの関数を呼び出す

>>> fun(1,2,3)
1 2 3 #output


この関数を3つの位置の引数をinとoutで呼び出すと、3つの引数が出力されることがわかると思います

今度は、3つの整数で系列を定義し、'*'を使用します。

>>> l = [1,2,3]
>>> fun(*l)
1 2 3 #output


は何をしたのですか?

配列 'l' の値を位置引数として分割し、それらの位置引数を関数 'fun' に渡して呼び出す。

つまり、系列を分割して位置引数を渡すということは、l = [1,2,3] なので fun(*l) は fun(1,2,3) と等価であるということです。

他の値をシリーズで使ってみる

>>> l = [4,8,0]
>>> fun(*l)
4 8 0 #output


次に、配列に4つの値を入れてみて、関数を呼び出すとどうなるでしょうか。

>>> l = [3,6,9,1]
>>> fun(*l)
Traceback (most recent call last):
  File "
"
, line 1, in <module>
TypeError: fun() takes exactly 3 arguments (4 given)


この呼び出しでは正しい結果が得られず、TypeWrror例外が発生する。エラー "fun() takes exactly 3 arguments (4 given) " を見るのは簡単です。

なぜこのようなことが起こるのでしょうか?

配列 'l' には4つの値が含まれています。そこで、'fun(*l)'を呼び出そうとすると、'l'の値が分割されて位置引数として関数funに渡されてしまいます。しかし、'l' には4つの値があり、'fun(*l)' を呼ぶことは 'fun(3,6,9,1)' を呼ぶことと同じであり、関数 'fun ' は3つの位置引数だけで定義されているので、このエラーが発生するのである。同様に、系列'l'の2つの値の場合にも同じ手順で、エラー内容に注意してください。

>>> l = [7,4]
>>> fun(*l)
Traceback (most recent call last):
  File "
"
, line 1, in <module>
TypeError: fun() takes exactly 3 arguments (2 given)


位置引数に '*l' を混ぜる

>>> fun(23, *l)
23 7 4


ここでは、位置の引数23と、系列「l」から削除された2つの値7と4が与えられているので、3つの引数23、7、4が関数「fun」に渡されます。

関数の定義を通して'*args'の意味を理解する

関数の定義を変更する。

>>> def fun(*args):
...     print args
... 


この関数を呼び出すための位置引数を渡します

>>> fun(13)
(13,)


複数の引数を渡してこの関数を呼び出す

>>> fun(11,93,43)
(11, 93, 43)


関数定義で'*args'は何をするのですか?

通常の引数のリストではなく、タプルを位置引数として受け取ります。この場合、"args"はタプルになります。この"common arguments"の部分の説明は、次の例で明らかになるので、理解するのに心配する必要はありません。前の例では、関数が呼び出されて "args" を表示するとき、タプルに含まれるすべての値を表示します。

ここで、"*args" と "regular argument list" を混ぜるように関数を再定義してみます。

>>> def fun(a, *args):
...     print "a is ", a
...     print "args is ", args
... 


この関数定義では、引数 "a" は "regular argument list" を表しています。
この関数を呼び出すには、4つの位置引数を渡します。

>>> fun(11,12,34,43)
a is 11
args is (12, 34, 43)


a'が最初の位置引数である11とプリントアウトされることは容易に理解できる。a' の後には、'*args' という一つの引数があるだけです。したがって、'args'は通常の引数以外の位置引数をタプルとして受け取ります。したがって、タプルargsは12,34,43をタプルとして受け取る。

また、この関数を呼び出す際に位置引数を渡すことができます。

>>> fun(91)
a is 91
args is ()


ここでは、渡した唯一の引数が通常の引数'a'に代入されています。したがって、'args'は空のタプルを受け取る。

これで"args"が得られたので、やりたいことをやるために必要な値を抽出することができる。fun"を再定義します。

>>> def fun(a, *args):
...     print a
...     print "args can receive a tuple of any number of arguments, let's print all that."
...     for arg in args:
...             print arg
... 


ここで、この関数を呼び出すために、任意の引数を渡します。

>>> fun(1,5,6,7)
1
args can receive a tuple of any number of arguments, let's print all that.
5
6
7


args' はタプルなので、それに対して反復処理を行うことができます。

では、得られるすべての引数を使うシナリオを考えてみよう。2つの関数を使い、1つ目の関数には任意の数の引数を与え、もう1つの関数で1つ目以外の引数の合計を計算する必要があります。奇妙な使用例ですが、これまでやってきたことを復習してみましょう。我々の目標は、一方の関数で変数の引数を取得し、その引数をもう一方の関数に食わせることである。

最初のステップでは、合計を計算する関数を書きます。このユースケースでは、この関数は最初の関数の中で適用されます。

>>> def calculate_sum(*args):
...     return sum(args)
... 


この関数では、タプルまたは配列を引数として受け取り、そのタプルの全要素の合計を返す組み込み関数 'sum' を使っています。関数の定義からわかるように、'args'は関数の位置に渡された引数を含むタプルを受け取ります。したがって、'args' は関数 'sum' の引数として簡単に使用されるタプルである。次に、任意の数の引数を持つ別の関数を定義し、前の関数を使用して、最初の引数を除いたすべての引数の合計を計算する。

>>> def ignore_first_calculate_sum(a,*iargs):
...     required_sum = calculate_sum(*iargs)
...     print "sum is ", required_sum
... 



この関数には、任意の数の引数を渡すことができます。最初の引数は通常の引数 'a' で受け取り、他の引数はタプルとして 'iargs' で受け取ります。今回考えているように、最初の引数を除いたすべての引数の合計が計算される。したがって、最初の引数を受け取るために'a'を使用し、他の引数を含むタプルとして'iargs'を使用しています。関数 'calculate_sum' を使うが、 'calculate_sum' は、タプルとして 'args' に渡される複数の位置の引数を必要とする . そこで、関数 'ignore_first_calculate_sum' はタプル 'iargs' を分割し、その要素を位置引数として 'calculate_sum' に渡す必要があります。 sum' は、タプル 'iargs' を分割し、その要素を位置引数として渡します。タプルは'*'で分割されていることに注意してください。

そこで、『required_sum=calculate_sum(*iargs)』をこのように呼び出します。

required_sum=calculate_sum(iargs)' はこのように呼び出すことはできません。なぜなら、'calculate_sum' に渡す前に値をアンパックする必要があるからです。なぜなら、'calculate_sum'に渡す前に値を解凍する必要があるからです。'*'がなければ、値を解凍することができず、目的の動作をさせることはできません。この関数は次のように呼び出される。

>>> ignore_first_calculate_sum(12, 1,4,5)
sum is 10
>>> ignore_first_calculate_sum()
Traceback (most recent call last):
  File "
"
, line 1, in <module>
TypeError: ignore_first_calculate_sum() takes at least 1 argument (0 given)


目的の結果を得る。

関数呼び出しによる'**'の理解

3つの引数を持つ関数を定義し、それを複数の方法で呼び出す。

>>> def fun(a, b, c):
...     print a, b, c
... 
>>> fun(1,5,7)
1 5 7
>>> fun(a=1,b=5,c=7)
1 5 7


を使用します。 関数を呼び出すには、辞書が必要です。注意:** 関数呼び出しで "*" を使用すると、タプルが必要になります。 "の場合、辞書が必要です**。

>>> d={'b':5, 'c':7}
>>> fun(1, **d)
1 5 7


関数呼び出しで "**"は何をするのですか?

辞書を解凍し、辞書内のデータ項目をKey-Valueの引数として関数に渡します。つまり、"fun(1, **d)" は "fun(1, b=5, c=7)" と同じように記述されます。
理解を深めるために、もう少し例を挙げます。

>>> d = {'c':3}
>>> fun(1, 4, **d)
1 4 3
>>> d = {'a':7, 'b':3, 'c':8}
>>> fun(**d)
7 3 8


いくつかエラーを出してみましょう。

>>> d = {'a':7, 'b':3, 'c':8, 'd':90}
>>> fun(**d)
Traceback (most recent call last):
  File "
"
, line 1, in <module>
TypeError: fun() got an unexpected keyword argument 'd'


この呼び出しは 'fun(a=7, b=3, c=8, d=90)' と同等ですが、この関数は3つの引数しか取らないので、TypeError が発生します。

>>> d = {'a':7, 'b':3, 'd':90}
>>> fun(**d)
Traceback (most recent call last):
  File "
"
, line 1, in <module>
TypeError: fun() got an unexpected keyword argument 'd'


fun(**d) は fun(a=7, b=3, d=90) と同じ意味です。関数 "fun" に好きな数の引数を渡しますが、引数リストに 'd' がなく、呼び出し中の関数に 'd' キーワード引数を渡すと TypeError が発生します。

つまり、"**" は辞書を展開し、すなわち辞書内のキーと値のペアをキーワード引数として、これらは呼び出される関数にキーワード引数として送られます。 "*" はリスト/タプルを展開し、すなわちリスト内の値を位置引数として、これらは呼び出される関数に位置引数として送られます。

関数定義から「**kwargs」の意味を理解する

関数 "fun" を再定義します。

>>> def fun(a, **kwargs):
...     print a, kwargs
... 


この関数は位置引数を1つだけ使用します。なぜなら、通常の引数リストには変数 'a' が1つだけ含まれているからです。しかし、"**kwargs"を使用すると、複数のキー-バリュー引数を渡すことができます。

>>> fun(1, b=4, c=5)
1 {'c': 5, 'b': 4}
>>> fun(45, b=6, c=7, d=8)
45 {'c': 7, 'b': 6, 'd': 8}


関数定義における "**kwargs" とはどのような意味ですか?
関数を "**kwargs" で定義すると、kwargs は通常の引数リスト位置に加えて、キーバリュー引数の辞書を受け取ります。この場合、'kwargs' は辞書である。

関数を再定義します。

>>> def fun(a, **kwargs):
...     print "a is ", a
...     print "We expect kwargs 'b' and 'c' in this function"
...     print "b is ", kwargs['b']
...     print "c is ", kwargs['c']
... 
>>> fun(1, b=3,c=5)
a is 1
We expect kwargs 'b' and 'c' in this function
b is 3
c is 5


エラーコールです。

>>> fun(1, b=3, d=5)
a is 1
We expect kwargs 'b' and 'c' in this function
b is 3
c is 
Traceback (most recent call last):
  File "
"
, line 1, in <module>
  File "
"
, line 5, in fun
KeyError: 'c'


上記の呼び出しでは、位置パラメーター 'a' とキーパラメーター 'b' の両方が出力される。渡されたもう一つのキーパラメータは 'd' で、この関数はキーパラメータ 'c' を受け取り、辞書 'kwargs' からそれを取得します。しかし、キー値 'c' は渡されないので、KeyError が発生する。キー値'c'が渡された場合は、このエラーは発生しません。

>>> fun(1, b=3, d=5, c=9)
a is 1
We expect kwargs 'b' and 'c' in this function
b is 3
c is 9


関数の引数リストに'**kwargs'があるので、任意のKey-Value引数を渡すことができる。上記の呼び出しは "d" を渡しますが、この関数はそれを使用しません。

またエラーです。

>>> fun(1, {'b':2, 'c':34})
Traceback (most recent call last):
  File "
"
, line 1, in <module>
TypeError: fun() takes exactly 1 argument (2 given)



エラーが示すように、関数 'fun' は位置引数を1つしか取らないのに、2つの引数を与えています。kwargs' は key-value の引数を辞書として受け取りますが、'kwargs' に位置引数として辞書を渡すことはできません。このように呼び出すことができる。

>>> fun(1, **{'b':2, 'c':34})
a is 1
We expect kwargs 'b' and 'c' in this function
b is 2
c is 34


辞書の前に "**" をつけて、辞書を展開し、辞書内のデータ項目を key-value 引数として渡します。

args', 'kwargs' シナリオを説明するためのアプリケーション例とその理由

クラスを継承してメソッドをオーバーライドするときは、必ず「*args」と「**kwargs」を使って、受け取った位置引数とキー引数を親メソッドに与える必要があります。例によって、よりよく理解することができます。

>>> class Model(object):
...     def __init__(self, name):
...             self.name = name
...     def save(self, force_update=False, force_insert=False):
...             if force_update and force_insert:
...                     raise ValueError("Cannot perform both operations")
...             ... if force_update:
...                     print "Updated an existing record"
...             if force_insert:
...                     print "Created a new record"
... 


クラスを定義し、そのクラスのオブジェクトを作成することができ、そのクラスのオブジェクトは「save()」というメソッドを持ちます。クラスのオブジェクトは、save()メソッドでデータベースに保存できるとします。save()パラメータは、データベースにレコードを作成するか、既存のレコードを更新するかを決定する。
モデル'の動作を持つ新しいクラスを構築します。ただし、このクラスのオブジェクトは、いくつかの条件をチェックした後にのみ保存します。この新しいクラスは 'Model' を継承し、'Model' の 'save()' をオーバーライドします。

>>> class ChildModel(Model):
...     def save(self, *args, **kwargs):
...             if self.name=='abcd':
...                     super(ChildModel, self).save(*args, **kwargs)
...             else:
...                     return None
... 


対応する保存動作は、実際には「モデル」の「save」メソッドで行われます。そこで、'Model' メソッドの代わりに子クラスの 'save()' メソッドを呼び出します。子である ChildModel の 'save()' は、親である save() が必要とするあらゆる引数を受け取り、親メソッドに渡します。したがって、サブクラスの 'save()' メソッドは、引数リストに "*args" と "**kwargs" を持ち、通常の引数リスト以外の任意の位置やキーバリューの引数を取ることができます。

以下はChildModelエンティティを作成し、保存しています。

>>> c=ChildModel('abcd')
>>> c.save(force_insert=True)
Created a new record
>>> c.save(force_update=True)
Updated an existing record


ここでは、パートタイムの引数がオブジェクトのsave()メソッドに渡されています。これは、"kwargs" でキーワード引数を含む辞書を受け取っています。そして、"**"を使ってこの辞書をキーワード引数として展開し、スーパークラスのsave()に渡しているのです。つまり、スーパークラスsave()はキーワード引数「force_insert」を取得し、それに従って動作していたのです。

キーワードだけの引数と位置だけのパラメータ

関連するPEP

def name(positional_only_parameters, /, positional_or_keyword_parameters,
         *, keyword_only_parameters):