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のイディオムはdocument関数が期待するものを明確にし、関数に渡されたものを使用して、例外を伝播させるか、属性エラーをキャッチして代わりにTypeError
を発生させます。型チェックは、ダックタイピングに反するため、できるだけ避けるべきです。値のテストは、コンテキストに応じて問題なく実行できます。
検証が本当に意味を持つ唯一の場所は、Webフォーム、コマンドライン引数などのシステムまたはサブシステムのエントリポイントです。他のすべての場所で、関数が適切に文書化されている限り、適切な引数を渡すのは呼び出し側の責任です。
この詳細な回答では、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。それで、摩擦、泡は何ですか?
私の限られた領域の知識の範囲内で、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があるかもしれません。
クマはアヒルを打ちます。アヒルは飛ぶことがありますが、クマはアヒルにサーモンを投げることがあります。 カナダでは、自然はあなたを驚かせることができます。
次の質問。
既存のソリューションは、ベアメタルタイプチェックを実行しますnot–少なくとも、私は理解していません。それらはすべて、各関数呼び出しで型チェックされた関数のシグネチャを繰り返し再検査します。 1回の呼び出しでは無視できますが、すべての呼び出しで集計した場合、再検査のオーバーヘッドは通常無視できません。 本当に、本当に無視できない。
ただし、単に効率の問題ではありません。また、既存のソリューションは、一般的なEdgeのケースを考慮に入れていないことがよくあります。これには、StackOverflowの回答として提供されるおもちゃデコレータのすべてではないにしても、ほとんどの場合が含まれます。従来の障害には次のものがあります。
@checkargs
decorator )。isinstance()
ビルトインで受け入れられるタイプのタプル(つまり、ユニオン)のサポートに失敗しました。AssertionError
例外ではなく、一般的なTypeError
例外を発生させます。粒度と健全性のために、型チェックはneverで一般的な例外を発生させる必要があります。ベアタイピングは、非ベアが失敗した場合に成功します。すべて1つ、すべてのクマ!
ベアタイピングは、関数シグネチャの検査のスペースと時間のコストを、関数呼び出し時間から関数定義時間に、つまり、@beartype
デコレーターによって返されたラッパー関数からデコレーター自体にシフトします。デコレータは関数定義ごとに1回しか呼び出されないため、この最適化はすべての人に喜びをもたらします。
ベアタイピングは、あなたのタイプチェックケーキを食べさせようとする試みでもあります。そのためには、@beartype
:
exec()
ビルトインを介してこのラッパー関数を動的に宣言します。しましょうか?奥深くに飛び込みましょう。
# 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
が型チェックを高速に実行するようにしました:そしてそうでした。
何も完璧ではありません。 ベアタイピングでも
ベアタイピングは、デフォルト値が割り当てられた未通過パラメーターをnotタイプチェックします。理論的には可能です。しかし、275行以下ではなく、確かにスタックオーバーフローの答えとしてではありません。
安全な(...おそらく完全に安全ではない)前提は、関数の実装者がデフォルト値を定義したときに何をしていたかを知っていると主張することです。デフォルト値は通常定数であるため(...ベターです!)、1つ以上のデフォルト値が割り当てられた各関数呼び出しで変更されない定数のタイプを再チェックすると、基本に違反しますベアタイピングの教義:「繰り返してooooverとoooo-ooooverを繰り返さないでください。」
間違ったことを見せてください。私はあなたに賛成票を浴びせます。
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ひげの暴言。
Python 3.5は、PEP 484型の使用を実際にサポートしていません。 wat?
それは本当です:型チェック、型推論、型なしです。代わりに、開発者は、そのようなサポートのファクシミリを実装する重いサードパーティのCPythonインタープリターラッパーを介してコードベース全体を定期的に実行することが期待されます(例: mypy )。もちろん、これらのラッパーは以下を課します:
Guidoに尋ねる:「なぜですか。実際にその抽象化で何かをする具体的なAPIをポニーアップしたくないのに、なぜ抽象APIを発明するのが面倒ですか?」百万人のPythonistaの運命を無料のオープンソース市場の関節炎の手に委ねるのはなぜですか?公式のPython stdlibの275行のデコレーターで簡単に解決できたテクノ問題をさらに作成するのはなぜですか?
私にはPythonがなく、叫ぶ必要があります。
編集: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))
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'
PythonDecoratorLibrary からType Enforcement accept/returnsデコレータを使用できます。非常に簡単で読みやすいです。
@accepts(int, int, float)
def myfunc(i1, i2, i3):
pass
Pythonで変数が何であるかを確認するには、さまざまな方法があります。したがって、いくつかをリストするには:
isinstance(obj, type)
関数は、変数obj
を受け取り、リストしたTrue
と同じタイプであるtype
を提供します。
issubclass(obj, class)
変数obj
を取り、True
が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
ステートメントを使用します。
私は多くのことに満足していなかったので、最近そのトピックについてかなりの調査をしました ライブラリ 私はそこに見つけました。
私はこれに対処するためにライブラリを開発することになりました。名前は 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].
通常、次のようなことを行います。
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
これは、関数を呼び出すときに入力引数のタイプをチェックします。
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
で確認します(アサーションエラーが発生する必要があります)
いくつかの関数の検証を行いたい場合、次のようなデコレータ内にロジックを追加できます。
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!'
お役に立てれば!
これは解決策ではありませんが、関数呼び出しを特定のパラメータータイプに制限する場合は、PROATOR {Python関数プロトタイプバリデーター}を使用する必要があります。次のリンクを参照できます。 https://github.com/mohit-thakur-721/proator
**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...
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() の詳細