Een echte retourwaarde uitbreiden
Vul with_prefix(fn, prefix) in: geef een wrapper die prefix vóór het stringresultaat van de echte fn zet. Geef alle argumenten en fouten door en gebruik wraps voor naam, documentatie en __wrapped__.
Je wilt vastleggen hoe vaak een controlefunctie wordt aangeroepen, maar de functie zelf hoort alleen die controle te doen. Je kunt het extra gedrag daarom rond de bestaande functie zetten. Zo blijven de bedrijfsregel en het meten afzonderlijk leesbaar.
Een wrapper is een nieuwe functie die de oorspronkelijke functie aanroept. Een decorator ontvangt een callable en geeft gewoonlijk zo'n aangepaste callable terug. De notatie @decorate boven een definitie betekent dat de gedefinieerde functie door decorate(function) wordt vervangen.
Je hebt eerder functies als argument gebruikt. Een decorator voegt daar een geneste functie aan toe die de ontvangen callable onthoudt. Die combinatie van een functie en de bewaarde buitenste namen heet een closure.
*args verzamelt extra positionele argumenten in een tuple. **kwargs verzamelt keywordargumenten in een dictionary. Bij het aanroepen pakt fn(*args, **kwargs) ze weer uit, zodat de wrapper het oorspronkelijke aanroeppatroon behoudt.
from functools import wraps
def cheerful(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
return fn(*args, **kwargs) + " :)"
return wrapper
@cheerful
def greet(name, *, ending="!"):
return name + ending
print(greet("Bo", ending="?"))
print(greet.__name__)Bo? :) greet
De buitenste functie in het voorbeeld krijgt de oorspronkelijke groet en geeft een andere functie terug. Pas de laatste aanroep voert de groet uit. De wrapper voegt iets toe aan de retourwaarde; hij vervangt de groet niet door een vaste tekst.
De nieuwe functie zou van zichzelf de naam en documentatie van de wrapper hebben. wraps uit functools kopieert geselecteerde metadata van de oorspronkelijke callable. De eigenschap __wrapped__ verwijst bovendien naar die callable.
Dat helpt bij documentatie, traceerbaarheid en introspectie. Het betekent niet dat beide functies hetzelfde object zijn: de wrapper blijft een nieuwe callable. Ook correcte metadata bewijst nog niet dat de wrapper waarden en fouten goed doorgeeft.
Een functieobject kan eigen attributen hebben. Een tellerwrapper kan daarom beginnen met wrapper.calls = 0 en bij iedere aanroep ophogen. Doe dat vóór de oorspronkelijke functie wordt uitgevoerd als ook een mislukte aanroep moet meetellen.
Een validatiewrapper heeft een andere afspraak: ongeldige invoer mag de oorspronkelijke functie juist niet bereiken. De volgorde van controleren en aanroepen bepaalt daar de betekenis. De oefening beperkt die invoer tot gewone getallen, zodat typecontrole niet van dit onderwerp afleidt.
Een algemene except die een bericht print en None teruggeeft, verandert het functiecontract. De aanroeper ziet dan geen fout meer en kan doorgaan met een ontbrekende uitkomst. Log daarom wat nodig is en geef de bestaande fout met kale raise door.
BaseException is de bovenste categorie voor fouten en afbreeksignalen; zowel Exception als KeyboardInterrupt vallen daaronder. KeyboardInterrupt valt niet onder Exception. Deze logger vangt BaseException alleen om te loggen en werpt hetzelfde object direct weer op; hij onderdrukt geen afbreekverzoek.
Het voorbeeld gebruikt een eigen StopSignal-subklasse zonder een echt proces te onderbreken. Alleen except Exception zou dit signaal missen. De buitenste proef vangt het signaal zelf weer op.
events = []
class StopSignal(BaseException):
pass
problem = StopSignal("stop")
def work():
raise problem
def run_logged():
events.append("start")
try:
return work()
except BaseException:
events.append("error")
raise
try:
run_logged()
except BaseException as caught:
print(caught is problem)
print(events)True ['start', 'error']
Dit voorbeeld bewaart een verwijzing naar de oorspronkelijke fout en vergelijkt echte objectidentiteit. Een nieuw aangemaakte fout met dezelfde tekst zou die controle niet doorstaan. De gebeurtenissen vertellen ook of de fout- of succesroute werd genomen.
Alle oefenwrappers gebruiken wraps, geven argumenten door en behouden het resultaat. De voorvoegselwrapper verwacht een stringresultaat; de overige wrappers mogen ieder object teruggeven. Een ontvangen foutenobject mag niet worden vervangen of stil worden ingeslikt.
De logwrapper voegt alleen "start" en daarna "ok" of "error" toe aan de ontvangen lijst. Hij maakt die lijst niet leeg. Eerdere gebeurtenissen horen mogelijk bij een andere stap van hetzelfde programma.
Vul with_prefix(fn, prefix) in: geef een wrapper die prefix vóór het stringresultaat van de echte fn zet. Geef alle argumenten en fouten door en gebruik wraps voor naam, documentatie en __wrapped__.
Vul count_calls(fn) in: geef een wrapper met calls=0 die vóór iedere aanroep ophoogt, ook bij een fout. Behoud argumenten, resultaat, oorspronkelijke fout en metadata.
Vul require_positive(fn) in voor een eerste numerieke parameter value: bij value<=0 geef je ValueError("waarde moet positief zijn") vóór fn wordt aangeroepen. Geef geldige argumenten/resultaat door en behoud metadata.
Vul log_outcome(fn, events) in: append start vóór iedere aanroep, daarna ok bij succes of error bij iedere BaseException. Behoud eerdere events, argumenten, resultaatidentiteit, oorspronkelijke foutidentiteit en metadata.
wraps bewaart metadata; je bewaakt zelf argumenten, resultaat en fouten.Hierna maak je herbruikbare functievarianten met andere hulpmiddelen uit functools.
Voer je code uit en ontdek
wat je hebt gemaakt.
Je hoeft het niet in één keer goed te doen. Na het uitvoeren zie je hier welke stappen al kloppen.