4. Construire des extensions C et C++

Une extension C pour CPython est une bibliothèque partagée (Un .so sur Linux, un .pyd sur Windows), qui expose une fonction d'initialisation.

Pour pouvoir être importée, la bibliothèque partagée doit pourvoir être trouvée dans PYTHONPATH, et doit porter le nom du module, avec l'extension appropriée. En utilisant distutils, le nom est généré automatiquement.

La fonction d'initialisation doit avoir le prototype :

PyObject *PyInit_modulename(void)

It returns either a fully initialized module, or a PyModuleDef instance. See Initializing C modules for details.

Pour les modules dont les noms sont entièrement en ASCII, la fonction doit être nommée PyInit_<modulename>, dont <modulename> est remplacé par le nom du module. En utilisant Multi-phase initialization, il est possible d'utiliser des noms de modules comptant des caractères non ASCII. Dans ce cas, le nom de la fonction d'initialisation est PyInitU_<modulename>, où modulename est encodé avec l'encodage punyencode de Python, dont les tirets sont remplacés par des tirets-bas. En Python ça donne :

def initfunc_name(name):
    try:
        suffix = b'_' + name.encode('ascii')
    except UnicodeEncodeError:
        suffix = b'U_' + name.encode('punycode').replace(b'-', b'_')
    return b'PyInit' + suffix

Il est possible d'exporter plusieurs modules depuis une seule bibliothèque partagée en définissant plusieurs fonctions d'initialisation. Cependant pour les importer, un lien symbolique doit être créé pour chacun, ou un importer personnalisé, puisque par défaut seule la fonction correspondant au nom du fichier est cherchée. Voir le chapitre "Multiple modules in one library" dans la PEP 489 pour plus d'informations.

4.1. Construire les extensions C et C++ avec distutils

Des modules d'extension peuvent être construits avec distutils, qui est inclus dans Python. Puisque distutils gère aussi la création de paquets binaires, les utilisateurs n'auront pas nécessairement besoin ni d'un compilateur ni de distutils pour installer l'extension.

Un paquet distutils contient un script setup.py. C'est un simple fichier Python, ressemblant dans la plupart des cas à :

from distutils.core import setup, Extension

module1 = Extension('demo',
                    sources = ['demo.c'])

setup (name = 'PackageName',
       version = '1.0',
       description = 'This is a demo package',
       ext_modules = [module1])

Avec ce setup.py et un fichier demo.c, lancer

python setup.py build

compilera demo.c, et produira un module d'extension nommé demo dans le dossier build. En fonction du système, le fichier du module peut se retrouver dans build/lib.system, et son nom peut être demo.py ou demo.pyd.

Dans le fichier setup.py, tout est exécuté en appelant la fonction setup. Elle prend un nombre variable d'arguments nommés, dont l'exemple précédent n'utilise qu'une partie. L'exemple précise des méta-informations pour construire les paquets, et définir le contenu du paquet. Normalement un paquet contient des modules additionnels, comme des modules sources, documentation, sous paquets, etc. Référez-vous à la documentation de distutils dans Distribuer des modules Python (Version historique) pour en apprendre plus sur les fonctionnalités de distutils. Cette section n'explique que la construction de modules d'extension.

Il est classique de pré-calculer les arguments à la fonction setup(), pour plus de lisibilité. Dans l'exemple ci-dessus, l'argument ext_modules à setup() est une liste de modules d'extension, chacun est une instance de la classe Extension. Dans l'exemple, l'instance définit une extension nommée demo construite par la compilation d'un seul fichier source demo.c.

Dans la plupart des cas, construire une extension est plus complexe à cause des bibliothèques et définitions de préprocesseurs dont la compilation pourrait dépendre. C'est ce qu'on remarque dans l'exemple plus bas.

from distutils.core import setup, Extension

module1 = Extension('demo',
                    define_macros = [('MAJOR_VERSION', '1'),
                                     ('MINOR_VERSION', '0')],
                    include_dirs = ['/usr/local/include'],
                    libraries = ['tcl83'],
                    library_dirs = ['/usr/local/lib'],
                    sources = ['demo.c'])

setup (name = 'PackageName',
       version = '1.0',
       description = 'This is a demo package',
       author = 'Martin v. Loewis',
       author_email = 'martin@v.loewis.de',
       url = 'https://docs.python.org/extending/building',
       long_description = '''
This is really just a demo package.
''',
       ext_modules = [module1])

Dans cet exemple, la fonction setup() est appelée avec quelques autres méta-informations, ce qui est recommandé pour distribuer des paquets. En ce qui concerne l'extension, sont définis quelques macros préprocesseur, dossiers pour les en-têtes et bibliothèques. En fonction du compilateur, distutils peut donner ces informations de manière différente. Par exemple, sur Unix, ça peut ressembler aux commandes

gcc -DNDEBUG -g -O3 -Wall -Wstrict-prototypes -fPIC -DMAJOR_VERSION=1 -DMINOR_VERSION=0 -I/usr/local/include -I/usr/local/include/python2.2 -c demo.c -o build/temp.linux-i686-2.2/demo.o

gcc -shared build/temp.linux-i686-2.2/demo.o -L/usr/local/lib -ltcl83 -o build/lib.linux-i686-2.2/demo.so

Ces lignes ne sont qu'à titre d'exemple, les utilisateurs de distutils doivent avoir confiance en distutils qui fera les appels correctement.

4.2. Distribuer vos modules d'extension

Lorsqu'une extension a été construite avec succès, il existe trois moyens de l'utiliser.

Typiquement, les utilisateurs vont vouloir installer le module, ils le font en exécutant

python setup.py install

Les mainteneurs de modules voudront produire des paquets source, pour ce faire ils exécuteront

python setup.py sdist

Dans certains cas, des fichiers supplémentaires doivent être inclus dans une distribution source : c'est possible via un fichier MANIFEST.in, c.f. Spécifier les fichiers à distribuer.

Si la distribution source a été construite avec succès, les mainteneurs peuvent aussi créer une distribution binaire. En fonction de la plateforme, une des commandes suivantes peut être utilisée.

python setup.py bdist_rpm
python setup.py bdist_dumb