web-dev-qa-db-ja.com

Python

Pythonでは、関数定義があるかもしれません:

def info(object, spacing=10, collapse=1)

次のいずれかの方法で呼び出すことができます。

info(odbchelper)                    
info(odbchelper, 12)                
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)

pythonは、名前が指定されている限り、任意の順序の引数を許可しています。

私たちが抱えている問題は、いくつかの大きな関数が成長するにつれて、人々がspacingcollapseの間にパラメーターを追加している可能性があることです。つまり、間違った値が名前のないパラメーターになります。さらに、時には何を入力する必要があるかが必ずしも明確ではない場合があります。特定のパラメーターに名前を付けるように強制する方法を模索しています-コーディング標準だけでなく、理想的にはフラグまたはpydevプラグインですか?

上記の4つの例では、すべてのパラメーターに名前が付けられているため、最後のチェックのみがパスします。

特定の機能に対してのみ有効にすることは不思議ですが、これを実装する方法についての提案、または可能かどうかは歓迎されます。

86
Mark Mayo

Python 3-はい、引数リストで*を指定できます。

docs から:

「*」または「* identifier」の後のパラメーターはキーワードのみのパラメーターであり、使用されるキーワード引数のみを渡すことができます。

>>> def foo(pos, *, forcenamed):
...   print(pos, forcenamed)
... 
>>> foo(pos=10, forcenamed=20)
10 20
>>> foo(10, forcenamed=20)
10 20
>>> foo(10, 20)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: foo() takes exactly 1 positional argument (2 given)

これは**kwargsと組み合わせることもできます:

def foo(pos, *, forcenamed, **kwargs):
171
Eli Bendersky

次の方法で関数を定義することにより、Python3でキーワード引数を強制的に使用できます。

def foo(*, arg0="default0", arg1="default1", arg2="default2"):
    pass

最初の引数を名前のない位置引数にすることで、関数を呼び出すすべての人にキーワード引数を使用するように強制します。 Python2でこれを行う唯一の方法は、このような関数を定義することです

def foo(**kwargs):
    pass

それは呼び出し側にkwargsを使用するように強制しますが、必要な引数のみを受け入れるためにチェックを入れる必要があるので、これはそれほど素晴らしい解決策ではありません。

24
martega

確かに、ほとんどのプログラミング言語関数呼び出しコントラクトのパラメーターの順序部分を作成しますが、これはそうではありません必要しません。なぜだろうか?質問の私の理解は、もしPythonがこの点で他のプログラミング言語と異なるなら。Python 2、以下を考慮してください。

__named_only_start = object()

def info(param1,param2,param3,_p=__named_only_start,spacing=10,collapse=1):
    if _p is not __named_only_start:
        raise TypeError("info() takes at most 3 positional arguments")
    return str(param1+param2+param3) +"-"+ str(spacing) +"-"+ str(collapse)

呼び出し元が引数spacingおよびcollapseを位置的に(例外なく)提供できる唯一の方法は次のとおりです。

info(arg1, arg2, arg3, module.__named_only_start, 11, 2)

Pythonでは、既に他のモジュールに属するプライベート要素を使用しないという慣習が非常に基本的です。 Python自体と同様に、パラメーターのこの規則は準強制のみです。

それ以外の場合、呼び出しは次の形式である必要があります。

info(arg1, arg2, arg3, spacing=11, collapse=2)

呼び出し

info(arg1, arg2, arg3, 11, 2)

パラメータ_pに値11を割り当て、関数の最初の命令によって例外が発生します。

特徴:

  • _p=__named_only_startの前のパラメーターは、位置的に(または名前によって)許可されます。
  • _p=__named_only_startの後のパラメータは、名前のみで提供する必要があります(特別な監視オブジェクト__named_only_startについての知識が取得され、使用されない限り)。

長所:

  • パラメーターの数と意味は明示的です(もちろん、適切な名前が選択されている場合は後のほうです)。
  • センチネルが最初のパラメーターとして指定されている場合、すべての引数は名前で指定する必要があります。
  • 関数を呼び出すとき、対応する位置でセンチネルオブジェクト__named_only_startを使用して位置モードに切り替えることができます。
  • 他の選択肢よりも優れたパフォーマンスが期待できます。

短所:

  • チェックは、コンパイル時ではなく実行時に行われます。
  • 追加のパラメーター(引数ではない)と追加のチェックの使用。通常の機能に対するパフォーマンスのわずかな低下。
  • 機能は、言語による直接のサポートなしのハックです(以下の注を参照)。
  • 関数を呼び出すとき、適切な位置でセンチネルオブジェクト__named_only_startを使用することにより、位置モードに切り替えることができます。はい、これはプロとして見ることもできます。

この答えは、Python 2に対してのみ有効であることに注意してください。Python 3は、答えます。

私は心を開いてそれについて考えるとき、質問や他の人の決定は愚か、愚か、または単にばかげているように見えないことがわかりました。それどころか、私は通常多くのことを学びます。

10
Mario Rossi

方法でそれを行うことができます=偽の最初のキーワードを作成することにより、Python 2とPythonの両方で動作します「自然」に発生しないデフォルト値を持つ引数。そのキーワード引数の前に、値のない1つ以上の引数を指定できます。

__dummy = object()

def info(object, _kw=_dummy, spacing=10, collapse=1):
    if _kw is not _dummy:
        raise TypeError("info() takes 1 positional argument but at least 2 were given")
_

これにより、次のことが可能になります。

_info(odbchelper)        
info(odbchelper, collapse=0)        
info(spacing=15, object=odbchelper)
_

だがしかし:

_info(odbchelper, 12)                
_

関数を次のように変更した場合:

_def info(_kw=_dummy, spacing=10, collapse=1):
_

すべての引数にはキーワードが必要であり、info(odbchelper)は機能しなくなります。

これにより、追加のキーワード引数を__kw_の後の任意の場所に配置できます。最後のエントリの後に強制する必要はありません。これはしばしば理にかなっています。物事を論理的にグループ化するか、キーワードをアルファベット順に配置すると、メンテナンスと開発に役立ちます。

したがって、スマートエディターでdef(**kwargs)を使用して署名情報を失うことに戻る必要はありません。あなたの社会的契約は、特定の情報を提供することであり、(それらのいくつかは)キーワードを要求することにより、それらが提示される順序は無関係になりました。

7
Anthon

更新:

_**kwargs_を使用しても問題が解決しないことに気付きました。プログラマが必要に応じて関数の引数を変更する場合、たとえば、関数を次のように変更できます。

_def info(foo, **kwargs):
_

古いコードは再び壊れます(すべての関数呼び出しに最初の引数を含める必要があるため)。

それはブライアンの言うことです。


(...)spacingcollapseの間にパラメーターを追加している可能性があります(...)

一般に、関数を変更する場合、新しい引数は常に最後に行く必要があります。そうしないと、コードが破損します。明らかなはずです。
コードが壊れるように誰かが関数を変更した場合、この変更は拒否する必要があります。
(ブライアンが言うように、それは契約のようなものです)

(...)場合によっては、何を入力する必要があるかが必ずしも明確ではありません。

関数のシグネチャ(つまりdef info(object, spacing=10, collapse=1))を見ると、notがデフォルト値であるすべての引数が必須であることがすぐにわかるはずです。
What引数は、docstringに入る必要があります。


古い回答(完全を期すために保持)

これはおそらく良い解決策ではありません:

この方法で関数を定義できます:

_def info(**kwargs):
    ''' Some docstring here describing possible and mandatory arguments. '''
    spacing = kwargs.get('spacing', 15)
    obj = kwargs.get('object', None)
    if not obj:
       raise ValueError('object is needed')
_

kwargsは、キーワード引数を含む辞書です。必須の引数が存在するかどうかを確認し、存在しない場合は例外を発生させることができます。

欠点は、どの引数が可能であるかがもはや明白ではないかもしれないということですが、適切なdocstringを使用すれば問題ありません。

2
Felix Kling

Python3キーワードのみの引数(*)は、**kwargsを使用してpython2.xでシミュレートできます。

次のpython3コードを検討してください。

def f(pos_arg, *, no_default, has_default='default'):
    print(pos_arg, no_default, has_default)

そしてその振る舞い:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes 1 positional argument but 3 were given
>>> f(1, no_default='hi')
1 hi default
>>> f(1, no_default='hi', has_default='hello')
1 hi hello
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() missing 1 required keyword-only argument: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() got an unexpected keyword argument 'wat'

これは以下を使用してシミュレートできます。「必要な名前付き引数」の場合にTypeErrorKeyErrorに切り替える自由をとったことに注意してください。同じ例外タイプ

def f(pos_arg, **kwargs):
    no_default = kwargs.pop('no_default')
    has_default = kwargs.pop('has_default', 'default')
    if kwargs:
        raise TypeError('unexpected keyword argument(s) {}'.format(', '.join(sorted(kwargs))))

    print(pos_arg, no_default, has_default)

そして振る舞い:

>>> f(1, 2, 3)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: f() takes exactly 1 argument (3 given)
>>> f(1, no_default='hi')
(1, 'hi', 'default')
>>> f(1, no_default='hi', has_default='hello')
(1, 'hi', 'hello')
>>> f(1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in f
KeyError: 'no_default'
>>> f(1, no_default=1, wat='wat')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 6, in f
TypeError: unexpected keyword argument(s) wat

レシピはpython3.xでも同様に機能しますが、python3.xのみを使用している場合は避けてください

2
Anthony Sottile

**argsのみを受け取るように関数を宣言できます。それはキーワード引数を要求しますが、有効な名前だけが渡されるようにするための追加の作業が必要になります。

def foo(**args):
   print args

foo(1,2) # Raises TypeError: foo() takes exactly 0 arguments (2 given)
foo(hello = 1, goodbye = 2) # Works fine.
1
Noufal Ibrahim

**演算子を使用できます。

def info(**kwargs):

このように、人々は名前付きパラメーターを使用せざるを得ません。

0
Olivier Verdier

他の答えが言うように、関数のシグネチャを変更することは悪い考えです。最後に新しいパラメーターを追加するか、引数が挿入されている場合はすべての呼び出し元を修正します。

それでもやりたい場合は、 function decoratorinspect.getargspec 関数を使用します。次のようなものが使用されます。

@require_named_args
def info(object, spacing=10, collapse=1):
    ....

require_named_argsの実装は、読者の課題として残されています。

私は気にしません。関数が呼び出されるたびに遅くなり、コードをより注意深く書くことでより良い結果が得られます。

0
Daniel Newby
def cheeseshop(kind, *arguments, **keywords):

in python if * argsを使用すると、このパラメーターにn個の引数を渡すことができます-アクセスする関数内のリストになります

キーワード引数を意味する** kwを使用すると、dictとしてアクセスできます-n個のkw引数を渡すことができ、ユーザーが順序と引数を順番に入力する必要がある場合は使用しないでください*および**-(大きなアーキテクチャ向けの汎用ソリューションを提供するPythonの方法...)

関数をデフォルト値で制限したい場合は、内部をチェックできます

def info(object, spacing, collapse)
  spacing = spacing or 10
  collapse = collapse or 1
0
shahjapan