Functools -Η δύναμη της ανώτερης τάξης συναρτήσεων στην Python

Κόμβος πηγής: 1865357

Αυτό το άρθρο δημοσιεύθηκε ως μέρος του Επιστήμη δεδομένων Blogathon

Εισαγωγή

Η τυπική βιβλιοθήκη Python διαθέτει πολλές εξαιρετικές ενότητες που θα σας βοηθήσουν να διατηρήσετε τον κώδικα σας καθαρότερο και απλούστερο και λειτουργικό σίγουρα ένα από τα

Προσωρινής αποθήκευσης

Ας ξεκινήσουμε με μερικές από τις πιο απλές αλλά ισχυρές λειτουργίες της λειτουργικής μονάδας λειτουργικού. Ας ξεκινήσουμε με τις λειτουργίες προσωρινής αποθήκευσης (καθώς και διακοσμητές) - lru_cache,cache και cached_propertyΤο Το πρώτο από αυτά - lru_cache παρέχει μια προσωρινή μνήμη των τελευταίων αποτελεσμάτων της εκτέλεσης των συναρτήσεων ή με άλλα λόγια, θυμάται το αποτέλεσμα της εργασίας τους:

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}

Σε αυτό το παράδειγμα, κάνουμε αιτήσεις GET και αποθηκεύουμε τα αποτελέσματα τους (έως 32 αποτελέσματα) χρησιμοποιώντας διακοσμητικό @lru_cacheΤο Για να δείτε αν η προσωρινή αποθήκευση λειτουργεί πραγματικά, μπορείτε να ελέγξετε τις πληροφορίες της προσωρινής μνήμης λειτουργίας χρησιμοποιώντας μια μέθοδο cache_infoπου δείχνει τον αριθμό των επισκέψεων και των επισκέψεων της προσωρινής μνήμης. Ένας διακοσμητής παρέχει επίσης μεθόδους clear_cacheκαι cache_parametersγια την ακύρωση των προσωρινών αποτελεσμάτων και των παραμέτρων δοκιμής, αντίστοιχα.

Εάν χρειάζεστε πιο λεπτομερή προσωρινή αποθήκευση, μπορείτε να συμπεριλάβετε ένα προαιρετικό όρισμα πληκτρολογημένο = true, το οποίο σας επιτρέπει να αποθηκεύσετε προσωρινά προσωρινά διαφορετικούς τύπους επιχειρημάτων.

Ένας άλλος διακοσμητής για προσωρινή αποθήκευση σε functools είναι μια λειτουργία που ονομάζεται απλά cacheΤο Είναι ένα απλό περιτύλιγμα lru_cacheπου παραλείπει το όρισμα max_size, μειώνοντάς το και δεν αφαιρεί τις παλιές τιμές.

Ένας άλλος διακοσμητής που μπορείτε να χρησιμοποιήσετε για προσωρινή αποθήκευση είναι cached_propertyΤο Όπως υποδηλώνει το όνομα, χρησιμοποιείται για την προσωρινή αποθήκευση των αποτελεσμάτων των χαρακτηριστικών κλάσης. Αυτός ο μηχανικός είναι πολύ χρήσιμος αν έχετε μια ιδιότητα που είναι ακριβή στον υπολογισμό, αλλά παραμένει η ίδια.

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

Αυτό το απλό παράδειγμα δείχνει. Όπως μπορεί να χρησιμοποιηθεί, προσωρινά αποθηκευμένη ιδιότητα, για παράδειγμα, για προσωρινή αποθήκευση μιας σελίδας απόδοσης HTML που πρέπει να εμφανίζεται στον χρήστη ξανά και ξανά. Το ίδιο μπορεί να γίνει για ορισμένα ερωτήματα βάσης δεδομένων ή μακροχρόνιους υπολογισμούς μαθηματικών.

Μια άλλη ομορφιά cached_propertyείναι ότι λειτουργεί μόνο με αναζήτηση, οπότε μας επιτρέπει να αλλάξουμε την τιμή του χαρακτηριστικού. Μετά την αλλαγή του χαρακτηριστικού, η προηγουμένως αποθηκευμένη τιμή δεν θα αλλάξει, αντίθετα, η νέα τιμή θα υπολογιστεί και θα αποθηκευτεί. Μπορείτε επίσης να διαγράψετε την προσωρινή μνήμη και το μόνο που χρειάζεται να κάνετε είναι να καταργήσετε το χαρακτηριστικό.

Θέλω να ολοκληρώσω αυτήν την ενότητα με μια προειδοποίηση για όλους τους παραπάνω διακοσμητές - μην τα χρησιμοποιείτε εάν η λειτουργία σας έχει κάποιες παρενέργειες ή αν δημιουργεί μεταβλητά αντικείμενα κάθε φορά που καλείται, καθώς αυτές σαφώς δεν είναι οι συναρτήσεις που θέλετε να αποθηκεύσετε Το

Σύγκριση και παραγγελία

Πιθανότατα γνωρίζετε ήδη ότι μπορείτε να εφαρμόσετε τελεστές σύγκρισης στην Python όπως π.χ. <, >=or ==, με lt, gtor eqΤο Ωστόσο, μπορεί να είναι αρκετά απογοητευτικό να συνειδητοποιήσουμε καθένα από αυτά eq, lt, le, gtor geΤο Ευτυχώς, functools υπάρχει διακοσμητής @total_orderingπου μπορεί να μας βοηθήσει με αυτό, γιατί το μόνο που χρειαζόμαστε είναι να εφαρμόσουμε eqμία από τις υπόλοιπες μεθόδους και ο υπόλοιπος διακοσμητής θα δημιουργηθεί αυτόματα.

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

Με αυτόν τον τρόπο, μπορούμε να εφαρμόσουμε όλες τις εκτεταμένες λειτουργίες σύγκρισης, παρά το ότι έχουμε μόνο eq και χειροκίνητα lt. Το πιο προφανές όφελος είναι η ευκολία, η οποία είναι ότι δεν χρειάζεται να γράψετε όλες αυτές τις πρόσθετες μαγικές μεθόδους, αλλά μάλλον είναι πιο σημαντικό να μειώσετε την ποσότητα του κώδικα και την καλύτερη αναγνωσιμότητά του.

Υπερφόρτωση

Πιθανότατα όλοι μας έχουμε διδαχτεί ότι δεν υπάρχει υπερφόρτωση στην Python, αλλά στην πραγματικότητα υπάρχει ένας εύκολος τρόπος να το εφαρμόσουμε χρησιμοποιώντας δύο συναρτήσεις από το functools, συγκεκριμένα μια μέθοδο αποστολής και/ή μία μέθοδο αποστολής. Αυτές οι συναρτήσεις μας βοηθούν να εφαρμόσουμε αυτό που θα ονομάζαμε αλγόριθμο πολλαπλής αποστολής που επιτρέπει σε δυναμικά πληκτρολογούμενες γλώσσες προγραμματισμού όπως η Python να διακρίνουν μεταξύ τύπων κατά τη διάρκεια εκτέλεσης.

Μερική

Όλοι εργαζόμαστε με διάφορες εξωτερικές βιβλιοθήκες ή πλαίσια, πολλά από τα οποία παρέχουν λειτουργίες και διεπαφές που απαιτούν από εμάς να περνάμε κλήσεις, όπως για ασύγχρονες λειτουργίες ή ακρόαση συμβάντων. Αυτό δεν είναι κάτι καινούργιο, αλλά τι γίνεται αν χρειαστεί επίσης να περάσουμε ορισμένα επιχειρήματα μαζί με την επανάκληση. Εδώ έρχεται σε εύχρηστα functools.partial. Μπορεί να χρησιμοποιηθεί partialγια να παγώσει μερικά (ή όλα) από τα ορίσματα σε μια συνάρτηση δημιουργώντας ένα νέο αντικείμενο με απλοποιημένη υπογραφή συνάρτησης. Ταραγμένος? Ας ρίξουμε μια ματιά σε μερικά πρακτικά παραδείγματα:

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

Ο παραπάνω κώδικας δείχνει πώς μπορείτε να τον χρησιμοποιήσετε partialγια να περάσει μια συνάρτηση ( output_result) μαζί με ένα επιχείρημα ( log=logger) ως επανάκληση. Σε αυτήν την περίπτωση, θα χρησιμοποιήσουμε το multiprocessing.apply_async, το οποίο υπολογίζει ασύγχρονα το αποτέλεσμα της συνάρτησης ( concat) και επιστρέφει το αποτέλεσμα της επανάκλησης. Ωστόσο, apply_asyncθα περνά πάντα το αποτέλεσμα ως το πρώτο όρισμα, και αν θέλουμε να συμπεριλάβουμε τυχόν πρόσθετα ορίσματα, όπως συμβαίνει με το log = logger, πρέπει να χρησιμοποιήσουμε partial.

Έχουμε εξετάσει μια αρκετά προηγμένη περίπτωση χρήσης και ένα απλούστερο παράδειγμα θα ήταν η συνήθης δημιουργία μιας συνάρτησης που γράφει σε stderr αντί για stdout:

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

Με αυτό το απλό κόλπο, δημιουργήσαμε μια νέα λειτουργία που καλείται και θα περνάει πάντα file=sys.stderrως ονομαστικό όρισμα προς έξοδο, το οποίο μας επιτρέπει να απλοποιήσουμε τον κώδικα μας και να μην χρειάζεται να καθορίζουμε την τιμή του ονομασμένου ορίσματος κάθε φορά.

Και ένα τελευταίο καλό παράδειγμα. Μπορούμε να χρησιμοποιήσουμε partialσε συνδυασμό με μια ελάχιστα γνωστή συνάρτηση iterγια να δημιουργήσετε έναν επαναληπτή περνώντας σε ένα κλήσιμο αντικείμενο και sentinelin iter, το οποίο μπορεί να εφαρμοστεί ως εξής:

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

Συνήθως, όταν διαβάζουμε ένα αρχείο, θέλουμε να επαναλαμβάνουμε γραμμές, αλλά στην περίπτωση δυαδικών δεδομένων, μπορεί να χρειαστεί να επαναλάβουμε εγγραφές σταθερού μεγέθους. Μπορείτε να το κάνετε αυτό δημιουργώντας ένα αντικείμενο που καλείται χρησιμοποιώντας partialπου διαβάζει το καθορισμένο κομμάτι δεδομένων και το μεταφέρει iterγια να δημιουργήσετε έναν επαναληπτή. Αυτός ο επαναληπτής καλεί στη συνέχεια τη λειτουργία ανάγνωσης μέχρι να φτάσει στο τέλος του αρχείου, παίρνοντας πάντα μόνο το καθορισμένο μέγεθος κομματιού ( RECORD_SIZE). Τέλος, όταν φτάσει το τέλος του αρχείου, η τιμή sentinel(β ”) επιστρέφεται και η επανάληψη σταματά.

Διακοσμητές

Έχουμε ήδη μιλήσει για μερικούς διακοσμητές στις προηγούμενες ενότητες, αλλά όχι για διακοσμητές για να δημιουργήσουμε περισσότερους διακοσμητές. Ένας τέτοιος διακοσμητής είναι το functools.wrapsΤο Για να καταλάβετε γιατί το χρειάζεστε, ας δούμε μόνο ένα παράδειγμα:

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

Αυτό το παράδειγμα δείχνει πώς μπορεί να εφαρμοστεί ένας απλός διακοσμητής. Τυλίγουμε μια συνάρτηση που εκτελεί μια συγκεκριμένη εργασία ( actual_func) με έναν εξωτερικό διακοσμητή και γίνεται διακοσμητής ο ίδιος, ο οποίος στη συνέχεια μπορεί να εφαρμοστεί σε άλλες λειτουργίες, για παράδειγμα, όπως συμβαίνει με greetΤο Όταν καλείτε τη λειτουργία, greet θα δείτε ότι εκτυπώνει μηνύματα και από actual_funcκαι από μόνο του. Φαίνεται εντάξει, έτσι δεν είναι; Τι γίνεται όμως αν το κάνουμε αυτό:

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

Όταν καλούμε το όνομα και την τεκμηρίωση της διακοσμημένης συνάρτησης, συνειδητοποιούμε ότι έχουν αντικατασταθεί με τιμές από τη συνάρτηση διακοσμητή. Αυτό είναι κακό καθώς δεν μπορούμε να ξαναγράψουμε όλα τα ονόματα των λειτουργιών και την τεκμηρίωσή μας όταν χρησιμοποιούμε κάποιο διακοσμητικό. Πώς μπορεί να λυθεί αυτό το πρόβλημα; Φυσικά, με 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

Ο μοναδικός σκοπός της λειτουργίας wrapsείναι να αντιγράψετε το όνομα, την τεκμηρίωση, τη λίστα επιχειρημάτων κ.λπ., για να αποτρέψετε την αντικατάσταση. Λαμβάνοντας υπ 'όψιν ότι wrapsείναι επίσης διακοσμητής, μπορείτε απλά να το προσθέσετε στο πραγματικό_func μας και το πρόβλημα λύθηκε!

Μειώστε

Τελευταίο αλλά όχι λιγότερο σημαντικό στη λειτουργική μονάδα είναι αυτή η μείωση. Fromσως από άλλες γλώσσες, ίσως το γνωρίζετε ως fold(Χάσκελ). Αυτή η συνάρτηση παίρνει ένα επαναλαμβανόμενο και διπλώνει (προσθέτει) όλες τις τιμές της σε μία. Υπάρχουν πολλές εφαρμογές για αυτό, για παράδειγμα:

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

Όπως μπορείτε να δείτε από τον κώδικα, reduce μπορεί να απλοποιήσει ή να συμπυκνώσει τον κώδικα σε μία γραμμή, η οποία διαφορετικά θα ήταν πολύ μεγαλύτερη. Με αυτά τα παραπάνω, είναι συνήθως κακή ιδέα η κατάχρηση αυτής της λειτουργίας μόνο για χάρη της συρρίκνωσης του κώδικα, καθιστώντας τον «πιο έξυπνο», καθώς γίνεται γρήγορα τρομακτικό και δυσανάγνωστο. Για το λόγο αυτό, κατά τη γνώμη μου, θα πρέπει να χρησιμοποιείται με φειδώ.

Και αν το θυμάστε αυτό reduce συχνά συντομεύει τα πάντα σε μια γραμμή, μπορεί να συνδυαστεί τέλεια με partial:

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

Και τέλος, εάν χρειάζεστε κάτι περισσότερο από το τελικό αποτέλεσμα «συμπτύχτηκε», τότε μπορείτε να το χρησιμοποιήσετε accumulate- από μια άλλη μεγάλη ενότητα itertoolsΤο Για να υπολογίσετε το μέγιστο, μπορεί να χρησιμοποιηθεί ως εξής:

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]

Συμπέρασμα

Όπως μπορείτε να δείτε functools, υπάρχουν πολλές χρήσιμες λειτουργίες και διακοσμητές που μπορούν να κάνουν τη ζωή σας πιο εύκολη, αλλά αυτό είναι μόνο η κορυφή του παγόβουνου. Όπως είπα στην αρχή, υπάρχουν πολλές λειτουργίες στην τυπική βιβλιοθήκη Python που σας βοηθούν να γράψετε καλύτερο κώδικα, οπότε εκτός από τις λειτουργίες που καλύψαμε εδώ, μπορείτε να δώσετε προσοχή και σε άλλες ενότητες, όπως operatoror itertool. Για τυχόν ερωτήματα, μπορείτε να πατήσετε το πλαίσιο σχολίων μου. Θα προσπαθήσω για να λύσω τις εισροές σας και ελπίζω να σας δώσω τις επιθυμητές εκροές. Μπορείτε επίσης να επικοινωνήσετε μαζί μου στο LinkedIn:-

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

Τα μέσα που εμφανίζονται σε αυτό το άρθρο δεν ανήκουν στο Analytics Vidhya και χρησιμοποιούνται κατά την κρίση του Συντάκτη.

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

Σφραγίδα ώρας:

Περισσότερα από Ανάλυση Vidhya