<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 <center>
<a href="http
Step1:  L'appel à la fonction range(n) permet d'itérer sur les entiers de 0 à n-1 mais il est possible de spécifier des intervalles des valeurs de chaque pas d'itération
Step2:  Dictionnaires
Un dictionnaire peut être parcouru terme à terme, cependant, comme la pluspart des objets python, les dictionnaires possèdent des fonctions permettant de les transformer en une liste
Step3:  Les fonctions .items() et .iteritems() permettent de parcourir les couples (clés, objets) des dictionnaires.
Step4:  Numpy Array
La fonction nditer permet de parcourir tous les éléments d'un tableau (array) Numpy.
Step5:  Appliquer directement la boucle for sur le tableau Numpy permet de parcourir les différentes lignes de ce tableau.
Step6:  Pour parcourir les colonnes, la métode la plus simple consiste à parcourir les lignes de la matrice transposée.
Step7:  Il est cependant très important de comprendre que les arrays numpy ont été conçus pour appliquer des fonctions directement sur l'ensemble du tableau en évitant des boucles. Ainsi nombre de fonctions natives permettent de résoudre de nombreux problèmes sans avoir à parcourir l'ensemble de la matrice
Step8:  Pour appliquer des fonctions plus complexes sur chaque ligne et/ou colonne de l'array, il existe également des fonctions natives de la librairie numpy permettant d'appliquer ces fonctions efficacement comme par exemple apply_along_axis, similaire à apply de R.
Step9:  Pandas DataFrame
De la même manière que pour les arrays numpy, les DataFrame Pandas ne sont pas conçus pour être parcourus facilement. Il est, là encore, préférable d'utiliser au maximum les méthodes natives de pandas ou de numpy, ou de convertir au préalable les colonnes souhaitées en listes ou encore même de convertir un DataFrame en array avant un traitemnte itératif.
Il éxiste deux fonctions permettant de parcourir les différentes colonnes d'une DataFrame Pandas
Step10:  Librarie Itertools
La librairie native de python itertools possède de nombreuses fonctions générant des iterators. 
Plus d'exemples dans la documentation.
Step11:  One-Line Statement
Après avoir parouru différents éléments d'un objet itérable et pour enregistrer un résultat pour chaque étape dans une liste, il est possible d'écrire la boucle for sur une seule ligne de la manière suivante
Step12:  Affectation des indices
Des expressions telles que x = x + 1 ou x = x - 2 apparaissent très souvent dans le corps des boucles. Le language Python permet de simplifier ces notations.
Step13:  1.2 Structure itérative while
La boucle for permet de parcourir l'ensemble des éléments d'un objet itérable, ou un nombre déterminé d'éléments de ce dernier. Cependant, ce nombre n'est pas toujours prévisible et il est possible de parcourir ces éléments et arrêter le parcours lorsqu'une condition est respéctée ou non. C'est l'objet de l'instruction while avec la syntaxe
Step14:  L'instruction break permet de sortir de la boucle while même si sa condition est respectée.
Step15:  1.3 Structures conditionnelles  if - else
L'instruction conditionelle if-else est une des plus communes en programmation informatique. En python, elle se présente sous la forme suivante
Step16:  Lorsque plus de deux alternative sont possibles, utiliser l'instruction elif pour énumérer les différentes possibilités.
Step17:  One-Line Statement
Comme pour la boucle for, il est possible d'écrire l'instruction if_else en une seule ligne lorsque le code est simple.
Step18:  One line with for Loop
Différentes combinaisons sont possibles pour associer à la fois l'instruction if_else avec une boucle for en une ligne.
Step19:  2 Programmation fonctionnelle
L'utilisation de higher-order functions permet d'éxecuter rapidement des schémas classiques
Step20:  2.1 filter
La fonction filter permet d'appliquer une fonction test à chaque valeur d'une liste. Si la fonction test est vérifiée, la valeur est ajoutée dans une nouvelle liste. Sinon, la valeur n'est pas prise en compte. La nouvelle liste, constitué de toutes les valeures "positive" selon la fonction test, est retournée.
Step21:  2.2 reduce
La dernière fonction est la fonction reduce. Cette approche est loin d'être intuitive. Le meilleur moyen de comprendre le mode d'emploi de cette fonction est d'utiliser un exemple.
L'objectif est de calculer la somme de tous les entier de 0 à 9.
Générer dans un premier temps la liste contenant tout ces éléments r10 = [0,1,2,3,4,5,6,7,8,9]
La fonction reduce, applique une première fois la fonction sum_and_print sur les deux premiers éléments de la liste. 
Exécution récursive
Step22:  Par défaut, la fonction passée en paramètre de la fonction reduce effectue sa première opérations sur les deux premiers éléments de la listes passés en paramètre. Mais il est possible de spécifier une valeur initiale en troisième paramètre. La première opération sera alors effectuée sur cette valeur initiale et le premier élément de la liste.
Step23:  2.4 lambda
L'utilisation de fonctions génériques permet de simplifier le code mais il est coûteux de définir une nouvelle fonction qui peut ne pas être réutilisée, comme par exemple celle de l'exemple précédent.
L'appel lambda permet de créer une fonction de façon temporaire. La définition de ces fonctions est assez restrictive, puisqu'elle implique une définition sur une seule ligne, et ne permet pas d'assignation. 
Autre point important, l'exécution de cette fonction sur des données distribuées est implicitement parallélisée.
Ainsi les précédents exemples peuvent-être ré-écrits de la manière suivante
Step32:  3 Classes et objets
3.1 Définitions et exemples
Les classes sont des objets communs à tous les langages orientés objets. Ce sont des objets constitués de
attributs
Step33:  Toutes les classes sont composées d'un constructeur qui a pour nom __init__. Il s'agit d'une méthode spéciale d'instance que Python reconnaît et sait utiliser dans certains contextes. La fonction __init__ est automatiquement appelée à la création d'une nouvelle classe et prend en paramètre self, qui représente l'objet instantié, et les différents attributs nécessaires à sa création.
Step34:  Les attributs de la classe sont directement accessibles de la manière suivante
Step35:  Les méthodes permettant de modifier les attributs d'un objet sont appelées des mutators. La fonction addNote, qui permet d'ajouter une note à la liste de notes de l'élève est un mutator.
Step38:  3.2 Héritage
L'héritage est une fonctionnalité qui permet de définir une classe "fille" à  partir d'une autre classe "mère". La classe fille hérite alors  automatiquement de tous les attributs et de toutes les méthodes de la classe mère.
Step39:  3.3 Classes de  Scikit-learn
Les méthodes d'apprentissage statistique de la librairie Scikit-learn sont un parfait exemple d'utilisation de classes. Sans entrer en détail dans l'implémentation de cette méthode, voici l'exemple de la Régression linéaire par moindres carrés. 
Toutes les fonctions de Scikit-learn sont définies sur de modèle. Voir dans le tutoriel d'apprentissage statistique avec Scikit-learn comment ces propriétés sont utilisées pour enchaîner (pipeline) des exécutions.
L'objet LinearRegression est une classe qui est initée avec des attributs suivants
Step40:  La classe LinearRegression possède également des attributs qui sont mis à jour à l'aide des méthodes
Step41:  La classe LinearRegression possède aussi d'autres méthodes qui utilisent les attributs de la classe. Par exemple
Step42:  4 Packing et Unpacking
Section plus technique qui peut être sautée en première lecture.
L'opérateur * permet, selon la situation, de "paquéter" ou "dépaquéter" les éléments d'une liste.
L'opérateur ** permet, selon la situation, de "paquéter" ou "dépaquéter" les éléments d'un dictionnaire.
4.1 Unpacking
Dans l'exemple ci-dessous. Les opérateurs * et **  dépaquettent les listes et dictionnaires pour les passer en arguments de fonctions.
Step43:  4.2 Packing
Ces opérateurs sont surtout utiles dans le sens du "packing". Les fonctions sont alors définies de sorte à recevoir un nombre inconnu d'argument qui seront ensuite "paquétés" et traités dans la fonction.
L'argument *args permet à la fonction de recevoir un nombre supplémentaire inconnu d'arguments sans mot-clef associé.
Step44:  L'argument **kwargs permet à la fonction de recevoir un nombre supplémentaire inconnu d'arguments avec mot-clef.
Step45:  Les arguments *args et **kwargs peuvent être combinés dans une autre fonctions.
Step46:  Ces deux opérateurs sont très utiles pour gérer des classes liées par des héritages. Les arguments *args **kwargs permettent alors de gérer la tranmission de cet héritage sans avoir à redéfinir les arguments à chaque étape.
<ASSISTANT_TASK:>
Python Code:
for i in range(5):
    print (i)
Explanation: <center>
<a href="http://www.insa-toulouse.fr/" ><img src="http://www.math.univ-toulouse.fr/~besse/Wikistat/Images/logo-insa.jpg" style="float:left; max-width: 120px; display: inline" alt="INSA"/></a> 
<a href="http://wikistat.fr/" ><img src="http://www.math.univ-toulouse.fr/~besse/Wikistat/Images/wikistat.jpg" style="float:right; max-width: 250px; display: inline"  alt="Wikistat"/></a>
</center>
<a href="https://www.python.org/"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Python_logo_and_wordmark.svg/390px-Python_logo_and_wordmark.svg.png" style="max-width: 200px; display: inline" alt="Python"/></a> pour Statistique et Science des Données
Eléments de programmation en  <a href="https://www.python.org/"><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/f/f8/Python_logo_and_wordmark.svg/390px-Python_logo_and_wordmark.svg.png" style="max-width: 150px; display: inline" alt="Python"/></a> pour Calcul Scientifique - Statistique
Résumé: Compléments de programmation en python: structures de contrôle, programmation fonctionnelle (map, reduce, lambda), introduction aux classes et objets. 
Introduction
L'objectif de ce tutoriel est d'introduire quelques outils et concepts plus avancés de la programmation en Python pour dans le but d'améliorer la performance et la lisibilité des codes. Les notions de classe, de programmation objet et celle de programmation fonctionnelle qui en découle sont fondamentales. Elles sont fondamentales pour le bon usage de certaines librairie dont Scikit-learn (section 3.3). C'est aussi une introduction à l'utilisation des fonctionnalités MapReduce parallélisables et donc à la base de l'algorithmique pour données distribuées (Hadoop) avec PySpark.
1 Structures de contrôle
1.1 Structure itérative for
Une boucle for permet, comme dans la pluspart des langages de programmation, de parcourir les éléments d'un objet itérable. En python cela peut-être une liste, un tuple, une chaîne de caractères (string), mais également des objets spécialement conçus pour cela, appelés iterator.
Syntaxe de la structure for:
for variable in range iterator: 
<br/> &nbsp;&nbsp;&nbsp;&nbsp;    instruction
La ligne for se termine par deux points ':'. Le bloc de codes à l'intérieur de la boucle est indenté. 
Voici comment parcourir différentes structures itératives en se souvenant que: de façon générale, il faut éviter les boucles for dans un langage interprété en utilisant les autres fonctionnalités (section 2) prévues dans les librairies pour parcourir des tableaux ou matrices.
range
La fonction range produit une liste d'entiers mais aussi un objet itérable d'entiers utilisés au fur et à mesure des itérations de la boucle.
Attention xrange de python2 est remplacé par range en python3.
End of explanation
for character in "Hi There!":
    print (character)
Explanation: L'appel à la fonction range(n) permet d'itérer sur les entiers de 0 à n-1 mais il est possible de spécifier des intervalles des valeurs de chaque pas d'itération: range(2,11,2) range(10,0,-1)
Strings
Les chaînes de caractères sont également des objets parcourables avec la boucle for: caractère après caractère.
End of explanation
dico={"a":1,"b":2,"c":3}
for k in dico.keys():
    print (k)
Explanation: Dictionnaires
Un dictionnaire peut être parcouru terme à terme, cependant, comme la pluspart des objets python, les dictionnaires possèdent des fonctions permettant de les transformer en une liste:
.items()
.keys()
.values()
ou un iterator
.iteritems()
.iterkeys()
.itervalues()
Les dictionnaires n'ont pas de structure ordonnée, les valeurs ne s'affichent pas forcément dans l'ordre dans lequel on les a entrées.
End of explanation
# Si une seule variable itérative est spécifiée, celle-ci est un-tuple.
for kv in dico.items():
    print (kv)
# Si deux variables itératives sont spécifiées, la première est la clé, la seconde la valeur correspondante
for k,v in dico.items():
    print (k)
    print (v)
# Si la clé ou la valeur du dictionnaire n'est pas nécessaire, elle n'est pas stockée dans une variable en utilisant "_"
for k,_ in dico.items():
    print (k)
Explanation: Les fonctions .items() et .iteritems() permettent de parcourir les couples (clés, objets) des dictionnaires.
End of explanation
import numpy as np
a = np.arange(6).reshape(2,3)
for x in np.nditer(a):
    print (x)
Explanation: Numpy Array
La fonction nditer permet de parcourir tous les éléments d'un tableau (array) Numpy.
End of explanation
for x in a:
    print (x)
Explanation: Appliquer directement la boucle for sur le tableau Numpy permet de parcourir les différentes lignes de ce tableau.
End of explanation
for x in a.T:
    print (x)
Explanation: Pour parcourir les colonnes, la métode la plus simple consiste à parcourir les lignes de la matrice transposée.
End of explanation
mina = a.max()              # Retourne la valeur maximum de l'array a
minxa = a.max(axis=1)       # Retourne la valeur maximum de chaque ligne de l'array a
minya = a.max(axis=0)       # Retourne la valeur maximum de chaque colonne de l'array a
print(mina, minxa, minya)
Explanation: Il est cependant très important de comprendre que les arrays numpy ont été conçus pour appliquer des fonctions directement sur l'ensemble du tableau en évitant des boucles. Ainsi nombre de fonctions natives permettent de résoudre de nombreux problèmes sans avoir à parcourir l'ensemble de la matrice: sum, max, etc... permettent d'obtenir les résultats escomptés de manière bien plus rapide qu'en utilisant les boucles for.
End of explanation
def scale(x):
    xcr = (x-np.mean(x))/np.std(x)
    return xcr
X = np.random.randint(5, size=(3, 3))
Xcr = np.apply_along_axis(scale,1,X)
print(X, Xcr)
Explanation: Pour appliquer des fonctions plus complexes sur chaque ligne et/ou colonne de l'array, il existe également des fonctions natives de la librairie numpy permettant d'appliquer ces fonctions efficacement comme par exemple apply_along_axis, similaire à apply de R.
End of explanation
import pandas as pd
df = pd.DataFrame([["A",4],["B",5],["C",6]],index=[1,2,3],columns=["Letter","Number"])
for i,r in df.iterrows():
    print(i,r)
for ir in df.itertuples():
    print(ir)
Explanation: Pandas DataFrame
De la même manière que pour les arrays numpy, les DataFrame Pandas ne sont pas conçus pour être parcourus facilement. Il est, là encore, préférable d'utiliser au maximum les méthodes natives de pandas ou de numpy, ou de convertir au préalable les colonnes souhaitées en listes ou encore même de convertir un DataFrame en array avant un traitemnte itératif.
Il éxiste deux fonctions permettant de parcourir les différentes colonnes d'une DataFrame Pandas:
iterrows(): retourne le couple (index, ligne). Chaque ligne est parcourue sous la forme d'un objet Series de Pandas. Se construction est très coûteuse et rend la méthode très lente .
itertuples(): retoure un object specifique Pandas, où l'index, et les valeurs des colonnes de chaque ligne est accesible via des méthodes spécifiques. Plus rapide que iterrows.
End of explanation
import itertools
#zip : Concatenne les éléments de plusieurs objets itérables
zip_list = []
for k in zip('ABCD',[1,2,3,4],"wxyz"):
    zip_list.append(k)
print("zip")
print(zip_list)
#permutation : retourne tous les arrangements possibles de liste de longueur n.
permutation_list = []
for k in itertools.permutations("ABCD",2):
    permutation_list.append(k)
print("permutations")
print(permutation_list)
Explanation: Librarie Itertools
La librairie native de python itertools possède de nombreuses fonctions générant des iterators. 
Plus d'exemples dans la documentation.
End of explanation
# Version1
A1=[]
for k in range(10):
    A1.append(k*k)
    
# Version 2
A2 = [k*k for k in range(10)]
print(A1,A2)
Explanation: One-Line Statement
Après avoir parouru différents éléments d'un objet itérable et pour enregistrer un résultat pour chaque étape dans une liste, il est possible d'écrire la boucle for sur une seule ligne de la manière suivante:
[result for variable in iterator]
End of explanation
a = 17
s = "hi"
a += 3           # Equivalent to a = a + 3
a -= 3           # Equivalent to a = a - 3
a *= 3           # Equivalent to a = a * 3
a /= 3           # Equivalent to a = a / 3
a %= 3           # Equivalent to a = a % 3
s += " there"    # Equivalent to s = s + “ there"
Explanation: Affectation des indices
Des expressions telles que x = x + 1 ou x = x - 2 apparaissent très souvent dans le corps des boucles. Le language Python permet de simplifier ces notations.
End of explanation
# Incrémentation de `count` jusqu'à ce qu'elle dépasse la valeur 100.000
count = 1 
while count <= 100000: 
    count += 1 
print (count)
Explanation: 1.2 Structure itérative while
La boucle for permet de parcourir l'ensemble des éléments d'un objet itérable, ou un nombre déterminé d'éléments de ce dernier. Cependant, ce nombre n'est pas toujours prévisible et il est possible de parcourir ces éléments et arrêter le parcours lorsqu'une condition est respéctée ou non. C'est l'objet de l'instruction while avec la syntaxe:
while condition:
<br/> &nbsp;&nbsp;&nbsp;&nbsp;    instructions
Cette instruction permet de répéter en boucle les instructions jusqu'à ce que la condition soit vérifiée.
End of explanation
while True: 
    number = int(input("Enter the numeric grade: ")) 
    if number >= 0 and number <= 100:
        break
    else:
        print ("Error: grade must be between 100 and 0" )
    print (number)
Explanation: L'instruction break permet de sortir de la boucle while même si sa condition est respectée.
End of explanation
number=1
if number==1:
    print (True)
else:
    print (False)
Explanation: 1.3 Structures conditionnelles  if - else
L'instruction conditionelle if-else est une des plus communes en programmation informatique. En python, elle se présente sous la forme suivante:
if condition:
<br/> &nbsp;&nbsp;&nbsp;&nbsp;   instructions 1
<br/> else:
<br/> &nbsp;&nbsp;&nbsp;&nbsp;   Instructions 2
End of explanation
number=13
if number<5:
    print("A")
elif number <10:
    print("B")
elif number <20:
    print("C")
else:
    print("D")
Explanation: Lorsque plus de deux alternative sont possibles, utiliser l'instruction elif pour énumérer les différentes possibilités.
End of explanation
number=10
"A" if number >10 else "B"
Explanation: One-Line Statement
Comme pour la boucle for, il est possible d'écrire l'instruction if_else en une seule ligne lorsque le code est simple.
End of explanation
#Sélectionne uniquement les valeur paire
l1 = [k for k in range(10) if k%2==0]
l1
#Retourne "even" si l'élement k est pair, "odd" sinon.
l2 = ["even" if k%2==0 else "odd" for k in range(10)]
l2
Explanation: One line with for Loop
Différentes combinaisons sont possibles pour associer à la fois l'instruction if_else avec une boucle for en une ligne.
End of explanation
import random
numbers = [random.randrange(-10,10) for k in range(10)]
abs_numbers = map(abs,numbers) # Applique la fonction "valeur absolue" à tout les élements de la liste
print(numbers,list(abs_numbers))
def first_capital_letters(txt):
    if txt[0].islower():
        txt = txt[0].upper()+txt[1:]
    return txt
name=["Jason","bryan","hercule","Karim"]
list(map(first_capital_letters,name))
Explanation: 2 Programmation fonctionnelle
L'utilisation de higher-order functions permet d'éxecuter rapidement des schémas classiques:
appliquer la même fonction aux éléments d'une liste,
séléctionner, ou non, les différents éléments d'une liste selon une certaine condition,
...
Important: il s'agit ici d'introduitre les éléments de programmation fonctionnelle, présents dans Python, et utilisés systématiquement, car parallélisable, dans des architectures distribuées (e. g. Hadoop, Spark).
2.1 map
La première de cette fonction est la fonction map. Elle permet d'appliquer une fonction sur toutes les valeurs d'une liste et retourne une nouvelle liste avec les résultats correspondants.
End of explanation
def is_odd(n):
    return n % 2 == 1
list(filter(is_odd,range(20)))
Explanation: 2.1 filter
La fonction filter permet d'appliquer une fonction test à chaque valeur d'une liste. Si la fonction test est vérifiée, la valeur est ajoutée dans une nouvelle liste. Sinon, la valeur n'est pas prise en compte. La nouvelle liste, constitué de toutes les valeures "positive" selon la fonction test, est retournée.
End of explanation
import functools
def sum_and_print(x,y):
    print("Input: ", x,y)
    print("Output: ", y)
    return x+y
r10 = range(10)
res =functools.reduce(sum_and_print, r10)
print(res)
Explanation: 2.2 reduce
La dernière fonction est la fonction reduce. Cette approche est loin d'être intuitive. Le meilleur moyen de comprendre le mode d'emploi de cette fonction est d'utiliser un exemple.
L'objectif est de calculer la somme de tous les entier de 0 à 9.
Générer dans un premier temps la liste contenant tout ces éléments r10 = [0,1,2,3,4,5,6,7,8,9]
La fonction reduce, applique une première fois la fonction sum_and_print sur les deux premiers éléments de la liste. 
Exécution récursive: la fonction sum_and_print est appliquée sur le résultat de la première opération et sur le 3ème éléments de la liste
Itération récursive jusqu'à ce que tous les éléments de la liste soient parcourus
La fonction reduce a donc deux arguments: une fonction et une liste.
End of explanation
def somme(x,y):
    return x+y
r10 = range(10)
res =functools.reduce(somme, r10,1000)
print(res)
Explanation: Par défaut, la fonction passée en paramètre de la fonction reduce effectue sa première opérations sur les deux premiers éléments de la listes passés en paramètre. Mais il est possible de spécifier une valeur initiale en troisième paramètre. La première opération sera alors effectuée sur cette valeur initiale et le premier élément de la liste.
End of explanation
name=["Jason","bryan","hercule","Karim"]
list(map(lambda x : x[0].upper()+x[1:] if x[0].islower() else x,name))
list(filter(lambda x : x % 2 == 1 ,range(10)))
r10 = range(10)
res =functools.reduce(lambda x,y:x+y, r10,1000)
res
Explanation: 2.4 lambda
L'utilisation de fonctions génériques permet de simplifier le code mais il est coûteux de définir une nouvelle fonction qui peut ne pas être réutilisée, comme par exemple celle de l'exemple précédent.
L'appel lambda permet de créer une fonction de façon temporaire. La définition de ces fonctions est assez restrictive, puisqu'elle implique une définition sur une seule ligne, et ne permet pas d'assignation. 
Autre point important, l'exécution de cette fonction sur des données distribuées est implicitement parallélisée.
Ainsi les précédents exemples peuvent-être ré-écrits de la manière suivante:
End of explanation
class Eleve:
    Classe définissant un élève caractérisé par:
        - son nom
        - son prénom
        - ses notes
       
    
    def __init__(self, nom, prenom): #constructeur de la classe
         Construit un élève avec les nom et prenom passé en paramètre et une liste de notes vide.
        self._nom = nom
        self._prenom=prenom
        self._notes = []
        
    def getNom(self):
         retourne le nom de l'élève 
        return self._nom
    
    def getNotes(self):
         retourne les notes de l'élève
        return self._notes
    
    def getNoteMax(self):
         retourne la note max de l'élève
        return max(self._notes)
    
    def getMean(self):
         retourne la moyenne de l'élève
        return np.mean(self._notes)
    
    def getNbNote(self):
         retourne le nombre de note de l'élève
        return len(self._notes)
    
    def addNote(self, note):
         ajoute la note 'note' à la liste de note de l'élève
        self._notes.append(note)
Explanation: 3 Classes et objets
3.1 Définitions et exemples
Les classes sont des objets communs à tous les langages orientés objets. Ce sont des objets constitués de
attributs: des paramètres fixes, de différentes natures, attribués à l'objet, 
méthodes: des fonctions qui permettent d'appliquer des transformations sur ces attributs. 
Ci dessous on définit une classe "Elève" dans laquelle un élève est décrit par son nom, son prénom et ses notes. On notera la convention de nommage des méthodes qui commence par une minuscule, mais qui possède une majuscule à chaque début de nouveau mot.
End of explanation
eleve1 = Eleve("Jean","Bon")
eleve1._nom
Explanation: Toutes les classes sont composées d'un constructeur qui a pour nom __init__. Il s'agit d'une méthode spéciale d'instance que Python reconnaît et sait utiliser dans certains contextes. La fonction __init__ est automatiquement appelée à la création d'une nouvelle classe et prend en paramètre self, qui représente l'objet instantié, et les différents attributs nécessaires à sa création.
End of explanation
eleve1.getNom()
Explanation: Les attributs de la classe sont directement accessibles de la manière suivante:
objet.nomDeLAtribbut
Cependant, par convention, il est conseillé de définir une méthode pour avoir accès à cet objet.
Les méthodes qui permettent d'accéder à des attributs de l'objets sont appelés des accessors. Dans la classe élève, les méthodes commençant par get sont des accessors.
End of explanation
print(eleve1.getNotes())
eleve1.addNote(15)
print(eleve1.getNotes())
for k in range(10):
    eleve1.addNote(np.random.randint(20))
print (eleve1.getNbNote())
print (eleve1.getNoteMax())
print (eleve1.getMean())
Explanation: Les méthodes permettant de modifier les attributs d'un objet sont appelées des mutators. La fonction addNote, qui permet d'ajouter une note à la liste de notes de l'élève est un mutator.
End of explanation
class EleveSpecial(Eleve):
    
    def __init__(self, nom, prenom, optionName):
        Eleve.__init__(self, nom, prenom)
        self._optionName = optionName
        self._optionNotes = []
    def getNotesOption(self):
         retourne les notes de l'élève
        return self._optionNotes
    
    def addNoteOption(self, note):
         ajoute la note 'note' à la liste de note de l'élève
        self._optionNotes.append(note)
eleve2 = EleveSpecial("Sam","Stress","latin")
eleve2.addNote(14)
print (eleve2.getNotes())
eleve2.addNoteOption(12)
print (eleve2.getNotesOption())
Explanation: 3.2 Héritage
L'héritage est une fonctionnalité qui permet de définir une classe "fille" à  partir d'une autre classe "mère". La classe fille hérite alors  automatiquement de tous les attributs et de toutes les méthodes de la classe mère.
End of explanation
from sklearn.linear_model import LinearRegression
lr = LinearRegression(fit_intercept=True, normalize=False, copy_X=True, n_jobs=1)
print (lr.fit_intercept, lr.normalize, lr.copy_X, lr.n_jobs)
Explanation: 3.3 Classes de  Scikit-learn
Les méthodes d'apprentissage statistique de la librairie Scikit-learn sont un parfait exemple d'utilisation de classes. Sans entrer en détail dans l'implémentation de cette méthode, voici l'exemple de la Régression linéaire par moindres carrés. 
Toutes les fonctions de Scikit-learn sont définies sur de modèle. Voir dans le tutoriel d'apprentissage statistique avec Scikit-learn comment ces propriétés sont utilisées pour enchaîner (pipeline) des exécutions.
L'objet LinearRegression est une classe qui est initée avec des attributs suivants:
fit_intercept : si le terme constant doit être estimé ou considéré à 0
normalize : si le jeux d'apprentissage doit être normalisé avant la regression
copy_X : si le jeux d'apprentissage doit être copié pour éviter les effets de bords
n_jobs : le nombre de processeur à utiliser
End of explanation
X_train=[[0, 0], [1, 1], [2, 2]]
Y_train =  [0, 1, 2]
lr.fit (X_train, Y_train)
lr.coef_
Explanation: La classe LinearRegression possède également des attributs qui sont mis à jour à l'aide des méthodes:
coef_ : coefficients estimés 
residues_ : somme des résidus
intercept_ : terme constant
La méthode fit de cette classe est un mutator. Elle prend en paramètre le jeux d'apprentissage, X_train et la variable réponse correspondante Y_train pour estimer les paramètres de la regression linéaire et ainsi mettre à jour les attributs correspondants.
End of explanation
X_test = [[1.5,1.5],[2,4],[7.3,7.1]]
Y_test = [1.5,2.4,7]
pred = lr.predict(X_test)
s = lr.score(X_test,Y_test)
print(pred,s)
Explanation: La classe LinearRegression possède aussi d'autres méthodes qui utilisent les attributs de la classe. Par exemple:
predict: estime la prévision de  la variable réponse d'un jeu test X_test
score: retourne le coefficient R2 de qualité de la prévision.
End of explanation
def unpacking_list_and_print(a, b):
    print (a)
    print (b)
listarg = [3,4]
unpacking_list_and_print(*listarg)
    
def unpacking_dict_and_print(k1=0, k2=0):
    print (k1)
    print (k2)
dictarg = {'k1':4, 'k2':8}
unpacking_dict_and_print(**dictarg)
Explanation: 4 Packing et Unpacking
Section plus technique qui peut être sautée en première lecture.
L'opérateur * permet, selon la situation, de "paquéter" ou "dépaquéter" les éléments d'une liste.
L'opérateur ** permet, selon la situation, de "paquéter" ou "dépaquéter" les éléments d'un dictionnaire.
4.1 Unpacking
Dans l'exemple ci-dessous. Les opérateurs * et **  dépaquettent les listes et dictionnaires pour les passer en arguments de fonctions.
End of explanation
def packing_and_print_args(required_arg, *args):
    print ("arg Nécessaire:", required_arg)
    for i, arg in enumerate(args):
        print ("args %d:" %i, arg)
packing_and_print_args(1, "two", 3)
packing_and_print_args(1, "two", [1,2,3],{"a":1,"b":2,"c":3})
Explanation: 4.2 Packing
Ces opérateurs sont surtout utiles dans le sens du "packing". Les fonctions sont alors définies de sorte à recevoir un nombre inconnu d'argument qui seront ensuite "paquétés" et traités dans la fonction.
L'argument *args permet à la fonction de recevoir un nombre supplémentaire inconnu d'arguments sans mot-clef associé.
End of explanation
def packing_and_print_kwargs(def_kwarg=2, **kwargs):
    print ("kwarg défini:", def_kwarg)
    for i,(k,v) in enumerate(kwargs.items()):
        print ("kwarg %d:" %i ,k , v) 
packing_and_print_kwargs(def_kwarg=1, sup_arg1="two", sup_arg2=3)
packing_and_print_kwargs(sup_arg1="two", sup_arg2=3, sup_arg3=[1,2,3])
Explanation: L'argument **kwargs permet à la fonction de recevoir un nombre supplémentaire inconnu d'arguments avec mot-clef.
End of explanation
def packing_and_print_args_and_kwargs(required_arg ,def_kwarg=2, *args, **kwargs):
    print ("arg Nécessaire:", required_arg)
    for i, arg in enumerate(args):
        print ("args %d:" %i, arg)
    print ("kwarg défini:", def_kwarg)
    for i,(k,v) in enumerate(kwargs.items()):
        print ("kwarg %d:" %i ,k , v )
packing_and_print_args_and_kwargs(1, "two", [1,2,3] ,sup_arg1="two", sup_arg2=3 )
Explanation: Les arguments *args et **kwargs peuvent être combinés dans une autre fonctions.
End of explanation
class Objet(object):
    def __init__(self, attribut=None, *args, **kwargs):
        print (attribut)
class Objet2Point0(Objet):
    def __init__(self, *args, **kwargs):
        super(Objet, self).__init__(*args, **kwargs)
class Objet3Point0(Objet2Point0):
    def __init__(self,attribut2=None, *args, **kwargs):
        super(Objet2Point0, self).__init__(*args, **kwargs)
        print (attribut2)
my_data = {'attribut': 'Argument1', 'attribut2': 'Argument2'}
Objet3Point0(**my_data)
Explanation: Ces deux opérateurs sont très utiles pour gérer des classes liées par des héritages. Les arguments *args **kwargs permettent alors de gérer la tranmission de cet héritage sans avoir à redéfinir les arguments à chaque étape.
End of explanation
<END_TASK>