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

この細長い答えで。, Python 3.x固有のタイプチェックデコレーターを実装しています。 [PEP 484。]https://www.python.org/dev/peps/pep-0484。)-スタイルタイプは、275行未満の純粋なPythonを示唆しています。 (そのほとんどは説明的な説明とコメントです。) –産業強度の現実世界での使用向けに大幅に最適化されています。 [py.test。]https://github.com/pytest-dev/pytest。)-すべての可能なエッジケースを実行する駆動テストスイート

bear typing の予想外の素晴らしいものをごちそう:

>>> @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 

この例が示唆するように、クマのタイピングはパラメータのタイプチェックと、そのようなタイプの単純なタイプまたはタプルのいずれかとして注釈が付けられた戻り値を明示的にサポートしています。 うれしい。!

OK、それは実際には印象的ではありません。 @beartypeは、PEP 484スタイルのタイプに基づくすべてのother Python 3.x固有のタイプチェックデコレーターに似ています。純粋なPythonの275行。 だから、摩 ⁇ は何ですか、バブ?

純粋なブルートフォースハードコア効率。

クマのタイピングは、Pythonでのタイプチェックの既存のすべての実装よりも、限られたドメインの知識の中で、空間と時間の両方で劇的に効率的です。 (後で詳しく説明します。)。

ただし、Pythonでは通常、効率は関係ありません。 もしそうなら、あなたはPythonを使用しないでしょう。 タイプチェックは実際には、Pythonでの早期最適化を回避するという確立された基準から逸脱していますか?? はい。 はい、そうです。

プロファイリングを検討してください。これにより、プロファイリングされた各関心のある指標に不可避のオーバーヘッドが追加されます(例:.、関数呼び出し、行)。 正確な結果を確実にするために、このオーバーヘッドは、最適化されたC拡張機能(例:.、最適化されていない純粋なPythonではなく、「cProfile」モジュールによって活用される「_lsprof」C拡張子(例:.、 プロファイルモジュール)。 プロファイリング時に効率は本当に重要です。

タイプチェックも例外ではありません。 タイプチェックは、アプリケーションによってチェックされた各関数呼び出しタイプにオーバーヘッドを追加します。理想的には、それらの_all_です。 先週の金曜日のカフェインを追加した徹夜で老人レガシーのDjango Webアプリに追加した後、意味のある(しかし悲しいことに小柄な)同僚が静かに追加したタイプチェックを削除できないようにするには、タイプチェックを高速にする必要があります。非常に高速なので、誰にも言わずに追加しても、そこに誰も気付かないでしょう。 私はいつもこれをします。! 同僚の場合は、これを読むのをやめてください。< / sup>。

ただし、滑 ⁇ な速度でさえ大食いアプリケーションに十分でない場合、Pythonの最適化を有効にすることで、クマのタイピングがグローバルに無効になることがあります(例:.、 -OオプションをPythonインタープリターに渡すことにより):

$ 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_関数に渡された、および/または返された値に渡された各パラメーターを手動で「アサート」します。 ボイラープレートは、より単純またはより平凡である可能性があります? 私たちは皆、それをグーグルプレックスの100倍見て、毎回口の中で少し吐きました。 繰り返しは早く古くなります。 DRY、よ。

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

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

Python 101、そうです? 私たちの多くはそのクラスを通過しました。

ベアタイピングは、上記のアプローチによって手動で実行されたタイプチェックを、同じチェックを自動的に実行する動的に定義されたラッパー関数に抽出します。あいまいな「AssertionError」例外ではなく、粒状の「TypeError」を発生させるという追加の利点があります。 自動化されたアプローチは次のとおりです。

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

長続きします。 しかし、それは基本的に *< / sup>です。手動アプローチと同じくらい速い。 \ * Squintingが推奨されます。< / sup>。

ラッパー機能での機能検査または反復の完全な欠如に注意してください。, これには、追加機能ではありますが、元の関数と同様の数のテストが含まれています。 (多分無視できます。) 型チェックするパラメーターが現在の関数呼び出しに渡されるかどうか、およびどのように渡されるかをテストするコスト。 すべての戦いに勝つことはできません。

このようなラッパー関数を確実に出力して、275行未満の純粋なPythonで任意の関数を型チェックできるようにできますか?? Snake Plisskinは、 "本当の話。 煙が出た?"

そして、はい。 首ひげがあるかもしれません。

いいえ、Srsly。 なぜ「クマ」?

クマはアヒルを倒します。 アヒルは飛ぶかもしれませんが、クマはアヒルにサケを投げるかもしれません。 カナダでは、自然があなたを驚かせる可能性があります。< / sup>。

次の質問。

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

既存のソリューションは、裸金属タイプのチェックを実行しません。少なくとも、私がグレッピングしたものはありません。 これらはすべて、各関数呼び出しで型チェック関数の署名を繰り返し再検査します。 1回の呼び出しでは無視できますが、すべての呼び出しで集約された場合、再検査オーバーヘッドは通常無視できません。 _本当に、本当に_無視できない。

ただし、これは単に効率の問題ではありません。 既存のソリューションでは、一般的なエッジケースを説明できないことがよくあります。 これには、ここや他の場所でstackoverflowの回答として提供される、すべてではないにしてもほとんどのおもちゃのデコレータが含まれます。 古典的な失敗は次のとおりです。

*チェックキーワードの引数や戻り値の入力に失敗(例:.、sweeneyrod@checkargs decorator)。

*タプルをサポートできません(つまり、.、ユニオン) isinstance()ビルトインによって受け入れられるタイプの。

*名前、docstring、およびその他の識別メタデータを元の関数からラッパー関数に伝 ⁇ できません。

*少なくとも一連のユニットテストを提供できません。 (キンドオブクリティカル。)。

*失敗したタイプチェックで特定の「TypeError」例外ではなく、一般的な「AssertionError」例外を発生させます。 粒度と正気については、タイプチェックは一般的な例外を_決して_上げるべきではありません。

クマのタイピングは、非クマが失敗すると成功します。 すべて1つ、すべてクマ。!

ベアタイピングアンバレド。

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

クマのタイピングは、タイプチェックケーキを食べさせようとする試みでもあります。 これを行うには、 @ beartype:

1。 元の関数の署名と注釈を検査します。

1。 元の機能をチェックするラッパー関数タイプの本体を動的に構築します。 そうだね。 Pythonコードを生成するPythonコード。

1。 exec()ビルトインを介してこのラッパー関数を動的に宣言します。

1。 このラッパー関数を返します。

しましょう? ディープエンドに飛び込みましょう。

# 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.__name__ + '()'

        # 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」にタイプチェックをすばやく行わせてください。

キャベツ、呪い、空の約束。

完璧なものはありません。 _クマも入力しています。

Caveat I:デフォルト値のチェックが解除されました。

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

金庫(。..おそらく完全に安全ではない)仮定は、関数実装者がデフォルト値を定義したときに何をしていたかを知っていると主張することです。 デフォルト値は通常定数(。..彼らはより良いでしょう。!)、割り当てられた1つ以上のデフォルト値で各関数呼び出しで決して変化しない定数のタイプを再チェックすると、クマのタイピングの基本的な信条に違反します。「自分自身を繰り返さないでください。"。

間違って見せてください。賛成票でシャワーを浴びます。

Caveat II:PEP 484はありません。

PEP 484( "Type Hints" )は、[PEP 3107](https:/ /www.python.org/dev/peps/pep-3107/unction ". Python 3.5は、この形式化を新しいトップレベルtyping moduleで表面的にサポートしています。これは、単純なタイプから任意に複雑なタイプを構成するための標準APIです。 (例えば.、 Callable [[Arg1Type、Arg2Type]、ReturnType]、タイプ Arg1TypeArg2Typeの2つの引数を受け入れ、タイプ ReturnTypeの値を返す関数を説明するタイプ)。

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

ただし、クマのタイピングは、「isinstance()」組み込みがタイプのユニオンをサポートするのと同じ方法でタイプのユニオンをサポートします。**これは表面的には「typing.Union」タイプに対応します。「typing.Union」は任意に複雑なタイプをサポートするという明らかな警告があり、「@ beartype」で受け入れられるタプルは_only_単純なクラスをサポートします。 私の防御では、275ライン。

テストまたはそれは起こりませんでした。

これがgistです。 入手してください、 gist? もうやめます。< / sup>。

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

今、必須の首ひげは誰も求めなかった。

API暴力の歴史。

Python 3.5は、PEP 484タイプの使用を実際にサポートしていません。 ワット?

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

  • A 互換性のペナルティ。 公式mypy FAQとして)よくある質問への回答を認めています「mypyを使用して、既存のPythonコードをタイプチェックできますか?":" 場合によります。互換性はかなり優れていますが、一部のPython機能はまだ実装されていないか、完全にサポートされていません。"後続のFAQ応答は、次のように述べてこの非互換性を明確にします。

    • "。..codeは属性を明示的にし、明示的なプロトコル表現を使用する必要があります。" 文法警察はあなたの「露骨な」を見て、暗黙の ⁇ をひそめます。< / sup>。

    *「Mypyはモジュール式の効率的なタイプチェックをサポートします。これにより、メソッドの任意のランタイム追加など、一部の言語機能をタイプチェックから除外するようです。 ただし、これらの機能の多くは制限された形式でサポートされる可能性があります(たとえば、ランタイムの変更は、動的または「パッチ可能」として登録されたクラスまたはメソッドでのみサポートされます)。"。

    *構文の非互換性の完全なリストについては、 "一般的な問題への対処" を参照してください。 きれいじゃない。 タイプチェックが必要なだけで、コードベース全体を作り直し、候補者のリリースから2日間、全員のビルドを破りました。カジュアルなビジネス服装の居心地の良いHRミゼットは、キュービクル兼マンケーブの亀裂にピンクのスリップを入れます。 どうもありがとう、mypy。

  • パフォーマンスのペナルティ静的に入力されたコードを解釈しても。 ハードボイルドコンピュータサイエンスの40年は、(。..他のすべては等しい)静的に入力されたコードの解釈は、動的に入力されたコードの解釈よりも速く、遅くはないはずです。 Pythonでは、upが新しいダウンです。

*追加の非重要な依存関係、増加:

*バグが多いプロジェクト展開の脆弱性、特にクロスプラットフォーム。

*プロジェクト開発の維持管理負担。

*攻撃面の可能性。

私はグイドに尋ねます:「なぜ? あなたが実際にその抽象化で何かをしている具体的なAPIをポニーアップするつもりがなかったのに、なぜ抽象APIを発明するのか?「100万人のPythonistasの運命を無料のオープンソースマーケットの関節炎の手に任せるのはなぜですか? 公式のPython stdlibの275行の装飾機で簡単に解決できた可能性のある、さらに別のテクノ問題を作成する理由?

私にはPythonがなく、悲鳴を上げる必要があります。

解説 (8)
ソリューション

最もPythonicのイディオムは、関数が期待することを明確に_document_し、関数に渡されたものをすべて使用して、例外を伝 ⁇ させるか、属性エラーをキャッチして代わりに「TypeError」を発生させることです。 タイプチェックは、ダックタイピングに反するため、できるだけ避ける必要があります。 コンテキストによっては、値のテストでも問題ありません。

検証が本当に意味のある唯一の場所は、Webフォーム、コマンドライン引数などのシステムまたはサブシステムのエントリポイントです。 それ以外の場合は、機能が適切に文書化されている限り、適切な議論を渡すのは発信者の責任です。

解説 (10)

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

----------。

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

コードでは、引数(例では a)が intのように歩き、 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 __name__ == "__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))
解説 (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'
解説 (5)

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

@accepts(int, int, float)
def myfunc(i1, i2, i3):
    pass
解説 (2)

Pythonの変数が何であるかをチェックする方法はいくつかあります。 だから、いくつかリストするには:

-isinstance(obj、type)関数は変数 objを取り、 Trueは、リストした typeと同じタイプです。

-変数 objを取り込み、 objclassのサブクラスである場合は Trueを提供する issubclass(obj、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」ステートメントを使用します。

解説 (0)

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

私はこれに対処するためのライブラリを開発することになりました、それはvalid8と名付けられました。 ドキュメントで説明されているように、それは主に値検証用です(ただし、単純なタイプ検証関数もバンドルされています)。[enforce](https:// github)などのPEP484ベースのタイプチェッカーに関連付けることをお勧めします。 .com / RussBaz / enforce)または[pytypes](https:/github.com/Stewori/typypes.

これは、検証ロジックを定義するために、実際に「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 . 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_": "HasWrongType: Value should be an instance of . Wrong value: [0]", 'len(s) > 0': "TypeError: object of type 'int' has no len()"}.
my_function(0, 1, '')     # InputValidationError for 'c' Successes: ["instance_of_"] / Failures: {'len(s) > 0': 'False'}

これは、PEP484タイプのヒントを活用し、タイプチェックを「強化」に委任する同じ例です。

# 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 
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 
my_function(0, 1, '')     # InputValidationError for 'c' [len(s) > 0] returned [False].
解説 (0)

通常はこのようなことをします:

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

   # Rest of function
解説 (4)

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

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)
def someFunc(a, b, c):
    params = locals()
    for _item in params:
        print type(params[_item]), _item, params[_item]

デモ:

>> someFunc(1, 'asd', 1.0)
>>  a 1
>>  c 1.0
>>  b asd

locals()の詳細。

解説 (0)

複数の関数の検証を行う場合は、次のようにデコレータ内にロジックを追加できます。

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!'

これが役立つことを願っています。!

解説 (7)

通常の引数だけでなく、**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)

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

解説 (0)
def myFunction(a,b,c):
"This is an example function I'd like to check arguments of"
    if type( a ) == int:
       #dostuff
    if 0 < b < 10:
       #dostuff
    if type( C ) == str and c != "":
       #dostuff
解説 (0)