Maison  >  Article  >  développement back-end  >  Maîtriser les fermetures et les décorateurs en Python : des bases à l'avancé

Maîtriser les fermetures et les décorateurs en Python : des bases à l'avancé

DDD
DDDoriginal
2024-09-19 01:39:33586parcourir

Mastering Closures and Decorators in Python: From Basics to Advanced

Introduction

Les fermetures et les décorateurs sont des fonctionnalités puissantes de Python qui vous permettent d'écrire du code plus flexible et réutilisable. Comprendre ces concepts fera passer vos compétences Python au niveau supérieur, vous permettant de gérer facilement des scénarios plus complexes tels que la journalisation, le contrôle d'accès et la mémorisation.

Dans cet article de blog, nous explorerons :

  1. Que sont les fermetures ?
  2. Comprendre le fonctionnement des fermetures en Python
  3. Cas d'utilisation des fermetures
  4. Que sont les décorateurs ?
  5. Comprendre le fonctionnement des décorateurs
  6. Utilisation de décorateurs intégrés
  7. Rédaction de décorateurs personnalisés
  8. Concepts avancés avec des décorateurs

À la fin de cet article, vous aurez une solide maîtrise des fermetures et des décorateurs, et vous serez en mesure de les appliquer efficacement dans votre propre code.

Que sont les fermetures ?

En Python, les fermetures sont des fonctions qui conservent les valeurs des variables de leur portée lexicale englobante même lorsque la fonction externe a fini de s'exécuter. Les fermetures sont un moyen de conserver l'état entre les appels de fonction, ce qui les rend utiles dans les scénarios dans lesquels vous devez conserver un certain contexte.

Les fermetures se composent de trois éléments principaux :

  1. Une fonction imbriquée
  2. Une référence à une variable libre dans la fonction englobante
  3. La fonction englobante a fini de s'exécuter, mais la fonction imbriquée se souvient toujours de l'état de la variable libre.

Exemple de base d'une fermeture

Voici un exemple de fermeture simple :

def outer_function(message):
    def inner_function():
        print(message)
    return inner_function

# Create a closure
closure = outer_function("Hello, World!")
closure()  # Output: Hello, World!

Dans cet exemple, inner_function fait référence à la variable de message de external_function, même après la fin de l'exécution de external_function. La fonction interne "ferme" la variable de la portée externe, d'où le terme fermeture.

Comment fonctionnent les fermetures en interne

Les fermetures fonctionnent en capturant l'état des variables libres et en les stockant dans l'attribut __closure__ de l'objet fonction.

Inspectons la fermeture de l'exemple précédent :

print(closure.__closure__[0].cell_contents)  # Output: Hello, World!

L'attribut __closure__ contient les références aux variables que la fermeture conserve. Chaque variable est stockée dans une « cellule » et vous pouvez accéder à son contenu avec cell_contents.

Cas d'utilisation des fermetures

Les fermetures sont particulièrement utiles lorsque vous souhaitez conserver l'état entre les appels de fonction sans utiliser de variables ou de classes globales. Voici quelques cas d'utilisation courants :

1. Usines de fonctions

Vous pouvez utiliser des fermetures pour créer des fonctions de manière dynamique.

def multiplier(factor):
    def multiply_by_factor(number):
        return number * factor
    return multiply_by_factor

times_two = multiplier(2)
times_three = multiplier(3)

print(times_two(5))  # Output: 10
print(times_three(5))  # Output: 15

Dans cet exemple, multiplicateur renvoie une fonction qui multiplie un nombre donné par un facteur spécifique. Les fermetures times_two et times_trois conservent la valeur du facteur de leur portée englobante.

2. Encapsulation

Les fermetures vous permettent d'encapsuler un comportement sans exposer l'état interne. Ceci est similaire au concept de méthodes privées dans la programmation orientée objet.

def counter():
    count = 0
    def increment():
        nonlocal count
        count += 1
        return count
    return increment

counter_fn = counter()
print(counter_fn())  # Output: 1
print(counter_fn())  # Output: 2

Dans cet exemple, la variable count est encapsulée dans la fermeture, et seule la fonction d'incrémentation peut modifier sa valeur.

Que sont les décorateurs ?

Un décorateur est une fonction qui prend une autre fonction et étend ou modifie son comportement sans modifier le code de la fonction d'origine. Les décorateurs sont souvent utilisés pour ajouter des fonctionnalités telles que la journalisation, le contrôle d'accès ou la synchronisation aux fonctions et méthodes.

En Python, les décorateurs sont appliqués aux fonctions en utilisant le symbole @ au-dessus de la définition de la fonction.

Exemple de base d'un décorateur

def decorator_function(original_function):
    def wrapper_function():
        print(f"Wrapper executed before {original_function.__name__}()")
        return original_function()
    return wrapper_function

@decorator_function
def say_hello():
    print("Hello!")

say_hello()

# Output:
# Wrapper executed before say_hello()
# Hello!

Ici, decorator_function est appliqué à say_hello, ajoutant des fonctionnalités supplémentaires avant l'exécution de say_hello().

Comment travaillent les décorateurs

Les décorateurs sont essentiellement du sucre syntaxique pour un modèle courant en Python : des fonctions d'ordre supérieur, qui prennent d'autres fonctions comme arguments. Quand vous écrivez @decorator, cela équivaut à :

say_hello = decorator_function(say_hello)

La fonction décorateur renvoie une nouvelle fonction (wrapper_function), qui étend le comportement de la fonction d'origine.

Décorateurs avec arguments

Si la fonction en cours de décoration prend des arguments, la fonction wrapper doit accepter *args et **kwargs pour transmettre les arguments.

def decorator_function(original_function):
    def wrapper_function(*args, **kwargs):
        print(f"Wrapper executed before {original_function.__name__}()")
        return original_function(*args, **kwargs)
    return wrapper_function

@decorator_function
def display_info(name, age):
    print(f"display_info ran with arguments ({name}, {age})")

display_info("John", 25)

# Output:
# Wrapper executed before display_info()
# display_info ran with arguments (John, 25)

Décorateurs intégrés en Python

Python fournit plusieurs décorateurs intégrés, tels que @staticmethod, @classmethod et @property.

@staticmethod and @classmethod

These decorators are commonly used in object-oriented programming to define methods that are either not bound to the instance (@staticmethod) or bound to the class itself (@classmethod).

class MyClass:
    @staticmethod
    def static_method():
        print("Static method called")

    @classmethod
    def class_method(cls):
        print(f"Class method called from {cls}")

MyClass.static_method()   # Output: Static method called
MyClass.class_method()    # Output: Class method called from <class '__main__.MyClass'>

@property

The @property decorator allows you to define a method that can be accessed like an attribute.

class Circle:
    def __init__(self, radius):
        self._radius = radius

    @property
    def radius(self):
        return self._radius

    @radius.setter
    def radius(self, value):
        if value <= 0:
            raise ValueError("Radius must be positive")
        self._radius = value

c = Circle(5)
print(c.radius)  # Output: 5

c.radius = 10
print(c.radius)  # Output: 10

Writing Custom Decorators

You can write your own decorators to add custom functionality to your functions or methods. Decorators can be stacked, meaning you can apply multiple decorators to a single function.

Example: Timing a Function

Here’s a custom decorator that measures the execution time of a function:

import time

def timer_decorator(func):
    def wrapper(*args, **kwargs):
        start_time = time.time()
        result = func(*args, **kwargs)
        end_time = time.time()
        print(f"{func.__name__} ran in {end_time - start_time:.4f} seconds")
        return result
    return wrapper

@timer_decorator
def calculate_square(numbers):
    result = [n * n for n in numbers]
    return result

nums = range(1, 1000000)
calculate_square(nums)

Decorators with Arguments

Decorators can also accept their own arguments. This is useful when you need to pass configuration values to the decorator.

Example: Logger with Custom Message

def logger_decorator(message):
    def decorator(func):
        def wrapper(*args, **kwargs):
            print(f"{message}: Executing {func.__name__}")
            return func(*args, **kwargs)
        return wrapper
    return decorator

@logger_decorator("DEBUG")
def greet(name):
    print(f"Hello, {name}!")

greet("Alice")

# Output:
# DEBUG: Executing greet
# Hello, Alice!

In this example, the decorator logger_decorator takes a message as an argument, and then it wraps the greet function with additional logging functionality.

Advanced Decorator Concepts

1. Decorating Classes

Decorators can be applied not only to functions but also to classes. Class decorators modify or extend the behavior of entire classes.

def add_str_repr(cls):
    cls.__str__ = lambda self: f"Instance of {cls.__name__}"
    return cls

@add_str_repr
class Dog:
    pass

dog = Dog()
print(dog)  # Output: Instance of Dog

2. Memoization with Decorators

Memoization is an optimization technique where the results of expensive function calls are cached, so subsequent calls with the same arguments can be returned faster.

def memoize(func):
    cache = {}
    def wrapper(*args):
        if args not in cache:
            cache[args] = func(*args)
        return cache[args]
    return wrapper

@memoize
def fibonacci(n):


 if n in [0, 1]:
        return n
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(30))  # Output: 832040

Conclusion

Closures and decorators are advanced Python concepts that unlock powerful capabilities for writing cleaner, more efficient code. Closures allow you to maintain state and encapsulate data, while decorators let you modify or extend the behavior of functions and methods in a reusable way. Whether you're optimizing performance with memoization, implementing access control, or adding logging, decorators are an essential tool in your Python toolkit.

By mastering these concepts, you'll be able to write more concise and maintainable code and handle complex programming tasks with ease.

Feel free to experiment with closures and decorators in your projects and discover how they can make your code more elegant and powerful!


Connect with Me

  • GitHub
  • Linkedin

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn