Pythonでオプションのパラメーターの値がデフォルト値から来ているかどうか、またはユーザーが関数呼び出しで明示的に設定したためかどうかを確認する簡単な方法はありますか?
回答の多くには完全な情報がほとんど含まれていないため、お気に入りのパターンと一緒にまとめたいと思います。
mutable
タイプですデフォルト値が可変オブジェクトである場合、幸運です。関数の定義時にPythonのデフォルト引数が1回評価されるという事実を活用できます(これについては回答の最後で詳しく説明します)。
これは、次の例の関数またはメソッドのように、is
を使用してデフォルトの可変値を簡単に比較して、引数として渡されたかデフォルトで残されたかを確認できることを意味します。
def f(value={}):
if value is f.__defaults__[0]:
print('default')
else:
print('passed in the call')
そして
class A:
def f(self, value={}):
if value is self.f.__defaults__[0]:
print('default')
else:
print('passed in the call')
デフォルトがimmutable
値であると予想される場合(文字列でさえ不変であることを忘れないでください!)好む:
def f(value={}):
"""
my function
:param value: value for my function; default is 1
"""
if value is f.__defaults__[0]:
print('default')
value = 1
else:
print('passed in the call')
# whatever I want to do with the value
print(value)
実際のデフォルトがNone
の場合は特に面白いと感じますが、None
は不変なので...
Default
クラスを使用するまたは、@ c-zの提案と同様に、python docsで十分でない場合:-)、間にドキュメントを読むことなくAPIをより明確にするためにオブジェクトを追加できます。
class Default:
def __repr__(self):
return "Default Value: {} ({})".format(self.value, type(self.value))
def __init__(self, value):
self.value = value
def f(default=Default(1)):
if default is f.__defaults__[0]:
print('default')
print(default)
default = default.value
else:
print('passed in the call')
print("argument is: {}".format(default))
今:
>>> f()
default
Default Value: 1 (<class 'int'>)
argument is: 1
>>> f(2)
passed in the call
argument is: 2
上記はDefault(None)
でもうまく機能します。
明らかに、上記のパターンは、どのように機能するかを示すためだけにあるすべてのprint
があるため、本来のパターンよりもugいように見えます。それ以外の場合、それらは簡潔で十分に再現可能です。
デコレータを記述して、@ dmgによって提案された__call__
パターンをより合理的な方法で追加できますが、これでも関数定義自体に奇妙なトリックを使用する必要があります-value
とvalue_default
ifあなたのコードはそれらを区別する必要があるので、私はあまり利点が見られないので、例を書きません:-)
#1 python gotcha! についてもう少し説明します。上記のご自身の喜びのために悪用されています。定義時の評価:
def testme(default=[]):
print(id(default))
testme()
は何回でも実行できます。常に同じデフォルトインスタンスへの参照が表示されます(したがって、基本的にデフォルトは不変です:-))。
Pythonには3つの可変 組み込み型 :set
、list
、dict
、しかし、他のすべて-文字列さえも-不変です。
あんまり。標準的な方法は、ユーザーが渡すことを期待されないデフォルト値を使用することです。 object
インスタンス:
DEFAULT = object()
def foo(param=DEFAULT):
if param is DEFAULT:
...
通常、ユーザーが渡したい値として意味をなさない場合は、None
をデフォルト値として使用できます。
別の方法は、kwargs
を使用することです。
def foo(**kwargs):
if 'param' in kwargs:
param = kwargs['param']
else:
...
ただし、これは非常に冗長であり、ドキュメントにparam
パラメーターが自動的に含まれないため、関数の使用がより難しくなります。
次の関数デコレーター、explicit_checker
は、明示的に指定されたすべてのパラメーターのパラメーター名のセットを作成します。結果を追加のパラメーター(explicit_params
)関数に。ただ'a' in explicit_params
パラメータa
が明示的に指定されているかどうかを確認します。
def explicit_checker(f):
varnames = f.func_code.co_varnames
def wrapper(*a, **kw):
kw['explicit_params'] = set(list(varnames[:len(a)]) + kw.keys())
return f(*a, **kw)
return wrapper
@explicit_checker
def my_function(a, b=0, c=1, explicit_params=None):
print a, b, c, explicit_params
if 'b' in explicit_params:
pass # Do whatever you want
my_function(1)
my_function(1, 0)
my_function(1, c=1)
普遍的に一意の文字列(UUIDなど)を使用することがあります。
import uuid
DEFAULT = uuid.uuid4()
def foo(arg=DEFAULT):
if arg is DEFAULT:
# it was not passed in
else:
# it was passed in
この方法では、ユーザーはデフォルトを推測することさえできなかったので、arg
の値が表示されたときに渡されなかったと確信できます。
foo.__defaults__
およびfoo.__kwdefaults__
から確認できます
以下の簡単な例を参照してください
def foo(a, b, c=123, d=456, *, e=789, f=100):
print(foo.__defaults__)
# (123, 456)
print(foo.__kwdefaults__)
# {'e': 789, 'f': 100}
print(a, b, c, d, e, f)
#and these variables are also accessible out of function body
print(foo.__defaults__)
# (123, 456)
print(foo.__kwdefaults__)
# {'e': 789, 'f': 100}
foo.__kwdefaults__['e'] = 100500
foo(1, 2)
#(123, 456)
#{'f': 100, 'e': 100500}
#1 2 123 456 100500 100
次に、演算子=
とis
を使用して、それらを比較できます
場合によっては、以下のコードで十分です
たとえば、デフォルト値を変更しないようにする必要があります。その後、同等であるかどうかを確認し、その場合はコピーできます
def update_and_show(data=Example):
if data is Example:
data = copy.deepcopy(data)
update_inplace(data) #some operation
print(data)
私はこのパターンを数回見ました(例えば、ライブラリunittest
、_py-flags
_、jinja
):
_class Default:
def __repr__( self ):
return "DEFAULT"
DEFAULT = Default()
_
...または同等のワンライナー...:
_DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
_
DEFAULT = object()
とは異なり、これは型チェックを支援し、エラーが発生したときに情報を提供します。多くの場合、文字列表現(_"DEFAULT"
_)またはクラス名(_"Default"
_)がエラーメッセージで使用されます。 。
Volatilityのコメントに同意します。ただし、次の方法で確認できます。
def function(arg1,...,**optional):
if 'optional_arg' in optional:
# user has set 'optional_arg'
else:
# user has not set 'optional_arg'
optional['optional_arg'] = optional_arg_default_value # set default
ちょっと変わったアプローチは次のとおりです。
class CheckerFunction(object):
def __init__(self, function, **defaults):
self.function = function
self.defaults = defaults
def __call__(self, **kwargs):
for key in self.defaults:
if(key in kwargs):
if(kwargs[key] == self.defaults[key]):
print 'passed default'
else:
print 'passed different'
else:
print 'not passed'
kwargs[key] = self.defaults[key]
return self.function(**kwargs)
def f(a):
print a
check_f = CheckerFunction(f, a='z')
check_f(a='z')
check_f(a='b')
check_f()
どの出力:
passed default
z
passed different
b
not passed
z
さて、これは、私が言ったように、かなりおかしいですが、仕事をします。しかし、これは非常に読みにくく、 ecatmur の suggestion と同様に自動的に文書化されません。