Objets
======


En fait, *tout ce qu'on manipule en Python* est un objet. Et tous les objets sont
toujours des instances d'une classe - on peut accéder à la classe qui a servi
à instancier un objet avec la fonction ``type``, par exemple::

    class MaClasse:
        pass

    mon_instance = MaClasse()
    print(type(mon_instance))
    # Affiche:
    # <class '__main__.MaClasse'>


Mais aussi::

    print(type(2))
    # affiche: int

    print(type("bonjour"))
    # affiche: str

Donc en Python, les entiers sont des instances de la classe ``int``, et les strings des instances de la
classe ``str``.

Ainsi, vous pouvez voir l'expression ``x = str(y)`` de deux façons:

* Soit on appelle la fonction native ``str`` pour convertir ``y`` en string

* Soit on crée une nouvelle instance de la classe ``str`` en appelant le constructeur
  de la classe ``str`` avec ``y`` en argument.

Notez que ça ne s'arrète pas là::

    def ma_fonction():
        pass

    print(type(ma_fonction))
    # affiche: function

    class MaClasse:
        def ma_méthode(self):
            pass

    mon_instance = MaClasse()
    print(type(mon_instance.ma_méthode))
    # affiche: method

    import sys
    print(type(sys))
    # affiche: module


Et même::

    print(type(MaClasse))
    # affiche: type

    print(type(type))
    # affiche: type

Et oui, les classes elles-mêmes sont des instances de classe! (la classe ``type``)

Du coup en Python, le terme 'objet' désigne *toujours* une instance de classe - même
``None`` est une instance d'une classe (elle s'appelle ``NoneType``).

Ordre de résolution
--------------------

Il est temps de revenir sur l'évaluation des expressions impliquant des
attributs.

On a vu trois systèmes différents:

Appeler une fonction définie dans un module::

    import mon_module
    mon_module.ma_fonction()

Appeler une méthode d'instance définie dans une classe::

    mon_instance = MaClasse()
    mon_instance.ma_méthode()

Appeler une méthode de classe définie dans une classe::

    MaClasse.ma_méthod_de_classe()

D'après ce qu'on a vu dans la section précédente, on sait maintenant que
dans tous les cas, à gauche du point se situe un objet, et que tous
les objets sont des instances d'une classe (appelé le "type" de l'objet).

Pour évaluer l'expression ``mon_objet.mon_attribut``, où `mon_objet`` est une
instance de ``mon_type``, Python cherche toujours l'attribut dans deux endroits:

* D'abord en tant qu'attribut de l'instance ``mon_objet``
* Ensuite, en tant qu'attribut de la classe ``mon_type``

La recherche se poursuit ainsi en suivant toutes les classe parentes de
``mon_type``.


On peut voir ce mécanisme en action dans le code suivant::

    class A:
        def f1(self):
            print("f1 dans A")

        def f2(self):
            print("f2")


    class B(A):
        @classmethod
        def f3(cls):
            print("f3")

        def f1(self):
            print("f1 dans B")


    b = B()
    b.f1()
    b.f3()
    b.f2()
    # affiche:
    # f1 dans B
    # f3
    # f2


Conclusion
------------

Maintenant vous devriez comprendre pourquoi on dit parfois qu'en
Python, **tout est objet**.

Dans un prochain chapitre, on expliquera pourquoi en plus de cela
on peut dire qu'en Python, **tout est dictionnaire**.
