Inspecter l'intérieur des fonctions

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

Dans ce laboratoire (lab), vous apprendrez à explorer les mécanismes internes des fonctions Python. Les fonctions en Python sont des objets dotés de leurs propres attributs et méthodes, et comprendre cela peut vous offrir une vision plus approfondie de leur fonctionnement. Cette connaissance vous permettra d'écrire un code plus puissant et adaptable.

Vous apprendrez à inspecter les attributs et les propriétés des fonctions, à utiliser le module inspect pour examiner les signatures des fonctions, et à appliquer ces techniques d'inspection pour améliorer une implémentation de classe. Le fichier que vous allez modifier est structure.py.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL python(("Python")) -.-> python/FunctionsGroup(["Functions"]) python(("Python")) -.-> python/ModulesandPackagesGroup(["Modules and Packages"]) python(("Python")) -.-> python/ObjectOrientedProgrammingGroup(["Object-Oriented Programming"]) python/FunctionsGroup -.-> python/function_definition("Function Definition") python/FunctionsGroup -.-> python/arguments_return("Arguments and Return Values") python/FunctionsGroup -.-> python/scope("Scope") python/FunctionsGroup -.-> python/build_in_functions("Build-in Functions") python/ModulesandPackagesGroup -.-> python/standard_libraries("Common Standard Libraries") python/ObjectOrientedProgrammingGroup -.-> python/classes_objects("Classes and Objects") subgraph Lab Skills python/function_definition -.-> lab-132511{{"Inspecter l'intérieur des fonctions"}} python/arguments_return -.-> lab-132511{{"Inspecter l'intérieur des fonctions"}} python/scope -.-> lab-132511{{"Inspecter l'intérieur des fonctions"}} python/build_in_functions -.-> lab-132511{{"Inspecter l'intérieur des fonctions"}} python/standard_libraries -.-> lab-132511{{"Inspecter l'intérieur des fonctions"}} python/classes_objects -.-> lab-132511{{"Inspecter l'intérieur des fonctions"}} end

Exploration des attributs de fonction

En Python, les fonctions sont considérées comme des objets de première classe. Que signifie cela ? Eh bien, c'est similaire à la façon dont vous avez différents types d'objets dans le monde réel, comme un livre ou un stylo. En Python, les fonctions sont également des objets, et tout comme les autres objets, elles ont leur propre ensemble d'attributs. Ces attributs peuvent nous fournir beaucoup d'informations utiles sur la fonction, comme son nom, où elle est définie et comment elle est implémentée.

Commençons notre exploration en ouvrant un shell interactif Python. Ce shell est comme une aire de jeu où nous pouvons écrire et exécuter du code Python immédiatement. Pour ce faire, nous allons d'abord naviguer jusqu'au répertoire du projet, puis démarrer l'interpréteur Python. Voici les commandes à exécuter dans votre terminal :

cd ~/project
python3

Maintenant que nous sommes dans le shell interactif Python, définissons une fonction simple. Cette fonction prendra deux nombres et les additionnera. Voici comment nous pouvons la définir :

def add(x, y):
    'Adds two things'
    return x + y

Dans ce code, nous avons créé une fonction nommée add. Elle prend deux paramètres, x et y, et retourne leur somme. La chaîne de caractères 'Adds two things' est appelée une docstring, qui est utilisée pour documenter ce que fait la fonction.

Utilisation de dir() pour inspecter les attributs de fonction

En Python, la fonction dir() est un outil pratique. Elle peut être utilisée pour obtenir une liste de tous les attributs et méthodes qu'un objet possède. Utilisons-la pour voir quels attributs notre fonction add a. Exécutez le code suivant dans le shell interactif Python :

dir(add)

Lorsque vous exécutez ce code, vous verrez une longue liste d'attributs. Voici un exemple de ce à quoi l'affichage pourrait ressembler :

['__annotations__', '__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__get__', '__getattribute__', '__globals__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__kwdefaults__', '__le__', '__lt__', '__module__', '__name__', '__ne__', '__new__', '__qualname__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__']

Cette liste montre tous les attributs et méthodes associés à la fonction add.

Accès aux informations de base sur la fonction

Maintenant, examinons de plus près certains des attributs de base des fonctions. Ces attributs peuvent nous indiquer des informations importantes sur la fonction. Exécutez le code suivant dans le shell interactif Python :

print(add.__name__)
print(add.__module__)
print(add.__doc__)

Lorsque vous exécutez ce code, vous verrez le résultat suivant :

add
__main__
Adds two things

Comprenons ce que chaque attribut signifie :

  • __name__ : Cet attribut nous donne le nom de la fonction. Dans notre cas, la fonction s'appelle add.
  • __module__ : Il nous indique le module dans lequel la fonction est définie. Lorsque nous exécutons du code dans le shell interactif, le module est généralement __main__.
  • __doc__ : C'est la chaîne de documentation de la fonction, ou docstring. Elle fournit une brève description de ce que fait la fonction.

Examen du code de la fonction

L'attribut __code__ d'une fonction est très intéressant. Il contient des informations sur la façon dont la fonction est implémentée, y compris son bytecode et d'autres détails. Voyons ce que nous pouvons en apprendre. Exécutez le code suivant dans le shell interactif Python :

print(add.__code__.co_varnames)
print(add.__code__.co_argcount)

Le résultat sera :

('x', 'y')
2

Voici ce que ces attributs nous indiquent :

  • co_varnames : C'est un tuple qui contient les noms de toutes les variables locales utilisées par la fonction. Dans notre fonction add, les variables locales sont x et y.
  • co_argcount : Cet attribut nous indique le nombre d'arguments que la fonction attend. Notre fonction add attend deux arguments, donc la valeur est 2.

Si vous êtes curieux de découvrir plus d'attributs de l'objet __code__, vous pouvez à nouveau utiliser la fonction dir(). Exécutez le code suivant :

dir(add.__code__)

Cela affichera tous les attributs de l'objet code, qui contiennent des détails de bas niveau sur la façon dont la fonction est implémentée.

Utilisation du module inspect

En Python, la bibliothèque standard comprend un module inspect très utile. Ce module est comme un outil de détective qui nous aide à collecter des informations sur les objets en temps réel (live objects) en Python. Les objets en temps réel peuvent être des modules, des classes et des fonctions. Au lieu de fouiller manuellement dans les attributs d'un objet pour trouver des informations, le module inspect offre des méthodes plus organisées et de haut niveau pour comprendre les propriétés des fonctions.

Continuons d'utiliser le même shell interactif Python pour explorer le fonctionnement de ce module.

Signatures de fonction

La fonction inspect.signature() est un outil pratique. Lorsque vous lui passez une fonction, elle retourne un objet Signature. Cet objet contient des détails importants sur les paramètres de la fonction.

Voici un exemple. Supposons que nous ayons une fonction nommée add. Nous pouvons utiliser la fonction inspect.signature() pour obtenir sa signature :

import inspect
sig = inspect.signature(add)
print(sig)

Lorsque vous exécutez ce code, le résultat sera :

(x, y)

Ce résultat nous montre la signature de la fonction, qui nous indique quels paramètres la fonction peut accepter.

Examen des détails des paramètres

Nous pouvons aller plus loin et obtenir des informations plus approfondies sur chaque paramètre de la fonction.

print(sig.parameters)

Le résultat de ce code sera :

OrderedDict([('x', <Parameter "x">), ('y', <Parameter "y">)])

Les paramètres de la fonction sont stockés dans un dictionnaire ordonné. Parfois, nous pouvons être seulement intéressés par les noms des paramètres. Nous pouvons convertir ce dictionnaire ordonné en un tuple pour extraire seulement les noms des paramètres.

param_names = tuple(sig.parameters)
print(param_names)

Le résultat sera :

('x', 'y')

Examen des paramètres individuels

Nous pouvons également examiner de plus près chaque paramètre individuel. Le code suivant parcourt chaque paramètre de la fonction et affiche quelques détails importants à son sujet.

for name, param in sig.parameters.items():
    print(f"Parameter: {name}")
    print(f"  Kind: {param.kind}")
    print(f"  Default: {param.default if param.default is not param.empty else 'No default'}")

Ce code nous montrera des détails sur chaque paramètre. Il nous indique le type de paramètre (s'il s'agit d'un paramètre positionnel, d'un paramètre mot - clé, etc.) et sa valeur par défaut s'il en a une.

Le module inspect a de nombreuses autres fonctions utiles pour l'introspection des fonctions. Voici quelques exemples :

  • inspect.getdoc(obj) : Cette fonction récupère la chaîne de documentation pour un objet. Les chaînes de documentation sont comme des notes que les programmeurs écrivent pour expliquer ce que fait un objet.
  • inspect.getfile(obj) : Elle nous aide à trouver le fichier où un objet est défini. Cela peut être très utile lorsque nous voulons localiser le code source d'un objet.
  • inspect.getsource(obj) : Cette fonction récupère le code source d'un objet. Elle nous permet de voir exactement comment l'objet est implémenté.

Application de l'inspection de fonction dans les classes

Maintenant, nous allons utiliser ce que nous avons appris sur l'inspection de fonction pour améliorer une implémentation de classe. L'inspection de fonction nous permet de regarder à l'intérieur des fonctions et de comprendre leur structure, comme les paramètres qu'elles prennent. Dans ce cas, nous l'utiliserons pour rendre notre code de classe plus efficace et moins sujet aux erreurs. Nous allons modifier une classe Structure afin qu'elle puisse détecter automatiquement les noms de champs à partir de la signature de la méthode __init__.

Comprendre la classe Structure

Le fichier structure.py contient une classe Structure. Cette classe agit comme une classe de base, ce qui signifie que d'autres classes peuvent en hériter pour créer des objets de données structurées. Actuellement, pour définir les attributs des objets créés à partir de classes héritant de Structure, nous devons définir une variable de classe _fields.

Ouvrons le fichier dans l'éditeur. Nous utiliserons la commande suivante pour naviguer jusqu'au répertoire du projet :

cd ~/project

Une fois que vous avez exécuté cette commande, vous pouvez trouver et afficher la classe Structure existante dans le fichier structure.py dans l'IDE Web.

Création d'une classe Stock

Créons une classe Stock qui hérite de la classe Structure. L'héritage signifie que la classe Stock aura toutes les fonctionnalités de la classe Structure et pourra également ajouter les siennes. Nous allons ajouter le code suivant à la fin du fichier structure.py :

class Stock(Structure):
    _fields = ('name', 'shares', 'price')

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

Cependant, il y a un problème avec cette approche. Nous devons définir à la fois le tuple _fields et la méthode __init__ avec les mêmes noms de paramètres. Cela est redondant car nous écrivons essentiellement les mêmes informations deux fois. Si nous oublions de mettre à jour l'un lorsque nous modifions l'autre, cela peut entraîner des erreurs.

Ajout d'une méthode de classe set_fields

Pour résoudre ce problème, nous allons ajouter une méthode de classe set_fields à la classe Structure. Cette méthode détectera automatiquement les noms de champs à partir de la signature de __init__. Voici le code que nous devons ajouter à la classe Structure :

@classmethod
def set_fields(cls):
    ## Get the signature of the __init__ method
    import inspect
    sig = inspect.signature(cls.__init__)

    ## Get parameter names, skipping 'self'
    params = list(sig.parameters.keys())[1:]

    ## Set _fields attribute on the class
    cls._fields = tuple(params)

Cette méthode utilise le module inspect, qui est un outil puissant en Python pour obtenir des informations sur des objets tels que des fonctions et des classes. Tout d'abord, elle obtient la signature de la méthode __init__. Ensuite, elle extrait les noms des paramètres, mais ignore le paramètre self car self est un paramètre spécial dans les classes Python qui fait référence à l'instance elle - même. Enfin, elle définit la variable de classe _fields avec ces noms de paramètres.

Modification de la classe Stock

Maintenant que nous avons la méthode set_fields, nous pouvons simplifier notre classe Stock. Remplacez le code précédent de la classe Stock par le suivant :

class Stock(Structure):
    def __init__(self, name, shares, price):
        self._init()

## Call set_fields to automatically set _fields from __init__
Stock.set_fields()

De cette façon, nous n'avons pas à définir manuellement le tuple _fields. La méthode set_fields s'en chargera pour nous.

Test de la classe modifiée

Pour nous assurer que notre classe modifiée fonctionne correctement, nous allons créer un script de test simple. Créez un nouveau fichier appelé test_structure.py et ajoutez le code suivant :

from structure import Stock

def test_stock():
    ## Create a Stock object
    s = Stock(name='GOOG', shares=100, price=490.1)

    ## Test string representation
    print(f"Stock representation: {s}")

    ## Test attribute access
    print(f"Name: {s.name}")
    print(f"Shares: {s.shares}")
    print(f"Price: {s.price}")

    ## Test attribute modification
    s.shares = 50
    print(f"Updated shares: {s.shares}")

    ## Test attribute error
    try:
        s.share = 50  ## Misspelled attribute
        print("Error: Did not raise AttributeError")
    except AttributeError as e:
        print(f"Correctly raised: {e}")

if __name__ == "__main__":
    test_stock()

Ce script de test crée un objet Stock, teste sa représentation sous forme de chaîne de caractères, accède à ses attributs, modifie un attribut et essaie d'accéder à un attribut mal orthographié pour vérifier s'il lève l'erreur correcte.

Pour exécuter le script de test, utilisez la commande suivante :

python3 test_structure.py

Vous devriez voir un résultat similaire à ceci :

Stock representation: Stock('GOOG',100,490.1)
Name: GOOG
Shares: 100
Price: 490.1
Updated shares: 50
Correctly raised: No attribute share

Comment cela fonctionne

  1. La méthode set_fields utilise inspect.signature() pour obtenir les noms de paramètres de la méthode __init__. Cette fonction nous donne des informations détaillées sur les paramètres de la méthode __init__.
  2. Elle définit ensuite automatiquement la variable de classe _fields en fonction de ces noms de paramètres. Ainsi, nous n'avons pas à écrire les mêmes noms de paramètres à deux endroits différents.
  3. Cela élimine le besoin de définir manuellement à la fois _fields et __init__ avec des noms de paramètres correspondants. Cela rend notre code plus facilement maintenable car si nous modifions les paramètres dans la méthode __init__, _fields sera mis à jour automatiquement.

Cette approche utilise l'inspection de fonction pour rendre notre code plus facilement maintenable et moins sujet aux erreurs. C'est une application pratique des capacités d'introspection de Python, qui nous permettent d'examiner et de modifier des objets à l'exécution.

✨ Vérifier la solution et pratiquer

Résumé

Dans ce laboratoire (lab), vous avez appris à inspecter l'intérieur des fonctions Python. Vous avez examiné directement les attributs des fonctions en utilisant des méthodes telles que dir() et accédé à des attributs spéciaux tels que __name__, __doc__ et __code__. Vous avez également utilisé le module inspect pour obtenir des informations structurées sur les signatures et les paramètres des fonctions.

L'inspection de fonction est une fonctionnalité puissante de Python qui vous permet d'écrire un code plus dynamique, flexible et facilement maintenable. La capacité d'examiner et de manipuler les propriétés des fonctions à l'exécution offre des possibilités pour la métaprogrammation, la création de code auto - documenté et la construction de frameworks avancés.