Python : les fondamentaux
Introduction
Le choix d’un langage est parfois un parcours sinueux et difficile. Une multitude de langages existent, du plus bas niveau au plus haut, chacun ayant ses caractéristiques. Pour n’en citer que quelques-uns, nous trouverons l’assembleur (différents mnémoniques suivant les architectures : x86, arm, 68000), C, C++, C#, Java, Perl, Ruby, Python...
Quand notre métier est la sécurité informatique, nous devons avoir une bonne vue d’ensemble de ces différents langages ou, au minimum, savoir retrouver l’information si nécessaire. Mais d’expérience, il faut au moins connaître l’assembleur x86 (et x64), le langage C et un langage dit de haut niveau.
Pourquoi choisir Python ?
Il me fallait à l’époque, connaissant l’assembleur et le C, un langage facile à appréhender qui donne un résultat rapide. Suite à plusieurs essais dont Java et C++, le langage Python m’ouvrit les portes du développement et m’apporta l’outil idéal pour mes cours en licence CDAISI (Cyberdéfense et anti-intrusion des systèmes d’information de l’UVHC http://www.univ-valenciennes.fr) et mes audits techniques (pentest) au sein de la société Serval-concept (http://www.serval-concept.com).
La programmation Python a pris maintenant ses lettres de noblesses et Python...
Historique
Le langage Python a été inventé par Guido van Rossum, développeur néerlandais. Il est considéré dans la communauté Python comme le « dictateur bienveillant à vie ».
La première version publique date de 1991. Une version 1.5 fut publiée en avril 1999 puis une version 2.0 en octobre 2000.
La dernière version de Python branche 2, la 2.7, sortit en juillet 2000.
La branche 3.x arrive en 2009 avec la 3.0.1 et a pour but d’éliminer les faiblesses du langage tout en assurant une compatibilité descendante avec 2.x.
La version 3.5.0 est sortie le 13 septembre 2015.
Vous pouvez télécharger les dernières versions de Python sur le site officiel : https://www.python.org/downloads/
Caractéristiques du langage
Python est :
-
un logiciel libre : son utilisation, l’étude, la modification et la duplication en vue de sa diffusion sont permises, techniquement et légalement.
-
portable : il est écrit en C ANSI portable.
-
interopérable avec les autres langages.
-
orienté objet et supporte des fonctionnalités avancées.
-
facile à utiliser et parfait pour le développement agile.
-
facile à apprendre : la syntaxe est simple et naturelle.
La bibliothèque standard fournit des modules écrits entièrement en Python. Ces modules sont robustes et facilement utilisables, ils permettent des interactions avec le système d’exploitation et le système de fichiers. Ils fonctionnent sur toutes les plateformes reconnues par Python.
Les modules d’extension, quant à eux, permettent aux applications d’avoir accès à des composants logiciels supplémentaires (réseaux, calcul numérique, traitement de données, traitement d’images…). Ces modules ne sont pas forcément écrits en Python, ils peuvent faire la liaison avec d’autres langages : Cpython (implémentation native), Jython (implémentation de Python en Java), IronPython (implémentation en C# visant .NET et Mono)...
Une documentation est incluse et accessible directement depuis l’interpréteur...
Types de données
Les types de données sont mutables (modifiables) ou immutables (non modifiables).
Les types immutables sont manipulés par recopie de valeurs (nombres, chaînes de caractères et tuples), les types mutables sont manipulés par pointeurs (listes, dictionnaires).
Les types de données Python sont des séquences (chaînes de caractères, tuples et listes) ou des collections (dictionnaires).
Une séquence est une liste ordonnée d’éléments ; une séquence peut être itérée.
1. Les nombres
Les nombres sont de types classiques pour ceux habitués aux langages de programmation. Nous retrouvons les entiers, les longs, les réels, les complexes.
Le typage en Python est dynamique, nul besoin de définir le type de la variable, il se débrouille seul.
Nous pouvons vérifier cela grâce à la fonction type().
Pour tester cela, nous pouvons entrer dans l’interpréteur interactif :
Python nous offre aussi différentes notations comme la notation exponentielle, octale, binaire, hexadécimale.
Le changement de type est dynamique, c’est-à-dire qu’il peut changer au fil de l’exécution du programme (que l’on peut appeler script).
Dans l’exemple ci-dessous, une boucle for est utilisée. Le but ici pour l’instant n’est pas de comprendre cette boucle, mais de voir le changement de type dynamiquement.
Nous pouvons aussi utiliser les opérateurs classiques tels que le décalage à droite (>>) ou à gauche (<<) et les opérateurs booléens (OU, ET et OU exclusif). Les opérateurs mathématiques classiques sont bien sûr aussi de la partie (addition +, soustraction -, division entière //, multiplication *, modulo % et puissance **).
Nous pouvons convertir des types en d’autres quand cela est possible, grâce aux constructeurs int(), float(), complex(), bin(), hex(), ord()...
Il existe quelques pièges à éviter avec les nombres. Il faut être prudent lors de la division d’entiers et de la définition d’un nombre complexe.
2. Les opérations arithmétiques
Nous avons déjà parcouru précédemment les opérations arithmétiques de base....
Structures conditionnelles et répétitives
1. Test if ... elif ... else
L’instruction if ... elif ... else va nous permettre de tester des conditions et, suivant le résultat du test, d’effectuer des actions associées à celles-ci.
if <condition> :
#exécution si la condition est vraie
elif <autre condition> :
#exécution si autre condition vraie
else :
#exécution si les conditions sont fausses.
Le if peut être utilisé seul ou avec le else ou avec le elif et le else.
Nous pouvons avoir autant de elif que nous souhaitons suivant nos conditions à tester.
En Python, il n’existe pas d’instruction swich ... case comme nous pouvons en trouver dans d’autres langages. La structure elif permet de reproduire facilement celle-ci.
Les conditions seront des tests tels que == (égal), != (différent), < (inférieur), > (supérieur), <= (inférieur ou égal) et >= (supérieur ou égal).
__author__ = 'franckebel'
#--*--coding:utf-8--*--
nbr=raw_input("entrez un nombre")
while not nbr.isdigit():
print "Veuillez saisir un nombre"
nbr=raw_input("entrez un nombre")
nbr=int(nbr)
if nbr<10:
print "vous avez entré un nombre inférieur à 10" ...
Fonctions, modules et paquets
1. Définition et appel de fonction
Pour définir une fonction, nous utiliserons l’instruction def. Comme tout en Python, l’indentation sera importante pour définir ce qui appartient à la fonction définie.
def ma_fonction :
print "je suis dans ma fonction"
Le nom de la fonction peut être n’importe quoi excepté les noms réservés par le langage lui-même. Il n’y a pas de normes proprement dites, mais il est conseillé d’utiliser des lettres minuscules, surtout en début du nom.
La fonction bonjour5() est déclarée sans paramètres. Cette fonction contient une boucle while qui affiche bonjour. La boucle se fera 5 fois. Nous n’oublions pas d’utiliser une variable pour vérifier le nombre de boucles qui est bien sûr incrémenté de 1 à chaque passage.
La fonction est appelée en écrivant son nom.
Si nous appelons un nom de fonction qui n’existe pas, nous avons, comme dans l’exemple, une erreur qui s’affiche. Nous verrons plus tard comment gérer ces erreurs.
Nous pouvons appeler cette fonction autant de fois que l’on veut, mais aussi l’appeler dans une autre fonction.
Nous pouvons bien sûr transmettre des paramètres à une fonction, paramètres qui se situeront dans les parenthèses.
Imaginons que nous souhaitions choisir le nombre de fois que nous voulons écrire bonjour. Nous devrons donc indiquer ce nombre à la fonction.
Nous demandons d’abord à l’utilisateur le nombre de fois qu’il souhaite afficher bonjour.
Puis nous appelons la fonction en lui transmettant la variable contenant le nombre.
Dans la fonction, le seul changement est que nous faisons le test du while avec ce nombre.
L’instruction return permet de retourner une valeur.
def carre(x) :
return x**2
Si nous souhaitons retourner une liste pas exemple (il faudra adapter pour un tuple ou un dictionnaire) :
def carre(x) :
return [x**2,x**3]
Lors de la définition de la fonction, nous pouvons donner aux paramètres des valeurs par défaut. Si la valeur est présente lors de l’appel, elle est utilisée, sinon c’est...
Les classes
En Python, comme nous l’avons déjà vu, tout est objet : les chaînes, les entiers, les listes, les fonctions, les classes, les modules, etc.
Tout objet est manipulé par référence : une variable contient une référence vers un objet et un objet peut être référencé par plusieurs variables.
Une fonction, une classe, un module sont des espaces de nommage organisés de manière hiérarchique : un module contient des classes qui contiennent des fonctions (les méthodes).
Une classe fonctionnera de la même manière qu’une fonction, c’est-à-dire qu’elle se finira par : et que le bloc lui appartenant sera indenté.
Par convention, le nom d’une classe commence par une majuscule.
Le mot-clé class est utilisé.
Toute méthode est définie comme une fonction avec un premier argument (self) qui représente l’objet sur lequel elle sera appliquée à l’exécution. Le nom est libre, mais on utilise en général self. Une méthode ne prenant pas de paramètres aura donc quand même un argument.
L’instanciation se fait sans mot-clé particulier. Il suffit de faire suivre un nom de classe de parenthèses (contenant ou non des paramètres) pour déclencher une instanciation. L’invocation se fait à l’aide de la notation pointée, un appel de méthode sur une variable référençant un objet.
Les attributs sont définis à leur première affectation. Une variable est considérée comme un attribut si elle est « rattachée » à l’objet : elle est accédée par self. Tout accès à un attribut ou à une méthode de l’objet depuis sa mise en œuvre se fait obligatoirement par la variable self. Le constructeur est une « méthode » nommée __init__. Comme toute méthode, certains de ses paramètres peuvent avoir des valeurs par défaut. Cette possibilité est importante car une classe a un seul constructeur en Python.
1. Déclaration d’une classe
Dans cet exemple, nous déclarons une classe Informatique :
class Informatique(object):
def...
Manipulation de fichiers
Il nous sera souvent très utile de pouvoir lire le contenu d’un fichier ou d’écrire dans un fichier. Par exemple, lorsque nous souhaitons sauvegarder des informations, si celles-ci ne sont pas trop nombreuses, nous utiliserons un fichier texte.
L’instruction open() permet d’ouvrir un fichier, elle retourne un objet de type File.
Les modes d’ouverture du fichier peuvent être r (read), w (write) et a (append). Nous pouvons ajouter à ces modes b (mode binaire), t (mode texte), + (ouverture en lecture et écriture).
mon_fichier=open("fichier.txt",'a+')
La méthode close() permet de fermer un fichier.
Les fichiers texte sont lisibles directement par l’utilisateur, ils contiennent du texte (lettres, ponctuations, nombres...). Leur contenu est souvent divisé en lignes. En environnement Unix, les fichiers de configuration dans /etc sont des exemples de fichiers texte. Il en est de même pour le code source d’un programme Python.
Les fichiers binaires ne contiennent pas (exclusivement) du texte, ils ne peuvent être convenablement traités que par des logiciels spécialisés. Un fichier PDF, une image TIFF ou un MP3 sont quelques exemples de fichiers binaires.
# ouverture en lecture du fichier fichier1.txt
fichier=open('fichier1.txt','r')
# lecture du fichier
texte=fichier.read() ...
Les exceptions
Nous avons vu au cours de ce livre que, certaines fois, nous nous retrouvions avec une erreur lors du lancement du programme.
>>> nbr = raw_input('veuillez entrer un nombre\n')
veuillez entrer un nombre
AZ
>>> int(nbr)
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: 'AZ'
>>>
Nous souhaiterions pouvoir gérer ces erreurs. Cela va se faire avec les exceptions.
Une exception permet de remonter une anomalie à un programme appelant. Cette anomalie peut être une erreur ou un cas particulier.
Une exception est caractérisée par un nom et un message d’erreur permettant d’identifier l’origine de l’erreur.
Python est fourni avec des classes très complètes d’exceptions standards et nous pouvons également créer nos propres exceptions par un mécanisme simple d’héritage d’objet.
L’inconvénient avec les erreurs non gérées dans les programmes est que notre script s’arrête. Nous devons donc trouver un moyen de gérer ces exceptions pour pouvoir pallier cette erreur et continuer notre programme.
try va nous permettre d’essayer un bout de code. Si le bout de code fonctionne, pas de problème, nous continuons notre...
Modules utiles pour la suite du livre
1. Module sys
Ce module sys va nous permettre une interaction avec l’interpréteur.
Le module sys contient la plupart des informations relatives à l’exécution en cours, mises à jour par l’interpréteur, ainsi qu’une série de fonctions et d’objets de bas niveau.
argv contient la liste des paramètres d’exécution d’un script. Le premier élément de la liste est le nom du script et est suivi de la liste des paramètres.
import sys
print "Ceci est le nom du script: ", sys.argv[0]
print "Voici le nombre d'arguments: ", len(sys.argv)
print "Les arguments sont: " , str(sys.argv)
print "Le premier argument est: " , sys.argv[1]
sys.exit(<code retour>) quitte l’interpréteur en levant une exception SystemError. Elle prend en paramètre un entier qui sera utilisé comme code de retour fourni au système en suivant la norme :
-
0 si le programme a fonctionné correctement.
-
> 0 en cas d’erreur.
sys.getfilesystemencoding() retourne l’encodage utilisé par le système de fichiers.
sys.getdefaultencoding() retourne l’encodage par defaut.
sys.path retourne une liste contenant tous les répertoires dans lesquels l’interpréteur recherche des modules lorsque la directive import est utilisée, ou lorsque des noms de fichiers sont utilisés sans leur chemin complet. path peut être modifiée à la volée dans un programme.
sys.platform informe sur le système d’exploitation courant (linux2, freebsd, win32...).
sys.stdin, sys.stdout et sys.stderr sont des objets fichiers pointant respectivement sur l’entrée standard, la sortie standard et la sortie standard pour les erreurs.
La fonction exec_info() retourne un tuple de trois valeurs qui nous donne des informations sur l’exception qui est en cours.
sys.version (chaîne) et sys.version_info (tuple) donnent le numéro de version de l’interpréteur.
Il existe bien d’autres fonctions et méthodes que nous pourrons trouver dans la documentation.
>>> import sys
>>> sys.platform
'darwin'
>>> sys.stdout.write("écriture avec stdout pour ENI")
écriture...
Conclusion
Nous venons de parcourir l’essentiel du langage Python. Il existe d’autres livres beaucoup plus détaillés sur l’apprentissage du langage.
Nous verrons, suivant les besoins des autres chapitres, d’autres modules qui seront présentés au préalable.
La suite du livre va se concentrer sur les applications en hacking et forensic, les notions vues dans ce chapitre seront considérées comme acquises. Bonne entrée dans le monde mystérieux du hacking et forensic...