attributs – Sam & Max http://sametmax.com Du code, du cul Wed, 30 Oct 2019 15:34:04 +0000 en-US hourly 1 https://wordpress.org/?v=4.9.7 32490438 Le guide ultime et définitif sur la programmation orientée objet en Python à l’usage des débutants qui sont rassurés par les textes détaillés qui prennent le temps de tout expliquer. Partie 3. http://sametmax.com/le-guide-ultime-et-definitif-sur-la-programmation-orientee-objet-en-python-a-lusage-des-debutants-qui-sont-rassures-par-les-textes-detailles-qui-prennent-le-temps-de-tout-expliquer-partie-3/ http://sametmax.com/le-guide-ultime-et-definitif-sur-la-programmation-orientee-objet-en-python-a-lusage-des-debutants-qui-sont-rassures-par-les-textes-detailles-qui-prennent-le-temps-de-tout-expliquer-partie-3/#comments Sun, 27 Jan 2013 06:59:49 +0000 http://sametmax.com/?p=4292 Prérequis :

Nous avons vu les attributs de classe et les propriétés, mais de manière très rudimentaire. Il y a beaucoup de choses à savoir sur la question.

Attribut de classe, la totale

Nous avons vu qu’un attribut de classe était accessible par la classe, sans instance :

>>> class TrueLies:
...
...         attribut_de_classe = 'valeur'
...
>>> print TrueLies.attribut_de_classe
valeur

Mais également par une instance :

>>> print TrueLies().attribut_de_classe
valeur

C’est simple et direct. Il n’y a pas de public static virtuel neoconceptuel à mettre devant comme dans certains langages trop cafféinés.

Ce qu’on a pas vu par contre, c’est ce qui arrive si on modifie la valeur de ces attributs.

Pour faire simple :

  • Si je modifie l’attribut au niveau de l’instance, seule l’instance voit les modifications.
  • Si je modifie l’attribut au niveau de la classe, la classe et toutes les instances crées après ont la nouvelle valeur.
>>> instance = TrueLies()
>>> instance.attribut_de_classe = 'nouvelle valeur'
>>> print instance.attribut_de_classe
nouvelle valeur
>>> print TrueLies.attribut_de_classe # ça ne change rien pour la classe
valeur
>>> TrueLies.attribut_de_classe = 'encore une nouvelle valeur'
>>> print TrueLies.attribut_de_classe
encore une nouvelle valeur
>>> print instance.attribut_de_classe # ça ne change rien pour cette instance
nouvelle valeur
>>> print TrueLies().attribut_de_classe # mais pour une nouvelle oui
encore une nouvelle valeur

Comme self est l’instance en cours, vous pouvez remplacer instance par self dans l’exemple ci-dessus (dans une méthode bien sûr, pas dans le shell), ça marche pareil.

Vu comme ça, ça à l’air d’être super pour donner une valeur par défaut à des attributs.

C’est une mauvaise idée.

En effet, les attributs de classes sont initialisés une seule fois, à la première lecture du code par Python. Pour les objets immutables comme les strings ou les int, on s’en branle. Mais pour les objets mutables comme les dicos ou les listes, c’est la merde.

>>> class TrueLies:
...
...         attribut_de_classe = [] # mettons une liste ici
...
...
>>> flim_pas_sur_le_cyclimse = TrueLies()
>>> etranger_qui_vole_le_travail_des_francais = TrueLies()

On a donc deux instances de TrueLies. A priori, on a aussi deux valeurs de attribut_de_classe, une pour chaque instance ?

Des faux seins sur un FAUX de norman fait des vidéos

Norman va bientôt faire un film avec les robins des bois au fait

>>> id(etranger_qui_vole_le_travail_des_francais.attribut_de_classe)
28992072
>>> id(flim_pas_sur_le_cyclimse.attribut_de_classe)
28992072

Comme vous pouvez le voir c’est le même id. attribut_de_classe est la même chose, car Python initialise attribut_de_classe une seule fois pour toute la session. Les deux instances ont donc un attribut qui contient une référence (puisque TOUT est référence en Python) qui pointe sur la même liste.

Dans les fait, ça veut dire que si on modifie l’un, ben forcément la modification est visible de l’autre côté aussi :

>>> etranger_qui_vole_le_travail_des_francais.attribut_de_classe.append(1)
>>> flim_pas_sur_le_cyclimse.attribut_de_classe
[1]

C’est rarement ce qu’on veut.

Donc ne donnez pas de valeur par défaut dans les attributs de classe. Faites ça avec __init__:

>>> class TrueLies:
...
...         def __init__(self):
...
...                 self.attribut_tres_classe = []

>>> flim_pas_sur_le_cyclimse = TrueLies()
>>> etranger_qui_vole_le_travail_des_francais = TrueLies()
>>> flim_pas_sur_le_cyclimse.attribut_tres_classe.append(1)
>>> flim_pas_sur_le_cyclimse.attribut_tres_classe
[1]
>>> etranger_qui_vole_le_travail_des_francais.attribut_tres_classe
[]

Le comportement attendu est le bon ici.

Ok, mais alors ils servent à quoi ces attributs à deux cents, là, cousin ?

– Et ben wesh, tu vois, ils servent à stocker les constantes et le cache.

– Ouais quand j’ai constamment du stock j’ai du cash, man.

– héééééééééééé… xactement.

Donc, d’abord, on met les pseudo constantes en attributs de classe. Je dis pseudo car il n’existe pas de constantes en Python. On parle de constante quand une variable est écrite toute en majuscule, une convention pour dire “le programme ne change jamais la valeur de cette variable (et t’as pas intérêt à la changer, pigé ?)”.

class TrueLies:

    NOMBRE_DE_BALLES_DANS_LE_CHARGEUR = 10000

On peut être certain que Schwarzy ne recharge jamais (y a que Thierry Lhermitte qui ferait un truc aussi ringard). Donc le nombre de balles dans le chargeur ne varie pas. On le met comme attribut de classe. C’est essentiellement à titre informatif. Pour nous ça change rien, mais pour quelqu’un qui va utiliser le code, il sait qu’il peut chercher toutes les constantes liées à TrueLies en faisant TrueLies.LES_TRUCS_EN_MAJUSCULES.

C’est l’usage le plus courant.

On peut aussi utiliser les attributs de classes pour créer une API déclarative, comme le fait l’ORM de Django. Mais bon, c’est mega advanced, donc je mets de côté le how to. Le jour où vous coderez un truc comme ça, vous lirez pas un article pour débutant.

Enfin on peut utiliser les attributs de classe pour partager des données entre les instances. Par exemple du cache.

class TrueFalseSomeWhatLiesMaybeWhoKnows :

    _cache = {}


    def calculer_le_nombre_de_balles_a_la_seconde(scene):

        # si y a rien dans le cache, on fait le calcul et on le met dans le cache
        if scene not in self._cache :

            self._cache[scene] = # mettre un calcul bien compliqué ici

        # on retourne le contenu du cache
        return self._cache[scene]

>>> TrueFalseSomeWhatLiesMaybeWhoKnows().calculer_le_nombre_de_balles_a_la_seconde(1)
56586586
>>> TrueFalseSomeWhatLiesMaybeWhoKnows().calculer_le_nombre_de_balles_a_la_seconde(1)
56586586

Le premier appel va faire le calcul. Mais pas le second, car le résultat a été stocké dans le dictionnaire qui est au niveau de la classe (et donc chaque nouvelle instance a une référence vers ce dictionnaire) et peut donc être réutilisé.

Vous noterez qu’on a appelé la variable _cache et pas cache. C’est encore une convention. Ça signifie, “cette variable est utilisée en interne, t’as pas à savoir ce qu’elle fait, imagine que c’est privé. Circulez, y a rien à voir.” Les outils de complétion de code masquent ces variables par défaut.

Pour la culture, sachez que nommer ses variables __nom les rend “privées” en Python. Néanmoins cette fonctionnalité est facilement contournable, et très peu appréciée dans la communauté, donc évitez-la.

Et puis il y a les méthodes statiques aussi

On a vu les méthodes de classe, qui sont, comme les attributs de classe, des méthodes qui n’ont pas besoin d’instance pour être appelées :

class ArmeeDesDouzeSinges:
    """
        Ouais c'est un remake d'un film français aussi.
        Ça vous la coupe hein ?
    """

    SINGES = 12

    @classmethod
    def nombre_de_singes_au_carre(cls):

        return cls.SINGES * cls.SINGES


>>> ArmeeDesDouzeSinges.nombre_de_singes_au_carre()
144

Le premier paramètre n’est pas l’objet en cours mais la classe en cours, et c’est tout ce dont nous avons besoin ici pour accéder à SINGES puisque c’est un attribut de classe.

Cet exemple est issu d’un de mes codes réels de productions, vous pouvez en constater l’intérêt évident dans la vie de tous les jours.

Nan je déconne, moi j’utilise plutôt des dromadaires au quotidien.

Bon, mais sachez plus sérieusement qu’il y a en prime des méthodes de classe, des méthodes statiques. C’est la même chose, mais aucun paramètre n’est passé automatiquement.

class LHommeALaChaussureRouge:
    """
        Oui c'est exactement ce que vous pensez. C'est affligeant.
    """

    @staticmethod
    def crie_son_desespoir(): # pas de cls

        print "Nooooooooooooooooooooooooooooooooon"


>>> LHommeALaChaussureRouge.crie_son_desespoir()
Nooooooooooooooooooooooooooooooooon

Alors là on arrive dans la feature anecdotique hein, du genre qu’on sort qu’au repas de Noël. Je la mets au menu juste pour que vous sachiez que ça existe, mais franchement je ne m’en sers presque jamais.

L’usage est le même qu’une méthode de classe, mais pour les trucs qui n’ont pas besoin d’avoir accès à la classe en cours.

Bon.

Bref, c’est juste une fonction préfixée. Avantage : elle est un poil plus rapide que la méthode de classe, et elle est mieux rangée qu’une simple fonction. Mais c’est vraiment tout.

Retour sur les properties

Si vous avez bonne mémoire, vous vous souviendrez qu’une propriété est juste un déguisement qu’on met sur une méthode pour qu’elle ressemble à un attribut :

class LesVisiteursEnAmerique:
    """
        Mais si, mais si...
    """

    @property
    def replique(self):

        return 'okay'


>>> film_pourri = LesVisiteursEnAmerique()
>>> film_pourri.replique
'okay'

Mais on peut aller plus loin que la lecture. On peut aussi décorer l’écriture et la suppression !

class LesVisiteursEnAmerique(object): # on rajoute (object) truc ici


    def __init__(self):
        self._replique = 'okay' # c'est privé, pas touche, y a un underscore


    @property
    def replique(self):
        print 'get'
        return self._replique


    @replique.setter # le décorateur a le même nom que la méthode
    def replique(self, value): # value est la valeur à droite du '=' quand on set
        print 'set to {}'.format(value)
        self._replique = value


    @replique.deleter
    def replique(self):
        print 'delete'
        self._replique = None


>>> film_pourri = LesVisiteursEnAmerique()
>>> film_pourri.replique
get
'okay'
>>> film_pourri.replique = 'zarma'
set to zarma
>>> film_pourri.replique
get
'zarma'
>>> film_pourri._replique
'zarma'
>>> del film_pourri.replique
delete
>>> film_pourri.replique
get

Et là vous allez me dire :

– c’est quoi ce (object) là haut là qui est apparu magiquement ?
– et à quoi ça sert ? Nom d’un remake hollywodien !

À la première question je dirai, réponse dans la prochaine partie. Sachez juste que s’il y a pas ce (object), le setter et le deleter ne marchent pas.

Pour la seconde, imaginez un truc un peu plus choupi, comme une réplique garantie d’être toujours en majuscule :

class LesVisiteursEnAmerique(object):


    def __init__(self):
        self._replique = 'OKAY'


    @property
    def replique(self):
        return self._replique


    @replique.setter
    def replique(self, value):
        self._replique = value.upper() # PAF ! on majusculise

>>> film_TRES_pourri = LesVisiteursEnAmerique()
>>> film_TRES_pourri.replique
'OKAY'
>>> film_TRES_pourri.replique = "D'ac"
>>> film_TRES_pourri.replique
"D'AC"

Et voilà le travail.

On peut donc “intercepter” la récupération, la suppression et la modification d’un attribut facilement. Cela permet d’exposer une belle API à base d’attribut, mais derrière faire des traitements complexes.

Il suffit de transformer un attribut normal en méthode, et de lui coller @property au cul.

C’est pour cette raison qu’on n’a jamais de getter et de setter en Python : on utilise les attributs tels quel, et si le besoin se présente, on en fait des propriétés.

Next stop, héritage, overriding, polymorphisme et autres gros mots que vous pourrez ressortir aux soirées mousses dans les hackerspaces.

]]>
http://sametmax.com/le-guide-ultime-et-definitif-sur-la-programmation-orientee-objet-en-python-a-lusage-des-debutants-qui-sont-rassures-par-les-textes-detailles-qui-prennent-le-temps-de-tout-expliquer-partie-3/feed/ 24 4292