Création de fonctions fonctionnelles

PythonPythonBeginner
Pratiquer maintenant

This tutorial is from open-source community. Access the source code

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Cette section présente l'idée d'utiliser des fonctions pour créer d'autres fonctions.

Introduction

Considérez la fonction suivante.

def add(x, y):
    def do_add():
        print('Adding', x, y)
        return x + y
    return do_add

Il s'agit d'une fonction qui renvoie une autre fonction.

>>> a = add(3,4)
>>> a
<function add.<locals>.do_add at 0x7f27d8a38790>
>>> a()
Adding 3 4
7

Variables locales

Observez comment la fonction interne fait référence à des variables définies par la fonction externe.

def add(x, y):
    def do_add():
        ## `x` et `y` sont définis au-dessus de `add(x, y)`
        print('Adding', x, y)
        return x + y
    return do_add

Observez également que ces variables sont de quelque manière conservées en vie après que add() ait terminé.

>>> a = add(3,4)
>>> a
<function do_add at 0x6a670>
>>> a()
Adding 3 4      ## D'où viennent ces valeurs?
7

Closures

Lorsqu'une fonction interne est renvoyée en tant que résultat, cette fonction interne est connue sous le nom de closure.

def add(x, y):
    ## `do_add` est un closure
    def do_add():
        print('Adding', x, y)
        return x + y
    return do_add

Fonctionnalité essentielle : Un closure conserve les valeurs de toutes les variables nécessaires pour que la fonction puisse fonctionner correctement plus tard. Pensez à un closure comme une fonction plus un environnement supplémentaire qui contient les valeurs des variables dont elle dépend.

Utilisation des closures

Les closures sont une fonctionnalité essentielle de Python. Cependant, leur utilisation est souvent subtile. Applications courantes :

  • Utilisation dans les fonctions de rappel.
  • Evaluation différée.
  • Fonctions décoratrices (plus tard).

Evaluation différée

Considérez une fonction comme celle-ci :

def after(seconds, func):
    import time
    time.sleep(seconds)
    func()

Exemple d'utilisation :

def greeting():
    print('Hello Guido')

after(30, greeting)

after exécute la fonction fournie... plus tard.

Les closures transportent des informations supplémentaires.

def add(x, y):
    def do_add():
        print(f'Adding {x} + {y} -> {x+y}')
    return do_add

def after(seconds, func):
    import time
    time.sleep(seconds)
    func()

after(30, add(2, 3))
## `do_add` a les références x -> 2 et y -> 3

Répétition de code

Les closures peuvent également être utilisées comme technique pour éviter une répétition excessive de code. Vous pouvez écrire des fonctions qui génèrent du code.

Exercice 7.7 : Utiliser les closures pour éviter la répétition

L'une des fonctionnalités les plus puissantes des closures est leur utilisation dans la génération de code répétitif. Si vous vous reportez à l'exercice 5.7, rappelez-vous le code pour définir une propriété avec une vérification de type.

class Stock:
    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price
  ...
    @property
    def shares(self):
        return self._shares

    @shares.setter
    def shares(self, value):
        if not isinstance(value, int):
            raise TypeError('Expected int')
        self._shares = value
  ...

Au lieu de taper répétitivement ce code à l'infini, vous pouvez le créer automatiquement à l'aide d'une closure.

Créez un fichier typedproperty.py et mettez le code suivant dedans :

## typedproperty.py

def typedproperty(name, expected_type):
    private_name = '_' + name
    @property
    def prop(self):
        return getattr(self, private_name)

    @prop.setter
    def prop(self, value):
        if not isinstance(value, expected_type):
            raise TypeError(f'Expected {expected_type}')
        setattr(self, private_name, value)

    return prop

Maintenant, essayez-le en définissant une classe comme ceci :

from typedproperty import typedproperty

class Stock:
    name = typedproperty('name', str)
    shares = typedproperty('shares', int)
    price = typedproperty('price', float)

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

Essayez de créer une instance et de vérifier que la vérification de type fonctionne.

>>> s = Stock('IBM', 50, 91.1)
>>> s.name
'IBM'
>>> s.shares = '100'
... devrait générer un TypeError...
>>>

Exercice 7.8 : Simplifier les appels de fonction

Dans l'exemple ci-dessus, les utilisateurs pourraient trouver des appels tels que typedproperty('shares', int) un peu verbeux à taper - surtout s'ils sont répétés fréquemment. Ajoutez les définitions suivantes au fichier typedproperty.py :

String = lambda name: typedproperty(name, str)
Integer = lambda name: typedproperty(name, int)
Float = lambda name: typedproperty(name, float)

Maintenant, réécrivez la classe Stock pour utiliser ces fonctions à la place :

class Stock:
    name = String('name')
    shares = Integer('shares')
    price = Float('price')

    def __init__(self, name, shares, price):
        self.name = name
        self.shares = shares
        self.price = price

Ah, c'est un peu mieux. Le principal enseignement ici est que les closures et les lambda peuvent souvent être utilisés pour simplifier le code et éliminer la répétition ennuyeuse. Cela est souvent bénéfique.

Sommaire

Félicitations ! Vous avez terminé le laboratoire sur les fonctions de retour. Vous pouvez pratiquer d'autres laboratoires sur LabEx pour améliorer vos compétences.