Leçon: Packager son code¶

Intéret d'un package (ou paquet)¶

Code plus facilement réutilisable et installable:¶

  • Permet d'encapsuler son code sous forme de modules empaquetés dans un package --> reprend les standart de python
  • Facilite la lecture et la revue de votre code et la contribution (collegues, open source, ...)
  • Facilite la documentation de votre code

Code plus facilement installable & déployable¶

  • Facilite l'installation en local ou depuis un serveur : pip install my-package
  • Facilite grandement le déploiement continu (CD) de votre code sur un serveur distant ou un dépot (par exemple avec Heroku)
  • Essentiel à l'intégration continue (CI) en permettant de faire des tests automatisés

Comment créer un package ?¶

Pré-requis¶

Un module est un fichier python .py. Généralement les modules contiennent du code organisé sous forme de fonctions et/ou classes remplissant une même fonction: par exemple le module python math.py

Un package est une collection de dossiers et sous-dossiers contenant des modules

Chacun de ces dossiers doit contenir un fichier __init__.py

Les conventions de python PEP recommandent la syntaxe suivante:

  • un nom composé de lettre minuscule: `mypackage'
  • ou plusieurs mots sépararés par un tiret: my-package

Anatomie d'un package¶

Configuration minimale pour créer un package my-package avec un seul module lib: (plus de détails sur la doc officielle de python)

my-project              # dossier du projet (a mettre à la racine de votre dépot)
    setup.py            # script d'installation des dépendances 
    my-package/         # package
        __init__.py     # définit  my-package comme package python
        lib.py          # module                 
    

Exemple d'un package sound contenant deux sous-paquet formats et effects ainsi que plusieurs modules

sound/                          # racine du package
      __init__.py 
      setup.py 
      formats/                  # sous package pour les conversions de formats
              __init__.py
              wavread.py        # module
              wavwrite.py
              ...
      effects/                  # sous package pour les effets de sons
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
     

le fichier setup.py¶

Il contient des informations pour l'installation automatique des paquets et utilise le module setup du package setuptools, par exemple, pour installer notre package sound on pourrait utiliser le code:

In [ ]:
from setuptools import setup
from setuptools import find_packages

setup(name='sound',
      description='A package for sound processing',
      packages=['sound'],
      version='0.1',
      url='http://github.com/jonhdoe/sound',
      author='John Doe',
      author_email='johndoe@example.com',
      license='GPL3',
      )

Si votre projet contient un bon nombre de sous paquets, il peut être intéressant d'utiliser le module find_package permettant de rechercher automatiquement les sous paquets:

In [ ]:
setup(pakages=find_packages())

le fichier requirements.txt¶

le fichier requirements.txt sert à renseigner toutes les dépendances nécessaires au bon fonctionnement du code dans votre paquet. Généralement, il est parsé par le fichier setup.py pour s'assurer que l'installation de votre paquet inclue automatiquement les dépendances nécessaires:

sound/                          # racine du package
      __init__.py 
      setup.py  
      requirements.txt          # indique les dépendances pour votre paquet
      formats/                  # sous package pour les conversions de formats
              __init__.py
              wavread.py        # module
              wavwrite.py
              ...
      effects/                  # sous package pour les effets de sons
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
     

In [ ]:
# parse dependencies from requirements.txt
with open('requirements.txt') as f:
    content = f.readlines()
requirements = [x.strip() for x in content]

setup(name='sound',
      description='A package for sound processing',
      version='0.1',
      url='http://github.com/jonhdoe/sound',
      author='John Doe',
      author_email='johndoe@example.com',
      license='GPL3',
      packages=find_packages(),
      install_requires=requirements
      )

Pour plus de détails, lisez la documentation du package setuptools

OPTIONNEL: utiliser un script d'installation pour son package¶

Afin de pouvoir installer votre package depuis n'importe ou et le lancer automatiquement un script après installation, il est courant d'inclure un script shell dans votre package, par exemple:

sound/                          # racine du package
      __init__.py 
      setup.py  
      requirements.txt          # indique les dépendances pour votre paquet
      scripts/
          sound-run             # script shell
      formats/                  # sous package pour les conversions de formats
              __init__.py
              wavread.py        # module
              wavwrite.py
              ...
      effects/                  # sous package pour les effets de sons
              __init__.py
              echo.py
              surround.py
              reverse.py
              ...
     

Ce script sera automatiquement executé au moment de l'installation, en spécifiant le kwarg scripts du fichier setup.py:

In [ ]:
# parse dependencies from requirements.txt
with open('requirements.txt') as f:
    content = f.readlines()
requirements = [x.strip() for x in content]

setup(name='sound',
      description='A package for sound processing',
      version='0.1',
      url='http://github.com/jonhdoe/sound',
      author='John Doe',
      author_email='johndoe@example.com',
      license='GPL3',
      packages=find_packages(),
      install_requires=requirements,
      scripts=['scripts/sound-run']
      )
sound-run

OPTIONNEL: Générer automatiquement un template de package¶

Vous pouvez, par exemple, utilisez ce package, cookiecutter pour générer automatiquement vos packages !

Installer un package¶

Installer le package localement:¶

pip install .

Installer le package pour le développement¶

En cours de développement du package on souhaite généralement ne pas avoir à le réinstaller à chaque modification :

pip install -e .

Vérifier la bonne installation du package¶

avec pip: pip show my-package
avec conda: conda info pandas

Utiliser un package¶

Importer le package en entier¶

In [ ]:
import sound as sn

importer spécifiquement un module¶

In [ ]:
import sound.effects.echo
In [ ]:
from sound.effects import echo

Je peux ensuite appeler la fonction echofilter:

In [ ]:
echo.echofilter(input, output, delay=0.7, atten=4)

Ou encore importer directement la echofilter:

In [ ]:
from sound.effects.echo import echofilter
echofilter(input, output, delay=0.7, atten=4)

importer mon code s'il n'est pas packagé¶

En général, pour faciliter le déploiement de votre projet final, il est fortement conseillé de commencer à developper en ayant packagé son code des le départ!

Si ca n'est pas le cas, pour une utilisation locale uniquement par exemple, on peut importer le code d'un module en ajoutant le module au dans la variable d'environnement PYTHONPATH

Pour des modules en particulier¶

Soit en rajoutant le nom de mon module dans la liste sys.path, pour chaque module !!

In [ ]:
import sys
sys.path.append('mon-module')

De manière plus robuste¶

Soit, de manière permanente, en éditant la variable PYTHONPATH dans le fichier de configuration de votre terminal

Par exemple dans un terminal bash, je peux éditer le fichier de configuration .bashrc:

PYTHONPATH="/home/nico/DataScience/code/projets":$PYTHONPATH
export PYTHONPATH

Documenter un package¶

Il est très important de documenter son package (si possible en anglais) pour faciliter la lecture du code, la compréhension de l'organisation de votre package, les instructions pour installer votre package, pour y contribuer, ...

Commenter son code¶

Dans tous les langages, commenter son code est une habitude indispensable à prendre, il vous permettra de faciliter sa relecture par vous même ou un potentiel contributeur. Il est donc préférable de le faire en anglais pour que sa lecture soit accessible a un locuteur d'une autre langue

En python, la communauté a émis de nombreuses recommandations visant normaliser les bonnes pratiques des développeurs au travers des PEP(Python Enhancement Proposal). En particulier, les conventions du PEP 8 donnent des recommandations précises pour écrire et commenter son code, dont voici les principales règles:

pour l'indentation:¶

In [ ]:
# Aligned with opening delimiter.
foo = long_function_name(var_one, var_two,
                         var_three, var_four)

# Add 4 spaces (an extra level of indentation) to distinguish arguments from the rest.
def long_function_name(
        var_one, var_two, var_three,
        var_four):
    print(var_one)

# Hanging indents should add a level.
foo = long_function_name(
    var_one, var_two,
    var_three, var_four)
In [ ]:
my_list = [
    1, 2, 3,
    4, 5, 6,
    ]
In [ ]:
# easy to match operators with operands
income = (gross_wages
          + taxable_interest
          + (dividends - qualified_dividends)
          - ira_deduction
          - student_loan_interest)

imports¶

un import par ligne, sauf pour les imports concernant un même module:

In [ ]:
import os
import sys
In [ ]:
from subprocess import Popen, PIPE

les imports absolus sont à privilégier par rapport aux imports relatifs:

In [ ]:
# absolute import
from datetime import datetime
from my_package.module import some_function
In [ ]:
# relative import
# A dot indicates one directory up from the current location and two dots indicates two directories up and so on.
from .module_in_same_dir import some_function
from ..module_in_parent_dir import other_function

Utiliser des annotations pour vos fonctions : PEP 484¶

In [2]:
def greeting(name: str) -> str:
    return 'Hello ' + name

Ecrire un docstring¶

Une autre principale recommandation que je vous conseille de suivre, a minmima, concerne le PEP 257 spécifiant les conventions pour écrire un docstring:

De manière générale, c'est un string qui se place au début de chaque module, fonction, classe ou méthode en tant que définition décrivant les effets de cet objet. Néanmoins, les informations contenues dans le docstring varient en fonction de chacun des objets cités plus haut. Si vous respectez les conventions d'écriture, le docstring apparait dans l'attribut __doc__ special de cet objet et est affiché dans l'auto-complétion de la console python.

Voici un exemple de docstring respectant le PEP 257 pour une fonction:

In [ ]:
def complex(real=0.0, imag=0.0):
    """Form a complex number.

    Keyword arguments:
    real -- the real part (default 0.0)
    imag -- the imaginary part (default 0.0)
    """
    if imag == 0.0 and real == 0.0:
        return complex_zero
    ...

Il existe néanmoins différenttes variantes de format de doctring qui ne respectent pas complètement le PEP 257 mais plus agréables à lire, comme par exemple le format google ou celui de numpy. Pour plus de détails sur les docstring, voir cet article

Formater automatiquement son code¶

Il est possible d'utiliser des outils pour formater automatiquement (linters) son code suivant les conventions PEP:

  • black un paquet pour le formatage automatique de votre code python pour le rendre compatible avec les PEP et reproductible (voir usage ici)
  • autopep8: un package python pour formater votre code suivant les conventions PEP8
  • l'extension autopep8 pour jupyter-notebook (requiers autopep8 d'installé)
  • ... des extensions pour votre IDE favori (par exemple Pylance ou Python Docstring Generator sur VScode)

Documenter son package¶

Ecrire un README¶

Le fichier README est le mimimum de de documentation à écrire lorsque vous distribuez votre package ou votre logiciel afin de faciliter au maximum sa compréhension et son utilisation par d'autres développeurs

Le markdown est le format souvent utilisé pour écrire ce type de fichier sous forme README.md. Par exemple, ce fichier est automatiquement lu et affiché en tant que description sur les plateformes comme Github, Gitlab, Bitbucket, ...

Je vous suggère d'y inclure au moins les paragraphes suivants:

# Project Title

## Project's description
What is this project usefull for ?

## Installation
How to install my software ?

## Sofware's usage
How to use my software ?

### Getting started
If you're software is quite complicating to use you can provide details exemple of specific usages

## Todo
Features that you want to implement in the future

## License
License that specify how to use, distribute and share your software

## Contributing/Code of conduct
Write your rules for contributors

Voici un bon exemple de README.md d'un projet bien documenté

Sources¶

Documentation officielle de python sur les paquets
python-packaging