Functools -kraften i högre ordningsfunktioner i Python

Källnod: 1865357

Denna artikel publicerades som en del av Data Science Blogathon

Beskrivning

Python Standard Library har många bra moduler som hjälper dig att hålla din kod renare och enklare och funktionsverktyg är definitivt en av de

caching

Låt oss börja med några av de enklaste men kraftfulla funktionerna i modulfunktionsverktygen. Låt oss börja med cachningsfunktionerna (liksom dekoratörer) – lru_cache,cache och cached_property. Den första av dem - lru_cache ger en cache för de senaste resultaten av utförandet av funktioner, eller med andra ord, kommer ihåg resultatet av deras arbete:

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 det här exemplet gör vi GET-förfrågningar och cachelagrar deras resultat (upp till 32 resultat) med hjälp av en dekoratör @lru_cache. För att se om cachelagring faktiskt fungerar kan du kontrollera funktionen cacheinformation med en metod cache_infosom visar antalet cacheträffar och träffar. En dekoratör ger också metoder clear_cacheoch cache_parametersför att avbryta de cachade resultaten respektive testparametrarna.

Om du behöver mer granulär cachelagring kan du inkludera ett valfritt argument typed=true, vilket gör att du kan cache olika typer av argument separat.

En annan dekorator för att cache i functools är en funktion som kallas helt enkelt cache. Det är ett enkelt omslag lru_cachesom utelämnar argumentet max_size, minskar det och tar inte bort de gamla värdena.

En annan dekoratör du kan använda för cachning är cached_property. Som namnet antyder används den för att cachelagra resultaten av klassattribut. Den här mekanikern är mycket användbar om du har en egenskap som är dyr att beräkna, men som förblir densamma.

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

Detta enkla exempel visar. Som kan användas, cachad egenskap, till exempel, för att cachelagra en renderad HTML-sida som måste visas för användaren om och om igen. Detsamma kan göras för vissa databasfrågor eller långa matematiska beräkningar.

En annan skönhet cached_propertyär att det bara körs vid uppslag, så det tillåter oss att ändra värdet på attributet. Efter att ha ändrat attributet kommer det tidigare cachade värdet inte att ändras, istället kommer det nya värdet att beräknas och cachelagras. Du kan också rensa cachen, och allt du behöver göra är att ta bort attributet.

Jag vill avsluta det här avsnittet med en varning om alla ovanstående dekoratörer – använd dem inte om din funktion har några bieffekter eller om den skapar föränderliga objekt varje gång den anropas eftersom det uppenbarligen inte är de funktioner du vill cache. .

Jämförelse och beställning

Du vet säkert redan att du kan implementera jämförelseoperatorer i Python som t.ex <, >=or ==, med lt, gtor eq. Det kan dock vara ganska frustrerande att inse var och en av dessa eq, lt, le, gtor ge. Lyckligtvis functools det finns en dekoratör @total_orderingsom kan hjälpa oss med detta, för allt vi behöver implementera är eqen av de återstående metoderna, och resten av dekoratören kommer att genereras automatiskt.

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å så sätt kan vi implementera alla utökade jämförelseoperationer, trots att vi bara har eq och för hand lt. Den mest uppenbara fördelen är bekvämligheten, som är att du inte behöver skriva alla dessa ytterligare magiska metoder, men det är förmodligen viktigare att minska mängden kod och dess bättre läsbarhet.

Överbelastning

Vi har nog alla fått lära oss att det inte finns någon överbelastning i Python, men det finns faktiskt ett enkelt sätt att implementera det med hjälp av två funktioner från functools, nämligen single dispatch och/eller single dispatch-metoden. Dessa funktioner hjälper oss att implementera vad vi skulle kalla en multipel sändningsalgoritm som tillåter dynamiskt typade programmeringsspråk som Python att skilja mellan typer under körning.

Partiell

Vi arbetar alla med olika externa bibliotek eller ramverk, av vilka många tillhandahåller funktioner och gränssnitt som kräver att vi skickar återuppringningar, till exempel för asynkrona operationer eller för att lyssna efter händelser. Detta är inget nytt, men tänk om vi också behöver skicka några argument tillsammans med återuppringningen. Det är här det kommer i praktiska funktionsverktyg.partial. Kan användas partialatt frysa några (eller alla) argument till en funktion genom att skapa ett nytt objekt med en förenklad funktionssignatur. Förvirrad? Låt oss ta en titt på några praktiska exempel:

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 ovan visar hur du kan använda den partialatt skicka en funktion ( output_result) tillsammans med ett argument ( log=logger) som en återuppringning. I det här fallet kommer vi att använda multiprocessing.apply_async, som asynkront beräknar resultatet av funktionen ( concat) och returnerar resultatet av återuppringningen. Dock, apply_asyncdet kommer alltid att skicka resultatet som det första argumentet, och om vi vill inkludera ytterligare argument, som är fallet med log=logger, måste vi använda partial.

Vi har övervägt ett ganska avancerat användningsfall, och ett enklare exempel skulle vara det vanliga skapandet av en funktion som skriver i stderr istället för stdout:

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

Med detta enkla trick skapade vi en ny anropsbar funktion som alltid kommer att passera file=sys.stderrsom ett namngivet argument att mata ut, vilket gör att vi kan förenkla vår kod och inte behöver specificera värdet på det namngivna argumentet varje gång.

Och ett sista bra exempel. Vi kan använda partiali samband med en föga känd funktion iteratt skapa en iterator genom att skicka in ett anropsbart objekt och sentinelin iter, som kan tillämpas så här:

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...

Vanligtvis, när vi läser en fil, vill vi iterera över rader, men i fallet med binära data kan vi behöva iterera över poster med en fast storlek. Du kan göra detta genom att skapa ett anropsbart objekt med hjälp av partialsom läser den angivna databiten och skickar in den iterför att skapa en iterator. Denna iterator anropar sedan läsfunktionen tills den når slutet av filen, och tar alltid bara den angivna bitstorleken ( RECORD_SIZE). Slutligen, när slutet av filen nås, värdet sentinel(b ”) returneras och iterationen stoppas.

dekoratörer

Vi har redan pratat om några dekoratörer i de tidigare avsnitten, men inte dekoratörer för att skapa fler dekoratörer. En sådan dekoratör är functools.wraps. För att förstå varför du behöver det, låt oss bara titta på ett exempel:

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

Detta exempel visar hur en enkel dekoratör kan implementeras. Vi slår in en funktion som utför en specifik uppgift ( actual_func) med en extern dekoratör, och det blir en dekoratör själv, som sedan kan appliceras på andra funktioner, till exempel, som är fallet med greet. När du anropar funktionen, greet du kommer att se att den skriver ut meddelanden från båda actual_funcoch på egen hand. Ser okej ut, eller hur? Men vad händer om vi gör så här:

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

När vi kallar namnet och dokumentationen för den dekorerade funktionen inser vi att de har ersatts med värden från dekoratörsfunktionen. Detta är dåligt eftersom vi inte kan skriva om alla våra funktionsnamn och dokumentation när vi använder någon dekoratör. Hur kan detta problem lösas? Naturligtvis med functools.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

Funktionens enda syfte wrapsär att kopiera namn, dokumentation, argumentlista etc. för att förhindra överskrivning. Med tanke på att wrapsdet är också en dekoratör, du kan helt enkelt lägga till den i vår actual_func, så är problemet löst!

Minska

Sist men inte minst i modulens funktionsverktyg är denna reducering. Kanske från andra språk, du kanske känner till det som fold(Haskell). Denna funktion tar en iterabel och viker (lägger till) alla dess värden till ett. Det finns många applikationer för detta, till exempel:

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 förenkla eller kondensera koden till en rad, som annars skulle vara mycket längre. Med det sagt är det vanligtvis en dålig idé att missbruka den här funktionen bara för att krympa koden, vilket gör den "smartare", eftersom den snabbt blir skrämmande och oläslig. Av denna anledning bör den enligt min mening användas sparsamt.

Och om du kommer ihåg det reduce ofta förkortar allt till en rad, det går perfekt att kombinera med partial:

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

Och slutligen, om du behöver mer än bara det slutliga "kollapsade" resultatet, kan du använda accumulate– från en annan bra modul itertools. För att beräkna maxvärdet kan det användas enligt följande:

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]

Slutsats

Som du kan se functools finns det många användbara funktioner och dekoratörer som kan göra ditt liv enklare, men detta är bara toppen av ett isberg. Som jag sa i början så finns det många funktioner i Python standardbiblioteket som hjälper dig att skriva bättre kod, så utöver de funktioner som vi tog upp här kan du vara uppmärksam på andra moduler, som t.ex. operatoror itertool. För eventuella frågor kan du trycka på min kommentarsruta. Jag kommer att göra mitt bästa för att lösa dina input och hoppas kunna ge dig önskade output. Du kan också nå mig på LinkedIn:-

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

Media som visas i denna artikel ägs inte av Analytics Vidhya och används efter författarens gottfinnande.

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

Tidsstämpel:

Mer från Analys Vidhya