Meilleure pratique pour Python assert


483
  1. Existe-t-il un problème de performances ou de maintenance du code lors de l'utilisation assertdans le cadre du code standard au lieu de l'utiliser uniquement à des fins de débogage?

    Est

    assert x >= 0, 'x is less than zero'

    mieux ou pire que

    if x < 0:
        raise Exception, 'x is less than zero'
  2. De plus, existe-t-il un moyen de définir une règle métier comme if x < 0 raise errorcelle-ci est toujours vérifiée sans le try/except/finallyso, si à tout moment dans le code xest inférieur à 0, une erreur est déclenchée, comme si vous définissez assert x < 0au début d'une fonction, n'importe où dans la fonction où xdevient inférieur à 0, une exception est levée?



29
Les paramètres -O et -OO python supprimeront vos assertions. Cela devrait guider votre réflexion sur ce à quoi cela sert.
Peter Lada

4
Le lien de Thomasz Zielinski a été rompu, c'est maintenant: mail.python.org/pipermail/python-list/2013-November/660568.html . Je suis à peu près sûr que pipermail a une fonction d'identification instable, j'ai trouvé d'autres liens à l'intérieur du même pipermail pointant vers la même URL avec la même intention.
quodlibetor

3
Dans le cas où mail.python.org/pipermail/python-list/2013-November/660568.html se déplace à nouveau, il est archivé à archive.is/5GfiG . Le titre de l'article est "Quand utiliser assert" et est un excellent article (vraiment un article) sur les meilleures pratiques pour Python assert.
clacke

Réponses:


144

Pour pouvoir lancer automatiquement une erreur lorsque x devient inférieur à zéro dans toute la fonction. Vous pouvez utiliser des descripteurs de classe . Voici un exemple:

class LessThanZeroException(Exception):
    pass

class variable(object):
    def __init__(self, value=0):
        self.__x = value

    def __set__(self, obj, value):
        if value < 0:
            raise LessThanZeroException('x is less than zero')

        self.__x  = value

    def __get__(self, obj, objType):
        return self.__x

class MyClass(object):
    x = variable()

>>> m = MyClass()
>>> m.x = 10
>>> m.x -= 20
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "my.py", line 7, in __set__
    raise LessThanZeroException('x is less than zero')
LessThanZeroException: x is less than zero

10
Bien que les propriétés soient implémentées en tant que descripteurs, je n'appellerais pas cela un exemple de leur utilisation. C'est plus un exemple de propriétés en elles-mêmes: docs.python.org/library/functions.html#property
Jason Baker

3
Les propriétés doivent être utilisées dans MyClass lors de la définition de x. Cette solution est trop générale.

114
Assez belle réponse, comme ça, mais n'a rien à voir avec la question ... Ne pouvons-nous pas marquer la réponse de Deestan ou John Mee comme réponse valide?
Vajk Hermecz

4
Cela ne semble pas répondre au titre de la question. En outre, c'est une mauvaise alternative à la fonctionnalité de propriété de classe de Python.
Dooms101

10
@VajkHermecz: En fait, si vous relisez la question, ce sont deux questions en une. Les personnes qui ne regardent que le titre ne connaissent que la première question, à laquelle cette réponse ne répond pas. Cette réponse contient en fait une réponse à la deuxième question.
ArtOfWarfare

742

Les assertions doivent être utilisées pour tester des conditions qui ne devraient jamais se produire . Le but est de planter tôt dans le cas d'un état de programme corrompu.

Les exceptions doivent être utilisées pour les erreurs qui peuvent se produire, et vous devez presque toujours créer vos propres classes d'exceptions .


Par exemple, si vous écrivez une fonction pour lire à partir d'un fichier de configuration dans un dict, une mise en forme incorrecte dans le fichier devrait générer un ConfigurationSyntaxError, alors que vous le pouvez assert, vous n'êtes pas sur le point de revenir None.


Dans votre exemple, si xune valeur est définie via une interface utilisateur ou à partir d'une source externe, une exception est préférable.

Si xn'est défini que par votre propre code dans le même programme, utilisez une assertion.


126
C'est la bonne façon d'utiliser les assertions. Ils ne doivent pas être utilisés pour contrôler le déroulement du programme.
Thane Brimhall

41
+1 pour le dernier paragraphe - bien que vous deviez mentionner explicitement qu'il assertcontient un implicite if __debug__et peut être optimisé - comme l' indique la réponse de John Mee
Tobias Kienzler

3
En relisant votre réponse, je pense que vous ne vouliez probablement pas dire des conditions qui ne devraient jamais se produire comme une règle, mais plutôt le but est de planter tôt dans le cas d'un état de programme corrompu qui coïncide généralement avec une condition à laquelle vous ne vous attendez pas jamais arriver .
Bentley4

10
assert ne doit être utilisé que pour détecter des problèmes sans récupération connue; presque toujours des bogues de code (pas de mauvaises entrées). Lorsqu'une assertion est déclenchée, cela devrait signifier que le programme est dans un état qui peut être dangereux de continuer, car il peut commencer à parler au réseau ou à écrire sur le disque. un code robuste passe «atomiquement» d'un état valide à un état valide face à une entrée incorrecte (ou malveillante). le niveau supérieur de chaque fil doit avoir une barrière de défaut. les barrières de défaut qui consomment des entrées du monde extérieur échouent généralement pour une seule itération de la barrière (pendant / essayer), erreur de restauration / connexion.
Rob

10
"Les assertions devraient être utilisées pour tester des conditions qui ne devraient jamais se produire." Oui. Et la signification du deuxième "devrait" est: Si cela se produit, le code du programme est incorrect.
Lutz Prechelt

362

Les instructions "assert" sont supprimées lorsque la compilation est optimisée . Donc, oui, il y a des différences de performances et de fonctionnalités.

Le générateur de code actuel n'émet aucun code pour une instruction assert lorsque l'optimisation est demandée au moment de la compilation. - Documents Python 2 Documents Python 3

Si vous utilisez assertpour implémenter des fonctionnalités d'application, puis optimisez le déploiement en production, vous serez en proie à des défauts "mais cela fonctionne en développement".

Voir PYTHONOPTIMIZE et -O -OO


26
Hou la la! C'est une note super importante! J'avais prévu d'utiliser des assertions pour vérifier quelques éléments qui ne devraient jamais échouer, dont l'échec indiquerait que quelqu'un manipulait très soigneusement mes données qu'ils envoyaient dans le but d'accéder à des données auxquelles ils ne devraient pas avoir accès. Cela ne fonctionnerait pas, mais je veux arrêter rapidement leur tentative avec une affirmation, donc l'optimisation de la production irait à l'encontre du but. Je suppose que je vais juste raiseun à la Exceptionplace. Oh - je viens de découvrir un bien nommé SuspiciousOperation Exceptionavec des sous-classes Django! Parfait!
ArtOfWarfare du

Par ailleurs @ArtOfWarfare si vous exécutez banditvotre code, il vous en avertira.
Nagev

132

Les quatre objectifs de assert

Supposons que vous travaillez sur 200 000 lignes de code avec quatre collègues Alice, Bernd, Carl et Daphne. Ils appellent votre code, vous appelez leur code.

Puis asserta quatre rôles :

  1. Informez Alice, Bernd, Carl et Daphne de ce que votre code attend.
    Supposons que vous ayez une méthode qui traite une liste de tuples et que la logique du programme puisse se casser si ces tuples ne sont pas immuables:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

    C'est plus fiable que des informations équivalentes dans la documentation et beaucoup plus facile à maintenir.

  2. Informez l'ordinateur de ce que votre code attend.
    assertapplique le bon comportement des appelants de votre code. Si votre code appelle le code d'Alices et de Bernd appelle le vôtre, alors sans le assert, si le programme plante dans le code d'Alices, Bernd pourrait supposer que c'était la faute d'Alice, Alice enquêterait et pourrait supposer que c'était votre faute, vous enquêtez et dites à Bernd que c'était en fait le sien. Beaucoup de travail perdu.
    Avec les assertions, quiconque se trompe, il pourra rapidement voir que c'est de sa faute, pas de la vôtre. Alice, Bernd et vous en bénéficiez tous. Économise d'immenses quantités de temps.

  3. Informez les lecteurs de votre code (y compris vous-même) de ce que votre code a réalisé à un moment donné.
    Supposons que vous ayez une liste d'entrées et que chacune d'elles puisse être propre (ce qui est bien) ou qu'elle puisse être smorsh, trale, gullup ou twinkled (qui ne sont pas toutes acceptables). Si c'est smorsh, il doit être non smorshed; si c'est vrai, il faut le balancer; si c'est du goéland, il doit être trotté (et peut-être aussi arpenté aussi); s'il est scintillant, il doit être scintillé à nouveau, sauf le jeudi. Vous voyez l'idée: c'est compliqué. Mais le résultat final est (ou devrait être) que toutes les entrées sont propres. La bonne chose à faire est de résumer l'effet de votre boucle de nettoyage comme

    assert(all(entry.isClean() for entry in mylist))

    Ces déclarations sauvent un casse-tête pour tous ceux qui essaient de comprendre exactement ce que la merveilleuse boucle réalise. Et le plus fréquent de ces personnes sera probablement vous-même.

  4. Informez l'ordinateur de ce que votre code a réalisé à un moment donné.
    Si jamais vous oubliez de suivre une entrée qui en a besoin après le trot, assertcela vous sauvera la journée et évitera que votre code casse le cher Daphné beaucoup plus tard.

À mon avis, assertles deux objectifs de la documentation (1 et 3) et de la sauvegarde (2 et 4) sont tout aussi précieux.
Informer les gens peut même être plus utile que d'informer l'ordinateur car cela peut éviter les erreurs mêmes que les assertobjectifs doivent attraper (dans le cas 1) et de nombreuses erreurs subséquentes dans tous les cas.


34
5. assert isinstance () aide PyCharm (python IDE) à connaître le type de variable, il est utilisé pour la saisie semi-automatique.
Cjkjvfnby

1
Affirme des hypothèses de code d'auto-documentation pour ce qui est vrai au moment de l'exécution en cours. C'est un commentaire d'hypothèse, qui est vérifié.
pyj

9
Concernant 2 et 4: vous devez faire très attention à ce que vos affirmations ne soient pas trop strictes. Sinon, les assertions elles-mêmes peuvent être la seule chose à garder votre programme utilisé dans un cadre plus général. En particulier, l'affirmation de types va à l'encontre du type de canard de python.
zwirbeltier

9
@Cjkjvfnby Faites attention à une utilisation excessive de isinstance () comme décrit dans cette entrée de blog: " isinstance () considéré comme dangereux ". Vous pouvez désormais utiliser des docstrings pour spécifier des types dans Pycharm.
binarysubstrate

2
Utiliser les assertions pour garantir le contrat. Plus d'infos sur Design by Contract en.wikipedia.org/wiki/Design_by_contract
Leszek Zarna

22

En plus des autres réponses, les assertions lèvent elles-mêmes des exceptions, mais seulement AssertionErrors. D'un point de vue utilitaire, les assertions ne conviennent pas lorsque vous avez besoin d'un contrôle de grain fin sur les exceptions que vous interceptez.


3
Droite. Il semblerait idiot de détecter des exceptions d'erreur d'assertion dans l'appelant.
Raffi Khatchadourian

Très bon point. Une nuance qui peut être facilement négligée en regardant simplement les questions originales au niveau macro. Même si ce n'était pas le problème avec les assertions supprimées lors de l'optimisation, la perte des détails spécifiques du type d'erreur s'est produite rendrait le débogage beaucoup plus difficile. Vive, outis!
cfwschmidt du

Votre réponse peut être lue comme si vous vouliez peut-être l'attraper AssertionErrors, lorsque vous êtes d'accord avec le fait qu'elle soit à grain grossier. En réalité, vous ne devriez pas les attraper.
Tomasz Gandor

19

La seule chose qui ne va vraiment pas avec cette approche est qu'il est difficile de faire une exception très descriptive en utilisant des instructions assert. Si vous recherchez une syntaxe plus simple, n'oubliez pas que vous pouvez également faire quelque chose comme ceci:

class XLessThanZeroException(Exception):
    pass

def CheckX(x):
    if x < 0:
        raise XLessThanZeroException()

def foo(x):
    CheckX(x)
    #do stuff here

Un autre problème est que l'utilisation de l'assertion pour la vérification de condition normale rend difficile la désactivation des assertions de débogage à l'aide de l'indicateur -O.


24
Vous pouvez ajouter un message d'erreur à une assertion. C'est le deuxième paramètre. Cela le rendra descriptif.
Raffi Khatchadourian

10

Le mot anglais affirmer ici est utilisé dans le sens de jurer , affirmer , avouer . Cela ne signifie pas "vérifier" ou "devrait être" . Cela signifie que vous, en tant que codeur, faites une déclaration sous serment ici:

# I solemnly swear that here I will tell the truth, the whole truth, 
# and nothing but the truth, under pains and penalties of perjury, so help me FSM
assert answer == 42

Si le code est correct, sauf bouleversements à événement unique , pannes matérielles et autres, aucune assertion n'échouera jamais . C'est pourquoi le comportement du programme envers un utilisateur final ne doit pas être affecté. Surtout, une assertion ne peut pas échouer même dans des conditions programmatiques exceptionnelles . Cela n'arrive jamais. Si cela se produit, le programmeur doit être zappé pour cela.


8

Comme cela a été dit précédemment, les assertions doivent être utilisées lorsque votre code NE DEVRAIT JAMAIS atteindre un point, ce qui signifie qu'il y a un bogue. La raison la plus utile que je peux voir pour utiliser une assertion est probablement un invariant / pré / postcondition. Il s'agit de quelque chose qui doit être vrai au début ou à la fin de chaque itération d'une boucle ou d'une fonction.

Par exemple, une fonction récursive (2 fonctions distinctes, donc 1 gère les mauvaises entrées et les autres gère le mauvais code, car il est difficile de distinguer avec la récursivité). Cela rendrait évident si j'avais oublié d'écrire la déclaration if, ce qui avait mal tourné.

def SumToN(n):
    if n <= 0:
        raise ValueError, "N must be greater than or equal to 0"
    else:
        return RecursiveSum(n)

def RecursiveSum(n):
    #precondition: n >= 0
    assert(n >= 0)
    if n == 0:
        return 0
    return RecursiveSum(n - 1) + n
    #postcondition: returned sum of 1 to n

Ces invariants de boucle peuvent souvent être représentés par une assertion.


2
C'est mieux fait avec les décorateurs (@precondition et @postcondition)
Caridorc

@Caridorc quel est l'avantage concret de cela?
Chiel ten Brinke

@ChieltenBrinke code auto-documenté, au lieu de #precondition: n >= 0 et une affirmation, il peut simplement écrire@precondition(lambda n: n >= 0)
Caridorc

@Caridorc Sont donc ces décorateurs intégrés? Et comment générer de la documentation à partir de cela?
Chiel ten Brinke

@ChieltenBrinke non intégré mais facile à mettre en œuvre stackoverflow.com/questions/12151182/… . Pour la documentation, il suffit de patcher l' __doc__attribut en donnant une chaîne supplémentaire
Caridorc

4

Y a-t- il un problème de performances?

  • N'oubliez pas de "le faire fonctionner avant de le faire fonctionner rapidement" .
    Très peu de pour cent de n'importe quel programme sont généralement pertinents pour sa vitesse. Vous pouvez toujours expulser ou simplifier un assertsi cela s'avérait être un problème de performances - et la plupart d'entre eux ne le seront jamais.

  • Soyez pragmatique :
    supposez que vous disposez d'une méthode qui traite une liste non vide de tuples et la logique du programme se brisera si ces tuples ne sont pas immuables. Vous devez écrire:

    def mymethod(listOfTuples):
        assert(all(type(tp)==tuple for tp in listOfTuples))

    C'est probablement bien si vos listes ont généralement dix entrées, mais cela peut devenir un problème si elles contiennent un million d'entrées. Mais plutôt que de jeter entièrement ce précieux chèque, vous pouvez simplement le rétrograder en

    def mymethod(listOfTuples):
        assert(type(listOfTuples[0])==tuple)  # in fact _all_ must be tuples!

    ce qui est bon marché mais va probablement attraper la plupart des erreurs de programme réelles de toute façon.


2
Devrait l'être assert(len(listOfTuples)==0 or type(listOfTyples[0])==tuple).
osa

Non, ça ne devrait pas. Ce serait un test beaucoup plus faible, car il ne vérifie plus la propriété «non vide», que la seconde assert vérifie. (Le premier ne le fait pas, bien qu'il le devrait.)
Lutz Prechelt

1
La deuxième assertion ne vérifie pas explicitement la propriété non vide; c'est plus un effet secondaire. S'il devait lever une exception en raison de la liste vide, la personne travaillant avec le code (quelqu'un d'autre ou l'auteur, un an après l'avoir écrit) le regarderait, essayant de comprendre si l'assertion était vraiment destinée à attraper la situation de liste vide, ou si c'est une erreur dans l'assertion elle-même. De plus, je ne vois pas comment le fait de ne pas vérifier le cas vide est "beaucoup plus faible", alors que seul le premier élément est "97% correct".
osa

3

Eh bien, c'est une question ouverte, et j'ai deux aspects que je veux aborder: quand ajouter des assertions et comment écrire les messages d'erreur.

Objectif

Pour l'expliquer à un débutant - les assertions sont des déclarations qui peuvent soulever des erreurs, mais vous ne les attraperez pas. Et ils ne devraient normalement pas être élevés, mais dans la vraie vie, ils le sont parfois de toute façon. Et c'est une situation grave, dont le code ne peut pas récupérer, ce que nous appelons une «erreur fatale».

Ensuite, c'est à des fins de débogage qui, bien que correctes, semblent très dédaigneuses. J'aime mieux la formulation `` déclarant les invariants, qui ne devraient jamais être violés '', même si cela fonctionne différemment selon les débutants ... ou même contrôler le flux avec elle.

Style

En Python, assertc'est une instruction, pas une fonction! (rappelez assert(False, 'is true')- vous ne soulèvera pas. Mais, ayant cela à l'écart:

Quand et comment écrire le «message d'erreur» facultatif?

Cela s'applique en fait aux frameworks de tests unitaires, qui ont souvent de nombreuses méthodes dédiées pour faire des assertions ( assertTrue(condition), assertFalse(condition), assertEqual(actual, expected)etc.). Ils fournissent souvent également un moyen de commenter l'assertion.

Dans le code jetable, vous pouvez vous passer des messages d'erreur.

Dans certains cas, il n'y a rien à ajouter à l'affirmation:

def dump (quelque chose): assert isinstance (quelque chose, Dumpable) # ...

Mais à part cela, un message est utile pour la communication avec d'autres programmeurs (qui sont parfois des utilisateurs interactifs de votre code, par exemple dans Ipython / Jupyter, etc.).

Donnez-leur des informations, pas seulement des détails sur la mise en œuvre interne.

au lieu de:

assert meaningless_identifier <= MAGIC_NUMBER_XXX, 'meaningless_identifier is greater than MAGIC_NUMBER_XXX!!!'

écrire:

assert meaningless_identifier > MAGIC_NUMBER_XXX, 'reactor temperature above critical threshold'

ou peut-être même:

assert meaningless_identifier > MAGIC_NUMBER_XXX, f'reactor temperature({meaningless_identifier }) above critical threshold ({MAGIC_NUMBER_XXX})'

Je sais, je sais - ce n'est pas un cas pour une assertion statique, mais je veux souligner la valeur informationnelle du message.

Message négatif ou positif?

Cela peut être controversé, mais cela me fait mal de lire des choses comme:

assert a == b, 'a is not equal to b'
  • ce sont deux choses contradictoires écrites l'une à côté de l'autre. Donc, chaque fois que j'ai une influence sur la base de code, je pousse pour spécifier ce que nous voulons, en utilisant des verbes supplémentaires comme «must» et «should», et pour ne pas dire ce que nous ne voulons pas.

    affirmer a == b, 'a doit être égal à b'

Ensuite, obtenir AssertionError: a must be equal to best également lisible et l'instruction semble logique dans le code. En outre, vous pouvez en tirer quelque chose sans lire le traceback (qui peut parfois même ne pas être disponible).


1

L'utilisation assertet la levée d'exceptions concernent la communication.

  • Les assertions sont des déclarations sur l'exactitude du code adressées aux développeurs : une assertion dans le code informe les lecteurs du code des conditions qui doivent être remplies pour que le code soit correct. Une assertion qui échoue au moment de l'exécution informe les développeurs qu'il existe un défaut dans le code qui doit être corrigé.

  • Les exceptions sont des indications sur des situations non typiques qui peuvent se produire au moment de l'exécution mais ne peuvent pas être résolues par le code à portée de main, adressées au code appelant pour y être traitées. L'occurrence d'une exception n'indique pas qu'il y a un bogue dans le code.

Meilleur entrainement

Par conséquent, si vous considérez l'occurrence d'une situation spécifique au moment de l'exécution comme un bug dont vous souhaitez informer les développeurs ("Salut développeur, cette condition indique qu'il y a un bug quelque part, veuillez corriger le code.") Alors allez pour une affirmation. Si l'assertion vérifie les arguments d'entrée de votre code, vous devez généralement ajouter à la documentation que votre code a un "comportement indéfini" lorsque les arguments d'entrée violent ces conditions.

Si à la place, l'occurrence de cette situation n'est pas une indication d'un bug dans vos yeux, mais plutôt une situation (peut-être rare mais) possible qui, selon vous, devrait plutôt être gérée par le code client, déclenchez une exception. Les situations dans lesquelles l'exception est levée doivent faire partie de la documentation du code respectif.

Y a-t-il un problème de performances [...] avec l'utilisation assert

L'évaluation des assertions prend un certain temps. Ils peuvent cependant être éliminés lors de la compilation. Cela a cependant quelques conséquences, voir ci-dessous.

Y a-t-il un problème de [...] maintenance du code avec l'utilisation assert

Normalement, les assertions améliorent la maintenabilité du code, car elles améliorent la lisibilité en rendant les hypothèses explicites et en vérifiant régulièrement ces hypothèses lors de l'exécution. Cela aidera également à détecter les régressions. Il y a cependant un problème à garder à l'esprit: les expressions utilisées dans les assertions ne devraient pas avoir d'effets secondaires. Comme mentionné ci-dessus, les assertions peuvent être éliminées au moment de la compilation - ce qui signifie que les effets secondaires potentiels disparaîtraient également. Cela peut - involontairement - changer le comportement du code.


1

Une assertion consiste à vérifier -
1. la condition valide,
2. l'énoncé valide,
3. la vraie logique;
du code source. Au lieu d'échouer tout le projet, il donne l'alarme que quelque chose n'est pas approprié dans votre fichier source.

Dans l'exemple 1, puisque la variable 'str' n'est pas nulle. Donc, aucune assertion ou exception n'est levée.

Exemple 1:

#!/usr/bin/python

str = 'hello Python!'
strNull = 'string is Null'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
hello Python!
FileName ..................... hello
FilePath ..................... C:/Python\hello.py

Dans l'exemple 2, var 'str' est null. Nous évitons donc à l'utilisateur d'aller de l'avant avec un programme défectueux par déclaration assert .

Exemple 2:

#!/usr/bin/python

str = ''
strNull = 'NULL String'

if __debug__:
    if not str: raise AssertionError(strNull)
print str

if __debug__:
    print 'FileName '.ljust(30,'.'),(__name__)
    print 'FilePath '.ljust(30,'.'),(__file__)


------------------------------------------------------

Output:
AssertionError: NULL String

Au moment où nous ne voulons pas déboguer et réalisé le problème d'assertion dans le code source. Désactiver l'indicateur d'optimisation

python -O assertStatement.py
rien ne sera imprimé


0

Dans les IDE tels que PTVS, PyCharm, les assert isinstance()instructions Wing peuvent être utilisées pour permettre la complétion de code pour certains objets peu clairs.


Cela semble précéder l'utilisation d'annotations de type ou de typing.cast.
Acumenus

-1

Pour ce que ça vaut, si vous traitez avec du code qui repose sur assertpour fonctionner correctement, l'ajout du code suivant garantira que les assertions sont activées:

try:
    assert False
    raise Exception('Python assertions are not working. This tool relies on Python assertions to do its job. Possible causes are running with the "-O" flag or running a precompiled (".pyo" or ".pyc") module.')
except AssertionError:
    pass

2
Cela ne répond pas à la question d'OP qui concerne les meilleures pratiques.
codeforester
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.