web-dev-qa-db-ja.com

Python:オプションの関数パラメーターが設定されているかどうかを確認する方法

Pythonでオプションのパラメーターの値がデフォルト値から来ているかどうか、またはユーザーが関数呼び出しで明示的に設定したためかどうかを確認する簡単な方法はありますか?

62
Matthias

回答の多くには完全な情報がほとんど含まれていないため、お気に入りのパターンと一緒にまとめたいと思います。

デフォルト値は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__パターンをより合理的な方法で追加できますが、これでも関数定義自体に奇妙なトリックを使用する必要があります-valuevalue_default ifあなたのコードはそれらを区別する必要があるので、私はあまり利点が見られないので、例を書きません:-)

デフォルト値としての可変タイプ

#1 python gotcha! についてもう少し説明します。上記のご自身の喜びのために悪用されています。定義時の評価

def testme(default=[]):
    print(id(default))

testme()は何回でも実行できます。常に同じデフォルトインスタンスへの参照が表示されます(したがって、基本的にデフォルトは不変です:-))。

Pythonには3つの可変 組み込み型setlistdict、しかし、他のすべて-文字列さえも-不変です。

1
Stefano

あんまり。標準的な方法は、ユーザーが渡すことを期待されないデフォルト値を使用することです。 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パラメーターが自動的に含まれないため、関数の使用がより難しくなります。

48
ecatmur

次の関数デコレーター、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)
12
Ellioh

普遍的に一意の文字列(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の値が表示されたときに渡されなかったと確信できます。

4
Jesse B Miller

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)
2
Alex

私はこのパターンを数回見ました(例えば、ライブラリunittest、_py-flags_、jinja):

_class Default:
    def __repr__( self ):
        return "DEFAULT"

DEFAULT = Default()
_

...または同等のワンライナー...:

_DEFAULT = type( 'Default', (), { '__repr__': lambda x: 'DEFAULT' } )()
_

DEFAULT = object()とは異なり、これは型チェックを支援し、エラーが発生したときに情報を提供します。多くの場合、文字列表現(_"DEFAULT"_)またはクラス名(_"Default"_)がエラーメッセージで使用されます。 。

2
c z

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
2
isedev

ちょっと変わったアプローチは次のとおりです。

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

さて、これは、私が言ったように、かなりおかしいですが、仕事をします。しかし、これは非常に読みにくく、 ecatmursuggestion と同様に自動的に文書化されません。

1
dmg