I'm mencari cara yang efisien untuk memeriksa variabel python fungsi. Sebagai contoh, saya'd seperti untuk memeriksa argumen jenis dan nilai. Apakah ada modul untuk ini? Atau saya harus menggunakan sesuatu seperti dekorator, atau idiom tertentu?
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
Dalam hal ini memanjang menjawab, kami menerapkan Python 3.x-jenis tertentu memeriksa dekorator berdasarkan PEP 484-jenis gaya petunjuk dalam waktu kurang dari 275 garis-garis murni-Python (yang sebagian besar adalah penjelasan docstrings dan komentar) – sangat dioptimalkan untuk industri-kekuatan yang digunakan di dunia nyata lengkap dengan py.test
-didorong test suite berolahraga semua kemungkinan kasus tepi.
Pesta yang tak terduga yang mengagumkan dari bear mengetik:
>>> @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">
Sebagai contoh ini menunjukkan, beruang mengetik secara eksplisit mendukung jenis pemeriksaan parameter dan mengembalikan nilai-nilai yang dijelaskan sebagai jenis yang sederhana atau tupel dari jenis seperti. Golly!
O. K., bahwa's benar-benar mengesankan. @beartype
menyerupai setiap other Python 3.x-jenis tertentu memeriksa dekorator berdasarkan PEP 484-jenis gaya petunjuk dalam waktu kurang dari 275 garis-garis murni-Python. Jadi apa yang's menggosok, dada?
Beruang mengetik secara dramatis lebih efisien dalam ruang dan waktu dari semua yang ada implementasi dari tipe check-in Python untuk yang terbaik dari saya yang terbatas domain pengetahuan. (More pada nanti.)
Efisiensi biasanya doesn't peduli di Python, namun. Jika itu terjadi, anda tidak't dapat menggunakan Python. Apakah jenis pemeriksaan yang benar-benar menyimpang dari mapan norma menghindari dini optimasi di Python? Ya. Ya, itu tidak.
Mempertimbangkan profil, yang menambahkan tidak dapat dihindari overhead untuk masing-masing profil metrik bunga (misalnya, panggilan fungsi, garis-garis). Untuk memastikan hasil yang akurat, overhead ini adalah dikurangi dengan memanfaatkan dioptimalkan C ekstensi (misalnya, _lsprof
C ekstensi leveraged oleh cProfile
modul) daripada unoptimized murni-Python (misalnya, profil
modul). Efisiensi benar-benar does peduli ketika profiling.
Jenis pemeriksaan ini tidak berbeda. Jenis pemeriksaan menambahkan overhead untuk masing-masing fungsi jenis panggilan diperiksa oleh aplikasi anda – idealnya, all dari mereka. Untuk mencegah bermaksud baik (tapi sayangnya kecil-minded) rekan kerja dari menghapus tipe memeriksa anda secara diam-diam ditambahkan setelah jumat lalu's kafein-kacau allnighter untuk anda geriatri warisan Django web app, jenis pemeriksaan harus cepat. Begitu cepat sehingga tidak ada pemberitahuan itu's ada ketika anda menambahkan itu tanpa memberitahu siapa pun. I melakukan hal ini sepanjang waktu! Berhenti membaca ini jika anda adalah rekan kerja.
Jika bahkan menggelikan, kecepatan angin dan isn't cukup untuk anda rakus aplikasi, namun, beruang mengetik dapat dinonaktifkan secara global dengan memungkinkan Python optimasi (misalnya, dengan melewati -O
pilihan untuk interpreter 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')
Hanya karena. Selamat datang untuk menanggung mengetik.
Beruang mengetik telanjang-logam jenis pemeriksaan – yang, jenis pemeriksaan sebagai dekat dengan pendekatan manual dari tipe check-in Python sebagai layak. Beruang mengetik ini dimaksudkan untuk memaksakan no hukuman kinerja, kompatibilitas, kendala, atau pihak ketiga dependensi (atas dan di atas yang dikenakan oleh pendekatan manual, sih). Beruang mengetik dapat mulus diintegrasikan ke dalam ada codebases dan tes suites tanpa modifikasi.
Semua orang's mungkin akrab dengan pendekatan manual. Anda secara manual menegaskan
masing-masing parameter yang dilewatkan ke dan/atau pengembalian nilai yang dihasilkan dari every fungsi dalam basis kode. Apa yang boilerplate bisa menjadi lebih sederhana atau lebih dangkal? Kami've melihat semua itu seratus kali googleplex kali, dan muntah di mulut kita setiap kali kita lakukan. Pengulangan akan cepat tua. DRY, yo.
Mendapatkan muntah tas siap. Untuk singkatnya, let's mengasumsikan modern easy_spirit_bear()
fungsi menerima hanya satu str
parameter. Berikut ini's apa yang anda benar-benar tidak terlihat seperti:
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, kan? Banyak dari kita melewati kelas itu.
Beruang mengetik ekstrak jenis pemeriksaan manual yang dilakukan dengan pendekatan di atas menjadi dinamis didefinisikan pembungkus fungsi yang secara otomatis melakukan pemeriksaan yang sama – dengan manfaat tambahan dari beternak granular TypeError
daripada ambigu AssertionError
pengecualian. Berikut ini's apa otomatis pendekatan terlihat seperti:
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
It's bertele-tele. Tapi itu's juga pada dasarnya* secepat pendekatan manual. * Squinting yang disarankan.
Catatan kurang lengkap fungsi pemeriksaan atau iterasi dalam pembungkus fungsi, yang mengandung jumlah yang sama dari tes sebagai fungsi asli – meskipun dengan tambahan (mungkin diabaikan) biaya pengujian apakah dan bagaimana parameter tipe diperiksa dilewatkan saat pemanggilan fungsi. Anda dapat't memenangkan setiap pertempuran. Bisa seperti fungsi pembungkus actually menjadi andal yang dihasilkan untuk jenis check fungsi sewenang-wenang dalam waktu kurang dari 275 baris Python murni? Ular Plisskin mengatakan, "True story. Punya rokok?" Dan, ya. Saya mungkin memiliki neckbeard.
Beruang beats bebek. Bebek bisa terbang, tapi beruang dapat membuang salmon di bebek. In Kanada, alam dapat mengejutkan anda. Pertanyaan berikutnya.
Ada solusi tidak not melakukan bare-metal type checking – setidaknya, tidak ada I've grepped di. Mereka semua iteratif reinspect tanda tangan dari jenis-diperiksa pada fungsi setiap pemanggilan fungsi. Sementara diabaikan untuk satu panggilan, inspeksi ulang overhead biasanya non-diabaikan ketika diagregasikan semua panggilan. Really, really non-diabaikan. It's tidak hanya menyangkut efisiensi, namun. Solusi yang ada juga sering gagal untuk memperhitungkan umum edge kasus. Ini termasuk sebagian besar, jika tidak semua mainan dekorator yang disediakan seperti stackoverflow jawaban di sini dan di tempat lain. Klasik kegagalan meliputi:
@checkargs
dekorator). isinstance()
builtin. AssertionError
pengecualian daripada spesifik TypeError
pengecualian pada gagal jenis pemeriksaan. Untuk perincian dan kewarasan, jenis pemeriksaan harus never menaikkan generik pengecualian.
Beruang mengetik berhasil di mana non-beruang gagal. Semua orang, semua beruang! Beruang mengetik pergeseran ruang dan waktu biaya memeriksa fungsi tanda tangan dari pemanggilan fungsi waktu untuk definisi fungsi waktu – artinya, dari bungkusnya fungsi yang dikembalikan oleh @beartype
dekorator menjadi dekorator itu sendiri. Sejak dekorator hanya disebut sekali per definisi fungsi, optimasi ini hasil gembira untuk semua.
Beruang mengetik adalah upaya untuk memeriksa jenis kue dan memakannya juga. Untuk melakukannya, @beartype
:
exec()
builtin. # 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
Dan leycec mengatakan, Biarkan @beartype
mendatangkan jenis pemeriksaan cepatnya: dan itu jadi.
Tidak ada yang sempurna. Even beruang mengetik.
Beruang mengetik tidak not jenis check unpassed parameter diberi nilai default. Dalam teori, itu bisa. Tapi tidak 275 garis atau kurang dan tentu saja tidak seperti stackoverflow menjawab. Aman (...probably benar-benar unsafe) asumsi adalah bahwa fungsi pelaksana mengklaim mereka tahu apa yang mereka lakukan ketika mereka mendefinisikan nilai default. Karena nilai-nilai default yang biasanya konstanta (...they'd lebih baik!), mengecek kembali jenis konstanta yang tidak pernah berubah pada masing-masing fungsi melakukan panggilan satu atau lebih nilai-nilai default akan bertentangan dengan prinsip dasar beruang mengetik: "Don't ulangi diri atas dan oooover dan oooo-oooover lagi." Tunjukkan yang salah dan aku akan mandi anda dengan upvotes.
PEP 484 ("Jenis Petunjuk") meresmikan penggunaan fungsi anotasi pertama kali diperkenalkan oleh PEP 3107 ("Fungsi Anotasi"). Python 3.5 dangkal mendukung formalisasi ini dengan yang baru tingkat atas mengetik
module, sebuah standar API untuk menyusun sewenang-wenang kompleks jenis dari sederhana jenis (misalnya, Callable[[Arg1Type, Arg2Type], ReturnType]
, jenis menggambarkan suatu fungsi yang menerima dua argumen dari jenis Arg1Type
dan Arg2Type
dan mengembalikan sebuah nilai dari tipe ReturnType
).
Beruang mengetik mendukung tidak satupun dari mereka. Dalam teori, itu bisa. Tapi tidak 275 garis atau kurang dan tentu saja tidak seperti stackoverflow menjawab.
Beruang mengetik, namun, dukungan serikat jenis dalam cara yang sama bahwa isinstance()
builtin mendukung serikat jenis: sebagai tupel. Ini dangkal sesuai dengan mengetik.Uni
tipe – dengan jelas peringatan bahwa mengetik.Uni
mendukung sewenang-wenang kompleks jenis, sementara tupel diterima oleh @beartype
dukungan only sederhana kelas. Dalam pembelaan saya, 275 garis.
Berikut ini's inti itu. Mendapatkannya, gist? I'll berhenti sekarang.
Seperti dengan @beartype
dekorator itu sendiri, ini py.test
tes dapat mulus diintegrasikan ke dalam ada tes suites tanpa modifikasi. Mulia, isn't itu?
Sekarang wajib neckbeard kata-kata kasar tidak ada yang meminta.
Python 3.5 tidak memberikan dukungan yang sebenarnya untuk menggunakan PEP 484 jenis. wat? It's benar: tidak ada jenis pemeriksaan, tidak ada jenis kesimpulan, tidak ada jenis nuthin'. Sebaliknya, pengembang diharapkan untuk rutin menjalankan seluruh codebases melalui heavyweight pihak ketiga CPython penerjemah pembungkus melaksanakan faksimili dukungan tersebut (misalnya, mypy). Tentu saja, ini bungkus memaksakan:
Yang paling Pythonic idiom adalah untuk secara jelas document apa fungsi mengharapkan dan kemudian hanya mencoba untuk menggunakan apa pun yang dilewatkan ke fungsi dan baik membiarkan pengecualian menyebarkan atau hanya menangkap atribut kesalahan dan meningkatkan TypeError
sebagai gantinya. Jenis pemeriksaan harus dihindari sebisa mungkin karena bertentangan duck-typing. Nilai pengujian dapat OK – tergantung pada konteks.
Satu-satunya tempat di mana validasi benar-benar masuk akal adalah di sistem atau subsistem entry point, seperti bentuk web, argumen baris perintah, dll. Di tempat lain, asalkan fungsi didokumentasikan dengan baik, it's pemanggil's tanggung jawab untuk lulus tepat argumen.
Edit: karena 2019 ada lebih banyak dukungan untuk menggunakan jenis penjelasan dan statis memeriksa di Python; check out mengetik modul dan mypy. 2013 jawaban berikut:
Jenis pemeriksaan ini umumnya tidak Pythonic. Di Python, itu lebih biasa digunakan duck typing. Contoh:
Dalam kode anda, menganggap bahwa argumen (dalam contoh a
) berjalan seperti sebuah int
, dan bersuara seperti sebuah int
. Misalnya:
def my_function(a):
return a + 7
Ini berarti bahwa tidak hanya anda fungsi bekerja dengan bilangan bulat, hal ini juga bekerja dengan mengapung dan setiap pengguna kelas didefinisikan dengan __add__
metode didefinisikan, sehingga lebih sedikit (kadang-kadang tidak ada) harus dilakukan jika anda, atau orang lain, ingin memperluas fungsi untuk bekerja dengan sesuatu yang lain. Namun, dalam beberapa kasus, anda mungkin perlu aplikasi int
, maka anda bisa melakukan sesuatu seperti ini:
def my_function(a):
b = int(a) + 7
c = (5, 6, 3, 123541)[b]
return c
dan fungsi masih bekerja untuk setiap a
yang mendefinisikan __int__
metode.
Dalam menjawab pertanyaan anda yang lain, saya pikir itu adalah yang terbaik (sebagai jawaban yang lain mengatakan untuk melakukan hal ini:
def my_function(a, b, c):
assert 0 < b < 10
assert c # A non-empty string has the Boolean value True
atau
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
Beberapa jenis pemeriksaan dekorator yang saya buat:
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))
Salah satu cara adalah dengan menggunakan menegaskan
:
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'
Anda dapat menggunakan Jenis Penegak menerima/kembali dekorator dari PythonDecoratorLibrary It's sangat mudah dan dapat dibaca:
@accepts(int, int, float)
def myfunc(i1, i2, i3):
pass
Ada berbagai cara untuk memeriksa apa itu variabel dalam Python. Jadi, untuk daftar beberapa:
isinstance(obj, jenis)
fungsi mengambil variabel anda, obj
dan memberikan Benar
itu adalah jenis yang sama dari jenis
anda terdaftar.
issubclass(obj, kelas)
fungsi yang diperlukan dalam suatu variabel obj
, dan memberi anda Benar
jika obj
adalah sebuah subclass dari kelas
. Jadi misalnya issubclass(Kelinci, Hewan)
akan memberikan anda sebuah Kenyataan
nilai
hasattr
adalah contoh lain, ditunjukkan oleh fungsi ini, 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
bersandar lebih ke arah bebek-mengetik, dan sesuatu yang biasanya lebih pythonic tapi istilah itu adalah dogmatis.
Sebagai catatan, menegaskan
pernyataan yang biasanya digunakan dalam pengujian, jika tidak, hanya menggunakan `if/else pernyataan.
Saya melakukan sedikit investigasi pada topik yang baru-baru ini karena saya tidak puas dengan banyak perpustakaan yang saya temukan di luar sana.
Saya akhirnya mengembangkan perpustakaan untuk mengatasi hal ini, bernama valid8. Seperti yang dijelaskan dalam dokumentasi, hal ini untuk nilai validasi sebagian besar (meskipun itu datang dibundel dengan sederhana tipe validasi fungsi juga), dan anda mungkin ingin mengasosiasikan dengan PEP484-berdasarkan jenis checker seperti menegakkan atau pytypes.
Ini adalah cara anda akan melakukan validasi dengan valid8
sendirian (dan mini_lambda
sebenarnya, untuk menentukan validasi logika - tapi itu tidak wajib) dalam kasus anda:
# 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'}
Dan ini adalah contoh yang sama memanfaatkan PEP484 jenis petunjuk dan mendelegasikan jenis pemeriksaan untuk menegakkan
:
# 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].
Biasanya, anda melakukan sesuatu seperti ini:
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
Ini memeriksa jenis input argumen pada saat pemanggilan fungsi:
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))
Juga periksa dengan kedua=9` (itu harus memberikan pernyataan kesalahan)
def someFunc(a, b, c):
params = locals()
for _item in params:
print type(params[_item]), _item, params[_item]
Demo:
>> someFunc(1, 'asd', 1.0)
>> <type 'int'> a 1
>> <type 'float'> c 1.0
>> <type 'str'> b asd
lebih lanjut tentang setempat()
Jika anda ingin melakukan validasi untuk beberapa fungsi yang dapat anda tambahkan logika dalam dekorator seperti ini:
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
dan menggunakannya:
@deco
def foo(a,b,c):
print 'ok!'
Harap ini membantu!
Jika anda ingin memeriksa **kwargs
, *args
serta normal argumen dalam satu pergi, anda dapat menggunakan setempat()
fungsi sebagai pernyataan pertama dalam definisi fungsi untuk mendapatkan kamus argumen.
Kemudian menggunakan jenis()
untuk memeriksa argumen, misalnya sementara iterasi 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...
Ini bukan solusi untuk anda, tetapi jika anda ingin membatasi fungsi panggilan untuk beberapa jenis parameter tertentu maka anda harus menggunakan PROATOR { Python prototipe Fungsi validator }. anda bisa lihat link berikut. https://github.com/mohit-thakur-721/proator