.. $Id: documentation.rst.in 14117 2011-05-11 08:12:37Z ycadour $

.. _python: http://www.python.org

.. index:: structuration code

#####################
Structuration du code
#####################


.. index:: fonction

=============
Les fonctions
=============

- La définition d'une fonction se fait par le mot clé *def* suivi du nom de la fonction.
- Les éventuels paramètres sont déclarés entre parenthèses.
- Le caratère ':' délimite le début de la fonction.
- Le bloc de la fonction est délimité par les indentations.

Premier exemple::

    >>> def say_hello(firstname, lastname):
    ...    print ("Hello {} {}".format(firstname.title(), lastname.title()))
    ... 
    >>> say_hello('Terry', 'gilliam')
    Hello Terry Gilliam

Il n'y a pas de distinction entre les fonctions et les procédures en Python. Les procédures sont des fonctions qui ne retournent pas de résultat.

Pour retourner un résultat on utilise la directive *return*.

    >>> def get_hello(firstname, lastname):
    ...     return "Hello {} {}".format(firstname.title(), lastname.title())
    ... 
    >>> result = get_hello('terry', 'gilliam')
    >>> result
    'Hello Terry Gilliam'
    
    >>> result2 = say_hello('terry', 'gilliam')
    Hello Terry Gilliam
    >>> result2
    >>> 

-----------------------------
Les paramètres d'une fonction
-----------------------------

Il existe 3 types de paramètre.

.. index:: paramètres explicites

Les paramètres explicites
#########################

- Les paramètres explicites sont séparés par une virgule et peuvent être enrichis d'une valeur par défaut, ils sont dans ce cas optionnels.
- Il est  nécessaire de regrouper les paramètres optionnels à la fin de la liste des paramètres.
- Les paramètres peuvent être nommés sans respecter un ordre précis.

::

    >>> def say_hello(firstname, gender='Mr', lastname=''):
    ...     fullname = ' '.join([gender, firstname, lastname])
    ...     print("Hello {}".format(fullname))
    ... 
    >>> say_hello('Terry')
    Hello Mr Terry 
    >>> say_hello('Yolande', 'Mme')
    Hello Mme Yolande 
    >>> say_hello('Yolande', lastname='Moreau', gender='Mme')
    Hello Mme Yolande Moreau
    

.. index:: paramètres non explicites

Les paramètres non explicites
#############################

- Les paramètres non explicites permettent de spécifier autant de valeurs que l'on souhaite sans qu'il soit nécessaire de les spécifier dans la signature de la fonction.
- Ils sont fournis sous la forme *nom=valeur*, et sont accessibles à l'interieur de la fonction sous forme de dictionnaire.
- Les paramètres non explicites sont préfixés d'une double étoile.

::

    >>> def show_actors(**actors):
    ...     for actor, name in actors.items():
    ...         print("{}:{}" .format(actor, name))
    ... 
    >>> show_actors(actor1='Terry Gilliam', actor2='John Cleese')
    actor2:John Cleese
    actor1:Terry Gilliam


.. index:: paramètres arbitraires

Les paramètres arbitraires
##########################

- Les paramètres arbitraires fonctionnent de la même manière que les paramètres non explicites mais ne sont pas nommés.
- Il sont accessibles à l'intérieur de la fonction sous forme d'un tuple.
- Les paramètres arbitraires sont préfixés d'une étoile.

    >>> def ajoute_acteurs(*actors):
    ...     liste = []
    ...     for actor in actors:
    ...         liste.append(actor)
    ...     return liste
    ... 
    >>> ajoute_cteurs('Terry Gilliam', 'John Cleese')
    ['John Cleese', 'Terry Gilliam']

Ces trois types de paramètres peuvent être utilisés dans une même fonction, dans ce cas leur ordre doit être le suivant:

*def fonction(param1, param2=2, *arbitraires, **non-explicites):*

.. index:: docstrings

--------------
Les docstrings
--------------

Les docstrings sont des chaines de caractères placées au début du corps des fonctions. Ils sont associés à la variable __doc__ de la fonction.

    >>> def get_full_name(firstname, lastname):
    ...     """Retourne le nom complet"""
    ...     return '{} {}'.format(firstname, lastname)
    ... 
    >>> get_full_name.__doc__
    'Retourne le nom complet'
    >>> help(get_full_name)
    'Retourne le nom complet'
    


--------------
Les typages
--------------

Avec Python 3.5, il est possible de déterminer le type des entrées/sorties de vos fonctions. Il suffit de respecter les règles décrites ici :

https://docs.python.org/3/library/typing.html

.. index:: classe

===========
Les classes
===========

Une classe est définie par le mot clé *class*.

Exemple de classe

    >>> class Human:
    ...     """Un humain"""
    ...     firstname = ''
    ...     lastname = ''
    
L'instanciation d'une classe s'effectue en appelant la classe.

::

    >>> humain1 = Human()
    >>> humain2 = Human()

Ces deux instances sont issues d'une même classe mais sont deux instances différentes.

::

    >>> id(humain1)
    39754640
    >>> id(humain2)
    39754712

Accessibilité et modfication des attributs::

    >>> humain1.firstname
    ''
    >>> humain1.firstname='John'
    >>> humain1.firstname
    'John'
    

.. index:: méthode

------------
Les méthodes
------------

- Les méthodes de la classe ont toutes comme premier paramètre l'objet instancié, par convention ce paramètre s'appelle *self*.
- Le premier paramètre est fourni de manière transparente lors de l'appel de la méthode.

::

    >>> class Human:
    ...     """Un classe représentant un humain"""
    ...     firstname = ''
    ...     lastname = ''
    ...     def get_full_name(self):
    ...         """Retourne le nom complet"""
    ...         return '{} {}'.format(self.firstname, self.lastname)
    ... 
    >>> human1 = Human()
    >>> human1.firstname = 'John'
    >>> human1.lastname = 'Cleese'
    >>> human1.get_full_name()
    'John Cleese'

.. index:: héritage

----------
L'héritage
----------

Python permet de définir l'héritage de classe de façon fort simple.

::

    >>> class Actor(Human):
    ...     """Un acteur"""
    ... 
    >>> actor1=Actor()
    >>> dir(actor1)
    ['__doc__', '__module__', 'firstname', 'get_full_name', 'lastname']

L'héritage multiple est supporté par python, mais en pratique il est fortement déconseillé.

------------
Constructeur
------------

Lors de l'instanciation d'une classe la méthode spéciale *__init__* est invoquée et prend en paramètre l'objet instancié par l'interpréteur.
Cette méthode permet l'initialisation des attributs lors de l'instanciation d'une classe.

::

        >>> class Actor(Human):
        ...     """Un acteur"""
        ...     def __init__(self, firstname, lastname):
        ...         self.firstname = firstname
        ...         self.lastname = lastname
        ... 
        >>> actor = Actor('John', 'Cleese')
        >>> actor.get_full_name()
        'John Cleese'
        
------------------
Propriétés privées
------------------

Il est possible de définir des attributs privés en les préfixant d'un double '_'. L'attribut n'est alors pas accessible directement.

::

    >>> class Actor(Human):
    ...     """Un acteur"""
    ...     def __init__(self, firstname, lastname, gender):
    ...         self.firstname = firstname
    ...         self.lastname = lastname
    ...         self.__gender = gender
    ... 
    >>> actor1 = Actor('John', 'Cleese', 'Mr')
    >>> actor1.firstname
    'John'
    >>> actor1.gender
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    AttributeError: Actor instance has no attribute 'gender'

Mais cette protection n'est pas absolue, il est possible d'accéder à l'attribut de cette manière

::

    >>> dir(actor1)
    ['_Actor__gender', '__doc__', '__init__', '__module__', 'firstname', 'get_full_name', 'lastname']
    >>> actor1._Actor__gender
    'Mr'


 
======================
Créer un projet Python
======================

Cookie cutter est votre ami !

D'abord, il faut l'installer:

https://cookiecutter.readthedocs.io/en/2.0.2/installation.html

Puis, à partir d'un template: 
- pour une logique "github" (CI/CD) : https://cookiecutter-python.readthedocs.io/en/latest/tutorial.html
- pour une logique "package local" : https://dev.to/chaps/creating-a-python-package-with-cookiecutter-3ik0


 

=========
Exercice
=========

Création d’un jeu du pendu : 1 classe « JeuPendu », variables : nb_essais, mots,
historique, méthode start() pour lancer l'application. Interaction via la console (fonction input("XXX"))
Trame: script tests_pendu.py