7. Noms et espaces de noms#

7.1. Vue d’ensemble#

Ce cours porte entièrement sur les noms de variables, sur la façon dont ils peuvent être utilisés et sur la manière dont ils sont interprétés par l’interpréteur Python.

Cela peut sembler un peu ennuyeux, mais le modèle qu’a adopté Python pour gérer les noms est élégant et intéressant.

De plus, vous vous épargnerez de nombreuses heures de débogage si vous comprenez bien le fonctionnement des noms en Python.

7.2. Noms de variables en Python#

Considérons l’instruction Python

x = 42

Nous savons désormais que lorsque cette instruction est exécutée, Python crée un objet de type int dans la mémoire de votre ordinateur, contenant

  • la valeur 42

  • certains attributs associés

Mais qu’est-ce que x lui-même ?

En Python, x est appelé un nom, et l’instruction x = 42 lie le nom x à l’objet entier dont nous venons de parler.

Sous le capot, ce processus de liaison des noms aux objets est implémenté sous la forme d’un dictionnaire — nous y reviendrons dans un instant.

Il n’y a aucun problème à lier deux noms ou plus à un même objet, quel que soit cet objet

def f(string):      # Crée une fonction appelée f
    print(string)   # qui affiche toute chaîne qui lui est passée

g = f
id(g) == id(f)
True
g('test')
test

Lors de la première étape, un objet fonction est créé, et le nom f lui est lié.

Après avoir lié le nom g au même objet, nous pouvons l’utiliser partout où nous utiliserions f.

Que se passe-t-il lorsque le nombre de noms liés à un objet tombe à zéro ?

Voici un exemple de cette situation, où le nom x est d’abord lié à un objet puis relié à un autre

x = 'foo'
id(x)
x = 'bar'  
id(x)
139655055732496

Dans ce cas, après avoir relié x à 'bar', aucun nom n’est lié au premier objet 'foo'.

Cela déclenche le ramassage de 'foo' par le garbage collector.

Autrement dit, l’emplacement mémoire qui stocke cet objet est libéré et rendu au système d’exploitation.

Le ramassage des ordures (garbage collection) est en réalité un domaine de recherche actif en informatique.

Vous pouvez en lire davantage sur le garbage collection si cela vous intéresse.

7.3. Espaces de noms#

Rappelons de la discussion précédente que l’instruction

x = 42

lie le nom x à l’objet entier situé à droite.

Nous avons également mentionné que ce processus de liaison de x au bon objet est implémenté sous la forme d’un dictionnaire.

Ce dictionnaire est appelé un espace de noms.

Définition

Un espace de noms est une table de symboles qui associe des noms à des objets en mémoire.

Python utilise plusieurs espaces de noms, qu’il crée à la volée selon les besoins.

Par exemple, chaque fois que nous importons un module, Python crée un espace de noms pour ce module.

Pour voir cela en action, supposons que nous écrivions un script mathfoo.py composé d’une seule ligne

%%file mathfoo.py
pi = 'foobar'
Writing mathfoo.py

Maintenant, nous démarrons l’interpréteur Python et l’importons

import mathfoo

Ensuite, importons le module math de la bibliothèque standard

import math

Ces deux modules possèdent un attribut appelé pi

math.pi
3.141592653589793
mathfoo.pi
'foobar'

Ces deux liaisons différentes de pi existent dans des espaces de noms différents, chacun implémenté sous la forme d’un dictionnaire.

Si vous le souhaitez, vous pouvez consulter directement le dictionnaire, en utilisant module_name.__dict__.

import math

math.__dict__.items()
dict_items([('__name__', 'math'), ('__doc__', 'This module provides access to the mathematical functions\ndefined by the C standard.'), ('__package__', ''), ('__loader__', <_frozen_importlib_external.ExtensionFileLoader object at 0x7f03fba29a30>), ('__spec__', ModuleSpec(name='math', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x7f03fba29a30>, origin='/home/runner/miniconda3/envs/quantecon/lib/python3.13/lib-dynload/math.cpython-313-x86_64-linux-gnu.so')), ('acos', <built-in function acos>), ('acosh', <built-in function acosh>), ('asin', <built-in function asin>), ('asinh', <built-in function asinh>), ('atan', <built-in function atan>), ('atan2', <built-in function atan2>), ('atanh', <built-in function atanh>), ('cbrt', <built-in function cbrt>), ('ceil', <built-in function ceil>), ('copysign', <built-in function copysign>), ('cos', <built-in function cos>), ('cosh', <built-in function cosh>), ('degrees', <built-in function degrees>), ('dist', <built-in function dist>), ('erf', <built-in function erf>), ('erfc', <built-in function erfc>), ('exp', <built-in function exp>), ('exp2', <built-in function exp2>), ('expm1', <built-in function expm1>), ('fabs', <built-in function fabs>), ('factorial', <built-in function factorial>), ('floor', <built-in function floor>), ('fma', <built-in function fma>), ('fmod', <built-in function fmod>), ('frexp', <built-in function frexp>), ('fsum', <built-in function fsum>), ('gamma', <built-in function gamma>), ('gcd', <built-in function gcd>), ('hypot', <built-in function hypot>), ('isclose', <built-in function isclose>), ('isfinite', <built-in function isfinite>), ('isinf', <built-in function isinf>), ('isnan', <built-in function isnan>), ('isqrt', <built-in function isqrt>), ('lcm', <built-in function lcm>), ('ldexp', <built-in function ldexp>), ('lgamma', <built-in function lgamma>), ('log', <built-in function log>), ('log1p', <built-in function log1p>), ('log10', <built-in function log10>), ('log2', <built-in function log2>), ('modf', <built-in function modf>), ('pow', <built-in function pow>), ('radians', <built-in function radians>), ('remainder', <built-in function remainder>), ('sin', <built-in function sin>), ('sinh', <built-in function sinh>), ('sqrt', <built-in function sqrt>), ('tan', <built-in function tan>), ('tanh', <built-in function tanh>), ('sumprod', <built-in function sumprod>), ('trunc', <built-in function trunc>), ('prod', <built-in function prod>), ('perm', <built-in function perm>), ('comb', <built-in function comb>), ('nextafter', <built-in function nextafter>), ('ulp', <built-in function ulp>), ('__file__', '/home/runner/miniconda3/envs/quantecon/lib/python3.13/lib-dynload/math.cpython-313-x86_64-linux-gnu.so'), ('pi', 3.141592653589793), ('e', 2.718281828459045), ('tau', 6.283185307179586), ('inf', inf), ('nan', nan)])
import mathfoo

mathfoo.__dict__
{'__name__': 'mathfoo',
 '__doc__': None,
 '__package__': '',
 '__loader__': <_frozen_importlib_external.SourceFileLoader at 0x7f03f48212b0>,
 '__spec__': ModuleSpec(name='mathfoo', loader=<_frozen_importlib_external.SourceFileLoader object at 0x7f03f48212b0>, origin='/home/runner/work/lecture-python-programming.fr/lecture-python-programming.fr/lectures/mathfoo.py'),
 '__file__': '/home/runner/work/lecture-python-programming.fr/lecture-python-programming.fr/lectures/mathfoo.py',
 '__cached__': '/home/runner/work/lecture-python-programming.fr/lecture-python-programming.fr/lectures/__pycache__/mathfoo.cpython-313.pyc',
 '__builtins__': {'__name__': 'builtins',
  '__doc__': "Built-in functions, types, exceptions, and other objects.\n\nThis module provides direct access to all 'built-in'\nidentifiers of Python; for example, builtins.len is\nthe full name for the built-in function len().\n\nThis module is not normally accessed explicitly by most\napplications, but can be useful in modules that provide\nobjects with the same name as a built-in value, but in\nwhich the built-in of that name is also needed.",
  '__package__': '',
  '__loader__': _frozen_importlib.BuiltinImporter,
  '__spec__': ModuleSpec(name='builtins', loader=<class '_frozen_importlib.BuiltinImporter'>, origin='built-in'),
  '__build_class__': <function __build_class__>,
  '__import__': <function __import__(name, globals=None, locals=None, fromlist=(), level=0)>,
  'abs': <function abs(x, /)>,
  'all': <function all(iterable, /)>,
  'any': <function any(iterable, /)>,
  'ascii': <function ascii(obj, /)>,
  'bin': <function bin(number, /)>,
  'breakpoint': <function breakpoint(*args, **kws)>,
  'callable': <function callable(obj, /)>,
  'chr': <function chr(i, /)>,
  'compile': <function compile(source, filename, mode, flags=0, dont_inherit=False, optimize=-1, *, _feature_version=-1)>,
  'delattr': <function delattr(obj, name, /)>,
  'dir': <function dir>,
  'divmod': <function divmod(x, y, /)>,
  'eval': <function eval(source, /, globals=None, locals=None)>,
  'exec': <function exec(source, /, globals=None, locals=None, *, closure=None)>,
  'format': <function format(value, format_spec='', /)>,
  'getattr': <function getattr>,
  'globals': <function globals()>,
  'hasattr': <function hasattr(obj, name, /)>,
  'hash': <function hash(obj, /)>,
  'hex': <function hex(number, /)>,
  'id': <function id(obj, /)>,
  'input': <bound method Kernel.raw_input of <ipykernel.ipkernel.IPythonKernel object at 0x7f03f8eb23c0>>,
  'isinstance': <function isinstance(obj, class_or_tuple, /)>,
  'issubclass': <function issubclass(cls, class_or_tuple, /)>,
  'iter': <function iter>,
  'aiter': <function aiter(async_iterable, /)>,
  'len': <function len(obj, /)>,
  'locals': <function locals()>,
  'max': <function max>,
  'min': <function min>,
  'next': <function next>,
  'anext': <function anext>,
  'oct': <function oct(number, /)>,
  'ord': <function ord(character, /)>,
  'pow': <function pow(base, exp, mod=None)>,
  'print': <function print(*args, sep=' ', end='\n', file=None, flush=False)>,
  'repr': <function repr(obj, /)>,
  'round': <function round(number, ndigits=None)>,
  'setattr': <function setattr(obj, name, value, /)>,
  'sorted': <function sorted(iterable, /, *, key=None, reverse=False)>,
  'sum': <function sum(iterable, /, start=0)>,
  'vars': <function vars>,
  'None': None,
  'Ellipsis': Ellipsis,
  'NotImplemented': NotImplemented,
  'False': False,
  'True': True,
  'bool': bool,
  'memoryview': memoryview,
  'bytearray': bytearray,
  'bytes': bytes,
  'classmethod': classmethod,
  'complex': complex,
  'dict': dict,
  'enumerate': enumerate,
  'filter': filter,
  'float': float,
  'frozenset': frozenset,
  'property': property,
  'int': int,
  'list': list,
  'map': map,
  'object': object,
  'range': range,
  'reversed': reversed,
  'set': set,
  'slice': slice,
  'staticmethod': staticmethod,
  'str': str,
  'super': super,
  'tuple': tuple,
  'type': type,
  'zip': zip,
  '__debug__': True,
  'BaseException': BaseException,
  'BaseExceptionGroup': BaseExceptionGroup,
  'Exception': Exception,
  'GeneratorExit': GeneratorExit,
  'KeyboardInterrupt': KeyboardInterrupt,
  'SystemExit': SystemExit,
  'ArithmeticError': ArithmeticError,
  'AssertionError': AssertionError,
  'AttributeError': AttributeError,
  'BufferError': BufferError,
  'EOFError': EOFError,
  'ImportError': ImportError,
  'LookupError': LookupError,
  'MemoryError': MemoryError,
  'NameError': NameError,
  'OSError': OSError,
  'ReferenceError': ReferenceError,
  'RuntimeError': RuntimeError,
  'StopAsyncIteration': StopAsyncIteration,
  'StopIteration': StopIteration,
  'SyntaxError': SyntaxError,
  'SystemError': SystemError,
  'TypeError': TypeError,
  'ValueError': ValueError,
  'Warning': Warning,
  'FloatingPointError': FloatingPointError,
  'OverflowError': OverflowError,
  'ZeroDivisionError': ZeroDivisionError,
  'BytesWarning': BytesWarning,
  'DeprecationWarning': DeprecationWarning,
  'EncodingWarning': EncodingWarning,
  'FutureWarning': FutureWarning,
  'ImportWarning': ImportWarning,
  'PendingDeprecationWarning': PendingDeprecationWarning,
  'ResourceWarning': ResourceWarning,
  'RuntimeWarning': RuntimeWarning,
  'SyntaxWarning': SyntaxWarning,
  'UnicodeWarning': UnicodeWarning,
  'UserWarning': UserWarning,
  'BlockingIOError': BlockingIOError,
  'ChildProcessError': ChildProcessError,
  'ConnectionError': ConnectionError,
  'FileExistsError': FileExistsError,
  'FileNotFoundError': FileNotFoundError,
  'InterruptedError': InterruptedError,
  'IsADirectoryError': IsADirectoryError,
  'NotADirectoryError': NotADirectoryError,
  'PermissionError': PermissionError,
  'ProcessLookupError': ProcessLookupError,
  'TimeoutError': TimeoutError,
  'IndentationError': IndentationError,
  '_IncompleteInputError': _IncompleteInputError,
  'IndexError': IndexError,
  'KeyError': KeyError,
  'ModuleNotFoundError': ModuleNotFoundError,
  'NotImplementedError': NotImplementedError,
  'PythonFinalizationError': PythonFinalizationError,
  'RecursionError': RecursionError,
  'UnboundLocalError': UnboundLocalError,
  'UnicodeError': UnicodeError,
  'BrokenPipeError': BrokenPipeError,
  'ConnectionAbortedError': ConnectionAbortedError,
  'ConnectionRefusedError': ConnectionRefusedError,
  'ConnectionResetError': ConnectionResetError,
  'TabError': TabError,
  'UnicodeDecodeError': UnicodeDecodeError,
  'UnicodeEncodeError': UnicodeEncodeError,
  'UnicodeTranslateError': UnicodeTranslateError,
  'ExceptionGroup': ExceptionGroup,
  'EnvironmentError': OSError,
  'IOError': OSError,
  'open': <function _io.open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, closefd=True, opener=None)>,
  'copyright': Copyright (c) 2001-2024 Python Software Foundation.
  All Rights Reserved.
  
  Copyright (c) 2000 BeOpen.com.
  All Rights Reserved.
  
  Copyright (c) 1995-2001 Corporation for National Research Initiatives.
  All Rights Reserved.
  
  Copyright (c) 1991-1995 Stichting Mathematisch Centrum, Amsterdam.
  All Rights Reserved.,
  'credits': Thanks to CWI, CNRI, BeOpen, Zope Corporation, the Python Software
  Foundation, and a cast of thousands for supporting Python
  development.  See www.python.org for more information.,
  'license': Type license() to see the full license text,
  'help': Type help() for interactive help, or help(object) for help about object.,
  'execfile': <function _pydev_bundle._pydev_execfile.execfile(file, glob=None, loc=None)>,
  'runfile': <function _pydev_bundle.pydev_umd.runfile(filename, args=None, wdir=None, namespace=None)>,
  '__IPYTHON__': True,
  'display': <function IPython.core.display_functions.display(*objs, include=None, exclude=None, metadata=None, transient=None, display_id=None, raw=False, clear=False, **kwargs)>,
  'get_ipython': <bound method InteractiveShell.get_ipython of <ipykernel.zmqshell.ZMQInteractiveShell object at 0x7f03f4a23cb0>>},
 'pi': 'foobar'}

Comme vous le savez, nous accédons aux éléments de l’espace de noms en utilisant la notation d’attribut par point

math.pi
3.141592653589793

Cela est entièrement équivalent à math.__dict__['pi']

math.__dict__['pi'] 
3.141592653589793

7.4. Visualiser les espaces de noms#

Comme nous l’avons vu ci-dessus, l’espace de noms math peut être affiché en tapant math.__dict__.

Une autre façon d’en voir le contenu est de taper vars(math)

vars(math).items()
dict_items([('__name__', 'math'), ('__doc__', 'This module provides access to the mathematical functions\ndefined by the C standard.'), ('__package__', ''), ('__loader__', <_frozen_importlib_external.ExtensionFileLoader object at 0x7f03fba29a30>), ('__spec__', ModuleSpec(name='math', loader=<_frozen_importlib_external.ExtensionFileLoader object at 0x7f03fba29a30>, origin='/home/runner/miniconda3/envs/quantecon/lib/python3.13/lib-dynload/math.cpython-313-x86_64-linux-gnu.so')), ('acos', <built-in function acos>), ('acosh', <built-in function acosh>), ('asin', <built-in function asin>), ('asinh', <built-in function asinh>), ('atan', <built-in function atan>), ('atan2', <built-in function atan2>), ('atanh', <built-in function atanh>), ('cbrt', <built-in function cbrt>), ('ceil', <built-in function ceil>), ('copysign', <built-in function copysign>), ('cos', <built-in function cos>), ('cosh', <built-in function cosh>), ('degrees', <built-in function degrees>), ('dist', <built-in function dist>), ('erf', <built-in function erf>), ('erfc', <built-in function erfc>), ('exp', <built-in function exp>), ('exp2', <built-in function exp2>), ('expm1', <built-in function expm1>), ('fabs', <built-in function fabs>), ('factorial', <built-in function factorial>), ('floor', <built-in function floor>), ('fma', <built-in function fma>), ('fmod', <built-in function fmod>), ('frexp', <built-in function frexp>), ('fsum', <built-in function fsum>), ('gamma', <built-in function gamma>), ('gcd', <built-in function gcd>), ('hypot', <built-in function hypot>), ('isclose', <built-in function isclose>), ('isfinite', <built-in function isfinite>), ('isinf', <built-in function isinf>), ('isnan', <built-in function isnan>), ('isqrt', <built-in function isqrt>), ('lcm', <built-in function lcm>), ('ldexp', <built-in function ldexp>), ('lgamma', <built-in function lgamma>), ('log', <built-in function log>), ('log1p', <built-in function log1p>), ('log10', <built-in function log10>), ('log2', <built-in function log2>), ('modf', <built-in function modf>), ('pow', <built-in function pow>), ('radians', <built-in function radians>), ('remainder', <built-in function remainder>), ('sin', <built-in function sin>), ('sinh', <built-in function sinh>), ('sqrt', <built-in function sqrt>), ('tan', <built-in function tan>), ('tanh', <built-in function tanh>), ('sumprod', <built-in function sumprod>), ('trunc', <built-in function trunc>), ('prod', <built-in function prod>), ('perm', <built-in function perm>), ('comb', <built-in function comb>), ('nextafter', <built-in function nextafter>), ('ulp', <built-in function ulp>), ('__file__', '/home/runner/miniconda3/envs/quantecon/lib/python3.13/lib-dynload/math.cpython-313-x86_64-linux-gnu.so'), ('pi', 3.141592653589793), ('e', 2.718281828459045), ('tau', 6.283185307179586), ('inf', inf), ('nan', nan)])

Si vous souhaitez seulement voir les noms, vous pouvez taper

# Affiche les 10 premiers noms
dir(math)[0:10]
['__doc__',
 '__file__',
 '__loader__',
 '__name__',
 '__package__',
 '__spec__',
 'acos',
 'acosh',
 'asin',
 'asinh']

Remarquez les noms spéciaux __doc__ et __name__.

Ceux-ci sont initialisés dans l’espace de noms chaque fois qu’un module est importé

  • __doc__ est la docstring du module

  • __name__ est le nom du module

print(math.__doc__)
This module provides access to the mathematical functions
defined by the C standard.
math.__name__
'math'

7.5. Sessions interactives#

En Python, tout code exécuté par l’interpréteur s’exécute dans un module.

Qu’en est-il des commandes tapées à l’invite ?

Elles sont également considérées comme étant exécutées au sein d’un module — dans ce cas, un module appelé __main__.

Pour vérifier cela, nous pouvons examiner le nom du module actuel via la valeur de __name__ donnée à l’invite

print(__name__)
__main__

Lorsque nous exécutons un script à l’aide de la commande run d’IPython, le contenu du fichier est également exécuté comme faisant partie de __main__.

Pour le voir, créons un fichier mod.py qui affiche son propre attribut __name__

%%file mod.py
print(__name__)
Writing mod.py

Maintenant, examinons deux façons différentes de l’exécuter dans IPython

import mod  # Importation standard
mod
%run mod.py  # Exécution interactive
__main__

Dans le second cas, le code est exécuté comme faisant partie de __main__, donc __name__ est égal à __main__.

Pour voir le contenu de l’espace de noms de __main__, nous utilisons vars() plutôt que vars(__main__).

Si vous faites cela dans IPython, vous verrez tout un tas de variables dont IPython a besoin et qu’il a initialisées lorsque vous avez démarré votre session.

Si vous préférez ne voir que les variables que vous avez initialisées, utilisez %whos

x = 2
y = 3

import numpy as np

%whos
Variable   Type        Data/Info
--------------------------------
f          function    <function f at 0x7f03f4841ee0>
g          function    <function f at 0x7f03f4841ee0>
math       module      <module 'math' from '/hom<...>313-x86_64-linux-gnu.so'>
mathfoo    module      <module 'mathfoo' from '/<...>.fr/lectures/mathfoo.py'>
mod        module      <module 'mod' from '/home<...>ming.fr/lectures/mod.py'>
np         module      <module 'numpy' from '/ho<...>kages/numpy/__init__.py'>
x          int         2
y          int         3

7.6. L’espace de noms global#

La documentation Python fait souvent référence à « l’espace de noms global ».

L’espace de noms global est l’espace de noms du module en cours d’exécution.

Par exemple, supposons que nous démarrions l’interpréteur et commencions à faire des affectations.

Nous travaillons maintenant dans le module __main__, et donc l’espace de noms de __main__ est l’espace de noms global.

Ensuite, nous importons un module appelé amodule

import amodule

À ce stade, l’interpréteur crée un espace de noms pour le module amodule et commence à exécuter les commandes du module.

Pendant ce temps, l’espace de noms amodule.__dict__ est l’espace de noms global.

Une fois l’exécution du module terminée, l’interpréteur revient au module depuis lequel l’instruction d’importation a été faite.

Dans ce cas, il s’agit de __main__, donc l’espace de noms de __main__ redevient l’espace de noms global.

7.7. Espaces de noms locaux#

Fait important : lorsque nous appelons une fonction, l’interpréteur crée un espace de noms local pour cette fonction, et y enregistre les variables.

La raison de ceci sera expliquée dans un instant.

Les variables de l’espace de noms local sont appelées variables locales.

Une fois la fonction terminée, l’espace de noms est libéré et perdu.

Pendant que la fonction s’exécute, nous pouvons voir le contenu de l’espace de noms local avec locals().

Par exemple, considérons

def f(x):
    a = 2
    print(locals())
    return a * x

Maintenant, appelons la fonction

f(1)
{'x': 1, 'a': 2}
2

Vous pouvez voir l’espace de noms local de f avant qu’il ne soit détruit.

7.8. L’espace de noms __builtins__#

Nous avons utilisé diverses fonctions intégrées, telles que max(), dir(), str(), list(), len(), range(), type(), etc.

Comment fonctionne l’accès à ces noms ?

  • Ces définitions sont stockées dans un module appelé __builtin__.

  • Elles disposent de leur propre espace de noms appelé __builtins__.

# Affiche les 10 premiers noms dans `__main__`
dir()[0:10]
['In', 'Out', '_', '_10', '_11', '_12', '_13', '_14', '_15', '_16']
# Affiche les 10 premiers noms dans `__builtins__`
dir(__builtins__)[0:10]
['ArithmeticError',
 'AssertionError',
 'AttributeError',
 'BaseException',
 'BaseExceptionGroup',
 'BlockingIOError',
 'BrokenPipeError',
 'BufferError',
 'BytesWarning',
 'ChildProcessError']

Nous pouvons accéder aux éléments de l’espace de noms comme suit

__builtins__.max
<function max>

Mais __builtins__ est spécial, car nous pouvons toujours y accéder directement aussi

max
<function max>
__builtins__.max == max
True

La section suivante explique comment cela fonctionne…

7.9. Résolution des noms#

Les espaces de noms sont formidables car ils nous aident à organiser les noms de variables.

(Tapez import this à l’invite et regardez le dernier élément affiché)

Cependant, nous devons comprendre comment l’interpréteur Python travaille avec plusieurs espaces de noms.

Comprendre le flux d’exécution nous aidera à vérifier quelles variables sont dans la portée et comment opérer sur elles lors de l’écriture et du débogage des programmes.

À tout moment de l’exécution, il existe en fait au moins deux espaces de noms accessibles directement.

(« Accessible directement » signifie sans utiliser de point, comme dans pi plutôt que math.pi)

Ces espaces de noms sont

  • L’espace de noms global (du module en cours d’exécution)

  • L’espace de noms builtin

Si l’interpréteur exécute une fonction, alors les espaces de noms directement accessibles sont

  • L’espace de noms local de la fonction

  • L’espace de noms global (du module en cours d’exécution)

  • L’espace de noms builtin

Parfois, des fonctions sont définies à l’intérieur d’autres fonctions, comme ceci

def f():
    a = 2
    def g():
        b = 4
        print(a * b)
    g()

Ici, f est la fonction englobante de g, et chaque fonction dispose de ses propres espaces de noms.

Maintenant, nous pouvons énoncer la règle qui régit la résolution des espaces de noms :

L’ordre dans lequel l’interpréteur recherche les noms est

  1. l’espace de noms local (s’il existe)

  2. la hiérarchie des espaces de noms englobants (s’ils existent)

  3. l’espace de noms global

  4. l’espace de noms builtin

Si le nom ne se trouve dans aucun de ces espaces de noms, l’interpréteur lève une erreur NameError.

C’est ce qu’on appelle la règle LEGB (local, englobant, global, builtin).

Voici un exemple qui aide à illustrer cela.

Les visualisations ici sont créées par nbtutor dans un notebook Jupyter.

Elles peuvent vous aider à mieux comprendre votre programme lorsque vous apprenez un nouveau langage.

Considérons un script test.py qui ressemble à ce qui suit

%%file test.py
def g(x):
    a = 1
    x = x + a
    return x

a = 0
y = g(10)
print("a = ", a, "y = ", y)
Writing test.py

Que se passe-t-il lorsque nous exécutons ce script ?

%run test.py
a =  0 y =  11

Tout d’abord,

  • L’espace de noms global {} est créé.

_images/global.png
  • L’objet fonction est créé, et g lui est lié au sein de l’espace de noms global.

  • Le nom a est lié à 0, encore une fois dans l’espace de noms global.

_images/global2.png

Ensuite, g est appelé via y = g(10), ce qui conduit à la séquence d’actions suivante

  • L’espace de noms local de la fonction est créé.

  • Les noms locaux x et a sont liés, de sorte que l’espace de noms local devient {'x': 10, 'a': 1}.

Notez que le a global n’a pas été affecté par le a local.

_images/local1.png
  • L’instruction x = x + a utilise le a local et le x local pour calculer x + a, et lie le nom local x au résultat.

  • Cette valeur est retournée, et y lui est lié dans l’espace de noms global.

  • Les x et a locaux sont supprimés (et l’espace de noms local est libéré).

_images/local_return.png

7.9.1. Paramètres Mutables versus Immuables#

C’est le bon moment pour en dire un peu plus sur les objets mutables versus immuables.

Considérons le segment de code

def f(x):
    x = x + 1
    return x

x = 1
print(f(x), x)
2 1

Nous comprenons maintenant ce qui va se passer ici : le code affiche 2 comme valeur de f(x) et 1 comme valeur de x.

Tout d’abord, f et x sont enregistrés dans l’espace de noms global.

L’appel f(x) crée un espace de noms local et y ajoute x, lié à 1.

Ensuite, ce x local est relié au nouvel objet entier 2, et cette valeur est retournée.

Rien de tout cela n’affecte le x global.

Cependant, c’est une autre histoire lorsque nous utilisons un type de données mutable tel qu’une liste

def f(x):
    x[0] = x[0] + 1
    return x

x = [1]
print(f(x), x)
[2] [2]

Cela affiche [2] comme valeur de f(x) et la même chose pour x.

Voici ce qui se passe

  • f est enregistré comme fonction dans l’espace de noms global

_images/mutable1.png
  • x est lié à [1] dans l’espace de noms global

_images/mutable2.png
  • L’appel f(x)

    • Crée un espace de noms local

    • Ajoute x à l’espace de noms local, lié à [1]

_images/mutable3.png

Note

Le x global et le x local se réfèrent au même [1]

Nous pouvons voir que l’identité du x local et l’identité du x global sont les mêmes

def f(x):
    x[0] = x[0] + 1
    print(f'the identity of local x is {id(x)}')
    return x

x = [1]
print(f'the identity of global x is {id(x)}')
print(f(x), x)
the identity of global x is 139654957143552
the identity of local x is 139654957143552
[2] [2]
  • Au sein de f(x)

    • La liste [1] est modifiée en [2]

    • Retourne la liste [2]

_images/mutable4.png
  • L’espace de noms local est libéré, et le x local est perdu

_images/mutable5.png

Si vous souhaitez modifier le x local et le x global séparément, vous pouvez créer une copie de la liste et affecter la copie au x local.

Nous vous laissons explorer cela.