103 votes

"Demandez le pardon, pas la permission" - expliquer

Je ne demande pas d'avis personnel "religieux" sur cette philosophie, mais plutôt quelque chose d'un peu plus technique.

Je crois savoir que cette phrase est l'un des nombreux tests décisifs permettant de déterminer si votre code est "pythonique". Mais pour moi, pythonique signifie propre, simple et intuitif, pas chargé de gestionnaires d'exceptions pour un mauvais codage.

Donc, exemple pratique. Je définis une classe :

class foo(object):
    bar = None

    def __init__(self):
        # a million lines of code
        self.bar = "Spike is my favorite vampire."
        # a million more lines of code

Maintenant, venant d'un fond procédural, dans une autre fonction je veux faire ceci :

if foo.bar:
    # do stuff

J'obtiendrai une exception d'attribut si j'ai été impatient et n'a pas faire l'initial foo = None. Donc, "demander pardon pas la permission" suggère que je devrais faire ceci à la place ?

try:
    if foo.bar:
        # do stuff
except:
    # this runs because my other code was sloppy?

Pourquoi serait-il préférable pour moi d'ajouter une logique supplémentaire dans un bloc try juste pour pouvoir laisser la définition de ma classe plus ambiguës ? Pourquoi ne pas définir tout au départ, donc explicitement accorder la permission ?

(Ne me reprochez pas d'utiliser les blocs try/except... Je les utilise partout. Je pense juste que ce n'est pas bien de les utiliser pour attraper mes propres erreurs parce que je n'étais pas un programmeur minutieux).

Ou... est-ce que j'ai complètement mal compris le mantra "Demandez pardon" ?

107voto

Gilles Points 37537

"Demandez le pardon, pas la permission" s'oppose à deux styles de programmation. "Demandez la permission" se présente comme suit :

if can_do_operation():
    perform_operation()
else:
    handle_error_case()

"Demander pardon", c'est comme ça :

try:
    perform_operation()
except Unable_to_perform:
    handle_error_case()

Il s'agit d'une situation où l'on s'attend à ce que la tentative d'exécution de l'opération échoue, et vous devez gérer la situation où l'opération est impossible, d'une manière ou d'une autre. Par exemple, si l'opération consiste à accéder à un fichier, il se peut que le fichier n'existe pas.

Il y a deux raisons principales pour lesquelles il est préférable de demander pardon :

  • Dans un monde concurrent (dans un programme multithread ou si l'opération implique des objets externes au programme tels que des fichiers, d'autres processus, des ressources de réseau, etc. can_do_operation() et le moment où vous exécutez perform_operation() . Vous devrez donc traiter l'erreur de toute façon.
  • Vous devez utiliser exactement les bons critères pour demander la permission. Si vous vous trompez, vous ne pourrez pas effectuer une opération que vous pourriez effectuer ou une erreur se produira parce que vous ne pourrez pas effectuer l'opération. Par exemple, si vous testez l'existence d'un fichier avant de l'ouvrir, il est possible que le fichier existe, mais que vous ne puissiez pas l'ouvrir parce que vous n'avez pas l'autorisation. À l'inverse, il se peut que le fichier soit créé lorsque vous l'ouvrez (par exemple parce qu'il provient d'une connexion réseau qui n'est activée que lorsque vous ouvrez réellement le fichier, et non lorsque vous vous contentez de vérifier s'il est présent).

Les situations de demande de pardon ont en commun le fait que vous tentez d'effectuer une opération, et que vous savez que l'opération peut échouer.

Quand vous écrivez foo.bar , la non-existence de bar n'est pas normalement considéré comme un échec de l'objet foo . Il s'agit généralement d'une erreur de programmation : on tente d'utiliser un objet d'une manière pour laquelle il n'a pas été conçu. La conséquence d'une erreur du programmeur en Python est une non géré exception (si vous avez de la chance : bien sûr, certaines erreurs de programmeur ne peuvent pas être détectées automatiquement). Ainsi, si bar est une partie facultative de l'objet, la façon normale de gérer cela est d'avoir un fichier de type bar qui est initialisé à None et défini à une autre valeur si la partie facultative est présente. Pour tester si bar est présent, écrivez

if foo.bar is not None:
     handle_optional_part(foo.bar)
else:
     default_handling()

Vous pouvez abréger if foo.bar is not None: a if foo.bar: seulement si bar sera toujours vrai lorsqu'il est interprété comme un booléen - si bar pourrait être de 0, [] , {} ou tout autre objet qui a une fausse valeur de vérité, vous avez besoin de l'option is not None . Il est également plus clair, si vous effectuez un test pour une partie facultative (par opposition à un test entre True y False ).

A ce stade, vous pouvez vous demander : pourquoi ne pas omettre l'initialisation de la fonction bar lorsqu'il n'est pas là, et de tester sa présence à l'aide de hasattr ou l'attraper avec un AttributeError manipulateur ? Parce que votre code n'a de sens que dans deux cas :

  • l'objet n'a pas bar champ ;
  • l'objet a un bar champ qui signifie ce que vous pensez qu'il signifie.

Ainsi, lorsque vous écrivez ou décidez d'utiliser l'objet, vous devez vous assurer qu'il n'a pas d'objet de type bar avec une signification différente. Si vous avez besoin d'utiliser un objet différent qui n'a pas d'objet bar mais ce n'est probablement pas la seule chose que vous devrez adapter. Vous voudrez donc probablement créer une classe dérivée ou encapsuler l'objet dans une autre.

86voto

Jonathan Eunice Points 1314

L'exemple classique de "demander pardon et non la permission" est l'accès aux valeurs d'une dict qui peuvent ne pas exister. Par exemple :

names = { 'joe': 'Joe Nathan', 'jo': 'Jo Mama', 'joy': 'Joy Full' }
name = 'hikaru'

try:
    print names[name]
except KeyError:
    print "Sorry, don't know this '{}' person".format(name)

Voici l'exception qui pourrait se produire ( KeyError ) est énoncé, de sorte que vous ne demandez pas le pardon pour toutes les erreurs qui pourraient se produire, mais seulement pour celle qui se produirait naturellement. À titre de comparaison, l'approche consistant à "demander la permission d'abord" pourrait ressembler à ceci :

if name in names:
    print names[name]
else:
    print "Sorry, don't know this '{}' person".format(name)

o

real_name = names.get(name, None)
if real_name:
    print real_name
else:
    print "Sorry, don't know this '{}' person".format(name)

Ces exemples de "demander pardon" sont souvent trop simples. IMO, il n'est pas clair comme de l'eau de roche que try / except sont intrinsèquement meilleurs que les blocs if / else . La valeur réelle est beaucoup plus claire lorsqu'il s'agit d'effectuer des opérations qui peuvent échouer de diverses manières, comme l'analyse syntaxique, l'utilisation de la fonction eval() ; l'accès aux ressources du système d'exploitation, des intergiciels, des bases de données ou du réseau ; ou l'exécution de calculs mathématiques complexes. Lorsqu'il existe de multiples modes de défaillance potentiels, il est extrêmement utile d'être prêt à se faire pardonner.

Autres notes sur vos exemples de code :

Vous n'avez pas besoin d'utiliser une louche try / except autour de chaque utilisation de variable. Ce serait horrible. Et vous n'avez pas besoin de mettre self.bar dans votre __init__() puisqu'il est défini dans votre class définition ci-dessus. Il est habituel de la définir soit dans la classe (s'il s'agit de données susceptibles d'être partagées entre toutes les instances de la classe), soit dans la classe __init__() (s'il s'agit de données d'instance, elles sont spécifiques à chaque instance).

Une valeur de None n'est pas indéfini, ni une erreur, d'ailleurs. Il s'agit d'une valeur spécifique et légitime, signifiant none, nil, null, ou rien. De nombreux langages ont de telles valeurs afin que les programmeurs ne "surchargent" pas 0 , -1 , '' (chaîne vide) ou des valeurs utiles similaires.

24voto

Jonathan Wylie Points 31

Il y a beaucoup de bonnes réponses ici, je pensais juste ajouter un point que je n'ai pas vu mentionné jusqu'à présent.

Demander le pardon au lieu de la permission permet souvent d'améliorer les performances.

  • Lorsque vous demandez une permission, vous devez effectuer une opération supplémentaire pour demander la permission chaque temps.
  • En demandant le pardon, vous devez seulement effectuer une opération supplémentaire parfois c'est-à-dire lorsque il échoue.

En général, les cas d'échec sont rares, ce qui signifie que si vous ne demandez qu'une autorisation, vous ne devez pratiquement jamais effectuer d'opérations supplémentaires. Oui, en cas d'échec, une exception est levée, et une opération supplémentaire est effectuée, mais les exceptions en python sont très rapides. Vous pouvez voir quelques timings ici : https://jeffknupp.com/blog/2013/02/06/write-cleaner-python-use-exceptions/

8voto

mgilson Points 92954

Vous avez raison, le but de try y except ne sont pas pour couvrir votre codage bâclé. Cela conduit simplement à un codage encore plus négligé.

La gestion des exceptions doit être utilisée pour traiter circonstances exceptionnelles (un codage bâclé n'est pas une circonstance exceptionnelle). Cependant, il est souvent facile de prédire quelles circonstances exceptionnelles peuvent se produire. (par exemple, votre programme accepte l'entrée de l'utilisateur et l'utilise pour accéder à un dictionnaire, mais l'entrée de l'utilisateur n'était pas une clé du dictionnaire...).

7voto

Shawn Chin Points 29756

Mon opinion personnelle non religieuse est que ce mantra s'applique principalement à documenté y bien compris et les cas limites (par exemple, les erreurs d'E/S), et ne devrait jamais être utilisé comme une carte de sortie de prison pour une programmation bâclée.

Cela dit, try/except est souvent utilisé lorsque de "meilleures" alternatives existent. Par exemple :

# Do this    
value = my_dict.get("key", None)

# instead of this
try:
  value = my_dict["key"]
except KeyError:
  value = None

Comme pour votre exemple, utilisez if hasattr(foo, "bar") si vous n'avez aucun contrôle sur foo et que vous avez besoin de vérifier la conformité avec vos attentes, sinon utilisez simplement foo.bar et laissez l'erreur qui en résulte vous servir de guide pour identifier et corriger le code bâclé.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X