Funksjonsverktøy - Kraften til funksjoner av høyere orden i Python

Kilde node: 1865357

Denne artikkelen ble publisert som en del av Data Science Blogathon

Introduksjon

Python Standard Library har mange flotte moduler som hjelper deg med å holde koden din renere og enklere, og funksjonsverktøy er definitivt en av de

caching

La oss starte med noen av de enkleste, men kraftige funksjonene til modulfunksjonsverktøyene. La oss starte med bufringsfunksjonene (så vel som dekoratører) – lru_cache,cache og cached_property. Den første av dem - lru_cache gir en hurtigbuffer av de siste resultatene av utførelsen av funksjoner, eller med andre ord, husker resultatet av arbeidet deres:

from functools import lru_cache
import requests @lru_cache(maxsize=32)
def get_with_cache(url): try: r = requests.get(url) return r.text except: return "Not Found" for url in ["https://google.com/", "https://reddit.com/", "https://google.com/", "https://google.com/"]: get_with_cache(url) print(get_with_cache.cache_info())
# CacheInfo(hits=2, misses=4, maxsize=32, currsize=4)
print(get_with_cache.cache_parameters())
# {'maxsize': 32, 'typed': False}

I dette eksemplet gjør vi GET-forespørsler og cacher resultatene deres (opptil 32 resultater) ved hjelp av en dekorator @lru_cache. For å se om caching faktisk fungerer, kan du sjekke funksjonens cacheinformasjon ved hjelp av en metode cache_infosom viser antall cache-treff og -treff. En dekoratør gir også metoder clear_cacheog cache_parametersfor kansellering av henholdsvis de hurtigbufrede resultatene og testparametrene.

Hvis du trenger mer detaljert hurtigbufring, kan du inkludere et valgfritt argument typed=true, som lar deg bufre forskjellige typer argumenter separat.

En annen dekorator for caching i functools er en funksjon som kalles ganske enkelt cache. Det er en enkel innpakning lru_cachesom utelater argumentet max_size, reduserer det, og fjerner ikke de gamle verdiene.

En annen dekorator du kan bruke for caching er cached_property. Som navnet antyder, brukes den til å cache resultatene av klasseattributter. Denne mekanikeren er veldig nyttig hvis du har en egenskap som er dyr å beregne, men som forblir den samme.

from functools import cached_property class Page: @cached_property def render(self, value): # Do something with supplied value... # Long computation that renders HTML page... return html

Dette enkle eksemplet viser. Som kan brukes, bufret eiendom, for eksempel, for å bufre en gjengitt HTML-side som må vises til brukeren om og om igjen. Det samme kan gjøres for visse databasespørringer eller lange matematiske beregninger.

En annen skjønnhet cached_propertyer at den bare kjører ved oppslag, så den lar oss endre verdien på attributtet. Etter endring av attributtet vil ikke den tidligere bufrede verdien endres, i stedet vil den nye verdien beregnes og bufres. Du kan også tømme hurtigbufferen, og alt du trenger å gjøre er å fjerne attributtet.

Jeg vil avslutte denne delen med et forbehold om alle de ovennevnte dekoratørene – ikke bruk dem hvis funksjonen din har noen bivirkninger eller hvis den lager foranderlige objekter hver gang den kalles, siden dette tydeligvis ikke er funksjonene du vil hurtigbufre. .

Sammenligning og bestilling

Du vet sikkert allerede at du kan implementere sammenligningsoperatorer i Python som f.eks <, >=or ==med lt, gtor eq. Imidlertid kan det være ganske frustrerende å innse hver av disse eq, lt, le, gtor ge. Heldigvis funksjonsverktøy det er en dekoratør @total_orderingsom kan hjelpe oss med dette, fordi alt vi trenger å implementere er eqen av de resterende metodene, og resten av dekoratøren vil bli generert automatisk.

from functools import total_ordering @total_ordering
class Number: def __init__(self, value): self.value = value def __lt__(self, other): return self.value Number(3))
# True
print(Number(1) = Number(15))
# True
print(Number(10) <= Number(2))
# False

På denne måten kan vi implementere alle de utvidede sammenligningsoperasjonene, til tross for at vi bare har eq og for hand lt. Den mest åpenbare fordelen er bekvemmeligheten, som er at du ikke trenger å skrive alle disse ekstra magiske metodene, men det er sannsynligvis viktigere å redusere mengden kode og dens bedre lesbarhet.

Overlast

Vi har nok alle blitt lært at det ikke er noen overbelastning i Python, men det er faktisk en enkel måte å implementere det på ved å bruke to funksjoner fra functools, nemlig single dispatch og/eller single dispatch metoden. Disse funksjonene hjelper oss med å implementere det vi vil kalle en multiple dispatch-algoritme som lar dynamisk skrivede programmeringsspråk som Python skille mellom typer under kjøring.

Delvis

Vi jobber alle med ulike eksterne biblioteker eller rammeverk, hvorav mange gir funksjoner og grensesnitt som krever at vi sender tilbakeringinger, for eksempel for asynkrone operasjoner eller lytting etter hendelser. Dette er ikke noe nytt, men hva om vi også trenger å sende noen argumenter sammen med tilbakeringingen. Det er her det kommer i nyttige funksjonsverktøy.partial. Kan bli brukt partialå fryse noen (eller alle) argumentene til en funksjon ved å lage et nytt objekt med en forenklet funksjonssignatur. Forvirret? La oss ta en titt på noen praktiske eksempler:

def output_result(result, log=None): if log is not None: log.debug(f"Result is: {result}") def concat(a, b): return a + b import logging
from multiprocessing import Pool
from functools import partial logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger("default") p = Pool()
p.apply_async(concat, ("Hello ", "World"), callback=partial(output_result, log=logger))
p.close()
p.join()

Koden ovenfor viser hvordan du kan bruke den partialå sende en funksjon ( output_result) sammen med et argument ( log=logger) som en tilbakeringing. I dette tilfellet vil vi bruke multiprocessing.apply_async, som asynkront beregner resultatet av funksjonen ( concat) og returnerer resultatet av tilbakeringingen. Derimot, apply_asyncdet vil alltid sende resultatet som det første argumentet, og hvis vi ønsker å inkludere ytterligere argumenter, slik tilfellet er med log=logger, må vi bruke partial.

Vi har vurdert et ganske avansert brukstilfelle, og et enklere eksempel vil være den vanlige opprettelsen av en funksjon som skriver i stderr i stedet for stdout:

import sys
from functools import partial print_stderr = partial(print, file=sys.stderr)
print_stderr("This goes to standard error output")

Med dette enkle trikset har vi laget en ny anropbar funksjon som alltid vil passere file=sys.stderrsom et navngitt argument til utgang, som lar oss forenkle koden vår og ikke trenger å spesifisere verdien av det navngitte argumentet hver gang.

Og et siste godt eksempel. Vi kan bruke partiali forbindelse med en lite kjent funksjon iterå lage en iterator ved å sende inn et anropbart objekt og sentinelin iter, som kan brukes slik:

from functools import partial RECORD_SIZE = 64 # Read binary file...
with open("file.data", "rb") as file: records = iter(partial(file.read, RECORD_SIZE), b'') for r in records: # Do something with the record...

Vanligvis, når vi leser en fil, ønsker vi å iterere over linjer, men når det gjelder binære data, kan det hende vi må iterere over poster med en fast størrelse. Du kan gjøre dette ved å lage et anropbart objekt ved å bruke partialsom leser den angitte databiten og sender den inn iterfor å lage en iterator. Denne iteratoren kaller deretter lesefunksjonen til den når slutten av filen, og tar alltid bare den angitte stykkestørrelsen ( RECORD_SIZE). Til slutt, når slutten av filen er nådd, verdien sentinel(b ”) returneres og iterasjonen stopper.

dekoratører

Vi har allerede snakket om noen dekoratører i de forrige avsnittene, men ikke dekoratører for å lage flere dekoratører. En slik dekoratør er functools.wraps. For å forstå hvorfor du trenger det, la oss bare se på et eksempel:

def decorator(func): def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") greet("Martin")
# Before Calling greet
# Hello, Martin!
# After Calling greet

Dette eksemplet viser hvordan en enkel dekoratør kan implementeres. Vi pakker inn en funksjon som utfører en spesifikk oppgave ( actual_func) med en ekstern dekoratør, og den blir selv en dekoratør, som deretter kan brukes på andre funksjoner, for eksempel, slik tilfellet er med greet. Når du ringer funksjonen, greet du vil se at den skriver ut meldinger både fra actual_funcog på egen hånd. Ser greit ut, ikke sant? Men hva skjer hvis vi gjør dette:

print(greet.__name__)
# actual_func
print(greet.__doc__)
# Inner function within decorator, which does the actual work

Når vi kaller navn og dokumentasjon på den dekorerte funksjonen, innser vi at de er erstattet med verdier fra dekoratørfunksjonen. Dette er dårlig siden vi ikke kan omskrive alle funksjonsnavn og dokumentasjon når vi bruker en eller annen dekoratør. Hvordan kan dette problemet løses? Selvfølgelig med funksjonsverktøy.wraps:

from functools import wraps def decorator(func): @wraps(func) def actual_func(*args, **kwargs): """Inner function within decorator, which does the actual work""" print(f"Before Calling {func.__name__}") func(*args, **kwargs) print(f"After Calling {func.__name__}") return actual_func @decorator
def greet(name): """Says hello to somebody""" print(f"Hello, {name}!") print(greet.__name__)
# greet
print(greet.__doc__)
# Says hello to somebody

Funksjonens eneste formål wrapser å kopiere navn, dokumentasjon, argumentliste osv. for å forhindre overskriving. Vurderer wrapsdet er også en dekoratør, du kan ganske enkelt legge det til vår actual_func, og problemet er løst!

Reduser

Sist men ikke minst i modulens funksjonsverktøy er denne reduksjonen. Kanskje fra andre språk, kanskje du kjenner det som fold(Haskell). Denne funksjonen tar en iterabel og bretter (legger til) alle verdiene til én. Det er mange bruksområder for dette, for eksempel:

from functools import reduce
import operator def product(iterable): return reduce(operator.mul, iterable, 1) def factorial(n): return reduce(operator.mul, range(1, n)) def sum(numbers): # Use `sum` function from standard library instead return reduce(operator.add, numbers, 1) def reverse(iterable): return reduce(lambda x, y: y+x, iterable) print(product([1, 2, 3]))
# 6
print(factorial(5))
# 24
print(sum([2, 6, 8, 3]))
# 20
print(reverse("hello"))
# olleh

Som du kan se av koden, reduce kan forenkle eller kondensere koden til én linje, som ellers ville vært mye lengre. Når det er sagt, er det vanligvis en dårlig idé å misbruke denne funksjonen bare for å krympe kode, noe som gjør den "smartere", siden den raskt blir skummel og uleselig. Av denne grunn bør den etter min mening brukes sparsomt.

Og hvis du husker det reduce ofte forkorter alt til en linje, det kan perfekt kombineres med partial:

product = partial(reduce, operator.mul) print(product([1, 2, 3]))
# 6

Og til slutt, hvis du trenger mer enn bare det endelige "kollapserte" resultatet, kan du bruke accumulate– fra en annen flott modul itertools. For å beregne maksimum, kan det brukes som følger:

from itertools import accumulate data = [3, 4, 1, 3, 5, 6, 9, 0, 1] print(list(accumulate(data, max)))
# [3, 4, 4, 4, 5, 6, 9, 9, 9]

konklusjonen

Som du kan se functools, er det mange nyttige funksjoner og dekoratorer som kan gjøre livet ditt enklere, men dette er bare toppen av isfjellet. Som jeg sa innledningsvis er det mange funksjoner i Python standardbiblioteket som hjelper deg med å skrive bedre kode, så i tillegg til funksjonene som vi dekket her, kan du ta hensyn til andre moduler, som f.eks. operatoror itertool. For eventuelle spørsmål kan du trykke på kommentarfeltet mitt. Jeg vil prøve mitt beste for å løse innspillene dine og håper å gi deg ønskede resultater. Du kan også nå meg på LinkedIn:-

https://www.linkedin.com/in/shivani-sharma-aba6141b6/

Media vist i denne artikkelen eies ikke av Analytics Vidhya og brukes etter forfatterens skjønn.

Kilde: https://www.analyticsvidhya.com/blog/2021/08/functools-the-power-of-higher-order-functions-in-python/

Tidstempel:

Mer fra Analytics Vidhya