Functieannotaties zijn een Python 3-functie waarmee u willekeurige metagegevens kunt toevoegen aan functieargumenten en geretourneerde waarde. Ze maakten deel uit van de originele Python 3.0-specificatie.
In deze zelfstudie laat ik u zien hoe u kunt profiteren van annotaties voor algemene doeleinden en deze kunt combineren met decorateurs. Je leert ook over de voors en tegens van functieannotaties, wanneer het gepast is om ze te gebruiken, en wanneer het het beste is om andere mechanismen zoals docstrings en eenvoudige decorateurs te gebruiken.
Functieannotaties worden gespecificeerd in PEP-3107. De belangrijkste motivatie was om een standaardmanier te bieden om metadata te koppelen aan functieargumenten en retourwaarde. Veel communityleden hebben nieuwe use-cases gevonden, maar verschillende methoden gebruikt, zoals custom decorators, aangepaste docstring-indelingen en aangepaste kenmerken aan het functieobject toevoegen.
Het is belangrijk om te begrijpen dat Python de annotaties niet zegen met een semantiek. Het biedt puur een mooie syntactische ondersteuning voor het associëren van metadata en een gemakkelijke manier om er toegang toe te krijgen. Annotaties zijn ook volledig optioneel.
Laten we een voorbeeld bekijken. Hier is een functie foo () dat neemt drie argumenten genaamd a, b en c en drukt hun som af. Merk op dat foo () niets terug geeft. Het eerste argument een is niet geannoteerd. Het tweede argument b is geannoteerd met de tekenreeks 'annotating b' en het derde argument c is geannoteerd met type int. De geretourneerde waarde is geannoteerd met het type vlotter. Let op de "->" syntaxis voor het annoteren van de geretourneerde waarde.
python def foo (a, b: 'annotating b', c: int) -> float: print (a + b + c)
De annotaties hebben geen enkele invloed op de uitvoering van de functie. Laten we bellen foo () tweemaal: één keer met int-argumenten en één keer met string-argumenten. In beide gevallen, foo () doet het juiste ding, en de annotaties worden eenvoudigweg genegeerd.
"python foo ('Hallo', ',', 'Wereld!') Hallo, Wereld!
foo (1, 2, 3) 6 "
Standaardargumenten worden opgegeven na de annotatie:
"python def foo (x: 'een argument dat standaard 5' = 5 is): print (x)
foo (7) 7
foo () 5 "
Het functieobject heeft een attribuut genaamd 'annotaties'. Het is een afbeelding die elke argumentnaam toewijst aan de annotatie. De annotatie van de retourwaarde wordt toegewezen aan de sleutel 'return', die niet in conflict kan komen met een willekeurige argumentnaam omdat 'return' een gereserveerd woord is dat niet als argumentnaam kan dienen. Merk op dat het mogelijk is om een trefwoordargument met de naam terugkeer naar een functie door te geven:
"python def bar (* args, ** kwargs: 'the keyword arguments dict'): print (kwargs ['return'])
d = 'return': 4 balk (** d) 4 "
Laten we teruggaan naar ons eerste voorbeeld en de annotaties controleren:
"python def foo (a, b: 'annotating b', c: int) -> float: print (a + b + c)
afdruk (foo.annotaties) 'c':
Dit is vrij eenvoudig. Als u een functie annoteert met een matrix voor argumenten en / of matrix met zoekwoordargumenten, kunt u natuurlijk geen afzonderlijke argumenten annoteren.
"python def foo (* args: 'lijst met niet nader genoemde argumenten', ** kwargs: 'dict of named arguments'): print (args, kwargs)
afdruk (foo.annotaties) 'args': 'lijst met niet nader genoemde argumenten', 'kwargs': 'dict of named arguments' "
Als u de sectie leest over het openen van functieannotaties in PEP-3107, staat er dat u ze opent via het kenmerk 'func_annotations' van het functieobject. Dit is verouderd vanaf Python 3.2. Wees niet in de war. Het is gewoon de 'annotaties'kenmerk.
Dit is de grote vraag. Annotaties hebben geen standaard betekenis of semantiek. Er zijn verschillende categorieën generieke toepassingen. U kunt ze gebruiken als betere documentatie en argumentatie verplaatsen en documentatie over de geretourneerde waarde uit de docstring ophalen. Bijvoorbeeld deze functie:
python def div (a, b): "" "Deel a door b args: a - het dividend b - de deler (moet anders zijn dan 0) return: het resultaat van a doorb delen" "" return a / b
Kan worden geconverteerd naar:
python def div (a: 'het dividend', b: 'de deler (moet anders zijn dan 0)') -> 'het resultaat van a door b delen': "" "Splits a by b" "" return a / b
Hoewel dezelfde informatie wordt vastgelegd, zijn er verschillende voordelen voor de annotatieversie:
Een ander gebruik waarover we later zullen praten, is optioneel typen. Python wordt dynamisch getypt, wat betekent dat u elk object kunt doorgeven als argument voor een functie. Maar vaak zullen functies vereisen dat argumenten van een specifiek type zijn. Met annotaties kun je het type direct naast het argument op een heel natuurlijke manier specificeren.
Houd er rekening mee dat als u alleen het type opgeeft dit niet wordt afgedwongen en er extra werk (veel werk) nodig is. Toch kan zelfs het specificeren van het type de intentie leesbaarder maken dan het type in de docstring te specificeren, en het kan gebruikers helpen te begrijpen hoe ze de functie moeten aanroepen.
Nog een ander voordeel van aantekeningen over docstring is dat je verschillende soorten metadata als tuples of dicts kunt koppelen. Nogmaals, je kunt dat ook doen met docstring, maar het zal op tekst gebaseerd zijn en vereist speciale parseerbewerkingen.
Ten slotte kunt u veel metagegevens toevoegen die worden gebruikt door speciale externe hulpmiddelen of tijdens runtime via decorateurs. Ik zal deze optie in het volgende gedeelte verkennen.
Stel dat u een argument wilt annoteren met zowel het type als een hulptekenreeks. Dit is heel eenvoudig met annotaties. Je kunt het argument eenvoudig annoteren met een dictaat dat twee sleutels heeft: 'type' en 'help'.
"python def div (a: dict (type = float, help =" the dividend "), b: dict (type = float, help =" de deler (moet anders zijn dan 0) ")) -> dict (type = float, help = "het resultaat van a by b"): "" "Divide a by b" "" return a / b
afdruk (div.annotaties) 'a': 'help': 'het dividend', 'type': float, 'b': 'help': 'de deler (moet anders zijn dan 0)', 'type': float , 'return': 'help': 'het resultaat van a by b', 'type': float "
Annotaties en decorateurs gaan hand in hand. Voor een goede inleiding tot de decorateurs van Python, bekijk mijn twee tutorials: Deep Dive Into Python Decorators en Write Your Own Python Decorators.
Ten eerste kunnen annotaties volledig worden geïmplementeerd als decorateurs. U kunt gewoon een definiëren @annoteren decorateur en laat het een argumentnaam en een Python-uitdrukking als argumenten gebruiken en sla ze vervolgens op in de doelfunctie's annotaties attribuut. Dit kan ook voor Python 2 worden gedaan.
De echte kracht van decorateurs is echter dat ze kunnen reageren op de annotaties. Dit vereist natuurlijk coördinatie over de semantiek van annotaties.
Laten we een voorbeeld bekijken. Stel dat we willen verifiëren dat argumenten binnen een bepaald bereik vallen. De annotatie is een tupel met de minimum- en maximumwaarde voor elk argument. Dan hebben we een binnenhuisarchitect nodig die de annotatie van elk trefwoordargument controleert, verifieert dat de waarde binnen het bereik ligt en anders een uitzondering verhoogt. Laten we beginnen met de binnenhuisarchitect:
python def check_range (f): def decorated (* args, ** kwargs): voor naam, bereik in f .__ annotaties __. items (): min_value, max_value = bereik indien niet (min_value <= kwargs[name] <= max_value): msg = 'argument is out of range [ - ]' raise ValueError(msg.format(name, min_value, max_value)) return f(*args, **kwargs) return decorated
Laten we nu onze functie definiëren en deze versieren met de @check_range decorateurs.
python @check_range def foo (a: (0, 8), b: (5, 9), c: (10, 20)): return a * b - c
Laten we bellen foo () met verschillende argumenten en zie wat er gebeurt. Als alle argumenten binnen hun bereik vallen, is er geen probleem.
python foo (a = 4, b = 6, c = 15) 9
Maar als we c op 100 zetten (buiten het bereik (10, 20)), dan wordt een uitzondering gemaakt:
python foo (a = 4, b = 6, c = 100) ValueError: argument c is buiten bereik [10 - 20]
Er zijn verschillende situaties waarin decorateurs beter zijn dan annotaties voor het bijvoegen van metadata.
Een voor de hand liggend geval is of uw code compatibel moet zijn met Python 2.
Een ander voorbeeld is als je veel metadata hebt. Zoals je eerder hebt gezien, is het mogelijk om metadata aan te vullen met dictaten als annotaties, maar het is behoorlijk omslachtig en doet de leesbaarheid pijn..
Tenslotte, als de metadata door een specifieke decorateur zou moeten worden geëxploiteerd, kan het beter zijn om de metadata als argumenten voor de decorateur zelf te gebruiken.
Annotaties zijn slechts een dictaatkenmerk van een functie.
python type (foo .__ annotaties__) dict
Dit betekent dat u ze on the fly kunt wijzigen terwijl het programma wordt uitgevoerd. Wat zijn enkele use-cases? Stel dat je wilt weten of een standaardwaarde van een argument ooit wordt gebruikt. Wanneer de functie wordt aangeroepen met de standaardwaarde, kunt u de waarde van een annotatie verhogen. Of misschien wilt u alle retourwaarden samenvatten. Het dynamische aspect kan worden gedaan binnen de functie zelf of door een binnenhuisarchitect.
"python def add (a, b) -> 0: result = a + b add.annotaties['return'] + = resultaat retourresultaat
afdrukken (voeg.annotaties['return']) 0
voeg (3, 4) 7 print toe (voeg toe.annotaties['terugkeer']) 7
voeg (5, 5) 10 afdrukken toe (voeg toe.annotaties['return']) 17 "
Functieannotaties zijn veelzijdig en spannend. Ze hebben het potentieel om een nieuw tijdperk van introspectieve tools in te luiden die ontwikkelaars helpen om steeds complexere systemen te beheersen. Ze bieden de meer geavanceerde ontwikkelaar ook een standaard en leesbare manier om metadata rechtstreeks aan argumenten te koppelen en waarde terug te geven om aangepaste hulpmiddelen te maken en interactie te hebben met binnenhuisarchitecturen. Maar het kost wat moeite om hiervan te profiteren en hun potentieel te benutten.
Leer Python met onze complete python-handleiding, of je nu net begint of dat je een ervaren coder bent die op zoek is naar nieuwe vaardigheden.