Cet article a été publié dans le cadre du Blogathon sur la science des données
Introduction
La bibliothèque standard Python contient de nombreux modules géniaux pour vous aider à garder votre code plus propre et plus simple et functools est certainement l'un des
Cache haute performance
Commençons par certaines des fonctions les plus simples mais les plus puissantes du module functools. Commençons par les fonctions de mise en cache (ainsi que les décorateurs) - lru_cache
,cache
ainsi que cached_property
. Le premier d'entre eux - lru_cache
fournit un cache des derniers résultats de l'exécution des fonctions, ou en d'autres termes, se souvient du résultat de leur travail :
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}
Dans cet exemple, nous faisons des requêtes GET et mettons en cache leurs résultats (jusqu'à 32 résultats) à l'aide d'un décorateur @lru_cache
. Pour voir si la mise en cache fonctionne réellement, vous pouvez vérifier les informations du cache de fonction à l'aide d'une méthode cache_info
qui affiche le nombre d'accès au cache et d'accès. Un décorateur fournit également des méthodes clear_cache
ainsi que cache_parameters
pour l'annulation des résultats mis en cache et des paramètres de test, respectivement.
Si vous avez besoin d'une mise en cache plus granulaire, vous pouvez inclure un argument facultatif typed=true, qui vous permet de mettre en cache différents types d'arguments séparément.
Un autre décorateur pour la mise en cache dans functools est une fonction appelée simplement cache
. C'est un simple emballage lru_cache
qui omet l'argument max_size, le diminue et ne supprime pas les anciennes valeurs.
Un autre décorateur que vous pouvez utiliser pour la mise en cache est cached_property
. Comme son nom l'indique, il est utilisé pour mettre en cache les résultats des attributs de classe. Ce mécanisme est très utile si vous avez une propriété coûteuse à calculer, mais qui reste la même.
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
Cet exemple simple montre. Comme cela peut être utilisé, la propriété mise en cache, par exemple, pour mettre en cache une page HTML rendue qui doit être montrée à l'utilisateur encore et encore. La même chose peut être faite pour certaines requêtes de base de données ou de longs calculs mathématiques.
Une autre beauté cached_property
est qu'il ne s'exécute que lors de la recherche, il nous permet donc de modifier la valeur de l'attribut. Après avoir modifié l'attribut, la valeur précédemment mise en cache ne changera pas, à la place, la nouvelle valeur sera calculée et mise en cache. Vous pouvez également vider le cache et tout ce que vous avez à faire est de supprimer l'attribut.
Je veux terminer cette section avec une mise en garde à propos de tous les décorateurs ci-dessus - ne les utilisez pas si votre fonction a des effets secondaires ou si elle crée des objets modifiables à chaque fois qu'elle est appelée car ce ne sont clairement pas les fonctions que vous voulez mettre en cache .
Comparaison et commande
Vous savez probablement déjà que vous pouvez implémenter des opérateurs de comparaison en Python tels que <
, >=
or ==
, avec lt
, gt
or eq
. Cependant, il peut être assez frustrant de réaliser chacun des eq
, lt
, le
, gt
or ge
. Heureusement, functools il y a un décorateur @total_ordering
qui peuvent nous y aider, car tout ce que nous devons mettre en œuvre, c'est eq
l'une des méthodes restantes, et le reste du décorateur sera généré automatiquement.
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
De cette façon, nous pouvons implémenter toutes les opérations de comparaison étendues, même si nous n'avons que eq et hand lt. L'avantage le plus évident est la commodité, c'est-à-dire que vous n'avez pas à écrire toutes ces méthodes magiques supplémentaires, mais il est probablement plus important de réduire la quantité de code et sa meilleure lisibilité.
Surcharge
On nous a probablement tous appris qu'il n'y a pas de surcharge en Python, mais il existe en fait un moyen simple de l'implémenter en utilisant deux fonctions de functools, à savoir la répartition unique et/ou la méthode de répartition unique. Ces fonctions nous aident à implémenter ce que nous appellerions un algorithme de répartition multiple qui permet aux langages de programmation à typage dynamique tels que Python de faire la distinction entre les types lors de l'exécution.
Partiel
Nous travaillons tous avec diverses bibliothèques ou frameworks externes, dont beaucoup fournissent des fonctions et des interfaces nous obligeant à passer des rappels, comme pour les opérations asynchrones ou l'écoute d'événements. Ce n'est pas nouveau, mais que se passe-t-il si nous devons également transmettre des arguments avec le rappel. C'est là qu'interviennent les functools pratiques.partial
. Peut être utilisé partial
pour geler certains (ou tous) des arguments d'une fonction en créant un nouvel objet avec une signature de fonction simplifiée. Confus? Voyons quelques exemples pratiques :
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()
Le code ci-dessus montre comment vous pouvez l'utiliser partial
passer une fonction ( output_result
) avec un argument ( log=logger
) comme rappel. Dans ce cas, nous utiliserons multiprocessing.apply_async, qui calcule de manière asynchrone le résultat de la fonction ( concat
) et renvoie le résultat du rappel. Cependant, apply_async
il passera toujours le résultat comme premier argument, et si nous voulons inclure des arguments supplémentaires, comme c'est le cas avec log=logger, nous devons utiliser partial.
Nous avons considéré un cas d'utilisation assez avancé, et un exemple plus simple serait la création habituelle d'une fonction qui écrit en stderr au lieu de stdout :
import sys
from functools import partial print_stderr = partial(print, file=sys.stderr)
print_stderr("This goes to standard error output")
Avec cette astuce simple, nous avons créé une nouvelle fonction appelable qui passera toujours file=sys.stderr
comme argument nommé à la sortie, ce qui nous permet de simplifier notre code et de ne pas avoir à spécifier la valeur de l'argument nommé à chaque fois.
Et un dernier bon exemple. On peut utiliser partial
en conjonction avec une fonction peu connue iter
pour créer un itérateur en passant un objet appelable et sentinel
in iter
, qui peut être appliqué comme ceci :
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...
Habituellement, lors de la lecture d'un fichier, nous voulons itérer sur des lignes, mais dans le cas de données binaires, nous pouvons avoir besoin d'itérer sur des enregistrements d'une taille fixe. Vous pouvez le faire en créant un objet appelable à l'aide de partial
qui lit le bloc de données spécifié et le transmet iter
pour créer un itérateur. Cet itérateur appelle ensuite la fonction read jusqu'à ce qu'il atteigne la fin du fichier, en ne prenant toujours que la taille de bloc spécifiée ( RECORD_SIZE
). Enfin, lorsque la fin du fichier est atteinte, la valeur sentinel
(b ") est retourné et l'itération s'arrête.
Décorateurs
Nous avons déjà parlé de certains décorateurs dans les sections précédentes, mais pas de décorateurs pour créer plus de décorateurs. Un tel décorateur est functools.wraps
. Pour comprendre pourquoi vous en avez besoin, regardons un exemple :
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
Cet exemple montre comment un décorateur simple peut être implémenté. Nous enveloppons une fonction qui exécute une tâche spécifique ( actual_func
) avec un décorateur externe, et il devient un décorateur lui-même, qui peut ensuite être appliqué à d'autres fonctions, par exemple, comme c'est le cas avec greet
. Lorsque vous appelez la fonction, greet
vous verrez qu'il imprime les messages à la fois de actual_func
et tout seul. Ça a l'air bien, n'est-ce pas ? Mais que se passe-t-il si nous faisons ceci :
print(greet.__name__)
# actual_func
print(greet.__doc__)
# Inner function within decorator, which does the actual work
Lorsque nous appelons le nom et la documentation de la fonction décorée, nous nous rendons compte qu'ils ont été remplacés par des valeurs de la fonction décoratrice. C'est mauvais car nous ne pouvons pas réécrire tous nos noms de fonctions et notre documentation lorsque nous utilisons un décorateur. Comment ce problème peut-il être résolu ? Bien sûr, avec 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
Le seul but de la fonction wraps
consiste à copier le nom, la documentation, la liste des arguments, etc., pour éviter l'écrasement. Étant donné que wraps
c'est aussi un décorateur, vous pouvez simplement l'ajouter à notre actual_func, et le problème est résolu !
Réduire
Le dernier mais non le moindre dans le module functools est cette réduction. Peut-être que d'autres langues, vous le connaissez peut-être comme fold
(Haskell). Cette fonction prend un itérable et replie (ajoute) toutes ses valeurs en une seule. Il existe de nombreuses applications pour cela, par exemple :
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
Comme vous pouvez le voir sur le code, reduce
peut simplifier ou condenser le code en une seule ligne, qui serait autrement beaucoup plus longue. Cela dit, c'est généralement une mauvaise idée d'abuser de cette fonction juste pour réduire le code, le rendant "plus intelligent", car il devient rapidement effrayant et illisible. Pour cette raison, à mon avis, il faut l'utiliser avec parcimonie.
Et si vous vous souvenez qu'il reduce
raccourcit souvent tout à une seule ligne, il peut être parfaitement combiné avec partial
:
product = partial(reduce, operator.mul) print(product([1, 2, 3]))
# 6
Et enfin, si vous avez besoin de plus que le résultat final "effondré", vous pouvez utiliser accumulate
– d'un autre excellent module itertools
. Pour calculer le maximum, il peut être utilisé comme suit :
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]
Conclusion
Comme vous pouvez le voir dans functools, il existe de nombreuses fonctions et décorateurs utiles qui peuvent vous faciliter la vie, mais ce n'est que la pointe de l'iceberg. Comme je l'ai dit au début, il existe de nombreuses fonctions dans la bibliothèque standard Python qui vous aident à écrire un meilleur code, donc en plus des fonctions que nous avons couvertes ici, vous pouvez prêter attention à d'autres modules, tels que operator
or itertool.
Pour toute question, vous pouvez cliquer sur ma boîte de commentaires. Je ferai de mon mieux pour résoudre vos entrées et j'espère vous donner les sorties souhaitées. Vous pouvez également me joindre sur LinkedIn :-
https://www.linkedin.com/in/shivani-sharma-aba6141b6/
Les médias présentés dans cet article ne sont pas la propriété d'Analytics Vidhya et sont utilisés à la discrétion de l'auteur.
Services Connexes
- '
- "
- 9
- Supplémentaire
- algorithme
- Tous
- analytique
- applications
- arguments
- article
- Beauté
- LES MEILLEURS
- Box
- Appelez-nous
- Change
- code
- calcul
- La création
- données
- Base de données
- Expédition
- etc
- événements
- exécution
- finalement
- Prénom
- Geler
- fonction
- Bien
- l'
- pratique
- ici
- Comment
- HTTPS
- idée
- d'information
- IT
- en gardant
- Langues
- Bibliothèque
- Gamme
- Liste
- Écoute
- Location
- rechercher
- Fabrication
- math
- Médias
- à savoir
- noms
- numéros
- Bien
- Opérations
- Opinion
- Autre
- Payer
- pool
- power
- Programmation
- langages de programmation
- propriété
- Python
- en cours
- Articles
- réduire
- REST
- Résultats
- Retours
- Sciences
- étapes
- Taille
- So
- RÉSOUDRE
- Commencer
- tester
- fiable
- us
- Plus-value
- dans les
- des mots
- Activités:
- vos contrats
- world
- X