Skip to article frontmatterSkip to article content
%load_ext ipythontutor

portée lexicale

Python utilise la portée lexicale,
c’est-à-dire que la portée des variables
est déterminée exclusivement
en fonction de leur place dans le code source

déclaration ?

toutefois:

# ici la variable `y` n'est pas considérée 
# comme déclarée puisqu'on se contente
# de la lire, et qu'on ne l'affecte pas
# (pas de code avec `y = ...`

def foo(x):
    print(x)  # <-- une variable locale
              #     (paramètre)
    print(y)  # <-- PAS une variable locale
              #     (et donc ici BOOM)
# ici au contraire la variable y
# est locale à la fonction
# comme le paramètre x

def foo(x):
    y = 10    # <-- on "déclare" y
    print(x)  # <-- une variable locale
              #     (paramètre)
    print(y)  # <-- aussi (car affectée
              #     plus haut dans foo)

règle LEGB

une variable est cherchée dans cet ordre LEGB

variable globale

du coup toutes les variables affectées à l’extérieur d’une classe ou fonction sont globales

i.e. susceptibles d’être lues depuis tout le code dans le fichier (on dit un module)

GLOBALE = 10

def foo():
    print("from foo:", GLOBALE)
    
    def bar():
        print("from bar:", GLOBALE)
    bar()
     
foo()
from foo: 10
from bar: 10

exemple de visibilité (1)

def foo():
    
    level1 = 10
    
    def bar():
        level2 = 20
        
        def tutu():
            level3 = 30
            
            print("from tutu:", level1, level2, level3)
        
        print("from bar: ", level1, level2) # level3 NOT visible
        tutu()
    
    print("from foo: ", level1) # level2 or level3 NOT visible here
    bar()
foo()
from foo:  10
from bar:  10 20
from tutu: 10 20 30

exemple de visibilité (2) cassé

une variable ne peut pas être à la fois globale et locale !

L = [1, 2]

def f():
    # ici on pourrait penser utiliser la globale 
    L.append(3) 
    # mais en fait non, ici on dit que L est locale !
    L = 1

try:
    f()
except UnboundLocalError:
    print("OOPS")
OOPS

exemple de visibilité (2) revu

pour réparer, on peut:

  1. enlever le L = 1 qui ne sert à rien :)

  2. ou encore passer la globale en paramètre

L = [1, 2]
 
def f(L):
    # ici L est le paramètre donc une locale
    L.append(3) 
    # 
    L = 1
f(L)
print(L)
[1, 2, 3]

attention aux classes

attention que ce système ne s’étend pas aux classes

en effet les symboles définis au premier niveau dans une instruction class
sont rangés comme des attributs de la classe

et à ce titre ils ne sont pas accessibles lexicalement

class Foo:
    
    class_variable = 10
    
    def method(self):
        # in this scope the symbols
        # 'class_variable' and `method`
        # ARE NOT lexically visible !!
        pass

global et nonlocal

mais revenons à nos fonctions:

exemple avec global (1)

# écrire une globale depuis une fonction

G = 10

def modify_G(x):
    # une fois la variable déclarée
    global G
    # je peux l'affecter
    G = x
    
modify_G(1000)

# combien vaut G ?
G
1000
# à votre avis 
# que se passe-t-il si on n'utilise
# pas global

G = 10

def does_not_modify_G(x):
    G = x
    
does_not_modify_G(1000)

# combien vaut G ?
G
10

exemple avec global (2)

# un exemple un peu plus tordu
# car ici dans la fonction
# on lit et on écrit G

G = 10

def increment_G():
    global G
    G = G + 10

increment_G()

# combien vaut G ?
G
20
# que se passe-t-il ici 
# d'après vous ?

G = 10

def increment_G():
    # pas de 'global'
    G = G + 10

try:
    increment_G()
except UnboundLocalError:
    print("OOPS !!")

# combien vaut G ?
G
OOPS !!
10

faut-il utiliser global ?

spécificités de global

exemple avec nonlocal

# nonlocal est très utile pour implémenter une cloture 

def make_counter():
    # cette variable est capturée dans la cloture
    counter = 0
    def increment():
        nonlocal counter
        counter += 1
        return counter
    # on retourne la fonction, qui a "capturé" le compteur
    return increment
c1 = make_counter()

c1()
c1()
2
c2 = make_counter()

c2()
c2()
c2()
3
c1()
3
c2()
4

les noms de builtins

les noms de builtins

# on peut accéder à la variable `__builtins__` 
# qui est .. une variable *builtin* 
__builtins__
<module 'builtins' (built-in)>
# ou encore on peut
# importer le module `builtins`
import builtins
# je n'en montre que 5 pour garder de la place
dir(builtins)[-5:]
['super', 'tuple', 'type', 'vars', 'zip']
# en fait il y en a vraiment beaucoup ! 
len(dir(__builtins__))
160
errors = (x for x in dir(builtins) if 'Error' in x or 'Warning' in x)

columns, width = 4, 18
for i, error in enumerate(errors, 1):
    print(f"{error:^{width}}", end=" ")
    if i % columns == 0:
        print()
 ArithmeticError     AssertionError     AttributeError    BlockingIOError   
 BrokenPipeError      BufferError        BytesWarning    ChildProcessError  
ConnectionAbortedError  ConnectionError   ConnectionRefusedError ConnectionResetError 
DeprecationWarning      EOFError       EncodingWarning    EnvironmentError  
 FileExistsError   FileNotFoundError  FloatingPointError   FutureWarning    
     IOError          ImportError       ImportWarning     IndentationError  
    IndexError      InterruptedError  IsADirectoryError       KeyError      
   LookupError        MemoryError     ModuleNotFoundError     NameError      
NotADirectoryError NotImplementedError      OSError         OverflowError    
PendingDeprecationWarning  PermissionError   ProcessLookupError   RecursionError   
  ReferenceError    ResourceWarning      RuntimeError      RuntimeWarning   
   SyntaxError       SyntaxWarning       SystemError          TabError      
   TimeoutError        TypeError      UnboundLocalError  UnicodeDecodeError 
UnicodeEncodeError    UnicodeError    UnicodeTranslateError   UnicodeWarning   
   UserWarning         ValueError          Warning       ZeroDivisionError  
others = (x for x in dir(builtins) 
          if not ('Error' in x or 'Warning' in x or '__' in x))

columns, width = 6, 16
for i, other in enumerate(others, 1):
    print(f"{other:^{width}}", end=" ")
    if i % columns == 0:
        print()
 BaseException   BaseExceptionGroup     Ellipsis        Exception      ExceptionGroup       False       
 GeneratorExit   KeyboardInterrupt       None        NotImplemented  StopAsyncIteration  StopIteration   
   SystemExit          True             abs             aiter             all             anext       
      any             ascii             bin              bool          breakpoint       bytearray     
     bytes           callable           chr          classmethod        compile          complex      
   copyright         credits          delattr            dict             dir            display      
     divmod         enumerate           eval             exec           execfile          filter      
     float            format         frozenset       get_ipython        getattr          globals      
    hasattr            hash             help             hex               id             input       
      int           isinstance       issubclass          iter             len            license      
      list            locals            map              max           memoryview          min        
      next            object            oct              open             ord              pow        
     print           property          range             repr           reversed          round       
    runfile            set            setattr           slice            sorted        staticmethod   
      str              sum             super            tuple             type             vars       
      zip