Acest articol a fost publicat ca parte a Blogathon Data Science
Introducere
Biblioteca standard Python are multe module excelente pentru a vă ajuta să vă păstrați codul mai curat și mai simplu, iar functools este cu siguranta unul dintre
Caching
Să începem cu unele dintre cele mai simple, dar puternice funcții ale functools modulului. Să începem cu funcțiile de caching (precum și decoratorii) – lru_cache
,cache
și cached_property
. Primul dintre ei - lru_cache
oferă un cache al ultimelor rezultate ale execuției funcțiilor sau, cu alte cuvinte, își amintește rezultatul muncii lor:
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}
În acest exemplu, facem cereri GET și memorăm în cache rezultatele acestora (până la 32 de rezultate) folosind un decorator @lru_cache
. Pentru a vedea dacă memorarea în cache funcționează, puteți verifica informațiile din memoria cache a funcției folosind o metodă cache_info
care arată numărul de accesări și accesări în cache. Un decorator oferă și metode clear_cache
și cache_parameters
pentru anularea rezultatelor memorate în cache și, respectiv, a parametrilor de testare.
Dacă aveți nevoie de stocare în cache mai granulară, puteți include un argument opțional typed=true, care vă permite să stocați în cache diferite tipuri de argumente separat.
Un alt decorator pentru memorarea în cache în functools este o funcție numită simplu cache
. Este un simplu ambalaj lru_cache
care omite argumentul max_size, micșorându-l și nu elimină vechile valori.
Un alt decorator pe care îl puteți folosi pentru stocarea în cache este cached_property
. După cum sugerează și numele, este folosit pentru a stoca în cache rezultatele atributelor de clasă. Această mecanică este foarte utilă dacă aveți o proprietate care este costisitoare de calculat, dar rămâne aceeași.
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
Acest exemplu simplu arată. După cum poate fi folosită, proprietatea stocată în cache, de exemplu, pentru a stoca în cache o pagină HTML redată care trebuie să fie afișată utilizatorului din nou și din nou. Același lucru se poate face pentru anumite interogări de baze de date sau calcule matematice lungi.
O altă frumusețe cached_property
este că rulează doar pe căutare, deci ne permite să schimbăm valoarea atributului. După modificarea atributului, valoarea stocată anterior în cache nu se va modifica, în schimb, noua valoare va fi calculată și stocată în cache. De asemenea, puteți șterge memoria cache și tot ce trebuie să faceți este să eliminați atributul.
Vreau să închei această secțiune cu o avertizare despre toți decoratorii de mai sus - nu-i utilizați dacă funcția dvs. are unele efecte secundare sau dacă creează obiecte mutabile de fiecare dată când este apelată, deoarece acestea nu sunt în mod clar funcțiile pe care doriți să le memorați în cache. .
Comparație și ordonare
Probabil știți deja că puteți implementa operatori de comparare în Python, cum ar fi <
, >=
or ==
, Cu lt
, gt
or eq
. Cu toate acestea, poate fi destul de frustrant să realizezi fiecare dintre ele eq
, lt
, le
, gt
or ge
. Din fericire, functools există un decorator @total_ordering
care ne poate ajuta cu asta, pentru că tot ce trebuie să punem în aplicare este eq
una dintre metodele rămase, iar restul decoratorului va fi generat automat.
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 acest fel, putem implementa toate operațiile de comparare extinse, în ciuda faptului că avem doar eq și manual lt. Cel mai evident beneficiu este comoditatea, și anume că nu trebuie să scrieți toate aceste metode magice suplimentare, dar probabil că este mai important să reduceți cantitatea de cod și o mai bună lizibilitate a acestuia.
Suprasarcină
Probabil cu toții am fost învățați că nu există supraîncărcare în Python, dar există de fapt o modalitate ușoară de a o implementa folosind două funcții din functools, și anume metoda de expediere unică și/sau metoda de expediere unică. Aceste funcții ne ajută să implementăm ceea ce am numi un algoritm de expediere multiplă care permite limbajelor de programare tipizate dinamic, cum ar fi Python, să facă distincția între tipuri în timpul execuției.
Parțial
Cu toții lucrăm cu diferite biblioteci sau cadre externe, multe dintre acestea oferind funcții și interfețe care ne solicită să transmitem apeluri inverse, cum ar fi pentru operațiuni asincrone sau pentru a asculta evenimente. Nu este nimic nou, dar ce se întâmplă dacă trebuie să transmitem și câteva argumente împreună cu apelul invers. Aici vine în functool-uri utile.partial
. Poate fi folosit partial
pentru a îngheța unele (sau toate) argumentele unei funcții prin crearea unui nou obiect cu o semnătură simplificată a funcției. Confuz? Să aruncăm o privire la câteva exemple practice:
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()
Codul de mai sus arată cum îl puteți folosi partial
a trece o funcție ( output_result
) împreună cu un argument ( log=logger
) ca un apel invers. În acest caz, vom folosi multiprocessing.apply_async, care calculează asincron rezultatul funcției ( concat
) și returnează rezultatul apelului invers. In orice caz, apply_async
va trece întotdeauna rezultatul ca prim argument, iar dacă dorim să includem orice argument suplimentar, așa cum este cazul log=logger, trebuie să folosim parțial.
Am luat în considerare un caz de utilizare destul de avansat, iar un exemplu mai simplu ar fi crearea obișnuită a unei funcții care scrie în stderr în loc de stdout:
import sys
from functools import partial print_stderr = partial(print, file=sys.stderr)
print_stderr("This goes to standard error output")
Cu acest truc simplu, am creat o nouă funcție apelabilă care va trece întotdeauna file=sys.stderr
ca argument numit la ieșire, ceea ce ne permite să ne simplificăm codul și să nu trebuie să specificăm de fiecare dată valoarea argumentului numit.
Și un ultim exemplu bun. Putem folosi partial
în legătură cu o funcţie puţin cunoscută iter
pentru a crea un iterator prin trecerea unui obiect apelabil și sentinel
in iter
, care poate fi aplicat astfel:
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...
De obicei, când citim un fișier, dorim să iterăm peste linii, dar în cazul datelor binare, este posibil să fie nevoie să iterăm peste înregistrări de dimensiune fixă. Puteți face acest lucru creând un obiect apelabil folosind partial
care citește porțiunea specificată de date și o transmite iter
pentru a crea un iterator. Acest iterator apelează apoi funcția de citire până când ajunge la sfârșitul fișierului, luând întotdeauna doar dimensiunea specificată ( RECORD_SIZE
). În cele din urmă, când se ajunge la sfârșitul fișierului, valoarea sentinel
(b”) este returnat și iterația se oprește.
decoratorii
Am vorbit deja despre unii decoratori în secțiunile anterioare, dar nu despre decoratori pentru a crea mai mulți decoratori. Un astfel de decorator este functools.wraps
. Pentru a înțelege de ce aveți nevoie de el, să ne uităm doar la un exemplu:
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
Acest exemplu arată cum poate fi implementat un decorator simplu. Încheiem o funcție care îndeplinește o anumită sarcină ( actual_func
) cu un decorator extern și devine în sine un decorator, care poate fi apoi aplicat altor funcții, de exemplu, cum este cazul cu greet
. Când apelați funcția, greet
veți vedea că imprimă mesaje ambele de la actual_func
și pe cont propriu. Pare în regulă, nu-i așa? Dar ce se întâmplă dacă facem asta:
print(greet.__name__)
# actual_func
print(greet.__doc__)
# Inner function within decorator, which does the actual work
Când numim numele și documentația funcției decorate, ne dăm seama că au fost înlocuite cu valori din funcția de decorator. Acest lucru este rău, deoarece nu putem rescrie toate numele funcțiilor și documentația noastră atunci când folosim un decorator. Cum poate fi rezolvată această problemă? Desigur, cu 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
Unicul scop al funcției wraps
este să copiați numele, documentația, lista de argumente etc., pentru a preveni suprascrierea. Având în vedere că wraps
este și un decorator, pur și simplu îl poți adăuga la actual_func-ul nostru și problema este rezolvată!
Reduce
Nu în ultimul rând, în modulul functools este această reducere. Poate din alte limbi, s-ar putea să-l cunoașteți ca fold
(Haskell). Această funcție ia un iterabil și pliază (adaugă) toate valorile sale într-una singură. Există multe aplicații pentru aceasta, de exemplu:
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
După cum puteți vedea din cod, reduce
poate simplifica sau condensa codul într-o singură linie, care altfel ar fi mult mai lungă. Acestea fiind spuse, este de obicei o idee proastă să abuzați de această funcție doar de dragul micșorării codului, făcându-l „mai inteligent”, deoarece devine rapid înfricoșător și imposibil de citit. Din acest motiv, în opinia mea, ar trebui să fie folosit cu moderație.
Și dacă îți amintești asta reduce
deseori scurtează totul la o singură linie, poate fi combinat perfect cu partial
:
product = partial(reduce, operator.mul) print(product([1, 2, 3]))
# 6
Și, în sfârșit, dacă aveți nevoie de mai mult decât de rezultatul final „restrâns”, atunci puteți utiliza accumulate
– dintr-un alt modul grozav itertools
. Pentru a calcula maximul, acesta poate fi utilizat după cum urmează:
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]
Concluzie
După cum puteți vedea functools, există multe funcții utile și decoratori care vă pot face viața mai ușoară, dar acesta este doar vârful aisbergului. După cum am spus la început, în biblioteca standard Python există multe funcții care vă ajută să scrieți un cod mai bun, așa că, pe lângă funcțiile pe care le-am acoperit aici, puteți acorda atenție altor module, cum ar fi operator
or itertool.
Pentru orice întrebări, puteți apăsa caseta mea de comentarii. Voi încerca tot posibilul să vă rezolv intrările și sper să vă ofer rezultatele dorite. Ma puteti contacta si pe LinkedIn:-
https://www.linkedin.com/in/shivani-sharma-aba6141b6/
Mediile prezentate în acest articol nu sunt deținute de Analytics Vidhya și sunt utilizate la discreția autorului.
Legate de
- '
- "
- 9
- Suplimentar
- Algoritmul
- TOATE
- Google Analytics
- aplicatii
- argumente
- articol
- Frumuseţe
- CEL MAI BUN
- Cutie
- apel
- Schimbare
- cod
- Calcula
- Crearea
- de date
- Baza de date
- Expediere
- etc
- evenimente
- execuție
- În cele din urmă
- First
- Îngheţa
- funcţie
- bine
- mare
- la indemana
- aici
- Cum
- HTTPS
- idee
- informații
- IT
- păstrare
- Limbă
- Bibliotecă
- Linie
- Listă
- Ascultare
- Lung
- căutare
- Efectuarea
- matematica
- Mass-media
- și anume
- nume
- numere
- Bine
- Operațiuni
- Opinie
- Altele
- Plătește
- piscină
- putere
- Programare
- limbaje de programare
- proprietate
- Piton
- Citind
- înregistrări
- reduce
- REST
- REZULTATE
- Returnează
- Ştiinţă
- simplu
- Mărimea
- So
- REZOLVAREA
- Începe
- test
- timp
- us
- valoare
- în
- cuvinte
- Apartamente
- fabrică
- lume
- X