FAQ de programmation

Sommaire

Questions générales

Existe-t’il un débogueur de code source avec points d’arrêts, exécution pas-à-pas, etc. ?

Oui.

Le module pdb est un débogueur console simple, mais parfaitement adapté à Python. Il fait partie de la bibliothèque standard de Python, sa documentation se trouve dans le manuel de référence. Vous pouvez vous inspirer du code de pdb pour écrire votre propre débogueur.

L’environnement de développement interactif IDLE, qui est fourni avec la distribution standard de Python (normalement disponible dans Tools/scripts/idle) contient un débogueur graphique.

PythonWin est un environnement de développement intégré (EDI) Python qui embarque un débogueur graphique basé sur pdb. Le débogueur PythonWin colore les points d’arrêts et possède quelques fonctionnalités sympathiques, comme la possibilité de déboguer des programmes développés sans PythonWin. PythonWin est disponible dans le projet Extensions Python pour Windows et fait partie de la distribution ActivePython (voir https://www.activestate.com/activepython).

Boa Constructor est un EDI et un constructeur d’interface homme-machine basé sur wxWidgets. Il propose la création et la manipulation de fenêtres, un inspecteur d’objets, de nombreuses façons de visualiser des sources comme un navigateur d’objets, les hiérarchies d’héritage, la documentation html générée par les docstrings, un débogueur avancé, une aide intégrée et la prise en charge de Zope.

Eric est un EDI basé sur PyQt et l’outil d’édition Scintilla.

Pydb est une version du débogueur standard Python pdb, modifié pour être utilisé avec DDD (Data Display Debugger), un célèbre débogueur graphique. Pydb est disponible sur http://bashdb.sourceforge.net/pydb/ et DDD est disponible sur https://www.gnu.org/software/ddd.

Il existe de nombreux EDI Python propriétaires qui embarquent un débogueur graphique. Notamment :

Existe-t’il des outils pour aider à trouver des bogues ou faire de l’analyse statique de code ?

Oui.

PyChecker est un outil d’analyse statique qui trouve les bogues dans le code source Python et émet des avertissements relatifs à la complexité et au style du code. PyChecker est disponible sur http://pychecker.sourceforge.net/.

Pylint <https://www.pylint.org/>`_ est un autre outil qui vérifie si un module satisfait aux normes de développement, et qui permet en plus d’écrire des greffons pour ajouter des fonctionnalités personnalisées. En plus de la vérification des bogues effectuée par PyChecker, Pylint effectue quelques vérifications supplémentaires comme la longueur des lignes, les conventions de nommage des variables, que les interfaces déclarées sont implémentées en totalité, et plus encore. https://docs.pylint.org/ fournit la liste complète des fonctionnalités de Pylint.

Comment créer un binaire autonome à partir d’un script Python ?

Pour créer un programme autonome, c’est-à-dire un programme que n’importe qui peut télécharger et exécuter sans avoir à installer une distribution Python au préalable, il n’est pas nécessaire de compiler du code Python en code C. Il existe en effet plusieurs outils qui déterminent les modules requis par un programme et lient ces modules avec un binaire Python pour produire un seul exécutable.

Un de ces outils est freeze, qui se trouve dans Tools/freeze de l’arborescence des sources de Python. Il convertit le code intermédiaire (bytecode) Python en tableaux C ; un compilateur C permet d’intégrer tous vos modules dans un nouveau programme, qui est ensuite lié aux modules standards Python.

Il fonctionne en cherchant de manière récursive les instructions d’import (sous les deux formes) dans le code source et en recherchant ces modules dans le chemin Python standard ainsi que dans le répertoire source (pour les modules natifs). Il transforme ensuite le code intermédiaire des modules écrits en Python en code C (des tableaux pré-remplis qui peuvent être transformés en objets code à l’aide du module marshal) et crée un fichier de configuration personnalisé qui contient uniquement les modules natifs qui sont réellement utilisés dans le programme. Il compile ensuite le code C généré et le lie au reste de l’interpréteur Python pour former un binaire autonome qui fait exactement la même chose que le script.

Bien évidemment, freeze nécessite un compilateur C. Il existe d’autres outils qui peuvent s’en passer. Un de ceux-ci est py2exe de Thomas Heller (pour Windows uniquement) disponible sur

Another tool is Anthony Tuininga’s cx_Freeze.

Existe-t’il des normes de développement ou un guide de style pour écrire des programmes Python ?

Oui. Le style de développement que les modules de la bibliothèque standard doivent obligatoirement respecter est documenté dans la PEP 8.

Mon programme est trop lent. Comment l’accélérer ?

That’s a tough one, in general. There are many tricks to speed up Python code; consider rewriting parts in C as a last resort.

In some cases it’s possible to automatically translate Python to C or x86 assembly language, meaning that you don’t have to modify your code to gain increased speed.

Pyrex can compile a slightly modified version of Python code into a C extension, and can be used on many different platforms.

Psyco is a just-in-time compiler that translates Python code into x86 assembly language. If you can use it, Psyco can provide dramatic speedups for critical functions.

The rest of this answer will discuss various tricks for squeezing a bit more speed out of Python code. Never apply any optimization tricks unless you know you need them, after profiling has indicated that a particular function is the heavily executed hot spot in the code. Optimizations almost always make the code less clear, and you shouldn’t pay the costs of reduced clarity (increased development time, greater likelihood of bugs) unless the resulting performance benefit is worth it.

There is a page on the wiki devoted to performance tips.

Guido van Rossum has written up an anecdote related to optimization at https://www.python.org/doc/essays/list2str.

One thing to notice is that function and (especially) method calls are rather expensive; if you have designed a purely OO interface with lots of tiny functions that don’t do much more than get or set an instance variable or call another method, you might consider using a more direct way such as directly accessing instance variables. Also see the standard module profile which makes it possible to find out where your program is spending most of its time (if you have some patience – the profiling itself can slow your program down by an order of magnitude).

Remember that many standard optimization heuristics you may know from other programming experience may well apply to Python. For example it may be faster to send output to output devices using larger writes rather than smaller ones in order to reduce the overhead of kernel system calls. Thus CGI scripts that write all output in « one shot » may be faster than those that write lots of small pieces of output.

Also, be sure to use Python’s core features where appropriate. For example, slicing allows programs to chop up lists and other sequence objects in a single tick of the interpreter’s mainloop using highly optimized C implementations. Thus to get the same effect as:

L2 = []
for i in range(3):
    L2.append(L1[i])

it is much shorter and far faster to use

L2 = list(L1[:3])  # "list" is redundant if L1 is a list.

Note that the functionally-oriented built-in functions such as map(), zip(), and friends can be a convenient accelerator for loops that perform a single task. For example to pair the elements of two lists together:

>>> zip([1, 2, 3], [4, 5, 6])
[(1, 4), (2, 5), (3, 6)]

or to compute a number of sines:

>>> map(math.sin, (1, 2, 3, 4))
[0.841470984808, 0.909297426826, 0.14112000806, -0.756802495308]

The operation completes very quickly in such cases.

Other examples include the join() and split() methods of string objects. For example if s1..s7 are large (10K+) strings then "".join([s1,s2,s3,s4,s5,s6,s7]) may be far faster than the more obvious s1+s2+s3+s4+s5+s6+s7, since the « summation » will compute many subexpressions, whereas join() does all the copying in one pass. For manipulating strings, use the replace() and the format() methods on string objects. Use regular expressions only when you’re not dealing with constant string patterns. You may still use the old % operations string % tuple and string % dictionary.

Be sure to use the list.sort() built-in method to do sorting, and see the sorting mini-HOWTO for examples of moderately advanced usage. list.sort() beats other techniques for sorting in all but the most extreme circumstances.

Another common trick is to « push loops into functions or methods. » For example suppose you have a program that runs slowly and you use the profiler to determine that a Python function ff() is being called lots of times. If you notice that ff():

def ff(x):
    ... # do something with x computing result...
    return result

tends to be called in loops like:

list = map(ff, oldlist)

ou :

for x in sequence:
    value = ff(x)
    ... # do something with value...

then you can often eliminate function call overhead by rewriting ff() to:

def ffseq(seq):
    resultseq = []
    for x in seq:
        ... # do something with x computing result...
        resultseq.append(result)
    return resultseq

and rewrite the two examples to list = ffseq(oldlist) and to:

for value in ffseq(sequence):
    ... # do something with value...

Single calls to ff(x) translate to ffseq([x])[0] with little penalty. Of course this technique is not always appropriate and there are other variants which you can figure out.

You can gain some performance by explicitly storing the results of a function or method lookup into a local variable. A loop like:

for key in token:
    dict[key] = dict.get(key, 0) + 1

resolves dict.get every iteration. If the method isn’t going to change, a slightly faster implementation is:

dict_get = dict.get  # look up the method once
for key in token:
    dict[key] = dict_get(key, 0) + 1

Default arguments can be used to determine values once, at compile time instead of at run time. This can only be done for functions or objects which will not be changed during program execution, such as replacing

def degree_sin(deg):
    return math.sin(deg * math.pi / 180.0)

with

def degree_sin(deg, factor=math.pi/180.0, sin=math.sin):
    return sin(deg * factor)

Because this trick uses default arguments for terms which should not be changed, it should only be used when you are not concerned with presenting a possibly confusing API to your users.

Fondamentaux

Pourquoi une UnboundLocalError est levée alors qu’une variable a une valeur ?

Il est parfois surprenant d’obtenir une UnboundLocalError dans du code jusqu’à présent correct, quand celui-ci est modifié en ajoutant une instruction d’affectation quelque part dans le corps d’une fonction.

Le code suivant :

>>> x = 10
>>> def bar():
...     print x
>>> bar()
10

fonctionne, mais le suivant :

>>> x = 10
>>> def foo():
...     print x
...     x += 1

lève une UnboundLocalError :

>>> foo()
Traceback (most recent call last):
  ...
UnboundLocalError: local variable 'x' referenced before assignment

This is because when you make an assignment to a variable in a scope, that variable becomes local to that scope and shadows any similarly named variable in the outer scope. Since the last statement in foo assigns a new value to x, the compiler recognizes it as a local variable. Consequently when the earlier print x attempts to print the uninitialized local variable and an error results.

Dans l’exemple ci-dessus, la variable du contexte appelant reste accessible en la déclarant globale :

>>> x = 10
>>> def foobar():
...     global x
...     print x
...     x += 1
>>> foobar()
10

Cette déclaration explicite est obligatoire pour se rappeler que (contrairement au cas à peu près similaire avec des variables de classe et d’instance), c’est la valeur de la variable du contexte appelant qui est modifiée :

>>> print x
11

Quelles sont les règles pour les variables locales et globales en Python ?

En Python, si une variable n’est pas modifiée dans une fonction mais seulement lue, elle est implicitement considérée comme globale. Si une valeur lui est affectée, elle est considérée locale (sauf si elle est explicitement déclarée globale).

Bien que surprenant au premier abord, ce choix s’explique facilement. D’une part, exiger global pour des variables affectées est une protection contre des effets de bord inattendus. D’autre part, si global était obligatoire pour toutes les références à des objets globaux, il faudrait mettre global partout, car il faudrait dans ce cas déclarer globale chaque référence à une fonction native ou à un composant d’un module importé. Le codé serait alors truffé de déclarations global, ce qui nuirait à leur raison d’être : identifier les effets de bords.

Pourquoi des expressions lambda définies dans une boucle avec des valeurs différentes retournent-elles le même résultat ?

Supposons que l’on utilise une boucle itérative pour définir des expressions lambda (voire même des fonctions) différentes, par exemple :

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda: x**2)

Le code précédent crée une liste de 5 expressions lambda qui calculent chacune x**2. En les exécutant, on pourrait s’attendre à obtenir 0, 1, 4, 9 et 16. Elles renvoient en réalité toutes 16 :

>>> squares[2]()
16
>>> squares[4]()
16

Ceci s’explique par le fait que x n’est pas une variable locale aux expressions, mais est définie dans le contexte appelant. Elle est lue à l’appel de l’expression lambda – et non au moment où cette expression est définie. À la fin de la boucle, x vaut 4, donc toutes les fonctions renvoient 4*2, i.e. 16. Ceci se vérifie également en changeant la valeur de x et en constatant que les résultats sont modifiés :

>>> x = 8
>>> squares[2]()
64

Pour éviter ce phénomène, les valeurs doivent être stockées dans des variables locales aux expressions lambda pour que celles-ci ne se basent plus sur la variable globale x :

>>> squares = []
>>> for x in range(5):
...     squares.append(lambda n=x: n**2)

Dans ce code, n=x crée une nouvelle variable n, locale à l’expression. Cette variable est évaluée quand l’expression est définie donc n a la même valeur que x à ce moment. La valeur de n est donc 0 dans la première lambda, 1 dans la deuxième, 2 dans la troisième et ainsi de suite. Chaque expression lambda renvoie donc le résultat correct :

>>> squares[2]()
4
>>> squares[4]()
16

Ce comportement n’est pas propre aux expressions lambda, mais s’applique aussi aux fonctions normales.

Comment partager des variables globales entre modules ?

La manière standard de partager des informations entre modules d’un même programme est de créer un module spécial (souvent appelé config ou cfg) et de l’importer dans tous les modules de l’application ; le module devient accessible depuis l’espace de nommage global. Vu qu’il n’y a qu’une instance de chaque module, tout changement dans l’instance est propagé partout. Par exemple :

config.py

x = 0   # Default value of the 'x' configuration setting

mod.py

import config
config.x = 1

main.py

import config
import mod
print config.x

Pour les mêmes raisons, l’utilisation d’un module est aussi à la base de l’implémentation du patron de conception singleton.

Quelles sont les « bonnes pratiques » pour utiliser import dans un module ?

De manière générale, il ne faut pas faire from modulename import *. Ceci encombre l’espace de nommage de l’importateur et rend la détection de noms non-définis beaucoup plus ardue pour les analyseurs de code.

Les modules doivent être importés au début d’un fichier. Ceci permet d’afficher clairement de quels modules le code à besoin et évite de se demander si le module est dans le contexte. Faire un seul import par ligne rend l’ajout et la suppression d’une importation de module plus aisé, mais importer plusieurs modules sur une même ligne prend moins d’espace.

Il est recommandé d’importer les modules dans l’ordre suivant :

  1. les modules de la bibliothèque standard — e.g. sys, os, getopt, re

  2. les modules externes (tout ce qui est installé dans le dossier site-packages de Python) — e.g. mx.DateTime, ZODB, PIL.Image, etc.

  3. les modules développés en local

Only use explicit relative package imports. If you’re writing code that’s in the package.sub.m1 module and want to import package.sub.m2, do not just write import m2, even though it’s legal. Write from package.sub import m2 or from . import m2 instead.

Il est parfois nécessaire de déplacer des importations dans une fonction ou une classe pour éviter les problèmes d’importations circulaires. Comme le souligne Gordon McMillan :

Il n’y a aucun souci à faire des importations circulaires tant que les deux modules utilisent la forme « import <module> » . Ça ne pose problème que si le second module cherche à récupérer un nom du premier module (« from module import name ») et que l’importation est dans l’espace de nommage du fichier. Les noms du premier module ne sont en effet pas encore disponibles car le premier module est occupé à importer le second.

Dans ce cas, si le second module n’est utilisé que dans une fonction, l’importation peut facilement être déplacée dans cette fonction. Au moment où l’importation sera appelée, le premier module aura fini de s’initialiser et le second pourra faire son importation.

Il peut parfois être nécessaire de déplacer des importations de modules hors de l’espace de plus haut niveau du code si certains de ces modules dépendent de la machine utilisée. Dans ce cas de figure, il est parfois impossible d’importer tous les modules au début du fichier. Dans ce cas, il est recommandé d’importer les modules adéquats dans le code spécifique à la machine.

Les imports ne devraient être déplacés dans un espace local, comme dans la définition d’une fonction, que si cela est nécessaire pour résoudre un problème comme éviter des dépendances circulaires ou réduire le temps d’initialisation d’un module. Cette technique est particulièrement utile si la majorité des imports est superflue selon le flux d’exécution du programme. Il est également pertinent de déplacer des importations dans une fonction si le module n’est utilisé qu’au sein de cette fonction. Le premier chargement d’un module peut être coûteux à cause du coût fixe d’initialisation d’un module, mais charger un module plusieurs fois est quasiment gratuit, cela ne coûte que quelques indirections dans un dictionnaire. Même si le nom du module est sorti du contexte courant, le module est probablement disponible dans sys.modules.

Pourquoi les arguments par défaut sont-ils partagés entre les objets ?

C’est un problème que rencontrent souvent les programmeurs débutants. Examinons la fonction suivante

def foo(mydict={}):  # Danger: shared reference to one dict for all calls
    ... compute something ...
    mydict[key] = value
    return mydict

Au premier appel de cette fonction, mydict ne contient qu’un seul élément. Au second appel, mydict contient deux éléments car quand foo() commence son exécution, mydict contient déjà un élément.

On est souvent amené à croire qu’un appel de fonction créé des nouveau objets pour les valeurs par défaut. Ce n’est pas le cas. Les valeurs par défaut ne sont créées qu’une et une seule fois, au moment où la fonction est définie. Si l’objet est modifié, comme le dictionnaire dans cet exemple, les appels suivants à cette fonction font référence à l’objet ainsi modifié.

Par définition, les objets immuables comme les nombres, les chaînes de caractères, les n-uplets et None ne sont pas modifiés. Les changements sur des objets muables comme les dictionnaires, les listes et les instances de classe peuvent porter à confusion.

En raison de cette fonctionnalité, il vaut mieux ne pas utiliser d’objets muables comme valeurs par défaut. Il vaut mieux utiliser None comme valeur par défaut et, à l’intérieur de la fonction, vérifier si le paramètre est à None et créer une nouvelle liste, dictionnaire ou autre, le cas échéant. Par exemple, il ne faut pas écrire

def foo(mydict={}):
    ...

mais plutôt

def foo(mydict=None):
    if mydict is None:
        mydict = {}  # create a new dict for local namespace

Cette fonctionnalité a une utilité. Il est courant de mettre en cache les paramètres et la valeur de retour de chacun des appels d’une fonction coûteuse à exécuter, et de renvoyer la valeur stockée en cache si le même appel est ré-effectué. C’est la technique dite de « mémoïsation », qui s’implémente de la manière suivante

# Callers will never provide a third parameter for this function.
def expensive(arg1, arg2, _cache={}):
    if (arg1, arg2) in _cache:
        return _cache[(arg1, arg2)]

    # Calculate the value
    result = ... expensive computation ...
    _cache[(arg1, arg2)] = result           # Store result in the cache
    return result

Il est possible d’utiliser une variable globale contenant un dictionnaire à la place de la valeur par défaut ; ce n’est qu’une question de goût.

Comment passer des paramètres optionnels ou nommés d’une fonction à l’autre ?

Il faut récupérer les arguments en utilisant les sélecteurs * et ** dans la liste des paramètres de la fonction ; ceci donne les arguments positionnels sous la forme d’un n-uplet et les arguments nommés sous forme de dictionnaire. Ces arguments peuvent être passés à une autre fonction en utilisant * et **

def f(x, *args, **kwargs):
    ...
    kwargs['width'] = '14.3c'
    ...
    g(x, *args, **kwargs)

In the unlikely case that you care about Python versions older than 2.0, use apply():

def f(x, *args, **kwargs):
    ...
    kwargs['width'] = '14.3c'
    ...
    apply(g, (x,)+args, kwargs)

Quelle est la différence entre les arguments et les paramètres ?

Les paramètres sont les noms qui apparaissent dans une définition de fonction, alors que les arguments sont les valeurs qui sont réellement passées à une fonction lors de l’appel de celle-ci. Les paramètres définissent les types des arguments qu’une fonction accepte. Ainsi, avec la définition de fonction suivante

def func(foo, bar=None, **kwargs):
    pass

foo, bar et kwargs sont des paramètres de func. Mais à l’appel de func avec, par exemple

func(42, bar=314, extra=somevar)

les valeurs 42, 314, et somevar sont des arguments.

Pourquoi modifier la liste “y” modifie aussi la liste “x” ?

Si vous avez écrit du code comme :

>>> x = []
>>> y = x
>>> y.append(10)
>>> y
[10]
>>> x
[10]

vous vous demandez peut-être pourquoi l’ajout d’un élément à y a aussi changé x.

Il y a deux raisons qui conduisent à ce comportement :

  1. Les variables ne sont que des noms qui font référence à des objets. La ligne y = x ne crée pas une copie de la liste — elle crée une nouvelle variable y qui pointe sur le même objet que x. Ceci signifie qu’il n’existe qu’un seul objet (la liste) auquel x et y font référence.

  2. Les listes sont des muable, ce qui signifie que leur contenu peut être modifié.

Après l’appel de append(), le contenu de l’objet muable est passé de [] à [10]. Vu que les deux variables font référence au même objet, il est possible d’accéder à la valeur modifiée [10] avec chacun des noms.

Si au contraire, on affecte un objet immuable à x

>>> x = 5  # ints are immutable
>>> y = x
>>> x = x + 1  # 5 can't be mutated, we are creating a new object here
>>> x
6
>>> y
5

on observe que x et y ne sont ici plus égales. Les entiers sont des immuables (immutable), et x = x + 1 ne change pas l’entier 5 en incrémentant sa valeur. Au contraire, un nouvel objet est créé (l’entier 6) et affecté à x (c’est à dire qu’on change l’objet auquel fait référence x). Après cette affectation on a deux objets (les entiers 6 et 5) et deux variables qui font référence à ces deux objets (x fait désormais référence à 6 mais y fait toujours référence à 5).

Certaines opérations (par exemple, y.append(10) et y.sort()) modifient l’objet, alors que des opérations identiques en apparence (par exemple y = y + [10] et sorted(y)) créent un nouvel objet. En général, en Python, une méthode qui modifie un objet renvoie None (c’est même systématique dans la bibliothèque standard) pour éviter la confusion entre les deux opérations. Donc écrire par erreur y.sort() en pensant obtenir une copie triée de y donne None, ce qui conduit très souvent le programme à générer une erreur facile à diagnostiquer.

Il existe cependant une classe d’opérations qui se comporte différemment selon le type : les opérateurs d’affectation incrémentaux. Par exemple, += modifie les listes mais pas les n-uplets ni les entiers (a_list += [1, 2, 3] équivaut à a_list.extend([1, 2, 3]) et modifie a_list, alors que some_tuple += (1, 2, 3) et some_int += 1 créent de nouveaux objets).

En d’autres termes :

  • Il est possible d’appliquer des opérations qui modifient un objet muable (list, dict, set, etc.) et toutes les variables qui y font référence verront le changement.

  • Toutes les variables qui font référence à un objet immuable (str, int, tuple, etc.) renvoient la même valeur, mais les opérations qui transforment cette valeur en une nouvelle valeur renvoient toujours un nouvel objet.

L’opérateur is ou la fonction native id() permettent de savoir si deux variables font référence au même objet.

Comment écrire une fonction qui modifie ses paramètres ? (passage par référence)

En Python, les arguments sont passés comme des affectations de variables. Vu qu’une affectation crée des références à des objets, il n’y pas de lien entre un argument dans l’appel de la fonction et sa définition, et donc pas de passage par référence en soi. Il y a cependant plusieurs façon d’en émuler un.

  1. En renvoyant un n-uplet de résultats

    def func2(a, b):
        a = 'new-value'        # a and b are local names
        b = b + 1              # assigned to new objects
        return a, b            # return new values
    
    x, y = 'old-value', 99
    x, y = func2(x, y)
    print x, y                 # output: new-value 100
    

    C’est presque toujours la meilleure solution.

  2. En utilisant des variables globales. Ce qui n’est pas thread-safe, et n’est donc pas recommandé.

  3. En passant un objet muable (modifiable sur place) :

    def func1(a):
        a[0] = 'new-value'     # 'a' references a mutable list
        a[1] = a[1] + 1        # changes a shared object
    
    args = ['old-value', 99]
    func1(args)
    print args[0], args[1]     # output: new-value 100
    
  4. En passant un dictionnaire, qui sera modifié :

    def func3(args):
        args['a'] = 'new-value'     # args is a mutable dictionary
        args['b'] = args['b'] + 1   # change it in-place
    
    args = {'a': 'old-value', 'b': 99}
    func3(args)
    print args['a'], args['b']
    
  5. Ou regrouper les valeurs dans une instance de classe :

    class callByRef:
        def __init__(self, **args):
            for (key, value) in args.items():
                setattr(self, key, value)
    
    def func4(args):
        args.a = 'new-value'        # args is a mutable callByRef
        args.b = args.b + 1         # change object in-place
    
    args = callByRef(a='old-value', b=99)
    func4(args)
    print args.a, args.b
    

    Il n’y a pratiquement jamais de bonne raison de faire quelque chose d’aussi compliqué.

Votre meilleure option est de renvoyer un tuple contenant les multiples résultats.

Comment construire une fonction d’ordre supérieur en Python ?

Vous avez deux choix : vous pouvez utiliser les portées imbriquées ou vous pouvez utiliser des objets appelables. Par exemple, supposons que vous vouliez définir linear(a, b) qui renvoie une fonction f(x) qui calcule la valeur a*x+b. En utilisant les portées imbriquées :

def linear(a, b):
    def result(x):
        return a * x + b
    return result

Ou en utilisant un objet appelable :

class linear:

    def __init__(self, a, b):
        self.a, self.b = a, b

    def __call__(self, x):
        return self.a * x + self.b

dans les deux cas,

taxes = linear(0.3, 2)

donne un objet appelable où taxes(10e6) == 0.3 * 10e6 + 2.

L’approche par objet appelable a le désavantage d’être légèrement plus lente et de produire un code légèrement plus long. Cependant, il faut noter qu’une collection d’objet appelables peuvent partager leur signatures par héritage :

class exponential(linear):
    # __init__ inherited
    def __call__(self, x):
        return self.a * (x ** self.b)

Les objets peuvent encapsuler un état pour plusieurs méthodes :

class counter:

    value = 0

    def set(self, x):
        self.value = x

    def up(self):
        self.value = self.value + 1

    def down(self):
        self.value = self.value - 1

count = counter()
inc, dec, reset = count.up, count.down, count.set

Ici inc(), dec() et reset() agissent comme des fonctions partageant une même variable compteur.

Comment copier un objet en Python?

En général, essayez copy.copy() ou copy.deepcopy() pour le cas général. Tout les objets ne peuvent pas être copiés, mais la plupart le peuvent.

Certains objets peuvent être copiés plus facilement. Les Dictionnaires ont une méthode copy() :

newdict = olddict.copy()

Les séquences peuvent être copiées via la syntaxe des tranches :

new_l = l[:]

Comment puis-je trouver les méthodes ou les attribues d’un objet?

Pour une instance x d’une classe définie par un utilisateur, dir(x) renvoie une liste alphabétique des noms contenants les attributs de l’instance, et les attributs et méthodes définies par sa classe.

Comment mon code peut il découvrir le nom d’un objet?

De façon générale, il ne peut pas, par ce que les objets n’ont pas réellement de noms. Essentiellement, l’assignation attache un nom à une valeur; C’est vrai aussi pour les instructions def et class, à la différence que dans ce cas la valeur est appelable. Par exemple, dans le code suivant :

>>> class A:
...     pass
...
>>> B = A
>>> a = B()
>>> b = a
>>> print b
<__main__.A instance at 0x16D07CC>
>>> print a
<__main__.A instance at 0x16D07CC>

Le fait que la classe ait un nom est discutable, bien qu’elles soit liée à deux noms, et qu’elle soit appelée via le nom B, l’instance crée déclare tout de même être une instance de la classe A. De même Il est impossible de dire si le nom de l’instance est a ou b, les deux noms sont attachés à la même valeur.

De façon général, il ne devrait pas être nécessaire pour votre application de « connaître le nom » d’une valeur particulière. À moins que vous soyez délibérément en train d’écrire un programme introspectif, c’est souvent une indication qu’un changement d’approche pourrait être bénéfique.

Sur comp.lang.python, Fredrik Lundh a donné un jour une excellente analogie pour répondre à cette question:

C’est pareil que trouver le nom du chat qui traîne devant votre porte : Le chat (objet) ne peux pas vous dire lui même son nom, et il s’en moque un peu – alors le meilleur moyen de savoir comment il s’appelle est de demander à tous vos voisins (espaces de noms) si c’est leur chat (objet)….

…et ne soyez pas surpris si vous découvrez qu’il est connus sous plusieurs noms différents, ou pas de nom du tout!

Qu’en est-il de la précédence de l’opérateur virgule ?

La virgule n’est pas un opérateur en Python. Observez la session suivante :

>>> "a" in "b", "a"
(False, 'a')

Comme la virgule n’est pas un opérateur, mais un séparateur entre deux expression, l’expression ci dessus, est évaluée de la même façon que si vous aviez écrit :

("a" in "b"), "a"

et non :

"a" in ("b", "a")

Ceci est vrai pour tous les opérateurs d’assignations (=, += etc). Ce ne sont pas vraiment des opérateurs mais des délimiteurs syntaxiques dans les instructions d’assignation.

Existe-t-il un équivalent à l’opérateur ternaire « ?: » du C ?

Oui, cette fonctionnalité à été ajouté à partir de Python 2.5. La syntaxe est la suivante:

[on_true] if [expression] else [on_false]

x, y = 50, 25

small = x if x < y else y

Pour les versions précédentes de python la réponse serait « Non ».

Est-il possible d’écrire des programmes obscurcis (obfuscated) d’une ligne en Python ?

Oui. Cela est généralement réalisé en imbriquant les lambda dans des lambda. Observez les trois exemples suivants de Ulf Bartelt :

# Primes < 1000
print filter(None,map(lambda y:y*reduce(lambda x,y:x*y!=0,
map(lambda x,y=y:y%x,range(2,int(pow(y,0.5)+1))),1),range(2,1000)))

# First 10 Fibonacci numbers
print map(lambda x,f=lambda x,f:(f(x-1,f)+f(x-2,f)) if x>1 else 1: f(x,f),
range(10))

# Mandelbrot set
print (lambda Ru,Ro,Iu,Io,IM,Sx,Sy:reduce(lambda x,y:x+y,map(lambda y,
Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,Sy=Sy,L=lambda yc,Iu=Iu,Io=Io,Ru=Ru,Ro=Ro,i=IM,
Sx=Sx,Sy=Sy:reduce(lambda x,y:x+y,map(lambda x,xc=Ru,yc=yc,Ru=Ru,Ro=Ro,
i=i,Sx=Sx,F=lambda xc,yc,x,y,k,f=lambda xc,yc,x,y,k,f:(k<=0)or (x*x+y*y
>=4.0) or 1+f(xc,yc,x*x-y*y+xc,2.0*x*y+yc,k-1,f):f(xc,yc,x,y,k,f):chr(
64+F(Ru+x*(Ro-Ru)/Sx,yc,0,0,i)),range(Sx))):L(Iu+y*(Io-Iu)/Sy),range(Sy
))))(-2.1, 0.7, -1.2, 1.2, 30, 80, 24)
#    \___ ___/  \___ ___/  |   |   |__ lines on screen
#        V          V      |   |______ columns on screen
#        |          |      |__________ maximum of "iterations"
#        |          |_________________ range on y axis
#        |____________________________ range on x axis

Les enfants, ne faîtes pas ça chez vous !

Nombres et chaînes de caractères

Comment puis-je écrire des entiers hexadécimaux ou octaux ?

Pour écrire un entier octal, faites précéder la valeur octale par un zéro, puis un « o » majuscule ou minuscule. Par exemple assigner la valeur octale « 10 » (8 en décimal) à la variable « a », tapez :

>>> a = 0o10
>>> a
8

L’hexadécimal est tout aussi simple, faîtes précéder le nombre hexadécimal par un zéro, puis un « x » majuscule ou minuscule. Les nombres hexadécimaux peuvent être écrit en majuscules ou en minuscules. Par exemple, dans l’interpréteur Python :

>>> a = 0xa5
>>> a
165
>>> b = 0XB2
>>> b
178

Pourquoi -22 // 10 donne-t-il -3 ?

Cela est principalement due à la volonté que i % j ait le même signe que j. Si vous voulez cela, vous voulez aussi :

i == (i // j) * j + (i % j)

Alors la division entière doit renvoyer l’entier inférieur. Le C demande aussi à ce que cette égalité soit vérifiée, et donc les compilateur qui tronquent i // j ont besoin que i % j ait le même signe que i.

Il y a peu de cas d’utilisation réels pour i%j quand j est négatif. Quand j est positif, il y en a beaucoup, et dans pratiquement tous, il est plus utile que i % j soit >=0. Si l’horloge dit 10h maintenant, que disait-elle il y a 200 heures? -190%12 == 2 est utile; -192 % 12 == -10 est un bug qui attends pour mordre.

Note

On Python 2, a / b returns the same as a // b if __future__.division is not in effect. This is also known as « classic » division.

Comment puis-je convertir une chaine de caractère en nombre?

Pour les entiers, utilisez la fonction native int() de type constructeur, par exemple int('144') == 144. De façon similaire, float() convertit en valeur flottante, par exemple float('144') == 144.0.

Par défaut, ces fonctions interprètent les nombre en tant que décimaux, de telles façons que int('0144')==144 et int('0x144') remontent ValueError. int(string, base) prends la base depuis laquelle il faut convertir dans le second argument, optionnel, donc int('0x144', 16) == 324. Si la base donnée est 0, le nombre est interprété selon les règles Python: un “0” en tête indique octal, et “0x” indique un hexadécimal.

N’utilisez pas la fonction native eval() si tout ce que vous avez besoin est de convertir des chaines en nombres. eval() sera significativement plus lent et implique des risque de sécurité: quelqu’un pourrait vous envoyez une expression Python pouvant avoir des effets de bord indésirables. Par exemple, quelqu’un pourrait passer __import__('os').system("rm -rf $HOME") ce qui aurait pour effet d’effacer votre répertoire personnel.

eval() also has the effect of interpreting numbers as Python expressions, so that e.g. eval('09') gives a syntax error because Python regards numbers starting with “0” as octal (base 8).

Comment convertir un nombre en chaine de caractère?

To convert, e.g., the number 144 to the string “144”, use the built-in type constructor str(). If you want a hexadecimal or octal representation, use the built-in functions hex() or oct(). For fancy formatting, see the Syntaxe de formatage de chaîne section, e.g. "{:04d}".format(144) yields '0144' and "{:.3f}".format(1.0/3.0) yields '0.333'. In Python 2, the division (/) operator returns the floor of the mathematical result of division if the arguments are ints or longs, but it returns a reasonable approximation of the division result if the arguments are floats or complex:

>>> print('{:.3f}'.format(1/3))
0.000
>>> print('{:.3f}'.format(1.0/3))
0.333

In Python 3, the default behaviour of the division operator (see PEP 238) has been changed but you can have the same behaviour in Python 2 if you import division from __future__:

>>> from __future__ import division
>>> print('{:.3f}'.format(1/3))
0.333

You may also use the % operator on strings. See the library reference manual for details.

Comment modifier une chaine de caractère « en place »?

Vous ne pouvez pas, par ce que les chaines de caractères sont immuables, Si vous avez besoin d’un objet ayant une telle capacité, essayez de convertir la chaine en liste, ou utilisez le module array:

>>> import io
>>> s = "Hello, world"
>>> a = list(s)
>>> print a
['H', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd']
>>> a[7:] = list("there!")
>>> ''.join(a)
'Hello, there!'

>>> import array
>>> a = array.array('c', s)
>>> print a
array('c', 'Hello, world')
>>> a[0] = 'y'; print a
array('c', 'yello, world')
>>> a.tostring()
'yello, world'

Comment utiliser des chaines de caractères pour appeler des fonctions/méthodes?

Il y a différentes techniques.

  • La meilleure est d’utiliser un dictionnaire qui fait correspondre les chaines de caractères à des fonctions. Le principal avantage de cette technique est que les chaines n’ont pas besoin d’être égales aux noms de fonctions. C’est aussi la principale façon d’imiter la construction « case » :

    def a():
        pass
    
    def b():
        pass
    
    dispatch = {'go': a, 'stop': b}  # Note lack of parens for funcs
    
    dispatch[get_input()]()  # Note trailing parens to call function
    
  • Utiliser la fonction getattr() :

    import foo
    getattr(foo, 'bar')()
    

    Notez que getattr() marche sur n’importe quel objet, ceci inclue les classes, les instances de classes, les modules et ainsi de suite.

    Ceci est utilisé dans plusieurs endroit de la bibliothèque standard, de cette façon :

    class Foo:
        def do_foo(self):
            ...
    
        def do_bar(self):
            ...
    
    f = getattr(foo_instance, 'do_' + opname)
    f()
    
  • Utilisez locals() ou eval() pour résoudre le nom de fonction :

    def myFunc():
        print "hello"
    
    fname = "myFunc"
    
    f = locals()[fname]
    f()
    
    f = eval(fname)
    f()
    

    Note: En utilisant eval() est lent est dangereux. Si vous n’avez pas un contrôle absolu sur le contenu de la chaine de caractère, quelqu’un peut passer une chaine de caractère pouvant résulter en l’exécution de code arbitraire.

Existe-t-il un équivalent à la fonction chomp() de Perl, pour retirer les caractères de fin de ligne d’une chaine de caractère ?

Starting with Python 2.2, you can use S.rstrip("\r\n") to remove all occurrences of any line terminator from the end of the string S without removing other trailing whitespace. If the string S represents more than one line, with several empty lines at the end, the line terminators for all the blank lines will be removed:

>>> lines = ("line 1 \r\n"
...          "\r\n"
...          "\r\n")
>>> lines.rstrip("\n\r")
'line 1 '

Du fait que ce soit principalement utile en lisant un texte ligne à ligne, utiliser S.rstrip() devrait marcher correctement.

Pour les versions plus anciennes de python, il y a deux substituts partiels disponibles.

  • Si vous voulez retirer tous les espaces de fin de ligne, utilisez la méthode rstrip() des chaines de caractères. Cela retire tous les espaces de fin de ligne, pas seulement le caractère de fin de ligne.

  • Sinon, s’il y a seulement une ligne dans la chaine S, utilisez S.splitlines()[0].

Existe-t-il un équivalent à scanf() ou sscanf() ?

Pas exactement.

Pour une simple analyse de chaine, l’approche la plus simple est généralement de découper la ligne en mots délimités par des espaces, en utilisant la méthode split() des objets chaine de caractères, et ensuite de convertir les chaines de décimales en valeurs numériques en utilisant la fonction int() ou float(), split() supporte un paramètre optionnel « sep » qui est utile si la ligne utilise autre chose que des espaces comme séparateur.

Pour les analyses plus compliquées, les expressions rationnelles sont plus puissantes que la fonction sscanf() de C et mieux adaptées à la tâche.

What does “UnicodeError: ASCII [decoding,encoding] error: ordinal not in range(128)” mean?

This error indicates that your Python installation can handle only 7-bit ASCII strings. There are a couple ways to fix or work around the problem.

If your programs must handle data in arbitrary character set encodings, the environment the application runs in will generally identify the encoding of the data it is handing you. You need to convert the input to Unicode data using that encoding. For example, a program that handles email or web input will typically find character set encoding information in Content-Type headers. This can then be used to properly convert input data to Unicode. Assuming the string referred to by value is encoded as UTF-8:

value = unicode(value, "utf-8")

will return a Unicode object. If the data is not correctly encoded as UTF-8, the above call will raise a UnicodeError exception.

If you only want strings converted to Unicode which have non-ASCII data, you can try converting them first assuming an ASCII encoding, and then generate Unicode objects if that fails:

try:
    x = unicode(value, "ascii")
except UnicodeError:
    value = unicode(value, "utf-8")
else:
    # value was valid ASCII data
    pass

It’s possible to set a default encoding in a file called sitecustomize.py that’s part of the Python library. However, this isn’t recommended because changing the Python-wide default encoding may cause third-party extension modules to fail.

Note that on Windows, there is an encoding known as « mbcs », which uses an encoding specific to your current locale. In many cases, and particularly when working with COM, this may be an appropriate default encoding to use.

Sequences (Tuples/Lists)

Comment convertir les listes en tuples et inversement?

Le constructeur de type tuple(seq) convertit toute séquence (en fait tout itérable) en un tuple avec les mêmes éléments dans le même ordre….

Par exemple tuple([1, 2, 3]) renvoi (1, 2, 3) et tuple('abc') renvoi ('a', 'b', 'c'). Si l’argument est un tuple, cela ne crée pas une copie, mais renvoi le même objet, ce qui fait de tuple() un fonction économique à appeler quand vous ne savez pas si votre objet est déjà un tuple.

Le constructeur de type list(seq) convertit toute séquence ou itérable en liste contenant les mêmes éléments dans le même ordre. Par exemple, list((1,2,3)) renvoie [1,2,3] et list('abc') renvoie ['a','b','c']. Si l’argument est une liste, il renvoie une copie, de la même façon que seq[:].

Qu’est-ce qu’un indexe négatif?

Les séquences Python sont indexées avec des nombres positifs aussi bien que négatifs. Pour les nombres positifs, 0 est le premier index, 1 est le second, et ainsi de suite. Pour les indexes négatifs, -1 est le dernier index, -2 est le pénultième (avant dernier), et ainsi de suite. On peut aussi dire que seq[-n] est équivalent à seq[len(seq)-n].

Utiliser des indexes négatifs peut être très pratique. Par exemple S[:-1] indique la chaine entière a l’exception du dernier caractère, ce qui est pratique pour retirer un caractère de fin de ligne en fin d’une chaine.

Comment itérer à rebours sur une séquence?

Utilisez la fonction embarquée reversed(), qui est apparue en Python 2.4 :

for x in reversed(sequence):
    ...  # do something with x ...

Cela ne modifiera pas votre séquence initiale, mais construira à la place une copie en ordre inverse pour itérer dessus.

Avec Python 2.3 vous pouvez utiliser la syntaxe étendue de tranches :

for x in sequence[::-1]:
    ...  # do something with x ...

Comment retirer les doublons d’une liste?

Lisez le Python Cookbook pour trouver une longue discussion sur les nombreuses façons de faire cela:

Si changer l’ordre de la liste ne vous dérange pas, commencez par trier celle ci, puis parcourez la d’un bout à l’autre, en supprimant les doublons trouvés en chemin :

if mylist:
    mylist.sort()
    last = mylist[-1]
    for i in range(len(mylist)-2, -1, -1):
        if last == mylist[i]:
            del mylist[i]
        else:
            last = mylist[i]

Si tous les éléments de la liste peuvent être utilisés comme des clés de dictionnaire (cad, elles sont toutes hashables) ceci est souvent plus rapide:

d = {}
for x in mylist:
    d[x] = 1
mylist = list(d.keys())

En Python 2.5 et suivant, la forme suivante est possible à la place:

mylist = list(set(mylist))

Ceci convertis la liste en un ensemble, ce qui supprime automatiquement les doublons, puis la transforme à nouveau en liste.

Comment construire un tableau en Python?

Utilisez une liste :

["this", 1, "is", "an", "array"]

Les listes ont un cout équivalent à celui des tableau C ou Pascal; la principale différence est qu’une liste Python peut contenir des objets de différents types.

Le module array fournit des méthodes pour créer des tableaux de types fixes dans une représentation compacte, mais ils sont plus lents à indexer que les listes. Notez aussi que l’extension Numeric et d’autres, fournissent différentes structures de types tableaux, avec des caractéristiques différentes.

Pour obtenir des listes chainées de type Lisp, vous pouvez émuler les cons cells en utilisant des tuples :

lisp_list = ("like",  ("this",  ("example", None) ) )

Si vous voulez pouvoir modifier les éléments, utilisez une liste plutôt qu’un tuple. Ici la version équivalente au car de Lisp est lisp_list[0] et l’équivalent à cdr est list_lip[1]. Ne faites ceci que si vous êtes réellement sûr d’en avoir besoin, cette méthode est en générale bien plus lente que les listes Python.

Comment puis-je créer une liste à plusieurs dimensions?

Vous avez probablement essayé de créer une liste à plusieurs dimensions de cette façon :

>>> A = [[None] * 2] * 3

Cela semble correct quand vous essayer de l’afficher:

>>> A
[[None, None], [None, None], [None, None]]

Mais quand vous assignez une valeur, elle apparait en de multiples endroits:

>>> A[0][0] = 5
>>> A
[[5, None], [5, None], [5, None]]

La raison en est que dupliquer une liste en utilisant * ne crée pas de copies, cela crée seulement des références aux objets existants. Le *3 crée une liste contenant trois références à la même liste de longueur deux. Un changement dans une colonne apparaîtra donc dans toutes les colonnes. Ce qui n’est de façon quasi certaine, pas ce que vous souhaitez.

L’approche suggérée est de créer une liste de la longueur désiré d’abords, puis de remplir tous les éléments avec une chaîne nouvellement créée :

A = [None] * 3
for i in range(3):
    A[i] = [None] * 2

Cette liste générée contient trois listes différentes de longueur deux. Vous pouvez aussi utilisez la notation de compréhension de listes :

w, h = 2, 3
A = [[None] * w for i in range(h)]

Vous pouvez aussi utiliser une extension qui fournit un type matriciel natif ; NumPy est la plus répandue.

Comment appliquer une méthode à une séquence d’objets?

Utilisez une compréhension de liste :

result = [obj.method() for obj in mylist]

More generically, you can try the following function:

def method_map(objects, method, arguments):
    """method_map([a,b], "meth", (1,2)) gives [a.meth(1,2), b.meth(1,2)]"""
    nobjects = len(objects)
    methods = map(getattr, objects, [method]*nobjects)
    return map(apply, methods, [arguments]*nobjects)

Pourquoi a_tuple[i] += [“item”] lève-t’il une exception alors que l’addition fonctionne ?

Ceci est dû à la combinaison de deux facteurs : le fait que les opérateurs d’affectation incrémentaux sont des opérateurs d”affectation et à la différence entre les objets muables et immuables en Python.

Cette discussion est valable, en général, quand des opérateurs d’affectation incrémentale sont appliqués aux élément d’un n-uplet qui pointe sur des objets muables, mais on prendra list et += comme exemple.

Si vous écrivez :

>>> a_tuple = (1, 2)
>>> a_tuple[0] += 1
Traceback (most recent call last):
   ...
TypeError: 'tuple' object does not support item assignment

La cause de l’exception est claire : 1 est ajouté à l’objet a_tuple[0] qui pointe sur (1), ce qui produit l’objet résultant 2, mais, lorsque l’on tente d’affecter le résultat du calcul, 2, à l’élément 0 du n-uplet, on obtient une erreur car il est impossible de modifier la cible sur laquelle pointe un élément d’un n-uplet.

Sous le capot, une instruction d’affectation incrémentale fait à peu près ceci :

>>> result = a_tuple[0] + 1
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

C’est la partie de l’affectation de l’opération qui génère l’erreur, vu qu’un n-uplet est immuable.

Quand vous écrivez un code du style :

>>> a_tuple = (['foo'], 'bar')
>>> a_tuple[0] += ['item']
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

L’exception est un peu plus surprenante et, chose encore plus étrange, malgré l’erreur, l’ajout a fonctionné

>>> a_tuple[0]
['foo', 'item']

Pour comprendre ce qui se passe, il faut savoir que, premièrement, si un objet implémente la méthode magique c, celle-ci est appelée quand l’affectation incrémentale += est exécutée et sa valeur de retour est utilisée dans l’instruction d’affectation ; et que, deuxièmement, pour les listes, __iadd__ équivaut à appeler extend sur la liste et à renvoyer celle-ci. C’est pour cette raison que l’on dit que pour les listes, += est un « raccourci » pour list.extend:

>>> a_list = []
>>> a_list += [1]
>>> a_list
[1]

C’est équivalent à :

>>> result = a_list.__iadd__([1])
>>> a_list = result

L’objet sur lequel pointe a_list a été modifié et le pointeur vers l’objet modifié est réaffecté à a_list. In fine, l’affectation ne change rien, puisque c’est un pointeur vers le même objet que sur lequel pointait a_list, mais l’affectation a tout de même lieu.

Donc, dans notre exemple avec un n-uplet, il se passe quelque chose équivalent à

>>> result = a_tuple[0].__iadd__(['item'])
>>> a_tuple[0] = result
Traceback (most recent call last):
  ...
TypeError: 'tuple' object does not support item assignment

L’appel à __iadd__ réussit et la liste est étendue, mais bien que result pointe sur le même objet que a_tuple[0], l’affectation finale échoue car les n-uplets ne sont pas muables.

Dictionnaires

Comment puis-je faire afficher les éléments d’un dictionnaire dans un ordre consistant?

Vous ne pouvez pas. Les dictionnaires enregistrent leurs clées dans un ordre non prévisible, l’ordre d’affichage des éléments d’un dictionnaire sera donc de la même façon imprévisible.

Cela peut être frustrant si vous voulez sauvegarder une version affichable dans un fichier, faire des changement puis comparer avec un autre dictionnaire affiché. Dans ce cas, utilisez le module pprint` pour afficher joliement le dictionnaire; les éléments seront présentés triés par clés.

Une solution plus compliquée est de sousclasser dict pour créer une classe``SorterDict`` qui s’affiche de façon prévisible. Voici une implémentation simple d’une telle classe:

class SortedDict(dict):
    def __repr__(self):
        keys = sorted(self.keys())
        result = ("{!r}: {!r}".format(k, self[k]) for k in keys)
        return "{{{}}}".format(", ".join(result))

    __str__ = __repr__

Cela marchera dans la plupart des situations que vous pourriez rencontrer, même si c’est loin d’être une solution parfaite. Le plus gros problème avec cette solution est que si certaines valeurs dans le dictionnaire sont aussi des dictionnaire, alors elles ne seront pas présentées dans un ordre particulier.

Je souhaite faire un tri compliqué: peut on faire une transformation de Schwartz en Python?

The technique, attributed to Randal Schwartz of the Perl community, sorts the elements of a list by a metric which maps each element to its « sort value ». In Python, use the key argument for the sort() function:

Isorted = L[:]
Isorted.sort(key=lambda s: int(s[10:15]))

Comment puis-je trier une liste en fonction des valeurs d’une autre liste?

Merge them into a single list of tuples, sort the resulting list, and then pick out the element you want.

>>> list1 = ["what", "I'm", "sorting", "by"]
>>> list2 = ["something", "else", "to", "sort"]
>>> pairs = zip(list1, list2)
>>> pairs
[('what', 'something'), ("I'm", 'else'), ('sorting', 'to'), ('by', 'sort')]
>>> pairs.sort()
>>> result = [ x[1] for x in pairs ]
>>> result
['else', 'sort', 'to', 'something']

Une alternative pour la dernière étape est :

>>> result = []
>>> for p in pairs: result.append(p[1])

Si vous trouvez cela plus lisible, vous préférez peut-être utiliser ceci à la place de la compréhension de la liste finale. Toutefois, ceci est presque deux fois plus lent pour les longues listes. Pourquoi? Tout d’abord, append () doit réaffecter la mémoire, et si il utilise quelques astuces pour éviter de le faire à chaque fois, il doit encore le faire de temps en temps, ce qui coûte assez cher. Deuxièmement, l’expression result.append exige une recherche d’attribut supplémentaire, et enfin, tous ces appels de fonction impactent la vitesse d’exécution.

Objets

Qu’est-ce qu’une classe?

Une classe est le type d’objet particulier créé par l’exécution d’une déclaration de classe. Les objets de classe sont utilisés comme modèles pour créer des objets, qui incarnent à la fois les données (attributs) et le code (méthodes) spécifiques à un type de données.

Une classe peut être fondée sur une ou plusieurs autres classes, appelée sa ou ses classes de base. Il hérite alors les attributs et les méthodes de ses classes de base. Cela permet à un modèle d’objet d’être successivement raffinés par héritage. Vous pourriez avoir une classe générique Mailbox qui fournit des méthodes d’accès de base pour une boîte aux lettres, et sous-classes telles que MboxMailbox, MaildirMailbox, OutlookMailbox qui gèrent les différents formats de boîtes aux lettres spécifiques.

Qu’est-ce qu’une méthode?

Une méthode est une fonction sur un objet x appelez normalement comme x.name(arguments…). Les méthodes sont définies comme des fonctions à l’intérieur de la définition de classe :

class C:
    def meth(self, arg):
        return arg * 2 + self.attribute

Qu’est-ce que self?

Self est simplement un nom conventionnel pour le premier argument d’une méthode. Une méthode définie comme meth(self, a, b, c) doit être appelée en tant que x.meth(a, b, c), pour une instance x de la classe dans laquelle elle est définie, la méthode appelée considérera qu’elle est appelée meth(x, a, b, c).

Voir aussi Pourquoi « self » doit-il être explicitement utilisé dans les définitions et les appels de méthode ?.

Comment puis-je vérifier si un objet est une instance d’une classe donnée ou d’une sous-classe de celui-ci?

Use the built-in function isinstance(obj, cls). You can check if an object is an instance of any of a number of classes by providing a tuple instead of a single class, e.g. isinstance(obj, (class1, class2, ...)), and can also check whether an object is one of Python’s built-in types, e.g. isinstance(obj, str) or isinstance(obj, (int, long, float, complex)).

Notez que la plupart des programmes n’utilisent pas isInstance() sur les classes définies par l’utilisateur, très souvent. Si vous développez vous-même les classes, un style plus appropriée orientée objet est de définir des méthodes sur les classes qui encapsulent un comportement particulier, au lieu de vérifier la classe de l’objet et de faire quelque chose de différent en fonction de sa classe. Par exemple, si vous avez une fonction qui fait quelque chose :

def search(obj):
    if isinstance(obj, Mailbox):
        ...  # code to search a mailbox
    elif isinstance(obj, Document):
        ...  # code to search a document
    elif ...

Une meilleure approche est de définir une méthode search() sur toutes les classes et qu’il suffit d’appeler :

class Mailbox:
    def search(self):
        ...  # code to search a mailbox

class Document:
    def search(self):
        ...  # code to search a document

obj.search()

Qu’est-ce que la délégation?

La délégation est une technique orientée objet (aussi appelé un modèle de conception). Disons que vous avez un objet x et que vous souhaitez modifier le comportement d’une seule de ses méthodes. Vous pouvez créer une nouvelle classe qui fournit une nouvelle implémentation de la méthode qui vous intéresse dans l’évolution et les délégués de toutes les autres méthodes la méthode correspondante de x.

Les programmeurs Python peuvent facilement mettre en œuvre la délégation. Par exemple, la classe suivante implémente une classe qui se comporte comme un fichier, mais convertit toutes les données écrites en majuscules :

class UpperOut:

    def __init__(self, outfile):
        self._outfile = outfile

    def write(self, s):
        self._outfile.write(s.upper())

    def __getattr__(self, name):
        return getattr(self._outfile, name)

Ici, la classe UpperOut redéfinit la méthode write() pour convertir la chaîne d’argument en majuscules avant d’appeler la méthode sous-jacentes self.__outfile.write(). Toutes les autres méthodes sont déléguées à l’objet sous-jacent self.__outfile. La délégation se fait par la méthode __getattr__, consulter the language reference pour plus d’informations sur le contrôle d’accès d’attribut.

Notez que pour une utilisation plus générale de la délégation, les choses peuvent se compliquer. Lorsque les attributs doivent être définis aussi bien que récupérés, la classe doit définir une méthode __setattr__() aussi, et il doit le faire avec soin. La mise en œuvre basique de la méthode __setattr__() est à peu près équivalent à ce qui suit :

class X:
    ...
    def __setattr__(self, name, value):
        self.__dict__[name] = value
    ...

La plupart des implémentations de __setattr__() doivent modifier self.__dict__ pour stocker l’état locale de self sans provoquer une récursion infinie.

Comment appeler une méthode définie dans une classe de base depuis une classe dérivée qui la surcharge?

Si vous utilisez des new-style classes, Utilisez la fonction native super():

class Derived(Base):
    def meth(self):
        super(Derived, self).meth()

If you’re using classic classes: For a class definition such as class Derived(Base): ... you can call method meth() defined in Base (or one of Base’s base classes) as Base.meth(self, arguments...). Here, Base.meth is an unbound method, so you need to provide the self argument.

Comment puis-je organiser mon code pour permettre de changer la classe de base plus facilement?

Vous pouvez définir un alias pour la classe de base, lui attribuer la classe de base réelle avant la définition de classe, et utiliser l’alias au long de votre classe. Ensuite, tout ce que vous devez changer est la valeur attribuée à l’alias. Incidemment, cette astuce est également utile si vous voulez décider dynamiquement (par exemple en fonction de la disponibilité des ressources) la classe de base à utiliser. Exemple :

BaseAlias = <real base class>

class Derived(BaseAlias):
    def meth(self):
        BaseAlias.meth(self)
        ...

Comment puis-je créer des données statiques de classe et des méthodes statiques de classe?

Tant les données statiques que les méthodes statiques (dans le sens de C + + ou Java) sont pris en charge en Python.

Pour les données statiques, il suffit de définir un attribut de classe. Pour attribuer une nouvelle valeur à l’attribut, vous devez explicitement utiliser le nom de classe dans l’affectation :

class C:
    count = 0   # number of times C.__init__ called

    def __init__(self):
        C.count = C.count + 1

    def getcount(self):
        return C.count  # or return self.count

c.count se réfère également à C.count pour tout c telle que isInstance (c, C) est vrai, sauf remplacement par c lui-même ou par une classe sur le chemin de recherche de classe de base de c.__class__ jusqu’à C.

Attention: dans une méthode de C, une affectation comme self.count = 42 crée une nouvelle instance et sans rapport avec le nom count dans dans le dictionnaire de données de self. La redéfinition d’une donnée statique de classe doit toujours spécifier la classe que l’on soit à l’intérieur d’une méthode ou non :

C.count = 314

Les méthodes statiques sont possibles depuis Python 2.2:

class C:
    def static(arg1, arg2, arg3):
        # No 'self' parameter!
        ...
    static = staticmethod(static)

Avec les décorateurs de Python 2.4, cela peut aussi s’écrire:

class C:
    @staticmethod
    def static(arg1, arg2, arg3):
        # No 'self' parameter!
        ...

Cependant, d’une manière beaucoup plus simple pour obtenir l’effet d’une méthode statique se fait par une simple fonction au niveau du module :

def getcount():
    return C.count

Si votre code est structuré de manière à définir une classe (ou bien la hiérarchie des classes connexes) par module, ceci fournira l’encapsulation souhaitée.

Comment puis-je surcharger les constructeurs (ou méthodes) en Python?

Cette réponse s’applique en fait à toutes les méthodes, mais la question vient généralement en premier dans le contexte des constructeurs.

In C++ you’d write

class C {
    C() { cout << "No arguments\n"; }
    C(int i) { cout << "Argument is " << i << "\n"; }
}

En Python, vous devez écrire un constructeur unique qui considère tous les cas en utilisant des arguments par défaut. Par exemple :

class C:
    def __init__(self, i=None):
        if i is None:
            print "No arguments"
        else:
            print "Argument is", i

Ce n’est pas tout à fait équivalent, mais suffisamment proche dans la pratique.

Vous pouvez aussi utiliser une liste d’arguments de longueur variable, par exemple :

def __init__(self, *args):
    ...

La même approche fonctionne pour toutes les définitions de méthode.

J’essaie d’utiliser __spam et j’obtiens une erreur à propos de _SomeClassName__spam.

Les noms de variables commençant avec deux tirets bas sont «déformés», c’est un moyen simple mais efficace de définir variables privées à une classe. Tout identifiant de la forme __spam (commençant par au moins deux tirets bas et se terminant par au plus un tiret bas) est textuellement remplacé par _classname__spam, où classname est le nom de la classe en cours dont les éventuels tirets bas ont été retirés.

Cela ne garantit pas la privauté de l’accès : un utilisateur extérieur peut encore délibérément accéder à l’attribut _classname__spam, et les valeurs privées sont visibles dans l’objet __dict__. De nombreux programmeurs Python ne prennent jamais la peine d’utiliser des noms de variable privée.

Ma classe définit __del__ mais il n’est pas appelé lorsque je supprime l’objet.

Il y a plusieurs raisons possibles pour cela.

La commande del n’appelle pas forcément __del__() — il décrémente simplement le compteur de références de l’objet, et si celui ci arrive à zéro __del__() est appelée.

Si la structure de données contient des références circulaires (e.g. un arbre dans lequel chaque fils référence son père, et chaque père garde une liste de ses fils), le compteur de références n’arrivera jamais à zéro. Python exécute périodiquement un algorithme pour détecter ce genre de cycles, mais il peut se passer un certain temps entre le moment où la structure est référencée pour la dernière fois et l’appel du ramasse-miettes, donc la méthode __del__() peut être appelée à un moment aléatoire et pas opportun. C’est gênant pour essayer reproduire un problème. Pire, l’ordre dans lequel les méthodes __del__() des objets sont appelées est arbitraire. Il est possible de forcer l’appel du ramasse-miettes avec la fonction gc.collect(), mais il existe certains cas où les objets ne seront jamais nettoyés.

Despite the cycle collector, it’s still a good idea to define an explicit close() method on objects to be called whenever you’re done with them. The close() method can then remove attributes that refer to subobjecs. Don’t call __del__() directly – __del__() should call close() and close() should make sure that it can be called more than once for the same object.

Une alternative pour éviter les références cycliques consiste à utiliser le module weakref, qui permet de faire référence à des objets sans incrémenter leur compteur de références. Par exemple, les structures d’arbres devraient utiliser des références faibles entre pères et fils (si nécessaire !).

If the object has ever been a local variable in a function that caught an expression in an except clause, chances are that a reference to the object still exists in that function’s stack frame as contained in the stack trace. Normally, calling sys.exc_clear() will take care of this by clearing the last recorded exception.

Enfin, si la méthode __del__() lève une exception, un message d’avertissement s’affiche dans sys.stderr.

Comment obtenir toutes les instances d’une classe ?

Python ne tient pas de registre de toutes les instances d’une classe (ni de n’importe quel type natif). Il est cependant possible de programmer le constructeur de la classe de façon à tenir un tel registre, en maintenant une liste de références faibles vers chaque instance.

Pourquoi le résultat de id() peut-il être le même pour deux objets différents ?

La fonction native id() renvoie un entier dont l’unicité est garantie durant toute la vie de l’objet. Vu qu’en CPython cet entier est en réalité l’adresse mémoire de l’objet, il est fréquent qu’un nouvel objet soit alloué à une adresse mémoire identique à celle d’un objet venant d’être supprimé. Comme l’illustre le code suivant :

>>> id(1000)
13901272
>>> id(2000)
13901272

Les deux identifiants appartiennent à des objets entiers créés juste avant l’appel à id() et détruits immédiatement après. Pour s’assurer que les objets dont on veut examiner les identifiants sont toujours en vie, créons une nouvelle référence à l’objet :

>>> a = 1000; b = 2000
>>> id(a)
13901272
>>> id(b)
13891296

Modules

Comment créer des fichiers .pyc ?

When a module is imported for the first time (or when the source is more recent than the current compiled file) a .pyc file containing the compiled code should be created in the same directory as the .py file.

One reason that a .pyc file may not be created is permissions problems with the directory. This can happen, for example, if you develop as one user but run as another, such as if you are testing with a web server. Creation of a .pyc file is automatic if you’re importing a module and Python has the ability (permissions, free space, etc…) to write the compiled module back to the directory.

Running Python on a top level script is not considered an import and no .pyc will be created. For example, if you have a top-level module foo.py that imports another module xyz.py, when you run foo, xyz.pyc will be created since xyz is imported, but no foo.pyc file will be created since foo.py isn’t being imported.

If you need to create foo.pyc – that is, to create a .pyc file for a module that is not imported – you can, using the py_compile and compileall modules.

Le module py_compile peut compiler n’importe quel module manuellement. Il est ainsi possible d’appeler la fonction compile() de manière interactive :

>>> import py_compile
>>> py_compile.compile('foo.py')                 

This will write the .pyc to the same location as foo.py (or you can override that with the optional parameter cfile).

Tous les fichiers d’un ou plusieurs dossiers peuvent aussi être compilés avec le module compileall. C’est possible depuis l’invite de commande en exécutant compileall.py avec le chemin du dossier contenant les fichiers Python à compiler

python -m compileall .

Comment obtenir le nom du module actuel ?

Un module peut déterminer son propre nom en examinant la variable globale prédéfinie __name__. Si celle-ci vaut '__main__', c’est que le programme est exécuté comme un script. Beaucoup de modules qui doivent normalement être importés pour pouvoir être utilisés fournissent aussi une interface en ligne de commande ou un test automatique. Ils n’exécutent cette portion du code qu’après avoir vérifié la valeur de __name__:

def main():
    print 'Running test...'
    ...

if __name__ == '__main__':
    main()

Comment avoir des modules qui s’importent mutuellement ?

Considérons les modules suivants :

foo.py

from bar import bar_var
foo_var = 1

bar.py

from foo import foo_var
bar_var = 2

Le problème réside dans les étapes que l’interpréteur va réaliser :

  • main importe foo

  • Les variables globales (vides) de foo sont créées

  • foo est compilé et commence à s’exécuter

  • foo importe bar

  • Les variables globales (vides) de bar sont créées

  • bar est compilé et commence à s’exécuter

  • bar importe foo (en réalité, rien ne passe car il y a déjà un module appelé foo)

  • bar.foo_var = foo.foo_var

La dernière étape échoue car Python n’a pas fini d’interpréter foo et le dictionnaire global des symboles de foo est encore vide.

Le même phénomène arrive quand on utilise import foo, et qu’on essaye ensuite d’accéder à foo.foo_var dans le code global.

Il y a (au moins) trois façons de contourner ce problème.

Guido van Rossum déconseille d’utiliser from <module> import ... et de mettre tout le code dans des fonctions. L’initialisation des variables globales et des variables de classe ne doit utiliser que des constantes ou des fonctions natives. Ceci implique que tout ce qui est fourni par un module soit référencé par <module>.<nom>.

Jim Roskind recommande d’effectuer les étapes suivantes dans cet ordre dans chaque module :

  • les exportations (variables globales, fonctions et les classes qui ne nécessitent d’importer des classes de base)

  • les instructions import

  • le code (avec les variables globales qui sont initialisées à partir de valeurs importées).

van Rossum désapprouve cette approche car les importations se trouvent à un endroit bizarre, mais cela fonctionne.

Matthias Urlichs conseille de restructurer le code pour éviter les importations récursives.

Ces solutions peuvent être combinées.

__import__(“x.y.z”) renvoie <module “x”> ; comment accéder à z ?

Utilisez plutôt la fonction import_module() de importlib

z = importlib.import_module('x.y.z')

Quand j’édite un module et que je le réimporte, je ne vois pas les changements. Pourquoi ?

For reasons of efficiency as well as consistency, Python only reads the module file on the first time a module is imported. If it didn’t, in a program consisting of many modules where each one imports the same basic module, the basic module would be parsed and re-parsed many times. To force rereading of a changed module, do this:

import modname
reload(modname)

Attention, cette technique ne marche pas systématiquement. En particulier, les modules qui contiennent des instructions comme

from modname import some_objects

will continue to work with the old version of the imported objects. If the module contains class definitions, existing class instances will not be updated to use the new class definition. This can result in the following paradoxical behaviour:

>>> import cls
>>> c = cls.C()                # Create an instance of C
>>> reload(cls)
<module 'cls' from 'cls.pyc'>
>>> isinstance(c, cls.C)       # isinstance is false?!?
False

The nature of the problem is made clear if you print out the class objects:

>>> c.__class__
<class cls.C at 0x7352a0>
>>> cls.C
<class cls.C at 0x4198d0>