Algorithmique

Scope

Une variable est accessible depuis n’importe quel sous-scope en lecture, mais pas en écriture. Pour pouvoir la modifier dans un sous-scope, il faut la décraler comme global, mais c’est mal !

variable = 40

def modifier(value):
    variable += value
    # Renvoie une UnboundLocalError
    return variable

def modifier(value):
    # Fonctionne
    return variable + value

def modifier(value):
    global variable
    variable += value
    # Fonctionne mais à éviter
    # parce que global CAYMAL
    return variable

En python 3 on peut utiliser nonlocal qui permet d’accéder au scope directement au-dessus.

Closure

Lors de la création d’une fonction, son environnement est stocké (dans l’attribut de fonction __closure__, mais c’est transparent pour le développeur). Ce qui permet de définir des comportements dans certains contextes

def myfunc_builder(x):

    def myfunc(y):
        return x + y

    return myfunc

func1 = myfunc_builder(1)
func2 = myfunc_builder(2)

func1(5)  # 6
func2(5)  # 7

Fonctions

La valeur par défaut d’un argument d’une fonction n’est évalué qu’une fois lors de la déclaration. Ainsi si elle fait référence à un objet qui n’existe pas encore, il y aura erreur.

Décorateurs

On peut créer ses propres décorateurs, de manière à ajouter une fonctionnalitée particulière. Par exemple, le décorateur suivant permet de mettre en cache les sorties d’une fonction.

#!/usr/bin/env python
# -*- coding: utf-8 -*-

from functools import wraps

def memorize(func):
    memo = {}
    @wraps(func)
    def memorized_func(x):
        if x not in memo:
            memo[x] = func(x)
        return memo[x]

    return memorized_func

calls = 0

@memorize
def fib(n):
    global calls
    calls += 1

    if n == 0:
        return 0
    elif n == 1:
        return 1
    else:
        return fib(n-1) + fib(n-2)

print "fib :", fib(40)
print "calls :", calls

Le décorateur wraps permet de faire passer le __doc__, __module__ et le __name__ de la fonction décorée (fib) à la fonction décoratrice (_memorize).

Des version sympa de décorateurs sont disponibles sur ce wiki:

  • deprecated

  • timing

  • retry

Boucles

En plus de la syntaxe classique for x in ... peut utiliser la méthode for-else. Le code contenu dans else ne sera exécuté que dans le cas où for n’est pas interrompu ou breaké.

Le même principe est applicable à while-else.

Exceptions

>>> try:
...     x = 5/0
... except:
...     print("Hello, il y a une erreur")
...     raise
... else:
...     print("Je passe ici si aucune exception n'est levée")
... finally:
...     print("Je passe ici quoiqu'il arrive")
...
Hello, il y a une erreur
Je passe ici quoiqu'il arrive
Traceback (most recent call last):
  File "<input>", line 2, in <module>
ZeroDivisionError: integer division or modulo by zero

Pour la tracabilité d’une exeception, on peut utiliser la syntaxe suivante

try:
    5 / 0
except ZeroDivisionError as e:
    raise ValueError("Something went wrong") from e

De plus, pour une gestion plus simple des cas d’erreur, à la place d’utiliser la syntaxe

foo = 5

try:
    raise ValueError("value of foo too high ({})".format(foo))

on peut utiliser la suivante

foo = 5

try:
    raise ValueError("Value of foo too high", foo )
except ValueError as e:
    print(e.args)
    if e.args[1] == 5:
        print("special case")

Custom exceptions

Il est également possible de créer ses propres exceptions, afin de faire remonter des erreurs spécifiques à son programme.

Un petit guide sur les bonnes pratiques ici

You might say that the exception type specifies what went wrong,
whereas the message / attributes specify how it went wrong.

Context Manager

contextlib.contextmanager(). Une utilisation régulière est

with open('file.txt') as f:
    # on fait des trucs ici

qui s’occupe de refermer le fichier automatiquement en fin d’utilisation. C’est un mix de décorateur et générateur. C’est très intéressant dans le cas de socket, connexions à des BDD, ouvertures de fichierts, etc. Voir l’article de Sam&Max.

Annotations

Depuis python 3.0 il est possible d’ajouter des annotations à une fonction ou méthode. Ces annotations n’ont aucun effet sur le code et son exécution.

def fonction(x: str, y: object) -> int:
    # faire des trucs avec
    return

Ce n’est qu’à partir de python 3.5 qu’elles sont utilisées pour définir le type de variable.

Ce typage statique de certaines portions permet, en plus de donner une indication directe des types attendus, de faire des optimisation mémoire par l’interpréteur mypy. Quelques infos ici.

Ce n’est pas incompatible avec des valeurs par défaut. Ça alourdit un peu la syntaxe.

from typing import Iterable

def fonction(x="hello": str, y=(1, 2): Iterable) -> int:
    # des trucs...
    return 4

Co-routines

Une explication claire et simple ici et le tutoriel un peu plus complet de S&M et apparement la bible c’est ça.