オプション機能パラメータが設定されているか確認する方法


93

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


12
もちろん、その関数でチェックしたいので:)
Matthias

2
Noneデフォルトとして使用し、それを確認してください。このテストを実際に設定できる場合は、ユーザーがデフォルトの動作を呼び出す値を明示的に渡す可能性も排除します。
Michael J. Barber

3
これは、少なくともCPythonの場合、受け入れた回答よりもはるかに再利用可能で美しい方法で実行できます。以下の私の答えを参照してください。
エリオ2013

2
@Volatility:デフォルトが2セットあるかどうかが重要です。再帰クラスについて考えてみましょう 。Class My(): def __init__(self, _p=None, a=True, b=True, c=False) ユーザーはそれをx=My(b=False)。で呼び出します。x=My(_p=self, c=True)関数がbが明示的に設定されておらず、未設定の変数がトップレベルから渡されることを検出できる場合、クラスメソッドはそれ自体を呼び出すことができます。ただし、それができない場合、再帰呼び出しはすべての変数を明示的に渡す必要がありますx=My(a=self.a, b=self.b, c=True, d=self.d, ...)
デーブ

@デイブしかし、それが質問の内容ですか?私の理解では、質問が差別化する方法を求めているx=My()x=My(a=True)。シナリオには、オプションのパラメーターにデフォルト値以外の値を割り当てることが含まれます。
ボラティリティ2016年

回答:


19

多くの回答には完全な情報の一部が含まれているので、すべてを私のお気に入りのパターンと一緒にまとめたいと思います。

デフォルト値は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不変のデフォルトにクラスを使用する

または、@ czの提案と同様に、Pythonドキュメントでは不十分な場合:-)、間にオブジェクトを追加して、APIをより明示的にすることができます(ドキュメントを読まなくても)。used_proxy_ Defaultクラスインスタンスは変更可能であり、使用する実際のデフォルト値が含まれます。

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どのように機能するかを示すためだけに存在するため、本来よりも醜く見えます。そうでなければ、私はそれらが簡潔で十分に再現可能であると思います。

あなたは追加するデコレータを書くことができ__call__、より合理化された方法で@dmgによって提案されたパターンが、これは関数定義そのものに奇妙なトリックを使用して、まだリージュ意志を-あなたはアウト分割する必要があるだろうvalueし、value_defaultあなたのコードの必要性は、それらを区別する場合には、そう私はあまり利点が見当たらず、例を書きません:-)

Pythonのデフォルト値としての可変型

#1 pythonの落とし穴についてもう少し、上記のあなた自身の喜びのために虐待されました。次の手順を実行すると、定義時評価によって何が起こるかを確認できます。

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

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

Pythonであることを覚えておいてくださいのみ3可変ビルトインタイプsetlistdict; 他のすべて-文字列さえ!-不変です。


「不変のデフォルト引数」の例には、実際には不変のデフォルト引数がありません。もしそうなら、それは機能しません。
カロル

@Karol、詳しく説明しますか?この例では、デフォルト値がある1...不変であるべき、
ステファノ

関数のシグネチャはdef f(value={})。として表示されます。
カロル

1
ハ、私は今それを手に入れました、ありがとう。誰かがあなたのテキストを非常に注意深く読まない限り、それに従うのは簡単ではありません。それはSOではそれほど頻繁には起こらないかもしれません。言い換えることを検討してください。
カロル

1
「デフォルトがf .__ defaults __ [0]:の場合」では、使用するデフォルトのパラメーター番号をハードコーディングする必要があります。これは、関数のシグネチャが変更されると壊れやすくなる可能性があります。別の方法は、「f .__ defaults__のデフォルトの場合:」です。引数ごとに異なるデフォルトインスタンスを使用すると仮定すると、「in」は「is」と同じように機能するはずです。
スティーブンウォーレン

57

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


9
また、これが必要で、Noneが有効な入力と見なされる場所にEllipsisビルトインを使用している人もいます。これは基本的に最初の例と同じです。
GrandOpener 2013

Noneが渡された場合に特別な動作を実装したいが、引数がユーザーによって指定されたかどうかをテストする方法が必要な場合は、この値をスキップEllipsisするように明示的に設計されたシングルトンをデフォルトとして使用できます。はのエイリアスであるため、位置引数を使用したいユーザーは、呼び出すだけでわかりやすく、読みやすくなります。...Ellipsisyour_function(p1, ..., p3)
バッハサウ

However this is overly verbose and makes your function more difficult to use as its documentation will not automatically include the param parameter.inspectモジュールを使用して関数とそのパラメーターの説明を設定できるため、これは実際には正しくありません。それが機能するかどうかはIDEによって異なります。
EZLearner

15

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

このコードはpython2でのみ機能します。python 3については、下記の私の答えを参照してください。stackoverflow.com/questions/14749328/...を
R.ヤン

1
これはかなりクールですが、可能であれば、そもそもより良いデザインの問題を回避する方が良いでしょう。
カロル

@Karol、同意します。ほとんどの場合、設計が合理的であれば、それは必要ありません。
エリオ

4

私は時々(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
Pythonオブジェクトは参照であり、object()代わりに使用できますuuid4()-それはまだ一意のインスタンスであり、これがisチェック対象です
cz 2017

3

私は、このパターンを見てきました数回(例えばライブラリーunittestpy-flagsjinja):

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

DEFAULT = Default()

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

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

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


3

@Elliohの答えはPython2で機能します。Python3では、次のコードが機能するはずです。

import inspect
def explicit_checker(f):
    varnames = inspect.getfullargspec(f)[0]
    def wrapper(*a, **kw):
        kw['explicit_params'] = set(list(varnames[:len(a)]) + list(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

このメソッドは、(** kwargsの代わりに)引数名とデフォルト値を読みやすく保つことができます。


3

あなたはからそれを確認することができます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)

また、関数が呼び出される実際の引数を返すためgetcallargs、fromを使用すると非常に便利inspectです。関数とargsおよびkwargsをそれに渡すと(inspect.getcallargs(func, /, *args, **kwds))、デフォルト値やその他のものを考慮して、呼び出しに使用される実際のメソッドの引数が返されます。以下の例をご覧ください。

from inspect import getcallargs

# we have a function with such signature
def show_params(first, second, third=3):
    pass

# if you wanted to invoke it with such params (you could get them from a decorator as example)
args = [1, 2, 5]
kwargs = {}
print(getcallargs(show_params, *args, **kwargs))
#{'first': 1, 'second': 2, 'third': 5}

# here we didn't specify value for d
args = [1, 2, 3, 4]
kwargs = {}

# ----------------------------------------------------------
# but d has default value =7
def show_params1(first, *second, d = 7):
    pass


print(getcallargs(show_params1, *args, **kwargs))
# it will consider b to be equal to default value 7 as it is in real method invocation
# {'first': 1, 'second': (2, 3, 4), 'd': 7}

# ----------------------------------------------------------
args = [1]
kwargs = {"d": 4}

def show_params2(first, d=3):
    pass


print(getcallargs(show_params2, *args, **kwargs))
#{'first': 1, 'd': 4}

https://docs.python.org/3/library/inspect.html


2

ボラティリティのコメントに同意します。ただし、次の方法で確認できます。

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

私は、オプションのパラメータのようなものであると信じてdef func(optional=value)いない**kwargs
Zaur Nasibov

それは、いくぶん解釈の余地があるものです。デフォルト値の引数とキーワード引数の実際の違いは何ですか?これらは両方とも同じ構文「keyword = value」を使用して表現されます。
isedev 2013

オプションのパラメータの目的と**kwargsは少し異なるので、私は同意しません。PS問題ありません-1について:)そしてあなたのための私の-1は偶然でした:)
Zaur Nasibov 2013

2

これはステファノの答えのバリエーションですが、もう少し読みやすくなっています。

not_specified = {}

def foo(x=not_specified):
    if x is not_specified:
            print("not specified")
    else:
            print("specified")

1つの賛成?? 私はこれが一番好きです。シンプルで、反射はありません。読み取り可能。
ヴィンセント

1

少し気まぐれなアプローチは次のようになります。

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提案と同様に自動的に文書化されません。


1
の動作を含めることをお勧めしますcheck_f('z')。これは、あなたが言うように、気まぐれです。
Michael J. Barber

@ MichaelJ.Barber良い点。* argsでも「魔法」を実行する必要があります。しかし、私のポイントはそれが可能であるということでしたが、デフォルト値が渡されるかどうかを今すぐに確認する必要があるのは悪い設計です。
dmg 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.