Recherche de site Web

Utilisation et création de variables globales dans vos fonctions Python


Une variable globale est une variable que vous pouvez utiliser à partir de n'importe quelle partie d'un programme, y compris dans les fonctions. Utiliser des variables globales dans vos fonctions Python peut être délicat. Vous devrez faire la différence entre l'accès et la modification des valeurs de la variable globale cible si vous souhaitez que votre code fonctionne correctement.

Les variables globales peuvent jouer un rôle fondamental dans de nombreux projets logiciels car elles permettent le partage de données dans l'ensemble d'un programme. Cependant, vous devez les utiliser judicieusement pour éviter les problèmes.

Dans ce didacticiel, vous allez :

  • Comprendre les variables globales et leur fonctionnement en Python
  • Accéder directement aux variables globales dans vos fonctions Python
  • Modifier et créer des variables globales dans les fonctions à l'aide du mot-clé global
  • Accédez, créez et modifiez des variables globales dans vos fonctions avec la fonction globals()
  • Explorez des stratégies pour éviter d'utiliser des variables globales dans le code Python

Pour suivre ce didacticiel, vous devez avoir une solide compréhension de la programmation Python, y compris des concepts fondamentaux tels que les variables, les types de données, la portée, la mutabilité, les fonctions et les classes.

Utilisation de variables globales dans les fonctions Python

Les variables globales sont celles auxquelles vous pouvez accéder et modifier depuis n'importe où dans votre code. En Python, vous définirez généralement des variables globales au niveau du module. Ainsi, le module conteneur est leur portée.

Une fois que vous avez défini une variable globale, vous pouvez l'utiliser depuis le module lui-même ou depuis d'autres modules de votre code. Vous pouvez également utiliser des variables globales dans vos fonctions. Cependant, ces cas peuvent devenir un peu déroutants en raison des différences entre accéder et modifier les variables globales dans les fonctions.

Pour comprendre ces différences, considérons que Python peut rechercher des variables dans quatre portées différentes :

  • La portée locale, ou au niveau de la fonction, qui existe à l'intérieur des fonctions
  • La portée englobante, ou non locale, qui apparaît dans les fonctions imbriquées
  • Le périmètre global, qui existe au niveau du module
  • La portée intégrée, qui est une portée spéciale pour les noms intégrés de Python

Pour illustrer, disons que vous êtes à l’intérieur d’une fonction interne. Dans ce cas, Python peut rechercher des noms dans les quatre étendues.

Lorsque vous accédez à une variable dans cette fonction interne, Python regarde d'abord à l'intérieur de cette fonction. Si la variable n’existe pas, alors Python continue avec la portée englobante de la fonction externe. Si la variable n'y est pas définie non plus, alors Python passe aux étendues globales et intégrées dans cet ordre. Si Python trouve la variable, vous récupérez la valeur. Sinon, vous obtenez une NameError :

>>> # Global scope

>>> def outer_func():
...     # Non-local scope
...     def inner_func():
...         # Local scope
...         print(some_variable)
...     inner_func()
...

>>> outer_func()
Traceback (most recent call last):
    ...
NameError: name 'some_variable' is not defined

>>> some_variable = "Hello from global scope!"
>>> outer_func()
Hello from global scope!

Lorsque vous lancez une session interactive, elle démarre au niveau du module de portée globale. Dans cet exemple, vous avez outer_func(), qui définit inner_func() comme une fonction imbriquée. Du point de vue de cette fonction imbriquée, son propre bloc de code représente la portée locale, tandis que le bloc de code outer_func() avant l'appel à inner_func() représente la portée non locale. portée.

Si vous appelez outer_func() sans définir some_variable dans aucune de vos étendues actuelles, vous obtenez une exception NameError car le nom n'est pas défini .

Si vous définissez some_variable dans la portée globale et que vous appelez ensuite outer_func(), alors vous obtenez Bonjour ! sur votre écran. En interne, Python a recherché les portées locale, non locale et globale pour trouver some_variable et imprimer son contenu. Notez que vous pouvez définir cette variable dans l’une des trois portées et Python la trouvera.

Ce mécanisme de recherche permet d'utiliser des variables globales depuis des fonctions internes. Cependant, en profitant de cette fonctionnalité, vous pouvez rencontrer quelques problèmes. Par exemple, accéder à une variable fonctionne, mais modifier directement une variable ne fonctionne pas :

>>> number = 42

>>> def access_number():
...     return number
...

>>> access_number()
42

>>> def modify_number():
...     number = 7
...

>>> modify_number()
>>> number
42

La fonction access_number() fonctionne correctement. Il recherche le numéro et le trouve dans la portée globale. En revanche, modify_number() ne fonctionne pas comme prévu. Pourquoi cette fonction ne met-elle pas à jour la valeur de votre variable globale, number ? Le problème est la portée de la variable. Vous ne pouvez pas modifier directement une variable depuis une portée de haut niveau comme global dans une portée de niveau inférieur comme local.

En interne, Python suppose que tout nom directement attribué dans une fonction est local à cette fonction. Par conséquent, le nom local, number, masque son frère global.

En ce sens, les variables globales se comportent comme des noms en lecture seule. Vous pouvez accéder à leurs valeurs, mais vous ne pouvez pas les modifier.

Obtenir une exception UnboundLocalError est un autre problème courant lorsque vous essayez de modifier une variable globale dans une fonction. Considérez la fonction démonstrative suivante qui tente d'utiliser certaines variables globales :

>>> a = 10
>>> b = 20
>>> c = 30

>>> def print_globals():
...     print(a, b, c)
...     c = 100
...     print(c)
...

Dans print_globals(), vous imprimez d'abord les variables globales a, b et c. Ensuite, vous mettez à jour directement la valeur de c. Enfin, vous imprimez la version mise à jour de c. Vous pouvez vous attendre au résultat suivant :

>>> # Expected output
>>> print_globals()
10 20 30
100

Cependant, cette sortie n'apparaît jamais sur votre écran. Au lieu de cela, vous obtenez une erreur qui pourrait vous surprendre :

>>> print_globals()
Traceback (most recent call last):
    ...
UnboundLocalError: cannot access local variable 'c'
    where it is not associated with a value

Le problème est que l'affectation c=100 crée une nouvelle variable locale qui remplace la variable globale d'origine, c. Donc, vous avez un conflit de nom. L'exception vient du premier appel à print() car, à ce moment-là, la version locale de c n'est pas encore définie. Ainsi, vous obtenez le UnboundLocalError.

En pratique, ce problème survient le plus souvent dans les opérations d'affectation augmentée avec des variables globales :

>>> counter = 0

>>> def increment():
...     counter += 1
...

>>> increment()
Traceback (most recent call last):
    ...
UnboundLocalError: cannot access local variable 'counter'
    where it is not associated with a value

En Python, si votre fonction fait simplement référence à une variable de la portée globale, alors la fonction suppose que la variable est globale. Si vous attribuez le nom de la variable n'importe où dans le corps de la fonction, vous définissez alors une nouvelle variable locale portant le même nom que votre variable globale d'origine.

Dans une fonction, vous ne pouvez pas accéder directement à une variable globale si vous avez défini des variables locales portant le même nom n'importe où dans la fonction.

Si vous souhaitez modifier une variable globale dans une fonction, vous devez alors indiquer explicitement à Python d'utiliser la variable globale plutôt que d'en créer une nouvelle locale. Pour ce faire, vous pouvez utiliser l'un des éléments suivants :

  1. Le mot clé global
  2. La fonction intégrée globals()

Dans les sections suivantes, vous apprendrez à utiliser les deux outils dans votre code pour modifier les variables globales depuis vos fonctions.

Le mot clé global

Vous avez déjà appris qu'accéder aux variables globales directement depuis l'intérieur d'une fonction est tout à fait possible, à moins que vous n'ayez des variables locales portant le même nom dans la fonction conteneur. En dehors de cela, que se passe-t-il si vous devez également modifier la valeur de la variable ? Dans ce cas, vous pouvez utiliser le mot-clé global pour déclarer que vous souhaitez faire référence à la variable globale.

La syntaxe générale pour écrire une instruction globale est la suivante :

global variable_0, variable_1, ..., variable_n

Notez que la première variable est obligatoire, tandis que les autres variables sont facultatives. Pour illustrer comment vous pouvez utiliser cette instruction, revenez à l'exemple counter et increment(). Vous pouvez corriger le code en procédant comme suit :

>>> counter = 0

>>> def increment():
...     global counter
...     counter += 1
...

>>> increment()
>>> counter
1
>>> increment()
>>> counter
2

Dans cette nouvelle version de increment(), vous utilisez le mot-clé global pour déclarer que vous souhaitez que la fonction mette à jour la variable globale, counter. Avec cette instruction, vous pouvez désormais modifier la valeur de counter dans votre fonction, comme vous pouvez le voir ci-dessus.

Il est important de noter que vous devez faire vos déclarations globales avant d'utiliser la variable cible. Sinon, vous obtiendrez une SyntaxError :

>>> counter = 0

>>> def increment():
...     counter += 1
...     global counter
...
SyntaxError: name 'counter' is assigned to before global declaration

Dans cet exemple, vous essayez d'incrémenter counter sans le déclarer comme global dans increment(). Par conséquent, vous obtenez une SyntaxError.

L'instruction global indique à l'interpréteur Python lorsque vous trouvez le nom counter dans cette fonction, il fait référence à la variable globale, donc n'en créez pas une locale.

En résumé, vous ne devez utiliser le mot-clé global que si vous souhaitez réaffecter la variable globale au sein d'une fonction. Si vous avez simplement besoin de lire ou d'accéder à la valeur de la variable globale depuis votre fonction, vous n'avez pas besoin du mot-clé global.

Même si l'objectif du mot-clé global est de gérer les variables globales dans les fonctions, ce mot-clé n'est pas le seul outil que vous pouvez utiliser à cette fin. Vous pouvez également utiliser la fonction intégrée globals().

La fonction globals()

La fonction intégrée globals() vous permet d'accéder à la table de noms de la portée globale, qui est un dictionnaire inscriptible contenant vos noms globaux actuels et leurs valeurs correspondantes. Vous pouvez utiliser cette fonction pour accéder ou modifier la valeur d'une variable globale à partir de vos fonctions.

Par exemple, cette fonction est pratique lorsque vous avez des variables locales portant le même nom que vos variables globales cibles et que vous devez toujours utiliser la variable globale à l'intérieur de la fonction :

>>> a = 10
>>> b = 20
>>> c = 30

>>> def print_globals():
...     print(a, b, globals()["c"])
...     c = 100
...     print(c)
...

>>> print_globals()
10 20 30
100

>>> c
30

Dans cette nouvelle implémentation de print_globals(), c fait référence à la version locale de la variable, tandis que globals()["c"] fait référence à la version globale.

Étant donné que globals() renvoie un dictionnaire, vous pouvez accéder à ses clés comme vous accéderiez aux clés d'un dictionnaire normal. Notez que vous devez utiliser le nom de la variable comme chaîne pour accéder à la clé correspondante dans globals().

Le dictionnaire de noms renvoyé par globals() est mutable, ce qui signifie que vous pouvez modifier la valeur des variables globales existantes en tirant parti de ce dictionnaire. Dans le dernier exemple, vous pouvez voir comment la variable globale, c, contient toujours la même valeur.

Voici une autre version de increment(). C'est un peu plus difficile à lire, mais ça marche :

>>> counter = 0

>>> def increment():
...     globals()["counter"] += 1
...

>>> increment()
>>> counter
1
>>> increment()
>>> counter
2

Dans cet exemple, vous avez implémenté increment() en utilisant globals() au lieu du mot-clé global. Les deux implémentations fonctionnent de la même manière, ce que vous pouvez confirmer à partir du contenu de counter après des appels consécutifs à la fonction.

Notez que l'utilisation de globals() pour accéder et mettre à jour les variables globales peut rendre votre code difficile à lire et à comprendre, en particulier pour les gros programmes. Il est généralement préférable d'utiliser le mot-clé global à moins que vous n'ayez des variables locales portant le même nom que vos variables globales cibles.

Comprendre comment la mutabilité affecte les variables globales

Python a des types de données mutables et immuables. Les types mutables, tels que les listes et les dictionnaires, vous permettent de modifier ou de muter leurs valeurs sur place. En revanche, les types immuables, comme les nombres, les chaînes et les tuples, ne vous permettent pas de modifier leurs valeurs au rythme.

Pour illustrer rapidement le fonctionnement de la mutabilité et de l'immuabilité en Python, considérons l'exemple suivant, dans lequel vous modifiez une liste sur place, en modifiant la valeur de ses éléments ou éléments :

>>> numbers = [1, 2, 3]
>>> id(numbers)
4390329600
>>> numbers[0] = 10
>>> numbers[1] = 20
>>> numbers[2] = 30
>>> numbers
[10, 20, 30]
>>> id(numbers)
4390329600

Dans cet exemple, vous modifiez la valeur de numbers. Étant donné que les listes Python sont des objets mutables, cette mutation ne change pas l'identité de votre objet liste, seulement sa valeur. Vous pouvez le confirmer en comparant la sortie de la fonction intégrée id() avant et après les mutations.

Maintenant, que se passera-t-il si vous essayez d’effectuer une action similaire sur un type immuable comme un tuple ? Dans cet exemple de code, vous essayez :

>>> letters = ("a", "b", "c", "d")
>>> letters[0] = "aa"
Traceback (most recent call last):
    ...
TypeError: 'tuple' object does not support item assignment

Ici, vous utilisez un tuple pour stocker les lettres. Les tuples Python sont immuables, vous ne pouvez donc pas modifier leurs éléments comme vous l'avez fait avec votre liste de nombres. Si vous essayez de le faire, vous obtenez une TypeError.

Lorsqu'il s'agit d'utiliser des variables globales qui pointent vers des objets mutables à l'intérieur de vos fonctions, vous remarquerez qu'il est possible de modifier leurs valeurs directement sur place.

Par exemple, supposons que vous créez une application API REST. Pour plus de commodité, vous utilisez un dictionnaire global pour partager les configurations d'API dans le code de l'application. Également pour plus de commodité, vous écrivez une courte fonction qui vous permet de mettre à jour les paramètres de configuration :

>>> config = {
...     "base_url": "https://example.com/api",
...     "timeout": 3,
... }

>>> def update_config(**kwargs):
...     for key, value in kwargs.items():
...         if key in {"api_key", "base_url", "timeout"}:
...             config[key] = value
...         else:
...             raise KeyError(key)
...

>>> update_config(api_key="111222333")
>>> config
{
    'api_key': '111222333',
    'base_url': 'https://example.com/api',
    'timeout': 3
}

>>> update_config(timeout=5)
>>> config
{
    'api_key': '111222333',
    'base_url': 'https://example.com/api',
    'timeout': 5
}

Dans cet exemple, la fonction update_config() permet de mettre à jour les paramètres de configuration de l'application. La fonction prend uniquement des arguments de mots-clés. Chaque argument doit être un paramètre de configuration valide avec sa valeur correspondante. Vous vérifiez cette condition avec l'opérateur in dans une instruction conditionnelle. Si le paramètre n'est pas valide, alors la fonction génère une KeyError.

Étant donné que les dictionnaires Python sont des types de données mutables, vous pouvez modifier les valeurs de config en place depuis update_config() sans utiliser global ou globals(). Vous pouvez même ajouter de nouvelles paires clé-valeur s'il s'agit de paramètres de configuration valides. Par exemple, le premier appel à update_config() ajoute le paramètre api_key, tandis que le deuxième appel met à jour le paramètre timeout.

Il est important de noter que les configurations et les paramètres constituent souvent une partie importante de l’état global d’une application ou d’un système. La modification d'une configuration pendant que ce système est en cours d'exécution entraîne une mutation de son état global. Vous ne devriez effectuer ce type de changement que si vous êtes très prudent. Certaines applications et systèmes logiciels demandent un redémarrage ou un rechargement après avoir modifié certains de leurs paramètres de configuration. C’est une bonne façon de faire face à ce type de mutation étatique.

Création de variables globales dans une fonction

Comme vous l'avez déjà appris, vous pouvez utiliser le mot-clé global ou la fonction intégrée globals() pour accéder et modifier une variable globale dans une fonction. Ce qui est vraiment intéressant et probablement surprenant, c'est que vous pouvez également utiliser ces outils pour créer des variables globales à l'intérieur d'une fonction.

Dans cet exemple, vous créez une variable globale dans une fonction à l'aide du mot-clé global :

>>> def set_global_variable():
...     global number
...     number = 7
...

>>> set_global_variable()
>>> number
7

Ici, la fonction set_global_variable() utilise le mot-clé global pour définir number comme variable globale. Lorsque vous appelez la fonction, numéro est défini et sa valeur est définie sur 7. Après l'appel, vous pouvez accéder au numéro depuis n'importe où ailleurs dans le programme.

Vous pouvez également utiliser la fonction globals() pour définir de nouvelles variables globales dans vos fonctions. Cet outil vous offre une flexibilité supplémentaire, vous permettant de créer dynamiquement des variables globales :

>>> def dynamic_global_variable(name, value):
...     globals()[name] = value
...

>>> dynamic_global_variable("number", 42)
>>> number
42

Dans cet exemple, la fonction dynamic_global_variable() utilise la fonction globals() pour définir une nouvelle variable globale en utilisant un nom comme chaîne et une valeur. L'appel à votre fonction crée une nouvelle variable globale appelée number avec une valeur de 42.

Pour un exemple plus réaliste, disons que vous disposez d'un fichier de configuration au format JSON. Vous souhaitez traiter le fichier et charger tous les paramètres de configuration directement dans votre espace de noms global afin de pouvoir utiliser ces paramètres dans d'autres parties de votre code en tant que variables globales.

Votre fichier config.json peut ressembler à ceci :

{
    "database": {
      "host": "localhost",
      "port": 5432,
      "name": "database.db",
      "user": "username",
      "password": "secret"
    },
    "api_key": "111222333",
    "base_url": "https://example.com/api",
    "timeout": 60
}

Ce fichier définit une clé "database", dont la valeur est un dictionnaire de clés et de valeurs. Vous utiliserez ce dictionnaire comme paramètre initial pour la base de données de votre application. Le fichier définit également une clé API, une URL de base et un délai d'attente pour la connexion à une API.

Le code suivant se charge de charger le fichier JSON et de créer pour vous l'ensemble requis de variables globales :

>>> import json

>>> def set_config_key(key, value):
...     globals()[key] = value
...

>>> with open("config.json") as json_config:
...     for key, value in json.load(json_config).items():
...         set_config_key(key, value)
...

>>> database
{
    'host': 'localhost',
    'port': 5432,
    'name': 'database.db',
    'user': 'username',
    'password': 'secret'
}
>>> api_key
'000111222333'
>>> base_url
'https://example.com/api'
>>> timeout
60

Dans cet extrait de code, vous définissez set_config_key(), qui prend key et value comme arguments. Ensuite, vous utilisez globals() pour créer une variable globale en utilisant key comme nom de variable et value comme valeur.

Dans l'instruction with, vous ouvrez le fichier de configuration en lecture et affectez l'objet fichier à la variable json_config. Ensuite, vous utilisez une boucle for pour traiter la configuration chargée et créer de nouvelles variables globales pour chaque paire clé-valeur.

Une fois ce processus terminé, vous pouvez accéder à tous vos paramètres de configuration en tant que variables globales dans votre code.

Même si définir des variables globales dans vos fonctions à l'aide du mot-clé global ou de la fonction globals() est parfaitement possible en Python, ce n'est pas une bonne pratique. Vous devez être prudent lorsque vous définissez des variables globales de cette manière. Ces variables deviennent disponibles pour l'ensemble de votre programme, afin que d'autres éléments de votre code puissent les modifier. Pour cette raison, vous pouvez vous retrouver avec un code difficile à comprendre, à tester, à maintenir et à déboguer.

Peut-être que vous remarquez une petite tendance. Il existe de nombreuses façons d'utiliser et de définir des variables globales dans vos fonctions, mais cela signifie-t-il que vous devriez ? Dans la section suivante, vous explorerez cette question.

Décider quand utiliser des variables globales

Au cours de votre parcours de programmation, vous rencontrerez des situations dans lesquelles l'utilisation d'une variable globale peut être appropriée, voire nécessaire. Par exemple, vous pouvez utiliser des variables globales pour gérer :

  • Configurations et paramètres : si vous disposez de paramètres qui s'appliquent à l'ensemble de votre programme, l'utilisation de variables globales peut être une solution rapide qui vous permettra d'accéder à ces paramètres depuis n'importe où dans votre code.
  • Petits scripts utilitaires : si vous écrivez de petits scripts pour automatiser certaines parties de votre flux de travail ou effectuer de petites tâches, l'utilisation de variables globales peut vous aider à être opérationnel plus rapidement, vous évitant ainsi de sur-ingénierier vos solutions.
  • Mise en cache des données : si vous devez mettre en cache des données pour des raisons de performances, vous pouvez profiter des variables globales pour stocker les données mises en cache.

Il est important de noter que vous devez utiliser les variables globales avec parcimonie et uniquement lorsque cela est absolument nécessaire. Si vous décidez d'utiliser des variables globales dans votre code, assurez-vous de documenter leur utilisation, en notant notamment pourquoi vous comptez sur elles.

En pratique, l’utilisation de variables globales dans votre code peut présenter certains inconvénients. Par exemple, les variables globales peuvent :

  • Rendre difficile le suivi des modifications apportées à la variable, car vous pouvez modifier la variable depuis n'importe où dans votre code.
  • Générez des effets secondaires indésirables, car les modifications apportées à une variable globale peuvent avoir un impact sur le comportement d'autres parties de votre code.
  • Rendez votre code difficile à tester et à déboguer car le comportement et le résultat de vos fonctions dépendront de la valeur actuelle de la variable globale
  • Affecte la réutilisabilité de votre code, car les fonctions qui reposent sur des variables globales sont étroitement couplées à ces variables.
  • Provoquer des conflits de noms, car vous pourriez accidentellement réutiliser un nom global dans différentes parties de votre code, entraînant des résultats inattendus.
  • Rompre l'encapsulation car les variables globales introduisent des dépendances cachées entre les différentes parties de votre code
  • Rendez votre code difficile à refactoriser car les modifications apportées à une partie du code peuvent affecter d'autres parties

Même si vous pouvez tirer parti des variables globales pour résoudre des problèmes spécifiques, vous feriez mieux de minimiser leur utilisation dans votre code. Si vous devez quand même les utiliser, vous pouvez éviter certains de leurs inconvénients en les rendant constantes. En effet, la plupart des problèmes liés à l'utilisation de variables globales proviennent de la modification de leur valeur lors de l'exécution de votre code.

Dans les sections suivantes, vous découvrirez certaines stratégies et techniques couramment utilisées pour éviter d'utiliser des variables globales dans vos fonctions.

Éviter les variables globales dans votre code et vos fonctions

Comme vous l'avez appris dans la section précédente, s'appuyer sur des variables globales dans vos fonctions et dans votre code peut provoquer quelques effets indésirables. Ces variables peuvent rendre votre code plus difficile à comprendre, à tester et à déboguer. Ils peuvent également conduire à un code moins maintenable et réutilisable. Vous devez donc utiliser les variables globales avec soin et contrôle.

Heureusement, il existe des stratégies intéressantes que vous pouvez utiliser pour minimiser le besoin de variables globales dans votre code.

Utiliser des constantes globales

La stratégie la plus intuitive pour éviter les variables globales consiste peut-être à utiliser des constantes globales. Contrairement aux variables, les constantes ne doivent pas changer de valeur lors de l'exécution du code. Ainsi, ils promeuvent l’utilisation sûre des noms mondiaux.

Pour illustrer comment utiliser des constantes au lieu d'utiliser des variables globales, supposons que vous disposez de certains paramètres de configuration généraux et que vous souhaitez les rendre accessibles depuis toutes les parties de votre programme. Dans ce cas, vous pouvez utiliser des constantes globales. Vous définirez généralement des constantes en haut de votre module juste après les importations.

Voici un exemple de quelques constantes en Python :

API_KEY = "111222333"
BASE_URL = "https://example.com/api"
TIMEOUT = 3

Vous pouvez utiliser des constantes pour stocker des valeurs qui ne changeront pas pendant l’exécution de votre programme. Les constantes peuvent vous aider à empêcher toute modification accidentelle des valeurs. Ils améliorent également la lisibilité et la maintenabilité du code.

Une fois que vous avez défini vos constantes, vous pouvez y accéder dans vos fonctions à travers votre code. Vous devez simplement vous assurer de les conserver inchangés. Par exemple, vous trouverez ci-dessous une fonction qui charge une image à partir de la page API principale de la NASA :

# nasa.py

import webbrowser

import requests

API_KEY = "DEMO_KEY"
BASE_URL = "https://api.nasa.gov/planetary"
TIMEOUT = 3

def load_earth_image(date):
    endpoint = f"{BASE_URL}/apod"
    try:
        response = requests.get(
            endpoint,
            params={
                "api_key": API_KEY,
                "date": date,
            },
            timeout=TIMEOUT,
        )
    except requests.exceptions.RequestException as e:
        print(f"Error connecting to API: {e}")
        return

    try:
        url = response.json()["url"]
    except KeyError:
        print(f"No image available on {date}")
        return

    webbrowser.open(url)

Dans cet exemple, vous importez un navigateur Web depuis la bibliothèque standard Python. Ce module vous permet d'ouvrir rapidement des URL dans votre navigateur par défaut. Ensuite, vous importez la bibliothèque requests pour envoyer des requêtes HTTP à l'API REST cible.

Dans load_earth_image(), vous accédez directement aux trois constantes globales, comme vous le feriez avec n'importe quelle variable globale en Python. Cependant, vous ne modifiez la valeur d’aucune des constantes de la fonction.

Vous pouvez désormais réutiliser ces constantes dans d’autres fonctions associées. Utiliser des constantes de cette manière vous permet d’améliorer la lisibilité et la maintenabilité de votre code. De plus, comme elles sont constantes, vous n’avez pas besoin de suivre leurs valeurs actuelles lorsque vous déboguez ou testez le code. Vous connaîtrez toujours leurs valeurs.

Pour essayer l’exemple ci-dessus, continuez et exécutez le code suivant :

>>> from nasa import load_earth_image

>>> load_earth_image("2023-04-21")

L'appel à load_earth_image() vous obtiendra l'image dont dispose la NASA pour le 21 avril 2023. L'image s'ouvrira dans votre navigateur par défaut. Vous pouvez expérimenter avec d'autres dates et obtenir vos propres images.

Prendre des arguments et renvoyer des valeurs dans les fonctions

Écrire des fonctions qui prennent des arguments et renvoient des valeurs au lieu de s'appuyer sur des variables globales est une autre bonne stratégie pour éviter les variables globales dans votre code. Ces types de fonctions sont appelés fonctions pures et constituent le fondement de la programmation fonctionnelle, qui est un paradigme de programmation populaire de nos jours.

Les fonctions pures sont souvent totalement découplées des autres parties de votre code. Plus important encore, ils ne produisent aucun effet secondaire, comme un changement d’état des variables globales. Ces fonctionnalités les rendent plus réutilisables et plus faciles à comprendre, à déboguer, à tester et à maintenir.

A titre d'exemple, voici une nouvelle implémentation de votre ancien ami increment() sans utiliser de variable globale à l'intérieur de la fonction. Dans ce cas, la fonction prend simplement une valeur en argument et la renvoie incrémentée de un :

>>> def increment(value=0):
...     return value + 1
...

>>> counter = increment()
>>> counter
1
>>> counter = increment(counter)
>>> counter
2
>>> counter = increment(counter)
>>> counter
3

Cette nouvelle version de increment() ne s'appuie pas sur les valeurs externes des variables globales. Ainsi, son résultat ne dépendra que de son argument d’entrée. Chaque fois que vous exécutez cette fonction avec le même argument, vous obtiendrez le même résultat, ce qui rend cette fonction plus facile à tester, à comprendre et à déboguer.

La fonction est complètement découplée de toute autre partie de votre programme afin que vous puissiez la réutiliser même dans un autre programme.

Dans cet exemple, vous modifiez votre variable counter uniquement dans la portée globale. Vous n’effectuez pas de mutations inter-portées, vous rendez donc votre code plus sûr et plus facile à comprendre.

Utiliser des classes, des méthodes et des attributs

Parfois, vous disposez d’une ou plusieurs fonctions qui opèrent sur ou avec certaines variables globales. Dans ces cas, vous pouvez penser à écrire une classe qui transforme les fonctions en méthodes et les variables globales en attributs d'instance.

Par exemple, disons que vous codez une application pour gérer votre compte bancaire. Vous commencez par créer un ensemble de fonctions qui opèrent sur le solde du compte :

# account_global.py

balance = 0

def deposit(amount):
    global balance
    balance += amount
    print(f"Successful deposit: +${amount:,.2f}")

def withdraw(amount):
    global balance
    if balance - amount > 0:
        balance -= amount
        print(f"Successful withdrawal: -${amount:,.2f}")
    else:
        print("Not enough funds for this transaction")

def get_balance():
    return balance

def main():
    while True:
        operation = input(
            "What would you like to do?\n"
            " d) deposit  "
            " w) withdraw  "
            " b) balance  "
            " q) quit\n"
            "> "
        )
        if operation in "dD":
            amount = float(input("Enter the deposit amount: "))
            deposit(amount)
        elif operation in "wW":
            amount = float(input("Enter the withdrawal amount: "))
            withdraw(amount)
        elif operation in "bB":
            print(f"Current balance: ${get_balance():,.2f}")
        elif operation in "qQ":
            print("Goodbye!")
            break
        else:
            print("Invalid operation. Please try again.")

main()

Dans ce code, vous vous appuyez sur une variable globale pour stocker le solde de votre compte bancaire. Vous disposez de trois fonctions qui assurent des opérations courantes sur le solde du compte. Vous pouvez déposer de l'argent, retirer de l'argent et vérifier le solde actuel de votre compte.

La fonction main() définit une boucle while qui vous présente une interface utilisateur textuelle (TUI). Grâce à cette interface, vous pouvez demander à l'application d'effectuer les opérations souhaitées sur votre compte.

Voici comment cette application fonctionne en pratique :

$ python account_global.py
What would you like to do?
 d) deposit   w) withdraw   b) balance   q) quit
> d
Enter the deposit amount: 6000
Successful deposit: +$6,000.00
What would you like to do?
 d) deposit   w) withdraw   b) balance   q) quit
> w
Enter the withdrawal amount: 2380
Successful withdrawal: -$2,380.00
What would you like to do?
 d) deposit   w) withdraw   b) balance   q) quit
> b
Current balance: $3,620.00
What would you like to do?
 d) deposit   w) withdraw   b) balance   q) quit
> q
Goodbye!

Super! L'application fonctionne comme prévu. Il vous permet d'effectuer des dépôts, de retirer des fonds et de vérifier votre solde actuel. Dites maintenant que vous n'aimez pas trop dépendre des variables globales et que vous décidez de refactoriser l'application à l'aide d'une classe. Pour ce faire, vous devez exécuter les étapes suivantes :

  1. Définissez une classe avec un nom approprié. Compte fonctionne pour cet exemple.
  2. Déplacez les variables globales dans la classe, en les convertissant en attributs d'instance.
  3. Déplacez les fonctions dans la classe en tant que méthodes d'instance.

Voici le code refactorisé :

# account_class.py

class Account:
    def __init__(self, balance=0):
        self.balance = balance

    def deposit(self, amount):
        self.balance += amount
        print(f"Successful deposit: +${amount:,.2f}")

    def withdraw(self, amount):
        if self.balance - amount > 0:
            self.balance -= amount
            print(f"Successful withdrawal: -${amount:,.2f}")
        else:
            print("Not enough funds for this transaction")

def main():
    account = Account()
    while True:
        operation = input(
            "What would you like to do?\n"
            " d) deposit  "
            " w) withdraw  "
            " b) balance  "
            " q) quit\n"
            "> "
        )
        if operation in "dD":
            amount = float(input("Enter the deposit amount: "))
            account.deposit(amount)
        elif operation in "wW":
            amount = float(input("Enter the withdrawal amount: "))
            account.withdraw(amount)
        elif operation in "bB":
            print(f"Current balance: ${account.balance:,.2f}")
        elif operation in "qQ":
            print("Goodbye!")
            break
        else:
            print("Invalid operation. Please try again.")

main()

Dans cette nouvelle version de votre programme de comptabilité, vous avez défini une classe appelée Compte. Cette classe possède un attribut d'instance pour stocker le solde du compte. Cet attribut était votre ancienne variable globale balance.

Vous avez également converti les fonctions deposit() et withdraw() en méthodes d'instance avec l'instance actuelle, self, comme premier argument. Enfin, à l'intérieur de la boucle, vous instanciez la classe Account et appelez ses méthodes sur cette instance pour que l'application fonctionne de la même manière que l'ancienne version.

Notez que dans cette implémentation, vous n'avez pas de méthode .get_balance(). Ainsi, pour accéder au solde du compte, vous pouvez directement utiliser l'attribut .balance.

Conclusion

Vous avez beaucoup appris sur l'utilisation des variables globales, en particulier dans vos fonctions Python. Vous avez appris que vous pouvez accéder aux variables globales directement dans vos fonctions. Cependant, pour modifier une variable globale dans une fonction, vous devez utiliser soit le mot-clé global, soit la fonction globals().

Les variables globales vous permettent de partager des données entre plusieurs fonctions, ce qui peut être utile dans certaines situations. Cependant, vous devez utiliser ce type de variable avec précaution et parcimonie pour éviter d’écrire du code difficile à comprendre, déboguer, tester et maintenir.

Dans ce didacticiel, vous avez appris à :

  • Travailler avec des variables globales en Python
  • Accédez directement aux variables globales dans vos fonctions Python
  • Modifier et créer des variables globales dans les fonctions à l'aide du mot-clé global
  • Accédez, créez et modifiez des variables globales dans vos fonctions avec la fonction globals()
  • Utiliser des stratégies pour éviter d'utiliser des variables globales dans le code Python

Vous savez maintenant ce qu'il faut faire pour utiliser correctement les variables globales dans les fonctions en Python, afin de pouvoir les utiliser efficacement le cas échéant. Vous savez également qu’éviter les variables globales est la meilleure façon de procéder la plupart du temps. Cela vous permettra d'écrire du code plus modulaire, maintenable et réutilisable.

Articles connexes