Vu que j’ai mis un peu de temps à trouver comment faire, je me dis que ça mérite d’être inscrit au fronton du mausolée des bonnes idées.

Soit une classe Python Cost définie comme suit :

class Cost(object):
   def __init__(self):
      self.write = 0
      self.exec = 0

Comment y adjoindre efficacement les opérateurs classiques de comparaison, c’est-à-dire en écrivant le moins de code possible (très important !) et en faisant en sorte qu’elle coûte le moins cher possible à l’exécution ? On prendra l’exemple d’une comparaison lexicographique classique, celle-là même qui fait que :

(1, 2) < (1, 3)
"abc" >= "aac"

Aparté pour technologue : on pourra utiliser cette même classe Cost pour évaluer les différentes solutions, on est en pleine auto-référence, c’est trop génial !

Les comparaisons en Python

La documentation de python (« pydoc \< » dans votre interpréteur de commande favori) nous apprend qu’il y a deux stratégies pour les opérateurs de comparaison :

  • les surcharger un par un. Il faudra donc écrire le comportement de <, >, <=, >=, == et !=, en espérant que ces six implémentations soient homogènes. Bonne chance. Le plus simple dans ce cas est d’en implémenter un seul, et de définir les autres plus par rapport aux attributs, mais par rapport à cette implémentation de référence. Par exemple, on peut définir les cinq autres opérateurs à partir de <:
a <= b : return not (b < a)
a >  b : return a < b
a >= b : return not (a < b)
a != b : return a < b or b < a
a == b : return not (a < b or b<a)
  • surcharger un opérateur unique faisant ce qu’il est communément appelée une trichotomie : il renvoit une valeur numérique entière qui est négative quand a est plus petit que b (typiquement, -1), nulle quand ils sont égaux, et positive sinon (tout aussi typiquement, 1). Il n’y a alors qu’une méthode à écrire, et l’interpréteur Python se charge de câbler les opérateurs usuelle sur cette implémentation de référence.

Il est bien entendu évident qu’on va utiliser la stratégie de l’opérateur unique, si on trouve un moyen d’écrire ce même opérateur efficacement.

Rédaction efficace de la trichotomie

Une première implémentation naïve de la trichotomie serait la suivante :

class Cost(object):
   ...
   def __cmp__(self, other):
      if self.write < other.write: return -1
      elif self.write > other.write: return 1
      else:
         if self.exec < other.exec: return -1
         elif self.exec > other.exec: return 1
         return 0

C’est un peu verbeux, et on s’y prend mal : pourquoi n’utiliserait-on pas aussi ce même opérateur cmp pour nos propres attributs ? Recommençons.

class Cost(object):
   ...
   def __cmp__(self, other):
      test_write = cmp(self.write, other.write)
      if test_write != 0: return test_write
      test_exec = cmp(self.exec, other.exec)
      return test_exec

C’est déjà plus concis (ce qui est arbitrairement impératif) Mais peut-on faire mieux ? Se passer des variables temporaires tout en garantissant que la comparaison de l’attribut ‘exec’ des deux instances ne soit effectuée que si nécessaire, donc quand les attributs ‘write’ sont égaux ?

Les opérateurs logiques de Python (and, or, etc.) ont l’aimable particularité d’être semis-stricts : l’expression à leur droite n’est évaluée que si l’expression à leur gauche ne permet pas, seule, de déterminer le résultat de l’opération. Pour e1 and e2, ça signifie que e2 n’est évalué que si e1 est vrai. Ils sont donc des candidats de choix pour nous aider à avoir une écriture encore plus concise.

class Cost(object):
   ...
   def __cmp__(self, other):
      return cmp(self.write, other.write) \
          or cmp(self.exec, other.exec)

Et c’est là que la magie de Python opère (ne soyons pas chiens, le comportement serait identique en C) : X or Y renvoit X si X est jugé vrai, Y sinon. Et c’est bien ce que l’on veut ici : le résultat de la première comparaison si elle est déterminante, celui de la seconde sinon. Encore une fois, c’est génial !

Conclusion

La méthode est scalable et peut-être étendue à peu de frais à un troisième, un quatrième attribut, voire plus.

Tags: , ,