Functools – kõrgema järgu funktsioonide jõud Pythonis

Allikasõlm: 1865357

See artikkel avaldati osana Andmeteaduse ajaveebi

Sissejuhatus

Pythoni standardraamatukogul on palju suurepäraseid mooduleid, mis aitavad teie koodi puhtamana ja lihtsamana hoida ning funktsionaalsed tööriistad on kindlasti üks neist

Vahemällu salvestamine

Alustame mooduli funktsioonitööriistade kõige lihtsamate, kuid võimsamate funktsioonidega. Alustame vahemällu salvestamise funktsioonidega (nagu ka dekoraatoritega) – lru_cache,cache ja cached_property. Esimene neist – lru_cache pakub vahemälu funktsioonide täitmise viimastest tulemustest või teisisõnu jätab meelde nende töö tulemuse:

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}

Selles näites teeme GET-päringuid ja salvestame nende tulemused (kuni 32 tulemust) vahemällu, kasutades dekoraatorit @lru_cache. Et näha, kas vahemälu tegelikult töötab, saate funktsiooni vahemälu teavet meetodi abil kontrollida cache_infomis näitab vahemälu tabamuste ja tabamuste arvu. Dekoraator pakub ka meetodeid clear_cacheja cache_parametersvastavalt vahemällu salvestatud tulemuste ja testiparameetrite tühistamiseks.

Kui vajate täpsemat vahemällu salvestamist, võite lisada valikulise argumendi typed=true, mis võimaldab eri tüüpi argumente eraldi vahemällu salvestada.

Veel üks dekoraator functoolide vahemällu salvestamiseks on funktsioon, mida nimetatakse lihtsalt cache. See on lihtne ümbris lru_cachemis jätab argumendi max_size välja, vähendab seda ega eemalda vanu väärtusi.

Teine dekoraator, mida saate vahemällu salvestamiseks kasutada, on cached_property. Nagu nimigi ütleb, kasutatakse seda klassi atribuutide tulemuste vahemällu salvestamiseks. See mehaanik on väga kasulik, kui teil on vara, mille arvutamine on kallis, kuid mis jääb samaks.

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

See lihtne näide näitab. Nagu saab kasutada, vahemällu salvestatud atribuut, näiteks renderdatud HTML-lehe vahemällu salvestamiseks, mida tuleb kasutajale ikka ja jälle näidata. Sama saab teha teatud andmebaasipäringute või pikkade matemaatiliste arvutuste puhul.

Veel üks kaunitar cached_propertyon see, et see töötab ainult otsingul, seega võimaldab see atribuudi väärtust muuta. Pärast atribuudi muutmist ei muutu varem vahemällu salvestatud väärtus, selle asemel arvutatakse ja salvestatakse uus väärtus. Samuti saate tühjendada vahemälu ja kõik, mida peate tegema, on atribuudi eemaldamine.

Ma tahan selle jaotise lõpetada hoiatusega kõigi ülaltoodud dekoraatorite kohta – ärge kasutage neid, kui teie funktsioonil on kõrvalmõjud või kui see loob muutuvaid objekte iga kord, kui seda kutsutakse, kuna need ei ole ilmselgelt funktsioonid, mida soovite vahemällu salvestada. .

Võrdlus ja järjestamine

Tõenäoliselt teate juba, et saate Pythonis rakendada võrdlusoperaatoreid, näiteks <, >=or ==Koos lt, gtor eq. Siiski võib kõigi nende mõistmine olla üsna masendav eq, lt, le, gtor ge. Õnneks functools on dekoraator @total_orderingmis võib meid selles aidata, sest meil on vaja ainult rakendada eqüks ülejäänud meetoditest ja ülejäänud dekoraator luuakse automaatselt.

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

Nii saame rakendada kõiki laiendatud võrdlusoperatsioone, hoolimata sellest, et meil on ainult eq ja käsitsi lt. Kõige ilmsem eelis on mugavus, mis seisneb selles, et te ei pea kõiki neid täiendavaid võlumeetodeid kirjutama, kuid tõenäoliselt on olulisem koodikoguse vähendamine ja selle parem loetavus.

Ülekoormus

Tõenäoliselt on meile kõigile õpetatud, et Pythonis pole ülekoormamist, kuid tegelikult on selle rakendamiseks lihtne viis functools'i kahe funktsiooni abil, nimelt ühe ja/või ühe saatmise meetodiga. Need funktsioonid aitavad meil rakendada seda, mida me nimetaksime mitmekordseks saatmisalgoritmiks, mis võimaldab dünaamiliselt tipitud programmeerimiskeeltel (nt Python) käitusajal tüüpe eristada.

Osaline

Me kõik töötame erinevate väliste teekide või raamistikega, millest paljud pakuvad funktsioone ja liideseid, mis nõuavad meilt tagasihelistuste edastamist, näiteks asünkroonsete toimingute või sündmuste kuulamise jaoks. See pole midagi uut, aga mis siis, kui peame koos tagasihelistamisega esitama ka mõned argumendid. See on koht, kus see on mugav functools.partial. Saab kasutada partialfunktsiooni mõnede (või kõigi) argumentide külmutamiseks, luues uue objekti lihtsustatud funktsiooni allkirjaga. Segaduses? Vaatame mõnda praktilist näidet:

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()

Ülaltoodud kood näitab, kuidas saate seda kasutada partialfunktsiooni läbima ( output_result) koos argumendiga ( log=logger) tagasihelistamisena. Sel juhul kasutame funktsiooni multiprocessing.apply_async, mis arvutab asünkroonselt funktsiooni ( concat) ja tagastab tagasihelistamise tulemuse. Kuid, apply_asyncsee edastab tulemuse alati esimese argumendina ja kui tahame lisada täiendavaid argumente, nagu log=logger puhul, peame kasutama osalist.

Oleme kaalunud üsna arenenud kasutusjuhtu ja lihtsam näide oleks tavaline funktsiooni loomine, mis kirjutab stdout asemel stderr:

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

Selle lihtsa nipiga lõime uue helistatava funktsiooni, mis läheb alati läbi file=sys.stderrnimega argumendina väljundisse, mis võimaldab meil oma koodi lihtsustada ja ei pea iga kord nimega argumendi väärtust täpsustama.

Ja viimane hea näide. Saame kasutada partialkoos vähetuntud funktsiooniga iteriteraatori loomiseks, sisestades kutsutava objekti ja sentinelin iter, mida saab rakendada järgmiselt:

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

Tavaliselt tahame faili lugemisel itereerida üle ridade, kuid binaarandmete puhul võib tekkida vajadus itereerida üle fikseeritud suurusega kirjete. Seda saate teha, luues helistatava objekti kasutades partialmis loeb ette määratud andmepaki ja edastab selle iteriteraatori loomiseks. See iteraator kutsub seejärel lugemisfunktsiooni, kuni see jõuab faili lõppu, võttes alati ainult määratud tüki suuruse ( RECORD_SIZE). Lõpuks, kui faili lõpp on saavutatud, väärtus sentinel(b) tagastatakse ja iteratsioon peatub.

maalrite

Oleme juba rääkinud mõnest dekoraatorist eelmistes jaotistes, kuid mitte dekoraatoritest, et luua rohkem dekoraatoreid. Üks selline dekoraator on functools.wraps. Et mõista, miks seda vajate, vaatame lihtsalt näidet:

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

See näide näitab, kuidas saab rakendada lihtsat dekoraatorit. Pakendame funktsiooni, mis täidab konkreetset ülesannet ( actual_func) välise dekoraatoriga ja sellest saab ise dekoraator, mida saab seejärel rakendada muudele funktsioonidele, näiteks nagu see on greet. Kui helistate funktsioonile, greet näete, et see prindib sõnumid mõlemalt actual_funcja omaette. Tundub korras, kas pole? Aga mis juhtub, kui teeme seda:

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

Kui nimetame kaunistatud funktsiooni nime ja dokumentatsiooni, mõistame, et need on asendatud dekoraatori funktsiooni väärtustega. See on halb, kuna me ei saa kõiki oma funktsioonide nimesid ja dokumentatsiooni ümber kirjutada, kui kasutame mõnda dekoraatorit. Kuidas seda probleemi lahendada? Muidugi functoolidega.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

Funktsiooni ainus eesmärk wrapson nime, dokumentatsiooni, argumentide loendi jne kopeerimine, et vältida ülekirjutamist. Võttes arvesse, et wrapssee on ka dekoraator, saate selle lihtsalt lisada meie tegelikku funktsiooni ja probleem on lahendatud!

Vähendama

Viimane, kuid mitte vähem oluline mooduli functools on see vähendamine. Võib-olla teistest keeltest võite seda teada kui fold(Haskell). See funktsioon võtab itereeritava ja voltib (liidab) kõik selle väärtused üheks. Selle jaoks on palju rakendusi, näiteks:

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

Nagu koodist näha, reduce võib koodi lihtsustada või koondada üheks reale, mis muidu oleks palju pikem. Sellegipoolest on tavaliselt halb mõte seda funktsiooni kuritarvitada lihtsalt koodi kokkutõmbamise huvides, muutes selle "targemaks", kuna see muutub kiiresti hirmutavaks ja loetamatuks. Sel põhjusel tuleks minu arvates seda säästlikult kasutada.

Ja kui seda mäletate reduce sageli lühendab kõike ühele reale, sellega saab suurepäraselt kombineerida partial:

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

Ja lõpuks, kui vajate enamat kui lihtsalt lõplikku "kokkuvarisenud" tulemust, saate seda kasutada accumulate– teisest suurepärasest moodulist itertools. Maksimumi arvutamiseks saab seda kasutada järgmiselt:

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]

Järeldus

Nagu näete functools, on palju kasulikke funktsioone ja dekoraatoreid, mis võivad teie elu lihtsamaks muuta, kuid see on vaid jäämäe tipp. Nagu ma alguses ütlesin, on Pythoni standardteegis palju funktsioone, mis aitavad teil paremat koodi kirjutada, nii et lisaks siin käsitletud funktsioonidele saate pöörata tähelepanu ka teistele moodulitele, nagu näiteks operatoror itertool. Kõigi küsimuste korral võite vajutada minu kommentaarikasti. Annan endast parima, et teie sisendid lahendada ja loodan teile soovitud tulemusi anda. Minuga saab ühendust ka LinkedInis:-

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

Selles artiklis näidatud meediumid ei kuulu Analytics Vidhyale ja neid kasutatakse autori äranägemisel.

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

Ajatempel:

Veel alates Analüütika Vidhya