web-dev-qa-db-ja.com

Pythonの関数引数をチェックする最良の方法

python関数の変数をチェックする効率的な方法を探しています。たとえば、引数のタイプと値を確認したいと思います。このためのモジュールはありますか?または、デコレータなどの特定のイディオムを使用する必要がありますか?

def my_function(a, b, c):
    """an example function I'd like to check the arguments of."""
    # check that a is an int
    # check that 0 < b < 10
    # check that c is not an empty string
55
carmellose

最もPythonのイディオムはdocument関数が期待するものを明確にし、関数に渡されたものを使用して、例外を伝播させるか、属性エラーをキャッチして代わりにTypeErrorを発生させます。型チェックは、ダックタイピングに反するため、できるだけ避けるべきです。値のテストは、コンテキストに応じて問題なく実行できます。

検証が本当に意味を持つ唯一の場所は、Webフォーム、コマンドライン引数などのシステムまたはサブシステムのエントリポイントです。他のすべての場所で、関数が適切に文書化されている限り、適切な引数を渡すのは呼び出し側の責任です。

61

この詳細な回答では、275行未満のpure-Pythonで PEP 484 -styleタイプのヒントに基づいてPython 3.x固有のタイプチェックデコレータを実装しています(ほとんどは説明用のドキュメント文字列とコメント)–可能な限りすべてのEdgeケースを実行する py.test -drivenテストスイートを備えた、非常に強力な実世界での使用向けに大幅に最適化されています。

ベアタイピング

>>> @beartype
... def spirit_bear(kermode: str, gitgaata: (str, int)) -> Tuple:
...     return (kermode, gitgaata, "Moksgm'ol", 'Ursus americanus kermodei')
>>> spirit_bear(0xdeadbeef, 'People of the Cane')
AssertionError: parameter kermode=0xdeadbeef not of <class "str">

この例が示すように、ベアタイピングはパラメーターの型チェックを明示的にサポートし、単純型またはそのような型のタプルとして注釈が付けられた値を返します。 Golly!

わかりました、それは実際に印象的ではありません。 @beartypeはすべてのotherPythonに似ています。 PEP 484 -styleに基づく3.x固有の型チェックデコレーターは、275行未満のpure- Python。それで、摩擦、泡は何ですか?

Pure Bruteforceハードコア効率

私の限られた領域の知識の範囲内で、Pythonのタイプチェックの既存のすべての実装よりも、スペースと時間の両方でベアタイピングは劇的に効率的です。 (詳細は後ほど。

ただし、Pythonでは通常、効率は重要ではありません。もしそうなら、あなたはPythonを使用していないでしょう。型チェックは、Pythonの時期尚早な最適化を回避するという十分に確立された規範から実際に逸脱していますか? はい。はい、あります。

プロファイリングを検討します。プロファイリングは、プロファイル対象の各メトリック(例えば、関数呼び出し、行)に避けられないオーバーヘッドを追加します。正確な結果を確保するために、このオーバーヘッドは、最適化されていないpure-Python(たとえば、cProfileモジュール)ではなく、最適化されたC拡張(たとえば、profileモジュールによって活用される_lsprof C拡張)を活用することによって軽減されます。プロファイリングするときの効率は本当にdoesである。

型チェックも同じです。タイプチェックは、アプリケーションがチェックする各関数呼び出しタイプにオーバーヘッドを追加します。理想的には、それらのすべてallです。先週の金曜日のカフェインで覆われたオールナイトを老人のレガシーDjango Webアプリに追加した後、善意の(しかし、心の狭い)同僚が静かに追加したタイプチェックを削除しないように、typeチェックは高速である必要があります。誰にも言わずに追加したときに、誰も気づかないほど高速です。 私はこれをいつもしています!あなたが同僚ならこれを読むのをやめてください。

食いしん坊なアプリケーションにとってはばかげた速度でさえ十分でない場合、Python最適化を有効にすることで(たとえば、Pythonインタープリターに-Oオプションを渡すことで)クマのタイピングをグローバルに無効にできます。

$ python3 -O
# This succeeds only when type checking is optimized away. See above!
>>> spirit_bear(0xdeadbeef, 'People of the Cane')
(0xdeadbeef, 'People of the Cane', "Moksgm'ol", 'Ursus americanus kermodei')

という理由だけで。ベアタイピングへようこそ。

何...?なぜ「熊」なのか?あなたは首ひげですよね?

ベアタイピングはベアメタルタイプチェックです。つまり、Pythonでの手動によるタイプチェックのアプローチに可能な限り近いタイプチェックです。ベアタイピングは、noのパフォーマンスペナルティ、互換性の制約、またはサードパーティの依存関係(とにかく手動のアプローチによって課せられたもの以上)を課すことを目的としています。ベアタイピングは、変更することなく既存のコードベースとテストスイートにシームレスに統合できます。

おそらく誰もが手動アプローチに精通しているでしょう。コードベースのevery関数に手動でassert渡される各パラメーターおよび/または戻り値を返します。どのボイラープレートがよりシンプルまたはもっと平凡なのでしょうか?私たちは皆、グーグルプレックスで100回それを見ました、そして、私たちがしたたびに、私たちの口の中で少し吐きました。繰り返しは早く古くなります。 ドライ 、よ。

嘔吐バッグを準備します。簡潔にするために、単一のstrパラメーターのみを受け入れる単純化されたeasy_spirit_bear()関数を想定します。手動のアプローチは次のようになります。

def easy_spirit_bear(kermode: str) -> str:
    assert isinstance(kermode, str), 'easy_spirit_bear() parameter kermode={} not of <class "str">'.format(kermode)
    return_value = (kermode, "Moksgm'ol", 'Ursus americanus kermodei')
    assert isinstance(return_value, str), 'easy_spirit_bear() return value {} not of <class "str">'.format(return_value)
    return return_value

Python 101、そうですか?私たちの多くはそのクラスに合格しました。

ベアタイピングは、上記のアプローチによって手動で実行される型チェックを、同じチェックを自動的に実行する動的に定義されたラッパー関数に抽出します-あいまいなTypeError例外ではなく、粒度のAssertionError自動化されたアプローチは次のようになります。

def easy_spirit_bear_wrapper(*args, __beartype_func=easy_spirit_bear, **kwargs):
    if not (
        isinstance(args[0], __beartype_func.__annotations__['kermode'])
        if 0 < len(args) else
        isinstance(kwargs['kermode'], __beartype_func.__annotations__['kermode'])
        if 'kermode' in kwargs else True):
            raise TypeError(
                'easy_spirit_bear() parameter kermode={} not of {!r}'.format(
                args[0] if 0 < len(args) else kwargs['kermode'],
                __beartype_func.__annotations__['kermode']))

    return_value = __beartype_func(*args, **kwargs)

    if not isinstance(return_value, __beartype_func.__annotations__['return']):
        raise TypeError(
            'easy_spirit_bear() return value {} not of {!r}'.format(
                return_value, __beartype_func.__annotations__['return']))

    return return_value

時間がかかります。でも基本的には* 手動のアプローチと同じくらい速い。 *推奨される斜視

元の関数と同数のテストを含むラッパー関数には、関数検査または反復の完全な欠如に注意してください。ただし、型チェックされるパラメーターが現在の関数呼び出し。すべての戦いに勝つことはできません。

このようなラッパー関数を実際に生成して、275行未満の純粋なPythonで任意の関数を型チェックできますか? スネークプリスキン 言う、 "実話。煙が出ましたか?"

はい。 neckがあるかもしれません。

いいえ、Srsly。なぜ「熊」なのか?

クマはアヒルを打ちます。アヒルは飛ぶことがありますが、クマはアヒルにサーモンを投げることがあります。 カナダでは、自然はあなたを驚かせることができます。

次の質問。

とにかく、クマの何がそんなに熱いの?

既存のソリューションは、ベアメタルタイプチェックを実行しますnot–少なくとも、私は理解していません。それらはすべて、各関数呼び出しで型チェックされた関数のシグネチャを繰り返し再検査します。 1回の呼び出しでは無視できますが、すべての呼び出しで集計した場合、再検査のオーバーヘッドは通常無視できません。 本当に、本当に無視できない。

ただし、単に効率の問題ではありません。また、既存のソリューションは、一般的なEdgeのケースを考慮に入れていないことがよくあります。これには、StackOverflowの回答として提供されるおもちゃデコレータのすべてではないにしても、ほとんどの場合が含まれます。従来の障害には次のものがあります。

  • タイプチェックキーワード引数および/または戻り値の失敗(例: sweeneyrod@checkargs decorator )。
  • isinstance()ビルトインで受け入れられるタイプのタプル(つまり、ユニオン)のサポートに失敗しました。
  • 名前、docstring、およびその他の識別メタデータを元の関数からラッパー関数に伝播できない。
  • 少なくとも単体テストの類似度を提供できない。 (クリティカルの種類。
  • 型チェックに失敗した場合、特定のAssertionError例外ではなく、一般的なTypeError例外を発生させます。粒度と健全性のために、型チェックはneverで一般的な例外を発生させる必要があります。

ベアタイピングは、非ベアが失敗した場合に成功します。すべて1つ、すべてのクマ!

むき出しのタイピング

ベアタイピングは、関数シグネチャの検査のスペースと時間のコストを、関数呼び出し時間から関数定義時間に、つまり、@beartypeデコレーターによって返されたラッパー関数からデコレーター自体にシフトします。デコレータは関数定義ごとに1回しか呼び出されないため、この最適化はすべての人に喜びをもたらします。

ベアタイピングは、あなたのタイプチェックケーキを食べさせようとする試みでもあります。そのためには、@beartype

  1. 元の関数の署名と注釈を検査します。
  2. 元の関数をチェックするラッパー関数型の本体を動的に構築します。いいねPythonコードを生成するPythonコード。
  3. exec()ビルトインを介してこのラッパー関数を動的に宣言します。
  4. このラッパー関数を返します。

しましょうか?奥深くに飛び込みましょう。

# If the active Python interpreter is *NOT* optimized (e.g., option "-O" was
# *NOT* passed to this interpreter), enable type checking.
if __debug__:
    import inspect
    from functools import wraps
    from inspect import Parameter, Signature

    def beartype(func: callable) -> callable:
        '''
        Decorate the passed **callable** (e.g., function, method) to validate
        both all annotated parameters passed to this callable _and_ the
        annotated value returned by this callable if any.

        This decorator performs rudimentary type checking based on Python 3.x
        function annotations, as officially documented by PEP 484 ("Type
        Hints"). While PEP 484 supports arbitrarily complex type composition,
        this decorator requires _all_ parameter and return value annotations to
        be either:

        * Classes (e.g., `int`, `OrderedDict`).
        * Tuples of classes (e.g., `(int, OrderedDict)`).

        If optimizations are enabled by the active Python interpreter (e.g., due
        to option `-O` passed to this interpreter), this decorator is a noop.

        Raises
        ----------
        NameError
            If any parameter has the reserved name `__beartype_func`.
        TypeError
            If either:
            * Any parameter or return value annotation is neither:
              * A type.
              * A Tuple of types.
            * The kind of any parameter is unrecognized. This should _never_
              happen, assuming no significant changes to Python semantics.
        '''

        # Raw string of Python statements comprising the body of this wrapper,
        # including (in order):
        #
        # * A "@wraps" decorator propagating the name, docstring, and other
        #   identifying metadata of the original function to this wrapper.
        # * A private "__beartype_func" parameter initialized to this function.
        #   In theory, the "func" parameter passed to this decorator should be
        #   accessible as a closure-style local in this wrapper. For unknown
        #   reasons (presumably, a subtle bug in the exec() builtin), this is
        #   not the case. Instead, a closure-style local must be simulated by
        #   passing the "func" parameter to this function at function
        #   definition time as the default value of an arbitrary parameter. To
        #   ensure this default is *NOT* overwritten by a function accepting a
        #   parameter of the same name, this Edge case is tested for below.
        # * Assert statements type checking parameters passed to this callable.
        # * A call to this callable.
        # * An assert statement type checking the value returned by this
        #   callable.
        #
        # While there exist numerous alternatives (e.g., appending to a list or
        # bytearray before joining the elements of that iterable into a string),
        # these alternatives are either slower (as in the case of a list, due to
        # the high up-front cost of list construction) or substantially more
        # cumbersome (as in the case of a bytearray). Since string concatenation
        # is heavily optimized by the official CPython interpreter, the simplest
        # approach is (curiously) the most ideal.
        func_body = '''
@wraps(__beartype_func)
def func_beartyped(*args, __beartype_func=__beartype_func, **kwargs):
'''

        # "inspect.Signature" instance encapsulating this callable's signature.
        func_sig = inspect.signature(func)

        # Human-readable name of this function for use in exceptions.
        func_name = func.__+ '()'

        # For the name of each parameter passed to this callable and the
        # "inspect.Parameter" instance encapsulating this parameter (in the
        # passed order)...
        for func_arg_index, func_arg in enumerate(func_sig.parameters.values()):
            # If this callable redefines a parameter initialized to a default
            # value by this wrapper, raise an exception. Permitting this
            # unlikely Edge case would permit unsuspecting users to
            # "accidentally" override these defaults.
            if func_arg.name == '__beartype_func':
                raise NameError(
                    'Parameter {} reserved for use by @beartype.'.format(
                        func_arg.name))

            # If this parameter is both annotated and non-ignorable for purposes
            # of type checking, type check this parameter.
            if (func_arg.annotation is not Parameter.empty and
                func_arg.kind not in _PARAMETER_KIND_IGNORED):
                # Validate this annotation.
                _check_type_annotation(
                    annotation=func_arg.annotation,
                    label='{} parameter {} type'.format(
                        func_name, func_arg.name))

                # String evaluating to this parameter's annotated type.
                func_arg_type_expr = (
                    '__beartype_func.__annotations__[{!r}]'.format(
                        func_arg.name))

                # String evaluating to this parameter's current value when
                # passed as a keyword.
                func_arg_value_key_expr = 'kwargs[{!r}]'.format(func_arg.name)

                # If this parameter is keyword-only, type check this parameter
                # only by lookup in the variadic "**kwargs" dictionary.
                if func_arg.kind is Parameter.KEYWORD_ONLY:
                    func_body += '''
    if {arg_name!r} in kwargs and not isinstance(
        {arg_value_key_expr}, {arg_type_expr}):
        raise TypeError(
            '{func_name} keyword-only parameter '
            '{arg_name}={{}} not a {{!r}}'.format(
                {arg_value_key_expr}, {arg_type_expr}))
'''.format(
                        func_name=func_name,
                        arg_name=func_arg.name,
                        arg_type_expr=func_arg_type_expr,
                        arg_value_key_expr=func_arg_value_key_expr,
                    )
                # Else, this parameter may be passed either positionally or as
                # a keyword. Type check this parameter both by lookup in the
                # variadic "**kwargs" dictionary *AND* by index into the
                # variadic "*args" Tuple.
                else:
                    # String evaluating to this parameter's current value when
                    # passed positionally.
                    func_arg_value_pos_expr = 'args[{!r}]'.format(
                        func_arg_index)

                    func_body += '''
    if not (
        isinstance({arg_value_pos_expr}, {arg_type_expr})
        if {arg_index} < len(args) else
        isinstance({arg_value_key_expr}, {arg_type_expr})
        if {arg_name!r} in kwargs else True):
            raise TypeError(
                '{func_name} parameter {arg_name}={{}} not of {{!r}}'.format(
                {arg_value_pos_expr} if {arg_index} < len(args) else {arg_value_key_expr},
                {arg_type_expr}))
'''.format(
                    func_name=func_name,
                    arg_name=func_arg.name,
                    arg_index=func_arg_index,
                    arg_type_expr=func_arg_type_expr,
                    arg_value_key_expr=func_arg_value_key_expr,
                    arg_value_pos_expr=func_arg_value_pos_expr,
                )

        # If this callable's return value is both annotated and non-ignorable
        # for purposes of type checking, type check this value.
        if func_sig.return_annotation not in _RETURN_ANNOTATION_IGNORED:
            # Validate this annotation.
            _check_type_annotation(
                annotation=func_sig.return_annotation,
                label='{} return type'.format(func_name))

            # Strings evaluating to this parameter's annotated type and
            # currently passed value, as above.
            func_return_type_expr = (
                "__beartype_func.__annotations__['return']")

            # Call this callable, type check the returned value, and return this
            # value from this wrapper.
            func_body += '''
    return_value = __beartype_func(*args, **kwargs)
    if not isinstance(return_value, {return_type}):
        raise TypeError(
            '{func_name} return value {{}} not of {{!r}}'.format(
                return_value, {return_type}))
    return return_value
'''.format(func_name=func_name, return_type=func_return_type_expr)
        # Else, call this callable and return this value from this wrapper.
        else:
            func_body += '''
    return __beartype_func(*args, **kwargs)
'''

        # Dictionary mapping from local attribute name to value. For efficiency,
        # only those local attributes explicitly required in the body of this
        # wrapper are copied from the current namespace. (See below.)
        local_attrs = {'__beartype_func': func}

        # Dynamically define this wrapper as a closure of this decorator. For
        # obscure and presumably uninteresting reasons, Python fails to locally
        # declare this closure when the locals() dictionary is passed; to
        # capture this closure, a local dictionary must be passed instead.
        exec(func_body, globals(), local_attrs)

        # Return this wrapper.
        return local_attrs['func_beartyped']

    _PARAMETER_KIND_IGNORED = {
        Parameter.POSITIONAL_ONLY, Parameter.VAR_POSITIONAL, Parameter.VAR_KEYWORD,
    }
    '''
    Set of all `inspect.Parameter.kind` constants to be ignored during
    annotation- based type checking in the `@beartype` decorator.

    This includes:

    * Constants specific to variadic parameters (e.g., `*args`, `**kwargs`).
      Variadic parameters cannot be annotated and hence cannot be type checked.
    * Constants specific to positional-only parameters, which apply to non-pure-
      Python callables (e.g., defined by C extensions). The `@beartype`
      decorator applies _only_ to pure-Python callables, which provide no
      syntactic means of specifying positional-only parameters.
    '''

    _RETURN_ANNOTATION_IGNORED = {Signature.empty, None}
    '''
    Set of all annotations for return values to be ignored during annotation-
    based type checking in the `@beartype` decorator.

    This includes:

    * `Signature.empty`, signifying a callable whose return value is _not_
      annotated.
    * `None`, signifying a callable returning no value. By convention, callables
      returning no value are typically annotated to return `None`. Technically,
      callables whose return values are annotated as `None` _could_ be
      explicitly checked to return `None` rather than a none-`None` value. Since
      return values are safely ignorable by callers, however, there appears to
      be little real-world utility in enforcing this constraint.
    '''

    def _check_type_annotation(annotation: object, label: str) -> None:
        '''
        Validate the passed annotation to be a valid type supported by the
        `@beartype` decorator.

        Parameters
        ----------
        annotation : object
            Annotation to be validated.
        label : str
            Human-readable label describing this annotation, interpolated into
            exceptions raised by this function.

        Raises
        ----------
        TypeError
            If this annotation is neither a new-style class nor a Tuple of
            new-style classes.
        '''

        # If this annotation is a Tuple, raise an exception if any member of
        # this Tuple is not a new-style class. Note that the "__name__"
        # attribute tested below is not defined by old-style classes and hence
        # serves as a helpful means of identifying new-style classes.
        if isinstance(annotation, Tuple):
            for member in annotation:
                if not (
                    isinstance(member, type) and hasattr(member, '__name__')):
                    raise TypeError(
                        '{} Tuple member {} not a new-style class'.format(
                            label, member))
        # Else if this annotation is not a new-style class, raise an exception.
        Elif not (
            isinstance(annotation, type) and hasattr(annotation, '__name__')):
            raise TypeError(
                '{} {} neither a new-style class nor '
                'Tuple of such classes'.format(label, annotation))

# Else, the active Python interpreter is optimized. In this case, disable type
# checking by reducing this decorator to the identity decorator.
else:
    def beartype(func: callable) -> callable:
        return func

そして leycec は、@beartypeが型チェックを高速に実行するようにしました:そしてそうでした。

警告、呪い、空の約束

何も完璧ではありません。 ベアタイピングでも

警告I:デフォルト値はオフ

ベアタイピングは、デフォルト値が割り当てられた未通過パラメーターをnotタイプチェックします。理論的には可能です。しかし、275行以下ではなく、確かにスタックオーバーフローの答えとしてではありません。

安全な(...おそらく完全に安全ではない)前提は、関数の実装者がデフォルト値を定義したときに何をしていたかを知っていると主張することです。デフォルト値は通常定数であるため(...ベターです!)、1つ以上のデフォルト値が割り当てられた各関数呼び出しで変更されない定数のタイプを再チェックすると、基本に違反しますベアタイピングの教義:「繰り返してooooveroooo-ooooverを繰り返さないでください。」

間違ったことを見せてください。私はあなたに賛成票を浴びせます。

警告II:PEP 484なし

PEP 484 "Type Hints"PEP 3107 "によって最初に導入された関数注釈の使用を形式化関数注釈」)。 Python 3.5は、新しいトップレベル typingモジュール 、より単純なタイプから任意の複雑なタイプを構成するための標準API(例、Callable[[Arg1Type, Arg2Type], ReturnType]Arg1Type型とArg2Type型の2つの引数を受け取り、ReturnType型の値を返す関数。

ベアタイピングはそれらのいずれもサポートしていません。理論的には可能です。しかし、275行以下ではなく、確かにスタックオーバーフローの答えとしてではありません。

ただし、ベアタイピングでは、isinstance()ビルトインが型の共用体をサポートするのと同じ方法で、型の共用体をサポートします。タプルとして。これは表面的にtyping.Union型に対応します– typing.Unionが任意の複雑な型をサポートする一方で、@beartypeが受け入れるタプルはonly単純クラスをサポートするという明らかな注意事項があります。私の防御では、275行です。

テストまたはそれが起こらなかった

これが Gist です。 わかった、Gist?今やめます.

@beartypeデコレーター自体と同様に、これらの py.test テストは、変更することなく既存のテストスイートにシームレスに統合できます。貴重ですね。

今、誰も要求していない必須のneckひげの暴言。

API暴力の歴史

Python 3.5は、PEP 484型の使用を実際にサポートしていません。 wat?

それは本当です:型チェック、型推論、型なしです。代わりに、開発者は、そのようなサポートのファクシミリを実装する重いサードパーティのCPythonインタープリターラッパーを介してコードベース全体を定期的に実行することが期待されます(例: mypy )。もちろん、これらのラッパーは以下を課します:

  • 互換性のペナルティ。公式mypy FAQ として、よくある質問「mypyを使用して型チェックを行うことはできますか?」私の既存のPythonコード? ":"それは依存します。互換性はかなり良いですが、いくつかのPython機能はまだ実装されていませんまたは完全にサポートされています。」 後続のFAQ応答 は、次のように述べることにより、この非互換性を明確にします。
    • 「...あなたのコードは属性を明示的にし、明示的なプロトコル表現を使用する必要があります。」 文法警察はあなたの「明示的な」を見て、暗黙の眉をひそめます。
    • 「Mypyはモジュール型の効率的な型チェックをサポートします。これにより、メソッドの任意のランタイム追加など、一部の言語機能の型チェックが除外されるようです。ただし、これらの機能の多くは制限された形式でサポートされる可能性があります(たとえば、 、ランタイムの変更は、動的または「パッチ可能」として登録されたクラスまたはメソッドでのみサポートされます。」
    • 構文上の非互換性の完全なリストについては、 「一般的な問題への対処」 を参照してください。それはnotきれいです。型チェックが必要だったので、コードベース全体をリファクタリングし、候補リリースから2日間で全員のビルドを中断しました。どうもありがとう、mypy。
  • 静的に型付けされたコードの解釈にもかかわらず、パフォーマンスの低下、。 40年のハードボイルドコンピューターサイエンスは、静的に型付けされたコードの解釈は(...その他すべて等しい)動的に型付けされたコードの解釈よりも速くなくてはならないことを示しています。 Pythonでは、upは新しいdownです。
  • 追加の非自明な依存関係、増加:
    • プロジェクトの展開、特にクロスプラットフォームのバグが多い脆弱性。
    • プロジェクト開発の保守負担。
    • 攻撃対象となる可能性があります。

Guidoに尋ねる:「なぜですか。実際にその抽象化で何かをする具体的なAPIをポニーアップしたくないのに、なぜ抽象APIを発明するのが面倒ですか?」百万人のPythonistaの運命を無料のオープンソース市場の関節炎の手に委ねるのはなぜですか?公式のPython stdlibの275行のデコレーターで簡単に解決できたテクノ問題をさらに作成するのはなぜですか?

私にはPythonがなく、叫ぶ必要があります。

69
Cecil Curry

編集:2019年以降、Pythonでの型注釈と静的チェックの使用のサポートが強化されました。 typing モジュールと mypy をチェックしてください。 2013年の回答は次のとおりです。


通常、型チェックはPythonicではありません。 Pythonでは、 ダックタイピング を使用するのがより一般的です。例:

コードでは、引数(この例ではa)はintのように動き、quacksはintのように動くと仮定します。例えば:

def my_function(a):
    return a + 7

これは、関数が整数で動作するだけでなく、フロートや__add__メソッドが定義されたユーザー定義クラスでも動作することを意味します。他の何かと連携するように機能を拡張します。ただし、場合によってはintが必要になることがあるため、次のようなことができます。

def my_function(a):
    b = int(a) + 7
    c = (5, 6, 3, 123541)[b]
    return c

関数は、__int__メソッドを定義するaに対して引き続き機能します。

あなたの他の質問への答えでは、私はそれが最善だと思います(他の答えがこれを行うと言っているように:

def my_function(a, b, c):
    assert 0 < b < 10
    assert c        # A non-empty string has the Boolean value True

または

def my_function(a, b, c):
    if 0 < b < 10:
        # Do stuff with b
    else:
        raise ValueError
    if c:
        # Do stuff with c
    else:
        raise ValueError

私が作ったいくつかの型検査デコレータ:

import inspect

def checkargs(function):
    def _f(*arguments):
        for index, argument in enumerate(inspect.getfullargspec(function)[0]):
            if not isinstance(arguments[index], function.__annotations__[argument]):
                raise TypeError("{} is not of type {}".format(arguments[index], function.__annotations__[argument]))
        return function(*arguments)
    _f.__doc__ = function.__doc__
    return _f

def coerceargs(function):
    def _f(*arguments):
        new_arguments = []
        for index, argument in enumerate(inspect.getfullargspec(function)[0]):
            new_arguments.append(function.__annotations__[argument](arguments[index]))
        return function(*new_arguments)
    _f.__doc__ = function.__doc__
    return _f

if __== "__main__":
    @checkargs
    def f(x: int, y: int):
        """
        A doc string!
        """
        return x, y

    @coerceargs
    def g(a: int, b: int):
        """
        Another doc string!
        """
        return a + b

    print(f(1, 2))
    try:
        print(f(3, 4.0))
    except TypeError as e:
        print(e)

    print(g(1, 2))
    print(g(3, 4.0))
19
rlms

1つの方法は、assertを使用することです。

def myFunction(a,b,c):
    "This is an example function I'd like to check arguments of"
    assert isinstance(a, int), 'a should be an int'
    # or if you want to allow whole number floats: assert int(a) == a
    assert b > 0 and b < 10, 'b should be betwen 0 and 10'
    assert isinstance(c, str) and c, 'c should be a non-empty string'
11
Matthew Plourde

PythonDecoratorLibrary からType Enforcement accept/returnsデコレータを使用できます。非常に簡単で読みやすいです。

@accepts(int, int, float)
def myfunc(i1, i2, i3):
    pass
6
DominikStyp

Pythonで変数が何であるかを確認するには、さまざまな方法があります。したがって、いくつかをリストするには:

  • isinstance(obj, type)関数は、変数objを受け取り、リストしたTrueと同じタイプであるtypeを提供します。

  • issubclass(obj, class)変数objを取り、Trueobjのサブクラスである場合にclassを与える関数。たとえば、issubclass(Rabbit, Animal)True値を提供します

  • hasattrは、この関数super_lenで示される別の例です。


def super_len(o):
    if hasattr(o, '__len__'):
        return len(o)

    if hasattr(o, 'len'):
        return o.len

    if hasattr(o, 'fileno'):
        try:
            fileno = o.fileno()
        except io.UnsupportedOperation:
            pass
        else:
            return os.fstat(fileno).st_size

    if hasattr(o, 'getvalue'):
        # e.g. BytesIO, cStringIO.StringI
        return len(o.getvalue())

hasattrはアヒルのタイピングに傾いており、通常はより多くの何かPythonicですが、その用語は考えられています。

注として、テストではassertステートメントが通常使用されますが、そうでない場合はif/elseステートメントを使用します。

5
Games Brainiac

私は多くのことに満足していなかったので、最近そのトピックについてかなりの調査をしました ライブラリ 私はそこに見つけました。

私はこれに対処するためにライブラリを開発することになりました。名前は valid8 です。ドキュメントで説明されているように、ほとんどの場合、値の検証用であり(単純な型検証関数もバンドルされています)、 enforce などのPEP484ベースの型チェッカーと関連付けることができます。 pytypes

これは、あなたの場合にvalid8だけで検証を実行する方法です(そして実際には、検証ロジックを定義するために mini_lambda ですが、必須ではありません):

# for type validation
from numbers import Integral
from valid8 import instance_of

# for value validation
from valid8 import validate_arg
from mini_lambda import x, s, Len

@validate_arg('a', instance_of(Integral))
@validate_arg('b', (0 < x) & (x < 10))
@validate_arg('c', instance_of(str), Len(s) > 0)
def my_function(a: Integral, b, c: str):
    """an example function I'd like to check the arguments of."""
    # check that a is an int
    # check that 0 < b < 10
    # check that c is not an empty string

# check that it works
my_function(0.2, 1, 'r')  # InputValidationError for 'a' HasWrongType: Value should be an instance of <class 'numbers.Integral'>. Wrong value: [0.2].
my_function(0, 0, 'r')    # InputValidationError for 'b' [(x > 0) & (x < 10)] returned [False]
my_function(0, 1, 0)      # InputValidationError for 'c' Successes: [] / Failures: {"instance_of_<class 'str'>": "HasWrongType: Value should be an instance of <class 'str'>. Wrong value: [0]", 'len(s) > 0': "TypeError: object of type 'int' has no len()"}.
my_function(0, 1, '')     # InputValidationError for 'c' Successes: ["instance_of_<class 'str'>"] / Failures: {'len(s) > 0': 'False'}

これは、PEP484型ヒントを活用し、型チェックをenforceに委任する同じ例です。

# for type validation
from numbers import Integral
from enforce import runtime_validation, config
config(dict(mode='covariant'))  # type validation will accept subclasses too

# for value validation
from valid8 import validate_arg
from mini_lambda import x, s, Len

@runtime_validation
@validate_arg('b', (0 < x) & (x < 10))
@validate_arg('c', Len(s) > 0)
def my_function(a: Integral, b, c: str):
    """an example function I'd like to check the arguments of."""
    # check that a is an int
    # check that 0 < b < 10
    # check that c is not an empty string

# check that it works
my_function(0.2, 1, 'r')  # RuntimeTypeError 'a' was not of type <class 'numbers.Integral'>
my_function(0, 0, 'r')    # InputValidationError for 'b' [(x > 0) & (x < 10)] returned [False]
my_function(0, 1, 0)      # RuntimeTypeError 'c' was not of type <class 'str'>
my_function(0, 1, '')     # InputValidationError for 'c' [len(s) > 0] returned [False].
2
smarie

通常、次のようなことを行います。

def myFunction(a,b,c):
   if not isinstance(a, int):
      raise TypeError("Expected int, got %s" % (type(a),))
   if b <= 0 or b >= 10:
      raise ValueError("Value %d out of range" % (b,))
   if not c:
      raise ValueError("String was empty")

   # Rest of function
2
Mats Kindahl

これは、関数を呼び出すときに入力引数のタイプをチェックします。

def func(inp1:int=0,inp2:str="*"):

    for item in func.__annotations__.keys():
        assert isinstance(locals()[item],func.__annotations__[item])

    return (something)

first=7
second="$"
print(func(first,second))

また、second=9で確認します(アサーションエラーが発生する必要があります)

1
Mahdi Ghelichi

いくつかの関数の検証を行いたい場合、次のようなデコレータ内にロジックを追加できます。

def deco(func):
     def wrapper(a,b,c):
         if not isinstance(a, int)\
            or not isinstance(b, int)\
            or not isinstance(c, str):
             raise TypeError
         if not 0 < b < 10:
             raise ValueError
         if c == '':
             raise ValueError
         return func(a,b,c)
     return wrapper

そしてそれを使用します:

@deco
def foo(a,b,c):
    print 'ok!'

お役に立てれば!

0
Paulo Bu

これは解決策ではありませんが、関数呼び出しを特定のパラメータータイプに制限する場合は、PROATOR {Python関数プロトタイプバリデーター}を使用する必要があります。次のリンクを参照できます。 https://github.com/mohit-thakur-721/proator

0
Mohit Thakur

**kwargs*argsおよび通常の引数を一度に確認する場合は、locals()関数を関数定義の最初のステートメントとして使用して、引数の辞書を取得できます。 。

次に、type()を使用して引数を調べます。たとえば、dictを繰り返し処理します。

def myfunc(my, args, to, this, function, **kwargs):
    d = locals()
    assert(type(d.get('x')) == str)
    for x in d:
        if x != 'x':
            assert(type(d[x]) == x
    for x in ['a','b','c']:
        assert(x in d)

    whatever more...
0
Jo So
def someFunc(a, b, c):
    params = locals()
    for _item in params:
        print type(params[_item]), _item, params[_item]

デモ:

>> someFunc(1, 'asd', 1.0)
>> <type 'int'> a 1
>> <type 'float'> c 1.0
>> <type 'str'> b asd

locals() の詳細

0
FallenAngel