Python startup!

Cette présentation est soumise à licence

Les exemples de code sont aussi soumis à licence

En gros, vous faites ce que vous voulez mais citez au moins l'auteur de l'original... Merci!

  • Le langage

    • Présentation
    • Quelques caractéristiques (forces et 'faiblesses')
  • La pratique

    • Avant toute chose...
    • Hello World!
    • Types de données: standards, tuples, dicos, listes
    • Workflow d'exécution: if, else, for, while...
    • Fonctions, paramètres par défaut
    • Modules & Packages
    • POO: Classes, Objets, exceptions
    • Un excercice complet
    • Refactoring
    • Les tests
    • La doc

Le programme

Disclaimer

Attention, je vais pas mal bâcher le language C (et C++). J'en ai fait pendant des années et j'en produis encore donc... #SansRancune

#Python4Ever

vs

Public

Développeurs "débutants" en Python, ayant - malheureusement - éventuellement quelques connaissances en C

#C/C++Bashing
#JeVousAvaisPrévenus

Python est multi-palteforme mais pour le dev, préférez un Linux ou un Mac car Windows... c'est le mal!

Python: présentation

  • créé par Guido Van Rossum en 1989, maintenant employé par Google
  • utilisé par les plus grands (NASA, Google, etc... tout le monde en fait!)
  • Interprété: no build, fast feedback
  • cross platform: développer une fois, déployer partout (Mac, Linux, Windows)
  • Multi-purpose: quasiment le seul qui fait du web, du scientifique, du script, ...
  • "batteries included": ~ tout en standard, doc sublime
  • pléthore de packages: cf. Python Package Index [pypi]

Python: ses forces

  • multi-plateforme (Unix, MacOS, Win)
  • batteries included! ... tout un écosystème
  • Interprété, procédural et objet, garbage collection
  • langage naturel: on arrête les caractères superflus et la syntaxe inbouffable qui masquent le sens réel du code
  • basé sur l'indentation, très bien reconnue par le cerveau humain
void quiCompliqueTout(bool faitCi, bool faitCa) {

    if (faitCi) {
        if (faitCa) {
            printf("euh....");
    }
    else {
        print("ah...");
    }
}
}

Qu'est-il affiché lorsqu'on appelle:

quiCompliqueTout(0, 1); // faitCi = false et faitCa = true

Une des forces: lisibilité

def quiCompliqueTout(bool faitCi, bool faitca):

    if faitCi:
        if faitca:
            print("euh....")
        else:
            print("ah...")

Plus lisible non ? on aurait répondu sans se casser la tête à la même question:

quiCompliqueTout(0, 1)  # faitCi = false et faitCa = true

Attention donc à bien configurer votre éditeur. Conseil: spaces only!

Python: ses faiblesses

  • Interprété donc moins rapide que compilé mais argument de plus en plus caduque avec pypy (à ne pas confondre avec pipy)
  • Les erreurs remontent à l'exécution d'où test unitaire important... mais c'est pas si mal non ;)

Si perf très critique, possibilité de mixer avec du code compilé

La pratique

...assez discuté, les mains dans le cambouis, y'a que ça de vrai !

Avant toute chose...

par pitié... utilisez tout de suite un gestionnaire de source...

 TOUT DE SUITE ! :)

Allez hop ! subversion ou (carrément) mieux: git init

J'ai dit:

#OuiJeVaisBacherSVNAussi

Coding style

En C, il y a autant de styles de codage (décrits dans des documents interminables) que de développeurs/équipes. En python, on est libre aussi mais il existe un standard: PEP8 à suivre car une écrasante majorité de la communauté l'utilise

 

Ah ... au fait... TOUT ou RIEN en anglais, pas un "franglais" pourri

Un bon éditeur

Il y en a plein... dont de bien lourds (Eclipse/PyDev) mais je conseille absolument Sublime Text

avec cette config

En plus, vous savez quoi ? Sublime est écrit en.... Python ;)

RTFM !

Cette présentation n'est qu'une petite introduction à Python.

Il existe de nombreux tutos et MOOC mais la doc officielle est une petite tuerie à conserver dans un onglet de votre navigateur

Hello world!

#!/usr/bin/python

print("Hello world!")
Python 2.7.6 (default, Jun 22 2015, 17:58:13) 
[GCC 4.8.2] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> print("Hello world!")
Hello world!
>>> 

dans un interpréteur:

ou en exécutant le fichier:

[code]
python hello_world.py

Types de données

Pas de déclaration de type comme en C, l'interpréteur se débrouille... très bien ! 

#DeroutantAuDebut
#PasDePanique
const char* ma_chaine = "une jolie chaine !";
ma_chaine = "une jolie chaine !"

Chaînes

first = "Pierre" # double quote

last = 'Roth' # single quote

cv = """
     Mon CV 
     sur 
     plusieurs lignes
     """

contact = '''
          pierreroth4@gmail.com
          @pierreroth64
          '''

Ecrire un script qui affiche:

Pierre Roth

Parcours
--
Mon CV 
sur 
plusieurs lignes


Contact
--
pierreroth4@gmail.com
@pierreroth64
[code]

Nombres

user_nb = 5

same_user_nb = int("5")

pi = 3.14159265359
bon... on va pas épiloguer là-dessus, hein ? ;-)

None

Lorsqu'une valeur n'est pas définie, sa valeur est None.
En C/C++, on utilise NULL (valeur 0) pour ça... mais justement parce que 0 == NULL, c'est pas toujours évident.

Listes

Les listes sont des conteneurs d'éléments qui peuvent être hétérogènes
>>> user_list = ["Pierre"]
>>> user_list.append("Cedric")
>>> print user_list
['Pierre', 'Cedric']

>>> others = ["Manu", 12, 3.14]
>>> user_list += others
>>> print user_list
['Pierre', 'Cedric', 'Manu', 12, 3.14]

>>> print user_list[1]
Cedric

>>> user_list.pop()
3.14
>>> print user_list
['Pierre', 'Cedric', 'Manu', 12]
[code]

Dictionnaires

De mon point de vue, le conteneur de données le plus intéressant / puissant. Permet de stocker les infos sous forme de clé/valeur.
company_info = {
	'name': 'My super company',
	'address': '45, beautiful avenue, blabla, France',
	'number of employees': 3,
	'employees': [
		"Pierre",
		"Cedric",
		"Manu"
	],
	'contact': {
		'email': 'contact@my-super-company.com',
		'phone': '+33 567874332'
	}
}
[code]

Allez, on joue avec ?

#NePasOublierDeCommiter

Booléens

# ET logique

a and b

# OU logique

a or b

# Negation

not a

# On complique un peu, mais lisible non ? 
# ...langage naturel :)

a and not (b or c)

Flux d'exécution

if, else, while, for, ... tous les mots clés que vous connaissez déjà mais à la mode Python, c'est à dire lisible donc maintenable

if, else, elif

AGE_LIMIT = 70
age = 23
is_a_boy = True
is_young = (age <= AGE_LIMIT)

if is_a_boy and not is_young:
    print 'Your are an "old" man... (above %d years old)' % AGE_LIMIT
[code]

while, for

index = 0
while index < 5:
    print index
    index += 1
[code]

Bon, on code ? parce que les slides... c'est bien mais...

List comprehensions

numbers = [2, 3, 7, 8, 90, 8, 8, 8]
odds = [number for number in numbers if number % 2]
print "odds:", odds
[code]

...ou comment créer une liste à partir d'une autre et de critères de façon concise

Ca semble obscur au début mais après un peu de pratique, vous en abuserez!

Fonctions

identiques à ce que vous connaissez en C mais avec des améliorations considérables comme:

  • les paramètres nommés
  • les valeurs par défaut

Arguments nommés

On peut passer les arguments comme en C... mais aussi en nommant les paramètres!

#VousVousDemandezPourquoi :)
def my_funny_routine(greet, name):
    '''This routine prints a greeting to the console'''

    print("%s %s!" % (greet, name))


# traditional (C-like)
my_funny_routine('Hello', 'Cedric')         

# Named args
my_funny_routine(greet='Hello', name='Cedric')

# ... identical to:
my_funny_routine(name='Cedric', greet='Hello')

Valeurs par défaut

Et si on ne passe pas tel ou tel argument ? Chouette, Python propose des valeurs par défaut :)

def my_funny_routine(name, greet='Hello'):
    '''This routine prints a greeting to the console'''

    print("%s %s!" % (greet, name))


# same output for these two calls:
my_funny_routine(name='Cedric', greet='Hello')
my_funny_routine(name='Cedric')
[code]
#OnCodeEtOnTeste ?

       ou carrément mieux...

#OnEcritUnTestPuisOnCode

Variable args

Permet de définir des fonctions qui n'ont pas de nombre d'arguments prédéfini.


def my_varargs_routine(*args, **kwargs):
    # args is a list of unamed args
    # kwargs is a dictionary of named args
    pass

Dangereux et à réserver pour des cas bien précis (par exemple: shell)

Doc strings


def display_greeting(msg, name):
    '''
    Display a greeting to the console
    msg is the greeting message
    name is the username to greet
    
    '''

    print("%s %s!" % (msg, name))
    
  • display_greeting.__doc__
  • générateur de documentation avec sources embarquées (cf. exemple de code final): Sphinx

Un fichier c'est bien mais généralement, vous codez plusieurs fonctionnalités que vous organisez dans plusieurs modules, packages

├── __init__.py
├── conf
│   ├── donothingreader.py
│   ├── factory.py
│   ├── __init__.py
│   └── jsonreader.py
├── core
│   ├── exceptions.py
│   ├── __init__.py
│   └── log.py
├── drivers
│   ├── basedriver.py
│   ├── dummydriver.py
│   ├── __init__.py
│   └── serialdriver.py
├── ...

Packages & modules

Pour faire court :

  • module <=> fichier.py
  • package <=> répertoire contenant __init__.py

from... import

def display_msg(msg="default message"):
    '''Display message to the console'''

    print('msg:' + msg)


def display_short_msg(msg):
    '''Display short message to the console'''

    print(msg)

display.py

# only import one routine
from display import print_msg 

print_msg('hey!')
print_short_msg --> Error!

# or import the whole module
import display

display.print_msg('hey!')
display.print_short_msg('hey!')

main.py

[code]
#DontForgetToCommit

Variable d'environnement qui permet de spécifier à l'interpréteur Python où aller chercher ses packages.

 

On en reparlera au moment des exos plus poussés

PYTHONPATH

Les packages que vous utiliserez dans votre application:

Packages internes/externes

#CestMaintenantQueVousAllezTomberEnAmour

A partir de maintenant, PyPi est votre meilleur ami !

PyPi

[code]

Si vous aviez à afficher une barre de progression, vous le feriez réellement à la main ?

hints: pip installer, progressbar

Pour faire de son code un joli package installable !

setup.py 

[code]

"Hey ! si ton code dépend de N librairies externes, t'es mignon mais je fais comment moi pour m'y retrouver et utiliser ton script ?"

  • Tu écris un joli README avec tous les étapes d'installation
  • Ou bien tu écris un setup.py ET un README allégé

Pour les dépendances de développement, j'utilise pip qui prend en entrée un fichier requirements.txt

requirements.txt

peio@tux-cf-53:~/python-startup/examples/06-files/refactoring [master]$ cat requirements.txt 
pytest
pep8
nose

POO

L'avantage avec Python, c'est qu'on peut faire du procédural et de l'objet !

Classes et Objets

Une classe est un modèle et un objet est une instance de ce modèle

class User(object):
    '''
        The User class!
    '''

    def __init__(self, first, last):
        '''This is the constructor'''
        self.first = first
        self.last = last

    def get_fullname()
        '''Get the user fullname'''
        return self.first + 
               ", the marvellous " +
               self.last + "!"

    
me = new User('Pierre', Roth')
ced = new User('Cedric', 'Jourdain')
diego = new User('Diego', 'Felipe')

print(ced.get_fullname())

    >>>> Cedric, the marvellous Jourdain!
Définition d'une classe User
Définition d'objets User

Exceptions

def do_that(data):
    if data['name'] is None:
        return -1

def do_this(data):
    if data is None:
        return -2

    rc = do_that(data)
    if rc != 0:
        return rc
    data['another"] = 12
    return do_that(data)

if __name__ == "__main__":

    rc = do_this(None)
    if rc == 0:
        rc = do_this({"name": "me"})
    
    if (rc == -1):
        print("data does not contain 'name'")
    elif (rc == -2):
        print("data is None!")
def do_that(data):
    if data['name'] is None:
        raise Exception("data does not contain 'name'")

def do_this(data):
    if data is None:
        raise Exception("data is None!")

    do_that(data)
    data['another"] = 12
    do_that(data)
    
if __name__ == "__main__":

    try:
        do_this(None)
        do_this({"name": "me"})
    except Exception as error:
        print "An error occured: %s" % error
    

try ... except

Allez on s'y colle, voici les specs:

  1. Lecture de ce fichier JSON
  2. Imprimer joliment sur la console
  3. Nom de fichier passé par la CLI, gestion des entrées utilisateur
  4. Ajouter un logger
  5. Envoyer un mail à chaque utilisatuer avec ses jours de congés restants

Un exercice complet

[code]
#AVousDeJouer
#SifflezSiBloqué
#CommitAChaqueEtapeEtCheckPEP8

Quelques questions "innocentes":

  1. Ce code est-il testable ?
  2. Et si le format de fichier change ? (XML par exemple)
  3. Et si je veux envoyer des SMS au lieu des emails ?
  4. Et si je veux envoyer des SMS en plus des emails ?
  5. etc....
  6. remember: 2ième principe Agile

Refactoring

Customers don’t know what they want until they see it, and they always reserve the right to change their mind.
                             -- Jeff Sutherland, Emergence of Essential Systems

"Always leave the campground cleaner than you found it."
[code]
  1. Ajoutons un notifier SMS ... quel impact ? (OCP principle)
  2. Supporter le format XML
  3. Rendre le code robuste à un fail d'une notification: les notifications suivantes sont émises

Commentaires

Cette solution - loin d'être idéale - reprend quelques concepts: SOLID pour encaisser plus facilement les changements.

Par exemple:

On n'en parle "que" maintenant mais on aurait dû attaquer par ça. Approche TDD qui permet une démarche architecturale et du code minimum.

Les tests !

#RefactorOnGreen

Plusieurs frameworks dont unittest, la version batteries included mais préférer: pytest ou nose

Lisez leur doc et installez-les, ça devrait prendre 2 minutes, n'est ce pas... !?  ;o)

Un seul mot: I.N.D.I.S.P.E.N.S.A.B.L.E

Intégration continue

Plusieurs possibilités, par exemple:

En développement Agile (type SCRUM), on livre tout régulièrement, c'est à dire le bundle {code + tests + doc} Un tout cohérent à chaque itération.

La doc !

Pour la génération de doc, je conseille le super outil sphinx

  • Formats de sortie: html, pdf, etc...
  • Templates
  • Référence aux sources
  • etc...
cd examples/06-files/refactoring
make doc-pdf

Windows n'est pas un environnement pour développeur

#FailOnWindows

doc.setup()

#RTFM

Petit exercice

  1. installer sphinx
  2. générer une documentation
  3. changer de thème
  4. insérer automatiquement le numéro de version
  5. insérér automatiquement le numéro de révision du gestionnaire de sources
quote = {}
quote['content'] = 'Si tu lis la doc, tu fais partie du top 1% des développeurs'
quote['author']  = 'Christophe Porteneuve'
quote['company'] = 'Delicious Insights'
#PourLesWarriors

Environnement

Pour faciliter le travail des contributeurs à votre code, c'est souvent pas mal d'ajouter une couche d'abstraction aux commandes habituelles.

 

Me concernant, j'utilise un bon vieux makefile...

peio@tux-cf-53:~/Desktop/perso/python-startup/examples/06-files/refactoring [master]$ make
Cheking PEP8 coding style...
Done.
Running tests...
PYTHONPATH=. nosetests -v
Test the decoding when a correct file is given ... ok
test_json_decoder.test_json_bad_format ... ok
test_json_decoder.test_json_not_found ... ok

----------------------------------------------------------------------
Ran 3 tests in 0.029s

OK

Questions?

Python c'est magique! ;)

Conclusion

#MerciPourVotreEcoute