Ich suche nach einer effizienten Möglichkeit, Variablen einer Python-Funktion zu überprüfen. Zum Beispiel möchte ich den Typ und den Wert der Argumente überprüfen. Gibt es dafür ein Modul? Oder sollte ich so etwas wie Dekorateure oder eine bestimmte Redewendung verwenden?
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
locals()
wird wahrscheinlich zu einer nutzlosen Komplikation führen - tatsächlich sehe ich keinen Anwendungsfall dafür, da Sie Ihre benannten Parameternamen (offensichtlich <g>) bereits kennen und direkt darauf zugreifen könnenargs
undkwargs
wenn Ihre Funktion sie verwendet. Assertion dient hauptsächlich zum Debuggen. Wenn der Vertrag Ihrer Funktion lautet, dass arg 'a' ein int zwischen 0 und 10 und das Argument 'b' eine nicht leere Zeichenfolge sein muss, lösen Sie die entsprechenden Ausnahmetypen aus, dhTypeError
oderValueError
- tryint('a')
undint(None)
in Ihrer Python-Shell.@checkargs
Dekorateur unten. tl; dr Weniger Fundamentalismus; aktuellere Antworten.In dieser langgestreckten Antwort implementieren wir einen Python 3.x-spezifischen Dekorator für die Typprüfung, der auf PEP 484- Typ-Hinweisen in weniger als 275 Zeilen reinen Python basiert (die meisten davon sind erklärende Dokumentationen und Kommentare) - stark optimiert für industrielle Stärke in der
py.test
Praxis mit einer gesteuerten Testsuite, die alle möglichen Randfälle ausübt.Genießen Sie das unerwartete Fantastische Bärentippen :
>>> @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">
Wie dieses Beispiel zeigt, unterstützt die Bärentypisierung explizit die Typprüfung von Parametern und Rückgabewerten, die entweder als einfache Typen oder als Tupel solcher Typen bezeichnet werden. Golly!
OK, das ist eigentlich nicht beeindruckend.
@beartype
ähnelt jedem anderen Python 3.x-spezifischen Dekorator für die Typprüfung, der auf Typhinweisen im PEP 484- Stil in weniger als 275 Zeilen Pure-Python basiert . Also, was ist das Problem, Bub?Reine Bruteforce Hardcore-Effizienz
Das Eingeben von Bären ist räumlich und zeitlich erheblich effizienter als alle vorhandenen Implementierungen der Typprüfung in Python nach bestem Wissen und Gewissen. ( Dazu später mehr. )
Effizienz spielt in Python jedoch normalerweise keine Rolle. Wenn dies der Fall wäre, würden Sie Python nicht verwenden. Weicht die Typprüfung tatsächlich von der etablierten Norm ab, eine vorzeitige Optimierung in Python zu vermeiden? Ja. Ja tut es.
Betrachten Sie die Profilerstellung, die jeder interessierenden profilierten Metrik (z. B. Funktionsaufrufe, Zeilen) unvermeidbaren Overhead hinzufügt. Um genaue Ergebnisse zu gewährleisten, wird dieser Overhead durch die Nutzung optimierter C-Erweiterungen (z. B. der
_lsprof
vomcProfile
Modul genutzten C-Erweiterung ) anstelle von nicht optimiertem Pure-Python (z. B. demprofile
Modul) verringert . Effizienz ist bei der Profilerstellung wirklich wichtig.Die Typprüfung ist nicht anders. Die Typprüfung erhöht den Aufwand für jeden von Ihrer Anwendung geprüften Funktionsaufruftyp - im Idealfall für alle . Um zu verhindern, dass gut gemeinte (aber leider kleinmütige) Mitarbeiter die Typprüfung entfernen, die Sie nach dem koffeinhaltigen Allnighter am vergangenen Freitag stillschweigend zu Ihrer geriatrischen Django-Web-App hinzugefügt haben , muss die Typprüfung schnell erfolgen. So schnell, dass niemand merkt, dass es da ist, wenn Sie es hinzufügen, ohne es jemandem zu sagen. Ich mache das die ganze Zeit! Hören Sie auf, dies zu lesen, wenn Sie ein Mitarbeiter sind.
Wenn selbst lächerliche Geschwindigkeit für Ihre gefräßige Anwendung nicht ausreicht, kann die Bärentypisierung durch Deaktivieren von Python-Optimierungen (z. B. durch Übergeben der
-O
Option an den Python-Interpreter) global deaktiviert werden :$ 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')
Nur weil. Willkommen beim Bärentippen.
Was zum...? Warum "Bär"? Du bist ein Nackenbart, oder?
Bei der Bärentypisierung handelt es sich um eine Bare-Metal-Typprüfung, dh eine Typprüfung, die dem manuellen Ansatz der Typprüfung in Python so nahe wie möglich kommt. Die Bärentypisierung soll keine Leistungseinbußen, Kompatibilitätsbeschränkungen oder Abhängigkeiten von Drittanbietern nach sich ziehen (über die ohnehin durch den manuellen Ansatz auferlegten hinaus). Die Bärentypisierung kann ohne Änderung nahtlos in vorhandene Codebasen und Testsuiten integriert werden.
Jeder kennt wahrscheinlich den manuellen Ansatz. Sie übergeben manuell
assert
jeden Parameter, der an jede Funktion in Ihrer Codebasis zurückgegeben wird , und / oder geben einen Wert zurück . Welche Kesselplatte könnte einfacher oder banaler sein? Wir haben es alle hundertmal bei Googleplex gesehen und uns jedes Mal ein wenig im Mund übergeben. Wiederholung wird schnell alt. TROCKEN , yo.Bereiten Sie Ihre Erbrochenenbeutel vor. Nehmen wir der Kürze halber eine vereinfachte
easy_spirit_bear()
Funktion an, die nur einen einzigenstr
Parameter akzeptiert . So sieht der manuelle Ansatz aus: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, richtig? Viele von uns haben diese Klasse bestanden.
Die Bear-Typisierung extrahiert die vom obigen Ansatz manuell durchgeführte Typprüfung in eine dynamisch definierte Wrapper-Funktion, die automatisch dieselben Überprüfungen durchführt - mit dem zusätzlichen Vorteil, dass
TypeError
eher granulare als mehrdeutigeAssertionError
Ausnahmen ausgelöst werden . So sieht der automatisierte Ansatz aus: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
Es ist langatmig. Aber es ist im Grunde auch * so schnell wie der manuelle Ansatz. * Schielen empfohlen.
Beachten Sie das völlige Fehlen einer Funktionsprüfung oder -iteration in der Wrapper-Funktion, die eine ähnliche Anzahl von Tests wie die ursprüngliche Funktion enthält - allerdings mit den zusätzlichen (möglicherweise vernachlässigbaren) Kosten für das Testen, ob und wie die zu überprüfenden Parameter an die übergeben werden aktueller Funktionsaufruf. Sie können nicht jede Schlacht gewinnen.
Können solche Wrapper-Funktionen tatsächlich zuverlässig generiert werden, um beliebige Funktionen in weniger als 275 Zeilen reinem Python zu überprüfen? Schlange Plisskin sagt: "Wahre Geschichte. Haben Sie einen Rauch?"
Und ja. Ich kann einen Nackenbart haben.
Nein, Srsly. Warum "Bär"?
Bär schlägt Ente. Ente kann fliegen, aber Bär kann Lachs auf Ente werfen. In Kanada kann Sie die Natur überraschen.
Nächste Frage.
Was ist überhaupt so heiß an Bären?
Bestehende Lösungen führen keine Bare-Metal-Typprüfung durch - zumindest keine, auf die ich gestoßen bin. Sie alle überprüfen iterativ die Signatur der typgeprüften Funktion bei jedem Funktionsaufruf . Während der Aufwand für die erneute Überprüfung für einen einzelnen Anruf vernachlässigbar ist, ist er normalerweise nicht vernachlässigbar, wenn er über alle Anrufe hinweg aggregiert wird. Wirklich, wirklich nicht zu vernachlässigen.
Es geht jedoch nicht nur um Effizienz. Bestehende Lösungen berücksichtigen häufig auch keine häufigen Randfälle. Dies schließt die meisten, wenn nicht alle Spielzeugdekorateure ein, die hier und anderswo als Stackoverflow-Antworten bereitgestellt werden. Klassische Fehler sind:
@checkargs
Dekorator ).isinstance()
eingebauten System akzeptiert werden .AssertionError
Ausnahmen anstelle spezifischerTypeError
Ausnahmen bei fehlgeschlagenen Typprüfungen. Aus Gründen der Granularität und Vernunft sollte die Typprüfung niemals generische Ausnahmen auslösen.Das Eingeben von Bären ist dort erfolgreich, wo Nichtbären versagen. Alle eins, alle tragen!
Bear Typing Unbared
Durch die Typisierung von Bären werden die Raum- und Zeitkosten für die Überprüfung von Funktionssignaturen von der Funktionsaufrufzeit zur Funktionsdefinitionszeit verschoben, dh von der vom
@beartype
Dekorateur zurückgegebenen Wrapper-Funktion in den Dekorator selbst. Da der Dekorateur nur einmal pro Funktionsdefinition aufgerufen wird, bringt diese Optimierung Freude für alle.Bärentippen ist ein Versuch, Ihren Typ Kuchen überprüfen zu lassen und ihn auch zu essen. Um dies zu tun
@beartype
:exec()
Deklariert diese Wrapper-Funktion dynamisch über das eingebaute System.Sollen wir? Tauchen wir in das tiefe Ende ein.
# 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
Und Leycec sprach: Lass den
@beartype
Typ schnell prüfen! Und es war so.Vorsichtsmaßnahmen, Flüche und leere Versprechen
Nichts ist perfekt. Sogar Bären tippen.
Vorsichtsmaßnahme I: Standardwerte deaktiviert
Bei der Bärentypisierung werden keine nicht übergebenen Parameter überprüft, denen Standardwerte zugewiesen wurden. Theoretisch könnte es. Aber nicht in 275 Zeilen oder weniger und schon gar nicht als Stackoverflow-Antwort.
Die sichere (... wahrscheinlich völlig unsichere ) Annahme ist, dass Funktionsimplementierer behaupten, sie wüssten, was sie taten, als sie Standardwerte definierten. Da Standardwerte normalerweise Konstanten sind (... besser! ), Würde eine erneute Überprüfung der Konstantentypen, die sich bei jedem Funktionsaufruf, der einem oder mehreren Standardwerten zugewiesen wurde, nie ändern, gegen den Grundgedanken der Bärentypisierung verstoßen: "Nicht wiederholen dich vorbei und oooover und oooo-oooover wieder. "
Zeigen Sie mir falsch und ich werde Sie mit Upvotes überschütten.
Vorsichtsmaßnahme II: Kein PEP 484
PEP 484 ( "Type Hints" ) formalisierte die Verwendung von Funktionsanmerkungen, die zuerst von PEP 3107 ( "Function Annotations" ) eingeführt wurden. Python 3.5 unterstützt oberflächlich diese Formalisierung mit einem neuen Top-Level -
typing
Modul , ein Standard - API für beliebig komplexe Typen von einfacheren Typen Komponieren ( zum BeispielCallable[[Arg1Type, Arg2Type], ReturnType]
, eine Art , die eine Funktion mit zwei Argumenten des Typs beschreibt ,Arg1Type
undArg2Type
und einen Wert vom Typ der RückkehrReturnType
).Die Bärentippung unterstützt keine von ihnen. Theoretisch könnte es. Aber nicht in 275 Zeilen oder weniger und schon gar nicht als Stackoverflow-Antwort.
Die
isinstance()
Bärentypisierung unterstützt jedoch Gewerkschaften von Typen auf dieselbe Weise wie die integrierte Typvereinigung: als Tupel. Dies entspricht oberflächlich demtyping.Union
Typ - mit der offensichtlichen Einschränkung, dasstyping.Union
beliebig komplexe Typen unterstützt werden, während Tupel, die von der@beartype
Unterstützung akzeptiert werden , nur einfache Klassen unterstützen. Zu meiner Verteidigung 275 Zeilen.Tests oder es ist nicht passiert
Hier ist der Kern davon. Erhalten Sie es, Kern ? Ich werde jetzt aufhören.
Wie beim
@beartype
Dekorateur selbst können diesepy.test
Tests ohne Änderung nahtlos in vorhandene Testsuiten integriert werden. Wertvoll, nicht wahr?Jetzt hat niemand nach dem obligatorischen Nackenbart-Rant gefragt.
Eine Geschichte der API-Gewalt
Python 3.5 bietet keine tatsächliche Unterstützung für die Verwendung von PEP 484-Typen. wat?
Es ist wahr: keine Typprüfung, keine Typinferenz, keine Typnuss. Stattdessen wird von Entwicklern erwartet, dass sie ihre gesamte Codebasis routinemäßig über schwergewichtige CPython-Interpreter-Wrapper von Drittanbietern ausführen, die ein Faksimile dieser Unterstützung (z . B. mypy ) implementieren . Natürlich schreiben diese Wrapper vor:
Ich frage Guido: "Warum? Warum eine abstrakte API erfinden, wenn Sie nicht bereit waren, eine konkrete API aufzubauen, die tatsächlich etwas mit dieser Abstraktion macht?" Warum das Schicksal einer Million Pythonisten der arthritischen Hand des freien Open-Source-Marktplatzes überlassen? Warum noch ein Techno-Problem erstellen, das mit einem 275-Zeilen-Dekorator in der offiziellen Python-Stdlib trivial hätte gelöst werden können?
Ich habe kein Python und ich muss schreien.
quelle
Bearbeiten: Ab 2019 gibt es mehr Unterstützung für die Verwendung von Typanmerkungen und statischen Überprüfungen in Python. überprüfen Sie die aus der Eingabe - Modul und mypy . Die Antwort von 2013 lautet:
Die Typprüfung ist im Allgemeinen nicht Pythonic. In Python ist es üblicher, Enten zu tippen . Beispiel:
Nehmen Sie in Ihrem Code an, dass das Argument (in Ihrem Beispiel
a
) wie ein läuft und wie einint
quaktint
. Zum Beispiel:def my_function(a): return a + 7
Dies bedeutet, dass Ihre Funktion nicht nur mit Ganzzahlen funktioniert, sondern auch mit Floats und jeder benutzerdefinierten Klasse mit der
__add__
definierten Methode. Daher muss weniger (manchmal nichts) getan werden, wenn Sie oder eine andere Person Ihre Funktion erweitern möchten arbeite mit etwas anderem. In einigen Fällen benötigen Sie jedoch möglicherweise eineint
, sodass Sie Folgendes tun können:def my_function(a): b = int(a) + 7 c = (5, 6, 3, 123541)[b] return c
und die Funktion funktioniert immer noch für alle
a
, die das definieren__int__
Methode .Als Antwort auf Ihre anderen Fragen halte ich es für das Beste (wie andere Antworten gesagt haben, tun Sie dies entweder:
def my_function(a, b, c): assert 0 < b < 10 assert c # A non-empty string has the Boolean value True
oder
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
Einige von mir erstellte Dekoratoren zur Typprüfung:
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))
quelle
Eine Möglichkeit ist zu verwenden
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'
quelle
ValueError
undTypeError
.assert
im Produktionscode. Es kann ignoriert werden, je nachdem, in welcher Umgebung Ihr Code ausgeführt wird. Schauen Sie sich diese Antwort an: stackoverflow.com/a/1838411/345290Sie können Type Enforcement verwenden. Dekoratoren akzeptieren / zurückgeben von PythonDecoratorLibrary Es ist sehr einfach und lesbar:
@accepts(int, int, float) def myfunc(i1, i2, i3): pass
quelle
def myfunc(i1: int, i2: int, i3: float)
) sind ein zutiefst mehr Pythonic mittels Typen deklarieren. Siehe sweeneyrod ‚s@checkargs
Dekorateur für eine robuste Art Überprüfung Lösung mit Funktion Anmerkungen in weniger als 10 (!) Zeilen Code.PythonDecoratorLibrary
?Es gibt verschiedene Möglichkeiten, um zu überprüfen, was eine Variable in Python ist. Um einige zu nennen:
isinstance(obj, type)
Die Funktion nimmt Ihre Variableobj
und gibt Ihnen an, dassTrue
es sich um denselben Typ handelt, dentype
Sie aufgelistet haben.issubclass(obj, class)
Funktion, die eine Variable aufnimmtobj
und Ihnen gibt,True
obobj
es sich um eine Unterklasse von handeltclass
. Soissubclass(Rabbit, Animal)
würde Ihnen zum Beispiel einTrue
Wert gebenhasattr
ist ein weiteres Beispiel, das durch diese Funktion demonstriert wirdsuper_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
neigt eher zur Ententypisierung und zu etwas, das normalerweise pythonischer ist, aber dieser Begriff hat eine hohe Meinung.Nur als Hinweis:
assert
Anweisungen werden normalerweise zum Testen verwendet, andernfalls werden nurif/else
Anweisungen verwendet.quelle
Ich habe in letzter Zeit einige Nachforschungen zu diesem Thema angestellt, da ich mit den vielen Bibliotheken, die ich dort herausgefunden habe, nicht zufrieden war .
Am Ende habe ich eine Bibliothek entwickelt, um dies zu beheben . Sie heißt valid8 . Wie in der Dokumentation erläutert, dient es hauptsächlich der Wertüberprüfung (obwohl es auch mit einfachen Typüberprüfungsfunktionen geliefert wird), und Sie möchten es möglicherweise einem PEP484-basierten Typprüfer wie z erzwingen oder pytypes .
Auf diese Weise würden Sie die Validierung
valid8
allein (und) durchführenmini_lambda
in Ihrem Fall tatsächlich die Validierungslogik definieren - dies ist jedoch nicht obligatorisch):# 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'}
Dies ist das gleiche Beispiel, in dem PEP484-Typhinweise verwendet und die Typprüfung an Folgendes delegiert werden
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].
quelle
exec
den Wrapper läuft ein wenig zu machen schneller (auf Kosten des Nicht-Debuggens).valid8
zielt auf die Validierung des Typs UND des Werts ab und kann mit einem vorhandenen PEP484-Typprüfer kombiniert werden, um sich nur auf dieNormalerweise machst du so etwas:
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
quelle
raise TypeError("Expected an int, got '%s'" % type(a))
Dies überprüft die Art der Eingabeargumente beim Aufrufen der Funktion:
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))
Überprüfen Sie auch mit
second=9
(es muss Assertionsfehler geben)quelle
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
mehr über Einheimische ()
quelle
Wenn Sie überprüfen möchten
**kwargs
,*args
sowie normale Argumente auf einmal, können Sie die verwendenlocals()
Funktion als erste Anweisung in der Funktionsdefinition ein Wörterbuch der Argumente zu bekommen.Verwenden Sie dann
type()
, um die Argumente zu untersuchen, z. B. während Sie über das Diktat iterieren.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...
quelle
Wenn Sie die Validierung für mehrere Funktionen durchführen möchten, können Sie die Logik in einem Dekorator wie folgt hinzufügen:
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
und benutze es:
@deco def foo(a,b,c): print 'ok!'
Hoffe das hilft!
quelle
isinstance
TypeError und erhöhen Sie es.return func(a, b, c)
?Dies ist nicht die Lösung für Sie, aber wenn Sie die Funktionsaufrufe auf bestimmte Parametertypen beschränken möchten, müssen Sie den PROATOR {The Python Function Prototype Validator} verwenden. Sie können auf den folgenden Link verweisen. https://github.com/mohit-thakur-721/proator
quelle
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
quelle