Functools -Korkeamman tason toimintojen voima Pythonissa

Lähdesolmu: 1865357

Tämä artikkeli julkaistiin osana Data Science Blogathon

esittely

Python Standard Library -kirjastossa on monia hienoja moduuleja, jotka auttavat pitämään koodisi puhtaampana ja yksinkertaisempana, ja toiminnalliset työkalut ovat ehdottomasti yksi niistä

välimuistia

Aloitetaan moduulin toimintotyökalujen yksinkertaisimmista mutta tehokkaimmista toiminnoista. Aloitetaan välimuistitoiminnoista (sekä sisustajista) – lru_cache,cache ja cached_property. Ensimmäinen niistä - lru_cache tarjoaa välimuistin toimintojen suorittamisen viimeisistä tuloksista tai toisin sanoen muistaa heidän työnsä tuloksen:

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}

Tässä esimerkissä teemme GET-pyyntöjä ja tallennamme niiden tulokset (enintään 32 tulosta) välimuistiin sisustajan avulla. @lru_cache. Jos haluat nähdä, toimiiko välimuisti todella, voit tarkistaa funktion välimuistin tiedot menetelmällä cache_infojoka näyttää välimuistin osumien ja osumien määrän. Sisustaja tarjoaa myös menetelmiä clear_cacheja cache_parametersvälimuistissa olevien tulosten ja testiparametrien peruuttamista varten.

Jos tarvitset tarkempaa välimuistia, voit sisällyttää valinnaisen argumentin typed=true, jonka avulla voit tallentaa erityyppisiä argumentteja välimuistiin erikseen.

Toinen sisustustyökalu välimuistiin tallentamiseen functooleissa on funktio, jota kutsutaan yksinkertaisesti cache. Se on yksinkertainen kääre lru_cachejoka jättää pois argumentin max_size, pienentää sitä eikä poista vanhoja arvoja.

Toinen koristelu, jota voit käyttää välimuistiin, on cached_property. Kuten nimestä voi päätellä, sitä käytetään luokkaattribuuttien tulosten välimuistiin. Tämä mekaniikka on erittäin hyödyllinen, jos sinulla on ominaisuus, jonka laskeminen on kallista, mutta joka pysyy samana.

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

Tämä yksinkertainen esimerkki osoittaa. Kuten voidaan käyttää, välimuistiin tallennettu ominaisuus, esimerkiksi renderoidun HTML-sivun välimuistiin, joka on näytettävä käyttäjälle yhä uudelleen ja uudelleen. Sama voidaan tehdä tietyille tietokantakyselyille tai pitkille matemaattisille laskutoimituksille.

Toinen kaunotar cached_propertyon, että se toimii vain haun yhteydessä, joten sen avulla voimme muuttaa määritteen arvoa. Attribuutin muuttamisen jälkeen aiemmin välimuistissa oleva arvo ei muutu, vaan uusi arvo lasketaan ja tallennetaan välimuistiin. Voit myös tyhjentää välimuistin, ja sinun tarvitsee vain poistaa määrite.

Haluan lopettaa tämän osion varoituksella kaikista yllä olevista sisustajista – älä käytä niitä, jos funktiollasi on sivuvaikutuksia tai jos se luo muuttuvia objekteja aina kun sitä kutsutaan, koska nämä eivät selvästikään ole niitä toimintoja, jotka haluat tallentaa välimuistiin. .

Vertailu ja tilaus

Tiedät todennäköisesti jo, että voit toteuttaa vertailuoperaattoreita Pythonissa, kuten <, >=or ==, kanssa lt, gtor eq. Voi kuitenkin olla melko turhauttavaa tajuta jokainen niistä eq, lt, le, gtor ge. Onneksi functoolsilla on sisustaja @total_orderingjotka voivat auttaa meitä tässä, koska meidän tarvitsee vain toteuttaa eqyksi jäljellä olevista tavoista, ja loput sisustaja luodaan automaattisesti.

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

Näin voimme toteuttaa kaikki laajennetut vertailuoperaatiot, vaikka meillä on vain eq ja käsin lt. Ilmeisin hyöty on mukavuus, eli se, että sinun ei tarvitse kirjoittaa kaikkia näitä ylimääräisiä taikamenetelmiä, mutta on luultavasti tärkeämpää vähentää koodin määrää ja sen parempi luettavuus.

Ylikuormittaa

Meille kaikille on luultavasti opetettu, että Pythonissa ei ole ylikuormitusta, mutta on itse asiassa helppo tapa toteuttaa se käyttämällä kahta funktiota funktiotyökaluista, nimittäin yhden lähetyksen ja/tai yhden lähetysmenetelmän avulla. Nämä toiminnot auttavat meitä toteuttamaan sen, mitä kutsumme useiksi lähetysalgoritmiksi, jonka avulla dynaamisesti kirjoitetut ohjelmointikielet, kuten Python, voivat erottaa tyypit ajon aikana.

Osittainen

Työskentelemme kaikki erilaisten ulkoisten kirjastojen tai puitteiden kanssa, joista monet tarjoavat toimintoja ja käyttöliittymiä, jotka vaativat meiltä takaisinkutsujen välittämistä, kuten asynkronisia toimintoja tai tapahtumien kuuntelua varten. Tämä ei ole mitään uutta, mutta entä jos meidän on myös välitettävä joitain argumentteja takaisinkutsun mukana. Tässä se tulee käteviin toimintotyökaluihin.partial. Voidaan käyttää partialjäädyttää osan (tai kaikki) funktion argumenteista luomalla uuden objektin yksinkertaistetulla funktion allekirjoituksella. Hämmentynyt? Katsotaanpa joitain käytännön esimerkkejä:

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

Yllä oleva koodi näyttää, kuinka voit käyttää sitä partialvälittää funktio ( output_result) argumentin kanssa ( log=logger) takaisinsoittona. Tässä tapauksessa käytämme multiprocessing.apply_async-funktiota, joka laskee asynkronisesti funktion ( concat) ja palauttaa takaisinsoiton tuloksen. Kuitenkin, apply_asyncse välittää aina tuloksen ensimmäisenä argumenttina, ja jos haluamme sisällyttää lisäargumentteja, kuten log=loggerin tapauksessa, meidän on käytettävä partialia.

Olemme tarkastelleet melko kehittynyttä käyttötapausta, ja yksinkertaisempi esimerkki olisi tavallinen funktion luominen, joka kirjoittaa stderrissä stdoutin sijaan:

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

Tällä yksinkertaisella temppulla loimme uuden kutsuttavan toiminnon, joka menee aina ohi file=sys.stderrnimettynä argumenttina tulostukseen, jonka avulla voimme yksinkertaistaa koodiamme eikä tarvitse määrittää nimetyn argumentin arvoa joka kerta.

Ja viimeinen hyvä esimerkki. Voimme käyttää partialyhdessä vähän tunnetun toiminnon kanssa iterluoda iteraattori syöttämällä kutsuttavan objektin ja sentinelin iter, jota voidaan soveltaa näin:

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

Yleensä tiedostoa luettaessa haluamme iteroida rivien yli, mutta binääritietojen tapauksessa saatamme joutua iteroimaan kiinteän kokoisia tietueita. Voit tehdä tämän luomalla kutsuttavan objektin käyttämällä partialjoka lukee määritellyn dataosan ja välittää sen iteriteraattorin luomiseen. Tämä iteraattori kutsuu sitten lukutoimintoa, kunnes se saavuttaa tiedoston loppuun, ja ottaa aina vain määritetyn kappaleen koon ( RECORD_SIZE). Lopuksi, kun tiedoston loppu saavutetaan, arvo sentinel(b) palautetaan ja iterointi pysähtyy.

Sisustajat

Olemme jo puhuneet joistakin sisustajista edellisissä osioissa, mutta emme sisustajia luomaan lisää sisustajia. Yksi tällainen sisustaja on functools.wraps. Ymmärtääksesi miksi tarvitset sitä, katsotaanpa vain esimerkkiä:

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

Tämä esimerkki näyttää, kuinka yksinkertainen koristelu voidaan toteuttaa. Käärimme funktion, joka suorittaa tietyn tehtävän ( actual_func) ulkoisella sisustajalla, ja siitä tulee itse sisustaja, jota voidaan sitten soveltaa muihin toimintoihin, kuten esim. greet. Kun kutsut toimintoa, greet näet, että se tulostaa viestit molemmista actual_funcja yksinään. Näyttää hyvältä, eikö? Mutta mitä tapahtuu, jos teemme näin:

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

Kun kutsumme koristellun toiminnon nimeä ja dokumentaatiota, huomaamme, että ne on korvattu koristelutoiminnon arvoilla. Tämä on huono asia, koska emme voi kirjoittaa uudelleen kaikkia funktioiden nimiä ja dokumentaatiota, kun käytämme jotain koristelua. Miten tämä ongelma voidaan ratkaista? Tietysti toimintotyökaluilla.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

Toiminnon ainoa tarkoitus wrapson kopioida nimi, dokumentaatio, argumenttiluettelo jne. päällekirjoittamisen estämiseksi. Ottaen huomioon wrapsse on myös sisustaja, voit lisätä sen tosiasialliseen_funktioihimme, ja ongelma on ratkaistu!

Vähentää

Viimeisenä mutta ei vähäisimpänä moduulin funktiotyökaluissa tämä vähennys on. Ehkä muista kielistä, saatat tuntea sen nimellä fold(Haskell). Tämä toiminto tekee iteroitavan ja taittaa (lisää) kaikki sen arvot yhdeksi. Tätä varten on monia sovelluksia, esim.

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

Kuten koodista näkyy, reduce voi yksinkertaistaa tai tiivistää koodin yhdeksi riviksi, joka muuten olisi paljon pidempi. Tästä huolimatta on yleensä huono idea käyttää tätä toimintoa väärin vain koodin pienentämiseksi, mikä tekee siitä "älykkäämmän", koska siitä tulee nopeasti pelottava ja lukukelvoton. Tästä syystä sitä pitäisi mielestäni käyttää säästeliäästi.

Ja jos muistat sen reduce lyhentää usein kaiken yhdeksi riviksi, se voidaan yhdistää täydellisesti partial:

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

Ja lopuksi, jos tarvitset muutakin kuin vain lopullisen "kutistetun" tuloksen, voit käyttää accumulate– toisesta hienosta moduulista itertools. Maksimiarvon laskemiseksi sitä voidaan käyttää seuraavasti:

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]

Yhteenveto

Kuten näet funktiotyökaluista, on monia hyödyllisiä toimintoja ja sisustajia, jotka voivat helpottaa elämääsi, mutta tämä on vain jäävuoren huippu. Kuten alussa sanoin, Python-standardikirjastossa on monia toimintoja, jotka auttavat sinua kirjoittamaan parempaa koodia, joten tässä käsiteltyjen toimintojen lisäksi voit kiinnittää huomiota muihin moduuleihin, kuten esim. operatoror itertool. Jos sinulla on kysyttävää, voit painaa kommenttilaatikkoani. Yritän parhaani mukaan ratkaista panoksesi ja toivon voivani antaa sinulle haluamasi tulokset. Minut tavoittaa myös LinkedInissä:-

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

Tässä artikkelissa esitetyt tiedotusvälineet eivät ole Analytics Vidhyan omistuksessa ja niitä käytetään tekijän harkinnan mukaan.

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

Aikaleima:

Lisää aiheesta Analyysi Vidhya