3. 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)

Elle doit donner soit un module entièrement initialisé, soit une instance de PyModuleDef. Voir Initializing C modules pour plus de détails.

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

It is possible to export multiple modules from a single shared library by defining multiple initialization functions. However, importing them requires using symbolic links or a custom importer, because by default only the function corresponding to the filename is found. See the « Multiple modules in one library » section in PEP 489 for details.

3.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. Referez-vous à la documentation de distutils dans Distributing Python Modules (Legacy version) pour en apprendre plus sur les fonctionnalités de distutils. Cette section n’explique que la construction de modules d’extension.

It is common to pre-compute arguments to setup(), to better structure the driver script. In the example above, the ext_modules argument to setup() is a list of extension modules, each of which is an instance of the Extension. In the example, the instance defines an extension named demo which is build by compiling a single source file, 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])

In this example, setup() is called with additional meta-information, which is recommended when distribution packages have to be built. For the extension itself, it specifies preprocessor defines, include directories, library directories, and libraries. Depending on the compiler, distutils passes this information in different ways to the compiler. For example, on Unix, this may result in the compilation commands

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.

3.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

In some cases, additional files need to be included in a source distribution; this is done through a MANIFEST.in file; see Spécifier les fichiers à distribuer for details.

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

python setup.py bdist_wininst
python setup.py bdist_rpm
python setup.py bdist_dumb