Python Start


Why Python?

« Life is short, you need python. » Bruce Eckel

À propos

Python est un langage de programmation procédurale interprétée et orientée objet. Il est multi-plateforme et sa licence est libre.

Le langage Python est placé sous une licence libre proche de la licence BSD et fonctionne sur la plupart des plates-formes informatiques, des smartphones aux ordinateurs centraux, de Windows à Unix avec notamment GNU/Linux en passant par macOS, ou encore Android, iOS, et peut aussi être traduit en Java ou .NET. Il est conçu pour optimiser la productivité des programmeurs en offrant des outils de haut niveau et une syntaxe simple à utiliser. Wikipédia: python

Le langage

Variables

Les variables en python sont des objets. Mais avant de parler d'objet, on va simplement regarder comment utiliser des variables.

En programmation, une variable doit être déclarée (on définit son type et son nom) puis, ensuite, elle peut être affectée d'une valeur. Python ne permet que la seconde partie : l'affectation de valeur. Ne vous étonnez pas s'il y a un abus de langage entre 'déclarer' et 'affecter' par la suite. Le principe est basé sur le typage dynamique de python : les variables ne sont que des noms et elles n'ont pas de type (seuls les objets en ont un).

On ne dira pas que l'on change le type d'une variable (ou alors par abus de langage) mais qu'on remplace l'objet pointé par une variable. De fait, cela permet une très grande flexibilité (voire du contortionnisme) pour manipuler nos variables mais cela n'empêche pas (dans ma vision) une maîtrise rigoureuse de nos types en sous-main (le langage vous laisse une grande partie de cette responsabilité).

Illustration (en utilisant la fonction type pour connaître le type de l'objet pointé par une variable) :

>>> x = 1
>>> type(x)
<class 'int'>

>>> x = '1'
>>> type(x)
<class 'str'>

>>> x = True
>>> type(x)
<class 'bool'>

>>> x = [1,2]
>>> type(x)
<class 'list'>

>>> x = (1,2)
>>> type(x)
<class 'tuple'>

Types natifs de variable

Logique
Booléen.........bool
Bytes...........bytes
ByteArray.......bytearray
MemoryView......memoryview
Numérique
Entier..........int
Flottant........float
Complexe........complex
Chaînes de caractères
Chaîne..........str
Listes / Type séquentiel
Liste...........list
Tuple...........tuple
Range...........range
Dictionnaire
Dictionnaire....dict

Chaque type est associé à une fonction homonyme pour forcer la classe de l'objet (ici pour changer son type en utilisant une classe spécifique) pointé par une variable :

>>> x = 1
>>> type(x)
<class 'int'>
>>> str(x)
'1'
>>> type( str(x) )
<class 'str'>

>>> bool(x)
True
>>> type( bool(x) )
<class 'bool'>

Les variables booléennes sont les plus utilisées car elles conditionnent les structures de contrôle, notamment les tests conditionnels. Des exemples de valeur binaire en fonction de l'évaluation d'une équation logique (ou une inéquation):

>>> 1 < 2
True

>>> 1 > 2
False

>>> 13.37*3.14 < 42
True

Le résultat peut évidemment être affecté à nos variables.

Affectation

Quelques exemples en vrac :

>>> x = 1

>>> x = 'une variable'

>>> x = [1, 2, 3]
>>> x = list([1, 2, 3])
>>> x = []                             # liste vide
>>> x = list()                         # -

>>> x = {}                             # dictionnaire vide
>>> x = dict()                         # -
>>> x = {'key':'value'}
>>> x['key'] = 'value'

>>> x,y = 1,2
# x = 1
# y = 2

>>> [x,y] = [1,2]
# x = 1
# y = 2

>>> [[x,y],z] = [[1,2],'test']
# x = 1
# y = 2
# z = 'test'

Noms réservés

Les mot-clefs sont des noms réservés par python (généralement pour les structures de contrôle) :

>>> import keyword
>>> keyword.iskeyword('if')
True
>>> keyword.iskeyword('def')
True
>>> keyword.iskeyword('for')
True

Ils ne sont pas réutilisables : une erreur se déclenche si on essaye d'affecter for = 1.

Les types font partie des noms réservés : chaque type possède une fonction associée pour forcer le type d'une variable à notre convenance.


Structures de contrôle

Un partie importante, s'il en est \o/.

if / elif / else

La déclaration if permet d'effectuer un test conditionnel en utilisant une variable booléenne. Le test est validé si la variable est "vraie" (True) et on rentre dans le bloc dédié en indentant le code :

if True:
    print("This is True")

La déclaration else permet de compléter le test avec les cas qui sont faux :

if True:
    print("This is True")
else:
    print("This is False")

On peut, au besoin, décomposer un test logique suivant son type : ici, on teste l'inclusion d'une valeur numérique dans le domaine [18,100] avec elif et les cas limites avec if et else:

if age < 18:
    print("Vous êtes mineur dans certains pays")
elif age < 100:
    print("Vous avez plus de 18 ans et moins de 100 ans (dans tous les pays, lol)")
else:
    print("Vous êtes encore là ? Alors ça fait quel effet ?")

Sachant que l'inclusion de l'âge dans le domaine [18,100] peut également se vérifier de cette façon :

if (age >= 18) and (age < 100):
    print("Vous êtes dans le domaine")

if 18 <= age < 100:
    print("Vous êtes dans le domaine")

Digression sur la logique

Si on veut inverser le test logique et tester directement l'exclusion, on applique les lois de De Morgan : non(A et B) = non(A) ou non(B).

Le test logique inversé est le suivant :

if (age < 18) or (age >= 100):
    print("Vous n'êtes pas dans le domaine")

On peut remarquer que < se transforme en >= par complémentarité des domaines : non(age < 100) égal (age >= 100).

Les structures if peuvent êtres imbriquées et on indente à mesure que l'on crée un nouveau test :

if 18 <= age < 100:
    if age < 20:
        print("Animation pour les jeunes #notSNU")
        if age < 19:
            print("Ateliers XY")
    if age == 42:
        print("Vous êtes la réponse")

La structure if / else peut être utilisée en one-liner :

>>> mybool = 'broken'
>>> x = 1 if mybool is 'broken' else 0  # la quantité de poésie dans cet exemple dépasse l'entendement !
>>> x
1

# l'encapsulation en tuple ou liste est obligatoire pour des ensembles
>>> norm = True
>>> x,y = (x_norm,y_norm) if norm else (x,y)

A noter : l'utilisation de if / else dans une "liste en compréhension" :

# échantillon
>>> [ i for i in range(5) ]
[0, 1, 2, 3, 4]

# le 'if' s'utilise à la fin  
>>> [ i for i in range(5) if i>> [ i for i in range(5) if i", line 1
    [ i for i in range(5) if i>> [ i if i>> [ i*(i

Boucles / Itérations for

Boucler vos incréments, c'est ti-par.

for / in

La structure for / in permet d'itérer sur des ensembles, par exemple :

>>> liste = [1,2,3,4]
>>> for i in liste:
...     print(i)
... 
1
2
3
4
>>> for i in liste:
...     print(i,liste.index( i ))
... 
1 0
2 1
3 2
4 3

On peut itérer sur beaucoup plus d'objets que les listes, ici une chaîne de caractères :

>>> for c in 'coucou':
...     print(c)
... 
c
o
u
c
o
u

De façon générale, le type des incrément n'est pas fixé (dans la logique ou une liste peut comporter plusieurs types d'éléments) :

>>> for x in [ 1, '1', [1,2,3], {'key':'value'} ]:
...     print(x,type(x))
... 
1 <class 'int'>
1 <class 'str'>
[1, 2, 3] <class 'list'>
{'key': 'value'} <class 'dict'>

Il faut garder ça en tête : python est flexible et se prête bien au prototypage. C'est également la possibilité de vouloir/pouvoir modifier son code facilement avec les risques que cela comporte : dans notre cas, le seul piège est de vouloir opérer de la même façon sur toutes les variables alors que toutes les opérations ne seront pas applicables identiquement sur des types différents (en tout cas, pas ceux utilisés ici).

enumerate

Il existe la fonction enumerate qui permet d'incrémenter sur les indices et les valeurs de notre objet:

>>> for i,c in enumerate('test'):
...     print(i,c)
... 
0 t
1 e
2 s
3 t

Il peut être intéressant de regarde de plus près le type d'enumerate :

>> enumerate('test')
<enumerate object at 0x7ffafdc1ac18>

>> list( enumerate('test') )
[(0, 't'), (1, 'e'), (2, 's'), (3, 't')]
Incréments multiples

On verra par la suite avec zip que l'on peut itérer sur plusieurs listes en même temps. Mais il est aussi possible de vouloir itérer sur une liste qui comporte des tuple (c'est un type à part entière en python pour désigner des tuplets : (a,b), (a,b,c), etc...) :

>>> for i,j in [ (1,2), (3,4), ('a','b')]:
...     print(i,j)
... 
1 2
3 4
a b

Le nombre d'indice est arbitraire :

>>> t = [ [1,2,3], [4,5,6], [7,8,9] ]

>>> for x,y,z in t:
...     print(x,y,z)
... 
1 2 3
4 5 6
7 8 9

Si un indice est inutile, il est possible d'utiliser la notation _ qui désigne une variable... inutilisée et inutilisable :

>>> for i,j,_ in [ (1,2,3),(3,4,5) ]:
...     print(i,j)
... 
1 2
3 4

Une autre solution, plus généraliste, implique de découpler les indices dans la boucle :

>>> for ij in [ (1,2,3),(3,4,5) ]:
...     i = ij[0]
...     j = ij[1]
...     print(i,j)
... 
1 2
3 4

Le mieux étant de connaître à l'avance la structure avec laquelle on opère ! :-)


range

La fonction range permet de générer un ensemble de valeurs entières :

>>> range(3)
range(0, 3)

>>> list( range(3) )
[0, 1, 2]

>>> for i in range(3):
...     print(i)
... 
0
1
2

>>> for i in range(3,6):
...     print(i)
... 
3
4
5

# les paramètres sont les bornes inférieures et supérieures du domaine et le pas
>>> for i in range(1,10,3):
...     print(i)
... 
1
4
7

zip

On peut également itérer sur plusieurs ensembles d'un coup grâce à la fonction zip:

>>> x = [1,2,3,4]
>>> y = ['a','b','c','d']
>>> for i,j in zip(x,y):
...     print(i,j)
... 
1 a
2 b
3 c
4 d

On peut utiliser la fonction même lorsque les listes ne sont pas de même taille, c'est la plus petite qui conditionne l'arrêt :

# On tronque la liste 'x'
# Grâce à la méthode .pop() : sortir le dernier élément '4' de la liste 'x'
>>> x.pop()
4
>>> for i,j in zip(x,y):
...     print(i,j)
... 
1 a
2 b
3 c
>>> for i,j in zip(y,x):
...     print(i,j)
... 
a 1
b 2
c 3
map

La fonction map permet d'appliquer une fonction à une liste de valeurs :

# définition d'une fonction 'square'
>>> square = lambda x: x*x

# utilisation de 'map' pour appliquer 'square' sur les valeurs données
>>> map(square, [0,1,2,3,4])
         # map est un objet (ou type) spécial

# passage en liste des valeurs
>>> list( map(square, [0,1,2,3,4]) )
[0, 1, 4, 9, 16]

# On peut construire en une ligne un filtre sur un ensemble (ici un range(3))
>>> x = list( map(lambda x: (x,x>=2),range(3)) )
# x = [(0, False), (1, False), (2, True)]

# Pour appliquer le filtre sur une autre variable (voir les listes en compréhension ci-dessous)
>>> y = [ u[0] for u in x if u[1] ]
# y = [2]

# Pour réaliser les 2 opérations en une fois
>>> y = [ u[0] for u in list( map(lambda x: (x,x>=2),range(3)) ) if u[1] ]
# y = [2]


Liste en compréhension

Traduction littérale de comprehension list : c'est une syntaxe particulière pour représenter une liste à partir d'une autre. Cette notation permet d'opérer rapidement une liste lorsque l'opération n'est pas très complexe (ajouter 1 à un entier, supprimer/filtrer une partie des valeurs, etc...):

>>> t = [ 1, 2, 3, 4, 10, 20, 30, 123, 345]

>>> [ x+1 for x in t ]
[2, 3, 4, 5, 11, 21, 31, 124, 346]

>>> [ x for x in t if x >> [ x if x>42 else x+42 for x in t ] 
[43, 44, 45, 46, 52, 62, 72, 123, 345]

Les listes en compréhension permettent d'imbriquer plusieurs niveaux d'itération :

>>> [ (i,j) for i in range(3) for j in range(3) ]
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

>>> [ (i,j) for i in range(3) for j in 'abc' ]
[(0, 'a'), (0, 'b'), (0, 'c'), (1, 'a'), (1, 'b'), (1, 'c'), (2, 'a'), (2, 'b'), (2, 'c')]

Dictionnaire en compréhension

Sur le même principe, on peut construire et filtrer un dictionnaire à la volée.

Instancier via les générateurs et itérateurs :

>>> { key:value for key,value in enumerate('ABC') }
{0: 'A', 1: 'B', 2: 'C'}

>>> { i:f"value {i}" for i in range(4) }
{0: 'value 0', 1: 'value 1', 2: 'value 2', 3: 'value 3'}

>>> { i:f"value {i}" for i in ["John", "Jane", "Joan"] }
{'John': 'value John', 'Jane': 'value Jane', 'Joan': 'value Joan'}

Filtrer avec les tests conditionnels :

>>> f = { "Orange":"Fruit", "Pomme":"Fruit", "Poivron":"Légume", "Lion":"Animal" }

# Exclure les valeurs différentes de "Fruit" ou "Légume"
>>> { k:v for k,v in f.items() if v in ["Fruit","Légume"] }
{'Orange': 'Fruit', 'Pomme': 'Fruit', 'Poivron': 'Légume'}

# Conserver et spécifier les valeurs inconnues sur l'ensemble d'arrivée ["Fruit","Légume"]
>>> { k:v if v in ["Fruit","Légume"] else "Inconnu" for k,v in f.items() }
{'Orange': 'Fruit', 'Pomme': 'Fruit', 'Poivron': 'Légume', 'Lion': 'Inconnu'}

# On peut jouer avec les itérateurs pour inverser la relation :
>>> { k:[ kk for kk,u in f.items() if u==k ] for k in set([ v for v in f.values() ]) }
{'Animal': ['Lion'], 'Légume': ['Poivron'], 'Fruit': ['Orange', 'Pomme']}

# Et compter les occurrences également (à titre d'exemple) :
>>> { k:sum([ vv==k for vv in f.values()]) for k in set([ v for v in f.values() ]) }
{'Fruit': 2, 'Animal': 1, 'Légume': 1}


with / as

La structure with / as permet de contrôler un flux de données, typiquement : l'ouverture, la lecture ou l'écriture d'un fichier et sa fermeture. De façon classique, on opère généralement de la sorte :

>>> f = open('testfile.dat','w')                     # ouverture d'un flux en écriture
>>> f.write('Ecriture dans le fichier\n')            # écriture
25        
>>> f.close()                                        # fermeture

>>> f = open('testfile.dat','r')                     # ouverture d'un flux en lecture
>>> f.readlines()                                    # lecture
['Ecriture dans le fichier\n']
>>> f.close()                                        # fermeture

Le flux doit toujours être fermé en fin d'utilisation. La structure with l'inclut automatiquement dans son comportement. Les instructions à réaliser sur le flux sont incluses dans un bloc indenté :

>>> with open('testfile','w') as f:
...     f.write('Ecriture dans le fichier\n')
... 
25
>>> with open('testfile','r') as f:
...     f.readlines()
... 
['Ecriture dans le fichier\n']

La sortie du bloc indenté signifie la fermeture du flux. Pour sortir de l'information, il suffit d'affecter une variable dans le bloc :

>>> with open('testfile','r') as f:
...     data = f.readlines()
... 

>>> data
['Ecriture dans le fichier\n']

C'est important de maîtriser les structures de contrôle et il est possible de créer notre propre structure pour des cas spécifiques 8-) : [[prog:python:timeout]]


Objets

Les objets font partie intégrante de python et s'il n'est pas nécessaire de les maîtriser pour utiliser python de façon avancée, il peut être très utile d'avoir un minimum de notion. C'est mon cas et voici de quoi comprendre le principe.

Les objets sont principalement un outil pour représenter et généraliser un "objet abstrait". Par exemple, je peux créer un objet qui représentera ma maison. Son niveau de complexité est arbitraire (selon mon besoin en fait) et je peux commencer par quelque chose de très simple : ma maison est bleue.

Je viens de créer un attribut à ma maison : sa couleur. Et sa valeur est le bleu.

Les objets peuvent porter des attributs avec des valeurs spécifiques.

Lorsqu'on dit qu'en python "tout est objet", c'est que chaque entité peut être obtenue à partir d'une classe primaire : la classe object.

>>> mon_objet = object()
>>> mon_objet

>>> mon_objet.__class__

On peut voir "les objets" comme un arbre généalogique ou lorsque un objet possède un nouvel attribut, on peut alors créer une nouvelle classe (une nouvelle branche) qui hérite des propriétés de la classe parente en plus de ce nouvel attribut.

Définition d'une classe

Pour créer une nouvelle classe, on définit sa structure à partir du mot-clé class :

class Maison():

    couleur = "bleu"

On peut ensuite générer une instance de l'objet Maison :

m = Maison()

print( m )
print( m.couleur )
<__main__.maison object at>
bleu

Voilà le nouvel objet avec un seul attribut. Les attributs sont de différents types, ils décrivent généralement l'objet mais pas seulement. Dans le vocabulaire, on distingue les méthodes (des fonctions) et les attributs simples (des variables ou des constantes) de l'objet.

L'objet avec une méthode en plus :

class Maison():

    couleur = "bleu"

    def affiche(self):
        return "Ma couleur est %s"%self.couleur
m = Maison()

print( m.couleur )
print( m.affiche() )

Ce qui renvoie :

bleu
Ma couleur est bleu

On a, ici, un objet "statique". On ne peut pas choisir sa couleur ou la modifier après la création (ou initialisation). On peut générer rapidement beaucoup d'instances de cette classe mais elles seront toutes similaires : l'intérêt des objets est certainement de pouvoir factoriser les capacités de nos "représentations" numériques (les tableaux, les entiers, etc..).

Bien que ça ne réponde pas nécessairement aux bonnes pratiques de la POO, il est tout de même possible de modifier un attribut à la volée pour un instance spécifique (ici l'instance nommée m) :

>>> m.couleur
bleu
>>> m.couleur = "blouge"
>>> m.couleur
blouge

Accès et modification des attributs : Getters & Setters

Les bonnes pratiques (je vous renvoie vers elles :) indiquent généralement que les attributs ne doivent pas être modifiés par les utilisateurs de cette façon. Il est plus propre de mettre en place une méthode pour accéder à un attribut (un getter) et une autre pour modifier cet attribut (un setter).

Voici un exemple :

class Maison():

    couleur = "bleu"

    def get_couleur(self):
        return self.couleur

    def set_couleur(self, couleur):
        self.couleur = couleur

>>> m = Maison()
>>> print( m.get_couleur() )             # GET: lecture d'attribut
bleu

>>> m.set_couleur('pourpre')             # SET: écriture d'attribut
>>> print( m.get_couleur() )
pourpre

Self

Le mot-clé self est réservé lorsqu'on est au sein d'une classe : il fait référence à la classe elle-même (et plus précisément, à une instance de la classe). Cela permet à l'objet d'avoir accès à ses attributs et ainsi ses méthodes peuvent les utiliser.

Les méthodes de l'objet prennent systématiquement comme premier argument l'objet via ce mot-clé self.

class Objet():
    def plus_un(self,x):  # On écrit (self,x) mais 'x' sera le premier argument explicite passée à la méthode
        return x+1
>>> obj = Objet()
>>> obj.plus_un(1)        # Le fait d'écrire 'obj.' fait de l'objet le premier argument de la méthode qui suit
2                         # la valeur '1' a bien été affectée à 'x' qui se trouve être le premier argument explicite 

Constructeur

Lorsque l'on instancie un objet, on passe par une méthode spéciale pour le construire : il s'agit du constructeur. C'est une méthode avec le nom réservé _ init _. En la définissant au sein de la classe, on peut alors contrôler ce passage, notamment pour définir des arguments qui seront des attributs ou qui serviront à l'initialisation de l'objet.

class Maison():

    def __init__(self, couleur):
        self.couleur = couleur

    def affiche(self):
        return f"Ma couleur est {self.couleur}"
>>> m = Maison("vert")
>>> m.affiche()
Ma couleur est vert

Noms réservés

Il existe d'autres noms réservés comme _ init _. Par exemple, ceux qui appartiennent à la classe primaire object :

>>> m = object()
>>> m.
m.__class__(          m.__eq__(             m.__gt__(             m.__le__(             
m.__reduce__(         m.__sizeof__(         m.__delattr__(        m.__format__(         
m.__hash__(           m.__lt__(             m.__reduce_ex__(      m.__str__(
m.__dir__(            m.__ge__(             m.__init__(           m.__ne__(             
m.__repr__(           m.__subclasshook__(   m.__doc__             m.__getattribute__(   
m.__init_subclass__(  m.__new__(            m.__setattr__(

Ou passer par le module help lorsque que la classe est plus élaborée et documentée :

>>> x = int(1)
>>> help( x )
Help on int object:

class int(object)
 |  int([x]) -> integer
 |  int(x, base=10) -> integer
 |  
[...] 
 |  
 |  Methods defined here:
 |  
 |  __abs__(self, /)
 |      abs(self)
 |  
 |  __add__(self, value, /)
 |      Return self+value.
 |  
 |  __and__(self, value, /)
 |      Return self&value.
 |  
 |  __bool__(self, /)
 |      self != 0
[...]

Le mot réservé est un peu abusif car on a déjà utilisé le constructeur dans l'exemple. En fait, ces méthodes peuvent être surchargées ou redéfinies à notre convenance. Par exemple pour redéfinir la représentation de l'objet en texte via _ str _ :

class Maison():

    def __init__(self, couleur):
        self.couleur = couleur

    def __str__(self):
        return f"Ma couleur est {self.couleur}"
>>> m = Maison("#123456")
>>> print(m)
Ma couleur est #123456

Classe VS instances

Le terme complet est instance de classe : une instance est spécifique à une classe qui sert à la définir (la classe est une matrice qui sert à la construction d'une instance de classe).

On peut résumer que plusieurs instances de classe "dérivent" d'une classe unique. Je ne rentre pas dans les détails du polymorphisme mais mon explication reste simpliste et suffisante quand on démarre (je peux me tromper...). L'analogie avec l'argre généalogique est une bonne analogie pour l'instant.

Concrètement, on travaille sur une classe (ici on prend la classe 'object') et on a généralement plusieurs instances :

>>> a = object()
>>> b = object()
>>> c = object()

>>> list( map(id,[a,b,c]) )
[3061397456, 3061397512, 3061397240]

>>> [ id(a), id(b), id(c) ]
[3061397456, 3061397512, 3061397240]


Exemple d'objet

[[prog:python:vector|Vecteur n-dimensionnel]]


Installation

Une question qui ne se pose plus vraiment : [[https://wiki.python.org/moin/Python2orPython3|python2 ou python3]]

Je laisse ces versions courantes mais ce n'est plus supporté :

Vous pouvez démarrer avec la 3.7 pour un moment :-):

Un peu d'[[https://wiki.python.org/moin/BeginnersGuide/Download|aide ici]].


python3.7 : "à la main"

Un cas d'installation.

$ sudo apt update && sudo apt upgrade                             # Mise à jour des sources et du système

$ wget https://www.python.org/ftp/python/3.7.2/Python-3.7.2.tgz   # Récupérer la dernière source stable de python3.7

$ sudo tar -xzf Python-3.7.2.tgz -C /usr/src                      # Décompresser dans le dossier /usr/src

$ cd /usr/src/Python-3.7.2                                        # Se déplacer dans le dossier /usr/src

$ sudo ./configure --enable-optimizations                         # Configurer l'installation

$ sudo make altinstall                                            # Lancer l'installation

Vérifier la version :

$ python3.7 --version
Python 3.7.2

$ python3 --version
Python 3.7.2

$ python --version
Python 3.7.2

Au besoin, triturer les [[https://fr.wikipedia.org/wiki/Lien_symbolique|liens symboliques]] /usr/bin/python3. Déjà, vérifier leur état :

$ ls -l /usr/bin/python*

D'après le résultat précédent et si le chemin de python est effectivement /usr/bin/ (ça peut être un autre chemin suivant votre config ou distro), on doit bien trouver les [[https://fr.wikipedia.org/wiki/Lien_symbolique|liens symboliques]] suivants :

/usr/bin/python3 -> /usr/bin/python3.7
/usr/bin/python -> /usr/bin/python3.7

[[https://vonkrafft.fr/guides/installer-python-3.7-debian-9-strecth/|Vu ici]]


Pip

Le Python Index Package est très utile, c'est même une base incontournable pour gérer les modules - depuis leur installation, configuration jusqu'à leur désinstallation.

Il a été considérablement amélioré et permet une gestion précise des versions des paquets (aka versioning).

= Version

$ pip --version
pip 21.0.1 from /usr/local/lib/python3.7/dist-packages/pip (python 3.7)

On constate la version utilisée et son emplacement situé dans dist-packages qui est l'emplacement principal des modules python installés.

La plupart des distributions Linux (sinon toutes ?) utilisent python lui-même et intègre pip directement. Il est maintenant très accessible via les dépôts publics (cela n'a pas toujours été le cas - à une époque les mécaniques d'install étaient ésotériques au possible #easyinstall #pip). Les installateurs windows (bien que je pratique de moins en moins) ont toujours été très pratiques et vous trouverez votre bonheur rapidement.


Modules utiles

https://pypi.org est la source principale et vous pouvez publier vos modules.

Énormément d'autre projets sont partagés sur github.com et autres dépôts git.


Le module d'aide

Le module d'aide est disponible via la commande interactive help :

>>> help()

Welcome to Python 3.7's help utility!

If this is your first time using Python, you should definitely check out
the tutorial on the Internet at https://docs.python.org/3.7/tutorial/.

Enter the name of any module, keyword, or topic to get help on writing
Python programs and using Python modules.  To quit this help utility and
return to the interpreter, just type "quit".

To get a list of available modules, keywords, symbols, or topics, type
"modules", "keywords", "symbols", or "topics".  Each module also comes
with a one-line summary of what it does; to list the modules whose name
or summary contain a given string such as "spam", type "modules spam".

help> 

On obtient la main pour accéder, par exemple, à la liste des modules en tapant le mot-clé.

A noter que l'on accède à tous les fichiers python présents dans les chemins du PYTHONPATH. Si je crée un script sandbox.py qui ne contient qu'un entête :

#!/usr/bin/python3
# coding: UTF8
""" Script bac à sable """

Il sera accessible via la commande help :

>>> help("sandbox")
Help on module sandbox:

NAME
    sandbox - Script bac à sable

FILE
    /home/duke/prog/python/sandbox.py

(END)

Le module fera une présentation de toute les fonctions, classes et même variables qui se trouvent dans le fichier. La présentation passe par l'utilisation d'un commentaire d'aide (l'exemple ci-dessus utilise un descriptif en troisième ligne) qui se situe généralement au début.

def ajouter_1(x):
    " Ajoute 1 à la valeur de x "
    return x+1

Via le module d'aide, on verra :

FUNCTIONS
    ajouter_1(x)
        Ajoute 1 à la valeur de x

Les triples guillemets permettent d'utiliser un descriptif multi-lignes :

#!/usr/bin/python3
# coding: UTF8
"""Script bac à sable

Ici on va faire ce qu'on veut.
Surtout n'importe quoi.
"""

Qui apparaîtra comme ceci via le module d'aide :

Help on module sandbox:

NAME
    sandbox - Script bac à sable

FILE
    /home/duke/prog/python/sandbox.py

DESCRIPTION
    Ici on va faire ce qu'on veut.
    Surtout n'importe quoi.

    Fin

(END)

Cette chaîne de caractère est automatiquement stockée dans la variable interne $doc$:

print( __doc__ )

<WRAP center round tip 60%> Une astuce pour vérifier l'état des modules en console est d'utiliser python -c pour lui passer une commande :

$ python -c 'help("modules")' | grep matplotlib
ImageSequence    contextlib    matplotlib    sph2D

N'hésitez pas d'en abuser et de vous demander à quoi servent tous ces modules. :-)

Scripts

Niveau 1

Créer un fichier avec l'extension .py:

#!/usr/bin/python # premier script x = 1 y = x*x + 1 z = 2*x-y**0.5 print( x, y, z)

Niveau 2

Gestion de l'appel.

#!/usr/bin/python # call me maybe print( f"Appel de {__name__}" ) if __name__ == '__main__': print( f"Appel de {__name__}" ) print( f"Fin de {__name__}" )

Si on appelle le script en commande:

$ python books.py
Appel de __main__
Appel de __main__
Fin de __main__

Si on charge le script en tant que module:

$ python -m books.py
Appel de books
Fin de books

On peut donc distinguer ce qui doit être charger naturellement en tant que contenu permanent et la partie fonctionnelle qui pourra être utilisée en ligne de commande (à la demande).

Gestion des options/arguments

En guise d'introduction pour la gestion des arguments passés à un script python, on peut démarrer avec le module natif argparse :

#!/usr/bin/python
# coding: UTF-8
""" Something about books """

from argparse import ArgumentParser

if __name__ == '__main__':

    argp = ArgumentParser( description=__doc__ )

        args = argp.parse_args()

Il ne se passera rien en apparence mais vous pouvez vérifier que le "tout" est en place via l'option d'aide par défaut -h:

$ python books.py -h
usage: books.py [-h]

Something about books

optional arguments:
  -h, --help  show this help message and exit


logging

Le système de journalisation de python. La documentation sur logging [[https://docs.python.org/3/library/logging.html|ici pour en savoir plus]].

L'avantage d'utiliser ce module est de pouvoir centraliser ou non les flux de journalisation sur plusieurs applications et à différents niveaux de journalisation.

Un flux de journalisation est une appplication dont le principe est de rapporter des évènements (dans un contexte de ligne de temps, un évènement se produit à un instant t) selon une mécanique personnalisée. Il peut y avoir plusieurs flux par projet / script / application. Au niveau d'un seul flux, il existe plusieurs niveaux de journalisation : les noms communs ERROR, WARNING, DEBUG, INFO peuvent vous évoquer quelque chose; il s'agit de ça. Ensuite on découpe un flux en plusieurs couches d'importances. Ces couches peuvent se recouvrir (généralement, DEBUG inclue toute les couches). Cela nous permet d'avoir un niveau de granularité plus fin pour chaque séries d'évènement à couvrir (typiquement, en cours de développement, on étudie beaucoup l'intégralité des journaux avec le niveau DEBUG et on peut s'attarder sur le niveau ERROR pour un cas spécifique).

Le journal permettra également aux utilisateurs de l'application d'avoir un niveau d'information pertinent et lisible sans interférer avec la journalisation "technique".

Démarrage


import logging

logging.getLogger().setLevel( logging.DEBUG )

logging.info("info")
logging.warning("warning")
logging.debug("debug")
logging.error("error")

On peut rapidement utiliser l'interface générale (ie, le flux root) pour produire un journal :


>>> logging.warning("Message d'alerte")
WARNING:root:Message d'alerte

>>> logging.error("Message d'erreur")
ERROR:root:Message d'erreur

>>> logging.critical("Message d'alerte critique")
CRITICAL:root:Message d'alerte critique

Par défaut, chaque enregistrement est composé du nom de la couche en majuscule, puis du nom du flux et enfin du message (séparés par :).

Un autre exemple pour établir le niveau du journal root :

>>> import logging
>>> logging.getLogger()                          # Journal par défaut : root
                      # Affiche le nom du journal et son niveau courant

>>> logging.getLogger().setLevel(logging.DEBUG)  # Définir le niveau du journal
>>> logging.debug("Debug message")               # Envoyer un DEBUG au journal
DEBUG:root:Debug message                         # Affichage du message enregistré

>>> logging.getLogger().setLevel(logging.INFO)   # Passer au niveau INFO
>>> logging.debug("Debug message")               # Envoyer un message DEBUG
>>>                                              # Le message enregistré n'est pas affiché

Typiquement, les messages qui ne sont pas affichés au niveau courant sont enregistrés et peuvent être redirigés vers un autre flux ou un fichier.

Créer un journal

Gestion des couches

Flask

FastAPI

En vrac


Références

FR

EN