Pythonの関数引数の大きな歴史

実は、Pythonの議論の歴史はそれほど大きくありません。



私はいつもPython関数の引数を持つ仕事に、あなただけ理解する必要があることに驚いた*args**kwargs。そして、私は無駄ではなく驚いた。結局のところ、議論は決して簡単ではありません。この投稿では、Pythonの関数引数に関連するすべての概要を説明したいと思います。最終的には、議論を扱う全体像を実際に示すことができ、この記事が、読者が新しいものを見つけることができない別の出版物にならないことを願っています。そして今-要点まで。







この記事の読者のほとんどは、関数引数の本質を理解していると思います。初心者のために、これらは呼び出しの開始者によって関数に送信されるオブジェクトであることを説明します。関数に引数を渡すとき、関数にディスパッチされるオブジェクトのタイプ(可変または不変オブジェクト)に応じて、多くのアクションが実行されます。関数呼び出しイニシエーターは、関数を呼び出してそれに引数を渡すエンティティです。関数の呼び出しと言えば、これから説明することを考慮する必要があります。



関数が宣言されるときに名前が指定される引数は、呼び出されたときに関数に渡されるオブジェクトを格納します。さらに、関数の対応するローカル変数とそのパラメーターに何かが割り当てられている場合、この操作は関数に渡される不変オブジェクトに影響を与えません。例えば:



def foo(a):
    a = a+5
    print(a)             #  15

a = 10
foo(a)
print(a)                 #  10


ご覧のとおり、関数呼び出しは変数にまったく影響を与えませんでしたaこれは、不変のオブジェクトが関数に渡されたときに起こることです。



また、可変オブジェクトが関数に渡されると、上記とは異なるシステム動作が発生する可能性があります。



def foo(lst):
    lst = lst + ['new entry']
    print(lst)                #  ['Book', 'Pen', 'new entry']

lst = ['Book', 'Pen']
print(lst)                    #  ['Book', 'Pen']
foo(lst)
print(lst)                    #  ['Book', 'Pen']


ここで何か新しいことに気づきましたか?「いいえ」と答えれば、あなたは正しいです。しかし、関数に渡される可変オブジェクトの要素に何らかの影響を与えると、何か別のことがわかります。



def foo(lst):
    lst[1] = 'new entry'
    print(lst)                #  ['Book', 'new entry']

lst = ['Book', 'Pen']
print(lst)                     #  ['Book', 'Pen']
foo(lst)
print(lst)                     #  ['Book', 'new entry']


ご覧のとおり、パラメータのオブジェクトlstは関数呼び出し後に変更されました。これは、パラメータに格納されているオブジェクトへの参照を処理しているために発生しましたlstその結果、このオブジェクトのコンテンツを変更することは、関数の範囲外です。このようなオブジェクトのディープコピーを作成し、それらを関数のローカル変数に書き込むだけで、これを回避できます。



def foo(lst):
    lst = lst[:]
    lst[1] = 'new entry'
    print(lst)                   #  ['Book', 'new entry']

lst = ['Book', 'Pen']
print(lst)                       #  ['Book', 'Pen']
foo(lst)
print(lst)                       #  ['Book', 'Pen']


それはまだあなたを驚かせませんでしたか?そうでない場合は、あなたが知っていることをスキップして、すぐにあなたのために新しい資料に移ることを確認したいと思います。もしそうなら、私の言葉に印を付けてください。あなたが議論をよりよく知るようになるにつれて、あなたはもっとたくさんの興味深いことを学ぶでしょう。



したがって、関数引数について知っておくべきことは次のとおりです。



  1. 位置引数が関数に渡される順序。
  2. 名前付き引数が関数に渡される順序。
  3. デフォルトの引数値を割り当てます。
  4. 可変長の引数のセットの処理の編成。
  5. 引数を解凍します。
  6. 名前でのみ渡すことができる引数を使用する(キーワードのみ)。


これらの各ポイントを見てみましょう。



1.位置引数を関数に渡す順序



位置引数は左から右に処理されます。つまり、関数に渡される引数の位置は、宣言されたときに関数のヘッダーで使用されたパラメーターの位置と直接対応していることがわかります。



def foo(d, e, f):
    print(d, e, f)

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


変数abおよびcの値は1、2、および3です。これらの変数は、関数が呼び出される引数の役割を果たしfooます。これらは、関数の最初の呼び出しで、パラメータに対応するdefこのメカニズムは、Pythonの関数引数について知っておく必要があることに関する上記の6つのポイントのほぼすべてに適用されます。関数が呼び出されたときに関数に渡される位置引数の位置は、関数パラメーターに値を割り当てる際に主要な役割を果たします。



2.名前付き引数を関数に渡す順序



名前付き引数は、関数が宣言されたときに割り当てられた名前に対応するこれらの引数の名前で関数に渡されます。



def foo(arg1=0, arg2=0, arg3=0):
    print(arg1, arg2, arg3)

a, b, c = 1, 2, 3
foo(a,b,c)                          #  1 2 3
foo(arg1=a, arg2=b, arg3=c)         #  1 2 3
foo(arg3=c, arg2=b, arg1=a)         #  1 2 3
foo(arg2=b, arg1=a, arg3=c)         #  1 2 3


ご覧のとおり、この関数fooは3つの引数を取ります。これらの引数は名前が付けられarg1arg2そしてarg3関数を呼び出すときは、引数の位置をどのように変更するかに注意してください。名前付き引数は、位置引数とは異なる方法で処理されますが、システムはそれらを左から右に読み取り続けます。Pythonは、関数パラメーターに適切な値を割り当てるときに、位置ではなく引数の名前を考慮します。その結果、渡された引数の位置に関係なく、関数は同じものを出力することがわかります。いつも1 2 3です。



パラグラフ1で説明されているメカニズムは、ここでも引き続き機能することに注意してください。



3.デフォルトの引数値の割り当て



名前付き引数にデフォルト値を割り当てることができます。このメカニズムを関数で使用する場合、特定の引数はオプションになります。そのような関数の宣言は、ポイント#2で検討したもののように見えます。唯一の違いは、これらの関数の呼び出し方法です。



def foo(arg1=0, arg2=0, arg3=0):
    print(arg1, arg2, arg3)

a, b, c = 1, 2, 3
foo(arg1=a)                         #  1 0 0
foo(arg1=a, arg2=b )                #  1 2 0
foo(arg1=a, arg2=b, arg3=c)         #  1 2 3


この例では、宣言で説明されているように、すべての引数を関数に渡していないことに注意してください。このような場合、対応するパラメーターにはデフォルト値が割り当てられます。この例を続けましょう:



foo(arg2=b)                         #  0 2 0
foo(arg2=b, arg3=c )                #  0 2 3

foo(arg3=c)                         #  0 0 3
foo(arg3=c, arg1=a )                #  1 0 3


これらは、名前付き引数を渡して関数を呼び出す上記のメカニズムを使用する単純で直接的な例です。ここで、これまでに説明したポイント#1、#2、および#3を組み合わせて、実験を複雑にしましょう。



foo(a, arg2=b)                      #  1 2 0
foo(a, arg2=b, arg3=c)              #  1 2 3
foo(a, b, arg3=c)                   #  1 2 3

foo(a)                              #  1 0 0
foo(a,b)                            #  1 2 0


ここでは、関数を呼び出すときに、定位置引数と名前付き引数の両方が使用されます。位置引数を使用する場合、それらが指定された順序は、入力を関数に正しく渡す上で引き続き重要な役割を果たします。



ここで、1つの注目すべき詳細に注意を向けたいと思います。名前付き引数の後に位置引数を指定することはできません。このアイデアをよりよく理解するのに役立つ例を次に示します。



foo(arg1=a, b)
>>>
foo(arg1=a, b)
           ^
SyntaxError: positional argument follows keyword argument
foo(a, arg2=b, c)
>>>
foo(a, arg2=b, c)
              ^
SyntaxError: positional argument follows keyword argument


原則としてご利用いただけます。関数を呼び出すときに、位置引数は名前付き引数に従う必要はありません。



4.可変長の引数のセットの処理の編成



ここでは、構造*argsについて説明**kwargsます。これらの構成が関数宣言で使用される場合、関数が呼び出されると、任意の長さの引数セットがパラメーターargsおよびとして表されることが期待されますkwargs構成が適用される*argsと、パラメーターargsはタプルとして表される位置引数を受け取ります秋の名前付き引数に適用**kwargsするkwargsと、辞書にリストされます。



def foo(*args):
    print(args)

a, b, c = 1, 2, 3

foo(a, b, c)                #  (1, 2, 3)
foo(a, b)                   #  (1, 2)
foo(a)                      #  (1)
foo(b, c)                   #  (2, 3)


このコードは、パラメーターargsが呼び出されたときに関数に渡されたものを含むタプルを格納していることを証明します



def foo(**kwargs):
    print(kwargs)

foo(a=1, b=2, c=3)        #  {'a': 1, 'b': 2, 'c': 3}
foo(a=1, b=2)             #  {'a': 1, 'b': 2}
foo(a=1)                  #  {'a': 1}
foo(b=2, c=3)             #  {'b': 2, 'c': 3}


上記のコードは、パラメーターkwargsが、呼び出されたときに関数に渡される名前付き引数を表すキーと値のペアの辞書を格納することを示しています



ただし、位置引数を受け入れるように設計された関数には、名前付き引数を渡すことができないことに注意してください(その逆も同様です)。



def foo(*args):
    print(args)

foo(a=1, b=2, c=3)
>>>
foo(a=1, b=2, c=3)
TypeError: foo() got an unexpected keyword argument 'a'
#########################################################
def foo(**kwargs):
    print(kwargs)

a, b, c = 1, 2, 3
foo(a, b, c)
>>>
TypeError: foo() takes 0 positional arguments but 3 were given


ここで、ポイント#1、#2、#3、および#4で分析したすべてをまとめ、これらすべてを試して、呼び出されたときに関数に渡すことができる引数のさまざまな組み合わせを調べてみましょう。



def foo(*args,**kwargs):
    print(args, kwargs)

foo(a=1,)
# () {'a': 1}

foo(a=1, b=2, c=3)
# () {'a': 1, 'b': 2, 'c': 3}

foo(1, 2, a=1, b=2)
# (1, 2) {'a': 1, 'b': 2}

foo(1, 2)
# (1, 2) {}


ご覧のとおり、タプルargsと辞書を自由に使用できkwargsます。



そして、ここに別のルールがあります。それは*args、構造の後に構造を使用できないという事実にあります**kwargs



def foo(**kwargs, *args):
    print(kwargs, args)
>>>
    def foo(**kwargs, *args):
                      ^
SyntaxError: invalid syntax


関数を呼び出すときに引数が指定される順序にも同じ規則が適用されます。位置引数は、名前付き引数の後に続くことはできません。



foo(a=1, 1)
>>>
    foo(a=1, 1)
            ^
SyntaxError: positional argument follows keyword argument
foo(1, a=1, 2)
>>>
    foo(1, a=1, 2)
               ^
SyntaxError: positional argument follows keyword argument


関数を宣言するときは、位置引数を組み合わせて、できる*args*kwagrs次のように:



def foo(var, *args,**kwargs):
    print(var, args, kwargs)

foo(1, a=1,)                            #  1
# 1 () {'a': 1}

foo(1, a=1, b=2, c=3)                   #  2
# 1 () {'a': 1, 'b': 2, 'c': 3}

foo(1, 2, a=1, b=2)                     #  3
# 1 (2,) {'a': 1, 'b': 2}
foo(1, 2, 3, a=1, b=2)                  #  4
# 1 (2, 3) {'a': 1, 'b': 2}
foo(1, 2)                               #  5
# 1 (2,) {}


関数を宣言するとき、foo必要な位置引数が1つ必要であると想定しました。その後に可変長の位置引数のセットが続き、このセットの後には可変長の名前付き引数のセットが続きます。これを知っていると、上記の各関数呼び出しを簡単に「復号化」できます。この関数は、引数を渡される。これらはそれぞれ、定位置引数と名前付き引数です。バラエティです。ここで、位置引数のセットの長さはゼロです。 では、私たちは関数を渡す。これは、2つの位置引数と2つの名前付き引数を受け入れるようになったことを意味します。関数宣言によると、



11a=1 2 1



312a=1,b=21要求される位置引数として取り、2可変長位置引数のセットに入り、a=1およびb=2可変長名前付き引数のセットで終わります。



この関数を正しく呼び出すには、少なくとも1つの位置引数を渡す必要があります。そうしないと、エラーが発生します。



def foo(var, *args,**kwargs):
    print(var, args, kwargs)

foo(a=1)
>>>
foo(a=1)
TypeError: foo() missing 1 required positional argument: 'var'


この関数の別のバリエーションは、1つの必須の位置引数と1つの名前付き引数を取り、その後に位置引数と名前付き引数の可変長セットを受け取ることを宣言する関数です。



def foo(var, kvar=0, *args,**kwargs):
    print(var, kvar, args, kwargs)

foo(1, a=1,)                               #  1
# 1 0 () {'a': 1}

foo(1, 2, a=1, b=2, c=3)                   #  2
# 1 0 () {'a': 1, 'b': 2, 'c': 3}

foo(1, 2, 3, a=1, b=2)                     #  3
# 1 2 () {'a': 1, 'b': 2}

foo(1, 2, 3, 4, a=1, b=2)                  #  4
# 1 2 (3,) {'a': 1, 'b': 2}

foo(1, kvar=2)                             #  5
# 1 2 () {}


この関数の呼び出しは、前の関数を分析したときと同じ方法で「復号化」できます。



この関数を呼び出すときは、少なくとも1つの位置引数を渡す必要があります。そうしないと、エラーが発生します。



foo()
>>>
foo()
TypeError: foo() missing 1 required positional argument: 'var'
foo(1)
# 1 0 () {}


通話foo(1)は正常に機能することに注意してくださいここでのポイントは、名前付き引数の値を指定せずに関数が呼び出されると、値が自動的に割り当てられるということです。



また、この関数が誤って呼び出された場合に発生する可能性のあるエラーがいくつかあります。



foo(kvar=1)                             #  1
>>>
TypeError: foo() missing 1 required positional argument: 'var'
foo(kvar=1, 1, a=1)                      #  2
>>>
SyntaxError: positional argument follows keyword argument
foo(1, kvar=2, 3, a=2)                   #  3
>>>
SyntaxError: positional argument follows keyword argument


ランタイムエラーに特に注意してください 3



5.引数の解凍



前のセクションでは、関数に渡された引数のセットをタプルと辞書に収集する方法について説明しました。そして、ここでは逆の操作について説明します。つまり、関数入力に提供された引数をアンパックできるメカニズムを分析します。



args = (1, 2, 3, 4)
print(*args)                  #  1 2 3 4
print(args)                   #  (1, 2, 3, 4)

kwargs = { 'a':1, 'b':2}
print(kwargs)                 #  {'a': 1, 'b': 2}
print(*kwargs)                #  a b


変数は、構文*を使用して解凍できます**これは、タプル、リスト、および辞書を関数に渡すときに使用される方法です。



def foo(a, b=0, *args, **kwargs):
    print(a, b, args, kwargs)

tup = (1, 2, 3, 4)
lst = [1, 2, 3, 4]
d = {'e':1, 'f':2, 'g':'3'}

foo(*tup)             # foo(1, 2, 3, 4)
# 1 2 (3, 4) {}

foo(*lst)             # foo(1, 2, 3, 4)
# 1 2 (3, 4) {}

foo(1, *tup)          # foo(1, 1, 2, 3, 4)
# 1 1 (2, 3, 4) {}

foo(1, 5, *tup)       # foo(1, 5, 1, 2, 3, 4)
# 1 5 (1, 2, 3, 4) {}

foo(1, *tup, **d)     # foo(1, 1, 2, 3, 4 ,e=1 ,f=2, g=3)
# 1 1 (2, 3, 4) {'e': 1, 'f': 2, 'g': '3'}

foo(*tup, **d)         # foo(1, 1, 2, 3, 4 ,e=1 ,f=2, g=3)
# 1 2 (3, 4) {'e': 1, 'f': 2, 'g': '3'}
d['b'] = 45
foo(2, **d)             # foo(1, e=1 ,f=2, g=3, b=45)
# 2 45 () {'e': 1, 'f': 2, 'g': '3'}


対応する呼び出しは使用せずにどのように見えるかを開梱し、予告引数を使用して、ここで示した関数呼び出しの各解体*とします**これらの呼び出しを行ったときに何が起こるか、およびさまざまなデータ構造がどのように解凍されるかを理解するようにしてください。



引数を解凍してみると、新しいエラーが発生する可能性があります。



foo(1, *tup, b=5)
>>>
TypeError: foo() got multiple values for argument 'b'
foo(1, b=5, *tup)
>>>
TypeError: foo() got multiple values for argument 'b'


このエラーは、名前付き引数、、b=5および位置引数の間の競合が原因で発生します。セクション2でわかったように、名前付き引数の順序は、渡されるときに重要ではありません。その結果、どちらの場合も同じエラーが発生します。



6.名前でのみ渡すことができる引数の使用(キーワードのみ)



場合によっては、関数に必要な名前付き引数を受け入れさせる必要があります。関数を宣言するときに、名前でのみ渡すことができる引数を記述している場合、そのような引数は、呼び出されるたびにその関数に渡す必要があります。



def foo(a, *args, b):
    print(a, args, b)

tup = (1, 2, 3, 4)

foo(*tup, b=35)
# 1 (2, 3, 4) 35

foo(1, *tup, b=35)
# 1 (1, 2, 3, 4) 35

foo(1, 5, *tup, b=35)
# 1 (5, 1, 2, 3, 4) 35

foo(1, *tup, b=35)
# 1 (1, 2, 3, 4) 35

foo(1, b=35)
# 1 () 35

foo(1, 2, b=35)
# 1 (2,) 35

foo(1)
# TypeError: foo() missing 1 required keyword-only argument: 'b'

foo(1, 2, 3)
# TypeError: foo() missing 1 required keyword-only argument: 'b'


ご覧のとおり、関数には必ず名前付き引数が渡されることが予想されますb。この引数は、関数宣言では、の後に指定され*argsます。この場合、関数宣言では、シンボルのみを使用できます*。その後、コンマで区切って、名前だけで関数に渡すことができる名前付き引数の識別子があります。このような関数は、可変長の位置引数のセットを受け入れるようには設計されていません。



def foo(a, *, b, c):
    print(a, b, c)

tup = (1, 2, 3, 4)

foo(1, b=35, c=55)
# 1 35 55

foo(c= 55, b=35, a=1)
# 1 35 55

foo(1, 2, 3)
# TypeError: foo() takes 1 positional argument but 3 were given

foo(*tup, b=35)
# TypeError: foo() takes 1 positional argument but 4 positional arguments (and 1 keyword-only argument) were given

foo(1, b=35)
# TypeError: foo() takes 1 positional argument but 4 positional arguments (and 1 keyword-only argument) were given


前の例で宣言された関数は、1つの位置引数と2つの名前付き引数を取ります。これらは、名前でのみ渡すことができます。これは、関数が正しく呼び出されるためには、両方の名前付き引数を渡す必要があるという事実につながります。その後*、デフォルト値が与えられている名前付き引数を記述することもできます。これにより、このような関数を呼び出すときにある程度の自由度が得られます。



def foo(a, *, b=0, c, d=0):
    print(a, b, c, d)

foo(1, c=55)
# 1 0 55 0

foo(1, c=55, b=35)
# 1 35 55 0

foo(1)
# TypeError: foo() missing 1 required keyword-only argument: 'c'


機能がそれに任意の引数を渡すことなく、正常に呼び出すことができることに注意してください、bそしてd、彼らはデフォルト値が与えられているので。



結果



おそらく、私たちは確かに、議論について非常に長い話をしています。この資料の読者が自分たちのために何か新しいことを学んだことを願っています。ちなみに、Pythonの関数引数の話は続きます。おそらく後でそれらについて話します。



この資料から、Pythonの関数引数について何か新しいことを学びましたか?






All Articles