Python: Mémo pour les reptiles étourdis

Aujourd’hui nous allons parler de quelques spécifités propres au langage Python. Comme je travaille pas mal avec Python en ce moment, je découvre (ou redécouvre) des particularités pas toujours évidentes (surtout quand on jongle entre plusieurs langages en même temps), alors je me propose de faire, ici même, un mémo sur les quelques trucs bien pratiques à savoir (et retenir) en Python, ainsi que quelques faux amis qui m’ont parfois fait arracher quelques cheveux…

Attention disclaimer:

Ce que cet article est:

  • Un mémo pour les programmeurs Python au moins un peu expérimentés, qui auraient du mal à se souvenir, ou du mal tout court, avec certaines notions parfois un peu fourbes du Python.
  • un mini-guide présentant quelques subtilités et faux-amis du Python (notamment quand on vient d’autres langages)

Ce que cet article n’est pas:

  • Un cours exhaustif et structuré de Python
  • La source du savoir absolu sur Python
  • Une explication sur comment marche le Python (protip: un python ça rampe, car ça n’a pas de pieds)
  • une biographie de Guido van Rossum

Voilà, c’est dit !

 

Le Python statique

sleeping python
Un python statique

L’utilisation de statiques en Python est un sujet qui enflamme parfois quelques passions. Entre ceux qui trouvent que c’est inutile, ceux qui trouvent ça super pratique, d’autres qui ne voient pas l’intérêt et enfin ceux qui coupent en disant « de toute façon en Python tout est public » c’est un fort sujet de discorde.

Il y a par ailleurs quelques points un peu traîtres auxquels il faut faire attention. Par exemple en C++ ou en Java, pour donner un attribut statique (c’est-à-dire commun à toutes les instances d’un objet), il faut le précéder du mot-clé static :

[cc lang= »cpp »]

class Robot {
static int robotsNumber;

};

[/cc]

En Python c’est différent… Si vous avez déjà fait un peu de Python orienté objet, vous savez que tous les attributs censés être propres à la classe sont déclarés comme faisant partie de l’objet self, qui comme son nom l’indique désigne la classe elle-même. Par exemple un tyrolien, ça sait chanter le yalalahihou:

[cc lang= »python »]

class Tyrolien:
def __init__(self):
self.yalalahihou = True
[/cc]

(et ça y est, cet article, c’est déjà n’importe quoi)

(Notez d’ailleurs que le mot self n’est qu’une convention, on pourrait l’appeler « toto » sans problème)

Et bien pour créer un attribut statique dans une classe en Python, il suffit de le déclarer dans la classe même et sans ce fameux self. Tous les tyroliens savent chanter le yalalahihou après tout:

[cc lang= »python »]

class Tyrolien:
yalalahihou = True
def __init__(self):

[/cc]

Mais il faut faire attention car un tyrolien c’est parfois traître. En reprenant un cas pris sur Stack Overflow, imaginons que notre tyrolien tombe malade:

[cc lang= »python »]
tyrolien = Tyrolien()
tyrolien.yalalahihou # on accède à la statique yalalahihou grâce à l’instance tyrolien True
tyrolienAphone = Tyrolien()
tyrolienAphone.yalalahihou = False # maintenant si on modifie la variable statique d’une instance…
tyrolienAphone.yalalahihou
False # Jusque là tout paraît logique
Tyrolien.yalalahihou
True # what ?!
[/cc]

Et en fait quand on y réfléchit c’est logique.

De la manière dont on l’a écrit, le tyrolien aphone a en réalité créé un nouveau yalalahihou qui a remplacé le yalalahihou statique, sans modifier ce dernier ! La preuve que c’est logique: un tyrolien aphone n’a jamais empêché les autres tyroliens de chanter le yalalahihou.

Une petite subtilité à ne pas oublier quand on manie les statiques en Python.

Un lien insoupçonné entre le Python et le Tyrol (artiste: Diana Kennedy)

 

Le ternaire Python

Le ternaire est une structure de contrôle bien connue dans les langages comme C/C++/Java/PHP et autres dérivés de près ou de loin du C. Aussi appelée inline if elle permet comme ce nom l’indique de raccourcir son code en plaçant une expression conditionnelle, généralement sur une ligne, qui remplace avantageusement une instruction if (machinchouette) { trucbidule… } .

Pour ceux qui auraient oublié ou les novices, ça donne ça:

[cc lang= »c »]

int quarantedeux = (42 > 0 ? 42 : 0); // quarantedeux vaut 42

// Même chose version if

if (42 > 0) {

quarantedeux = 42;

} else {

quarantedeux = 0;

}

[/cc]

Le principe de l’expression (qui possède d’ailleurs une page Wikipédia !), c’est de pouvoir faire valeur = (condition ? valeur si condition est vraie : valeur si condition est fausse).

Python se distingue par le fait qu’il fait partie des rares langages à ne pas implémenter cette syntaxe. Oui oui: ça ne marche pas. Mais ce pour une raison simple: il propose plein d’autres manières de le faire !

J’en connais principalement deux. D’abord la plus évidente, puis plus une sorte d’ « astuce » qui utilise la syntaxe du langage.

Un ternaire en Python peut s’écrire avec un if et un else, il suffit d’ « inverser » les termes de l’expression par rapport à d’habitude.

Ça donne valeur = valeur si condition est vraie if condition else valeur si condition est fausse.

Imaginons le client d’un bar qui veut un café au lait, ou avec du sucre s’il n’y a pas de lait:

[cc lang= »python »]

>>> avecLait, avecSucre, laitEnStock = (1, 2, 3)
>>> cafe = avecLait if laitEnStock else avecSucre

>>> print cafe
1 # Valeur avec lait

[/cc]

Ça change un peu de d’habitude, mais ça reste quand même plutôt direct (straightforward).

Mais il y a une autre méthode ! On peut aussi utiliser les opérateurs and / or avec ce qui s’appelle l’astuce and-or (and-or trick).

Comme le programmeur expérimenté peut s’y attendre, and et or sont des équivalents aux opérateurs logiques ET et OU, respectivement && et ||, de nombreux langages.

Il existe cependant une petite subtilité : plutôt qu’une valeur booléenne (vrai ou faux), and et or retournent les valeurs avec lesquelles on les utilise. Vous ne voyez pas ce que ça change ? Démonstration…

D’abord en C++:

[cc lang= »cpp »]

#include

int main() {

bool metallicaSongs = « One » && « Fade to Black »;
std::cout << std::boolalpha << metallicaSongs << std::endl; // « true »

bool ACDCSongs = « Thunderstruck » || « Back In Black »;
std::cout << metallicaSongs << std::endl; // « true » aussi
}
[/cc]

Maintenant en Python :
[cc lang= »python »] >>> « One » and « Fade to Black »
‘Fade to Black’
>>> « Thunderstruck » or « Back in Black »
‘Thunderstruck’

[/cc]

Bon, alors ça ne veut pas dire que Python préfère Fade to Black ou Thunderstruck, mais ça montre bien comment fonctionne les opérateurs and et or du Python: and retourne la première valeur fausse, sinon la dernière, et or retourne la première valeur qui vaut ‘vrai’, sinon la dernière. D’autres exemples:

[cc lang= »python »]

>>> 0 and 1
0
>>> 0 or 1
1
>>> 0 or 0 or 1
1
>>> 1 and 0 and 0
0
>>> 1 and 0 and False
0
>>> 1 and False and 0
False
>>> 0 or False
False

[/cc]

Concrètement, comment ça peut marcher dans un contexte de ternaire ? C’est simple: on commence par écrire la condition booléenne qu’on veut tester, and, on place la valeur qu’on veut utiliser si cette condition est vraie, or, on place la valeur si cette condition est fausse.

Un exemple vaut mieux qu’un long discours :

[cc lang= »python »]
>>> toto = 42
>>> toto = (toto < 42) and 21 or 84 # « (toto < 42) » s’évalue à False, donc « (toto < 42) and 21 » s’évalue à False, mais « False or 84 » retourne 84 !
>>> print toto
84
>>> toto = (toto > 2) and 21 or 84 # Si la condition est vraie, alors la valeur placée après le and prévaut, car or retourne la première valeur qui vaut vrai
>>> print toto
21
[/cc]

Voilà, à défaut de paraître intuitive à certains, c’est une manière très idiomatique (propre au langage) d’exprimer une condition ternaire.

Faites quand même attention si vous l’utilisez, car cette méthode a ses limites et ses problèmes… Pour approfondir, allez lire l’avis de Dive Into Python (un excellent guide, soit dit en passant !) sur la question 😉

Une blague classique sur la logique booléenne

 

« is », ou l’instruction qui reconnaît les clones

Une erreur courante des débutants en Python est de confondre l’opérateur de comparaison == et is. is c’est un peu le Canada Dry de ==: ça y ressemble fortement, parfois même ça agit pareil… Et pourtant ce n’est pas la même chose ! (passons sur l’aspect péjoratif de l’expression)

Donnons-la en mille: == est un opérateur qui teste l’égalité, is est un opérateur qui teste l’identité. La différence ? == va vérifier que les deux objets qu’on lui présente contiennnent bien la même valeur. is va plutôt vérifier qu’il s’agit du même objet.

Oui mais c’est bizarre, quand on utilise is sur deux objets différents, bah très souvent ça marche…

Effectivement, faisons quelques tests:

[cc lang= »python »]
>>> a = 200
>>> b = 200
>>> a == b
True # Jusque là tout va bien
>>> a is b
True # Tiens, est-ce que is ça serait vraiment la même chose que == ?
>>> a = 500
>>> b = 500
>>> a is b
False # What the duck ??
>>> a = ‘for whom the bell tolls’
>>> b = ‘for whom the bell tolls’
>>> a == b
True # « ding dong »
>>> a is b
False # What the peking duck ?
>>> a = ‘Navras’
>>> b = ‘Navras’
>>> a is b
True # Mais.. mais… ça vient de retourner False dans l’exemple d’avant !!
[/cc]

C’est là une belle illustration de où une mauvaise utilisation de is peut mener: parfois il retourne True, et parfois, il retourne False, et du coup ben parfois, ça casse tout. Qu’est-ce que c’est que ce délire ?

L’explication derrière ce mystère est finalement assez simple. Tout d’abord, pour les chaînes de caractère, Python utilise ce qu’on appelle le string interning.

Une string internée
Une string internée

En gros, ça consiste à garder un pool interne de données dans lequel Python stocke des données en général immuables (comme des chaînes exprimées en dur, tout comme dans notre exemple). Et ce qu’il fait pour des chaînes de ce genre, c’est qu’il en stocke une unique copie en mémoire et il en fait un Singleton. Si vous attribuez la même chaîne à deux variables, Python les fera toutes deux pointer sur le même objet.

Pourquoi, diront certains ? Ce n’est pas évident pour l’utilisateur final (le programmeur), mais de cete manière, à chaque fois qu’on veut tester l’égalité entre deux chaînes, Python n’a qu’à faire un test d’identité de l’objet (voir si il s’agit du même objet) plutôt que de devoir comparer, caractère par caractère, si les deux chaînes sont identiques (une procédure en O(n) où n est le nombre de caractères, par définition). Voilà pourquoi a is b renvoie True dans l’exemple ‘Navras’. Pour ‘for whom the bell tolls’, Python n’applique le string interning que jusqu’à une certaine taille, le mécanisme n’a pas été appliqué dans ce cas, les deux chaînes sont alors deux objets distincts en mémoire donc a is b vaut False.

C’est un peu le même genre d’explication pour le coup des nombres entiers: Python met en cache les petits nombres entiers pour les réutiliser plus facilement, plus précisément ceux allant de – 5 à 256. C’est donc pour ça que lorsque a et b valent 200, a is b renvoie True, mais quand ils valent 500, soudainement ça se met à renvoyer False !

Tout dépend de l’utilisation qu’on en fait donc, mais une chose est sûre: n’utilisez pas is à la légère et à moins d’être absolument sûr que ce que vous voulez tester, c’est que deux objets sont en fait deux références vers le même objet, utilisez == ! Comme d’hab, pour plus d’info, les gens en parlent sur Stack Overflow.

 

« Mettez-moi n’importe lequel… En fait non donnez-moi tout »

On a souvent tendance à oublier deux éléments de langage utiles en Python: any et all. Ils fournissent un moyen simple d’effectuer des vérifications sur n’importe quel itérable.

any retourne vrai si au moins un des objets contenus dans l’itérable répond à la condition. all retourne vrai si tous les objets répondent à la condition. C’est aussi simple que ça, mais ce sont des opérations tellement simples qu’on a vite fait d’oublier que ça existe !

La preuve par l’exemple, pour la forme:

[cc lang= »python »]

# On veut savoir si dans une liste d’entiers, au moins un est égal à 0

>>> any(number > 0 for number in [1, -2, -3])
True # Il y a le 1

# On cherche à savoir si dans une liste, tous les objets sont bien des nombres entiers…
>>> all(isinstance(object, int) for object in [1, « c’est deja beaucoup ! »])

False # la liste contient une chaîne de caractères

[/cc]

Bref, deux fonctions qui peuvent s’avérer très pratiques, en plus d’être assez bien lisibles (je trouve) !

Notez que l’exemple utilise des listes en compréhension (ou comprehension lists). J’aurai sans doute l’occasion d’en reparler un jour… 🙂

——

Voilà, cet article se termine ici… Il y a pourtant encore tant à dire sur le Python !
Ça fera peut-être l’objet d’un autre article, qui sait… Pour le moment, moi, je vais me coucher !

 

En attendant, codez heureux avec Python

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *