IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Python, de zéro


précédentsommairesuivant

XV. Compléments sur les classes

XV-1. L’héritage

L’héritage de classes est un mécanisme qui permet à une classe Y de bénéficier des attributs et méthodes d’une classe X tout en pouvant se rajouter ses propres attributs et méthodes. Cela permet de définir facilement un arbre d’objets tout en centralisant les éléments communs.

Un bon résumé pour définir l’héritage est de dire « est un ». Ainsi, si on admet qu’une voiture est un véhicule, alors on peut concevoir un héritage entre un objet véhicule et un objet voiture.

La syntaxe d'un héritage se fait à la définition de l'objet où l’on rajoute, après son nom, le nom de l'objet hérité entre parenthèses. Dans le cas d’un héritage multiple (un objet héritant de plusieurs), alors on met les différents noms des objets séparés par des virgules.

Exemple : une gestion de véhicules pourra commencera par définir un objet véhicule pour l’affiner ensuite avec un objet voiture et un autre avion.

On commence alors par créer l'objet de base vehicule

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
class vehicule:
    def __init__(self, vitesse, roues):
        self._vitesse=vitesse
        self._roues=roues

    def affich(self):
        print(
            "véhicule (%s) - vitesse=%d, roues=%d" % (
                self,
                self._vitesse,
                self._roues,
            )
        )

Ensuite, on crée les objets voiture et avion qui héritent de cet objet vehicule.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
class voiture(vehicule):
    def __init__(self, motorisation, vitesse, roues):
        vehicule.__init__(self, vitesse, roues)
        self.__motorisation=motorisation

    def affich(self):
        vehicule.affich(self)
        print(
            "voiture (%s) - motorisation=%s, vitesse=%d, roues=%d" % (
                self,
                self.__motorisation,
                self._vitesse,
                self._roues,
            )
        )

class avion(vehicule):
    def __init__(self, altitude, vitesse, roues):
        super(avion, self).__init__(vitesse, roues)
        self.__altitude=altitude

    def affich(self):
        super(avion, self).affich()
        print(
            "avion (%s) - altitude=%d, vitesse=%d, roues=%d" % (
                self,
                self.__altitude,
                self._vitesse,
                self._roues,
            )
        )

>>> v=voiture("diesel", 100, 4)
>>> v.affich()
véhicule (<__main__.voiture object at 0xb72308ac>) - vitesse=100, roues=4
voiture (<__main__.voiture object at 0xb72308ac>) - motorisation=diesel, vitesse=100, roues=4
>>> a=avion(25000, 200, 2)
>>> a.affich()
véhicule (<__main__.avion object at 0xb723090c>) - vitesse=200, roues=2
avion (<__main__.avion object at 0xb723090c>) - altitude=25000, vitesse=200, roues=2

Grâce à l’héritage, les objets voiture et avion récupèrent automatiquement les attributs de l’objet parent vehicule (vitesse et roues) qui peuvent alors être considérés et manipulés comme attributs au même titre que les autres.

L’héritage de classes s’étend bien entendu aussi aux éléments privés, mais comme ils sont privés, ils ne sont pas accessibles depuis la classe fille (sauf en donnant leur vrai nom obfusqué). D’où le simple underscore dans les attributs vitesse et roues de l’exemple pour que les classes filles puissent y accéder tout en indiquant aux autres développeurs que ces éléments doivent rester protégés).

De plus, l’héritage entraine une délégation des méthodes (la classe fille délègue certaines tâches à la classe mère ; telle la méthode __init__() de cet exemple qui fait appel à vehicule.__init__() pour initialiser les éléments de vehicule ou la méthode affich() qui fait appel à vehicule.affich() pour les afficher).

Dans la majorité des langages objets (C++, C#, php), cette délégation est implicite. En Python, elle sera implicite si la classe fille ne définit pas de méthode du même nom, mais devra être explicitement demandée dans le cas contraire (surcharge).

Celle-ci peut se faire de deux façons :

  • appel explicite de l’objet et de la méthode dont on veut hériter. La syntaxe est alors la suivante : classe_mere.methode(instance de classe, paramètres de la methode). Dans l'exemple, c'est la façon dont l'objet voiture délègue l'initialisation et l'affichage à l'objet vehicule ;
  • utilisation de la fonction super(). La syntaxe est alors la suivante : super(classe courante, instance de classe).methode(paramètres). Dans l'exemple, c'est la façon dont l'objet avion délègue l'initialisation et l'affichage à l'objet vehicule.

La différence entre les deux écritures est généralement minime. Elle ne devient réellement visible que dans les cas d’héritages complexes (tel un héritage en diamant où deux classes B et C héritent chacune de A ; et une quatrième classe D hérite de B et C en même temps) :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
class A:
    def __init__(self):
        print("A.init")

class B(A):
    def __init__(self):
        print("B.init")
        A.__init__(self)

class C(A):
    def __init__(self):
        print("C.init")
        A.__init__(self)

class D(B, C):
    def __init__(self):
        print("D.init")
        B.__init__(self)
        C.__init__(self)

>>> D()
D.init
B.init
A.init
C.init
A.init
 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
class A:
    def __init__(self):
        print("A.init")

class B(A):
    def __init__(self):
        print("B.init")
        super(B, self).__init__()

class C(A):
    def __init__(self):
        print("C.init")
        super(C, self).__init__()

class D(B, C):
    def __init__(self):
        print("D.init")
        super(D, self).__init__()

>>> D()
D.init
B.init
C.init
A.init

Avec la syntaxe classique, la méthode __init__() de la classe A est appelée deux fois (une fois depuis la classe B et une autre depuis la classe C). Avec la syntaxe passant par la fonction super(), la méthode __init__() de la classe A n’est appelée qu’une seule fois via le « MRO » (Method Resolution Order) de Python.

À noter : puisque les paramètres de la fonction super() respectent toujours le même schéma (classe courante, instance courante), elle a été simplifiée dans Python 3 pour pouvoir être appelée sans paramètres ; ceux-ci étant alors pris par défaut comme étant le nom et l’instance de la classe qui invoque la fonction.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
class A:
    def __init__(self, mot):
        print("a", mot)

class B(A):
    def __init__( self, mot):
        print("b")
        super(B, self).__init__(mot)                    # Ancienne syntaxe

class C(A):
    def __init__( self, mot):
        print("c")
        super(C, self).__init__(mot)                    # Ancienne syntaxe

class D(A):
    def __init__(self, mot):
        print("d")
        super().__init__(mot)                           # Nouvelle syntaxe

class E(B, C, D):
    def __init__(self, mot):
        print("e")
        super().__init__(mot)                           # Nouvelle syntaxe

>>> E("xxx")
e
b
c
d
a xxx

Cela apporte un avantage non négligeable quand une classe privée veut hériter d'un objet public. La première syntaxe oblige à passer par le nom obfusqué…

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
class A:
    def __init__(self): print("A: %s" % self)

class B:
    class __private(A):
        def __init__(self):
            super(B._B__private, self).__init__()
            print("__private: %s" % self)

    def __init__(self):
        B.__private()
        print("B: %s" % self)

>>> B()
A: <__main__.B.__private object at 0x7f52803002e8>
__private: <__main__.B.__private object at 0x7f52803002e8>
B: <__main__.B object at 0x7f52802f6dd8>

… tandis que la nouvelle syntaxe laisse Python gérer tout seul.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
class A:
    def __init__(self): print("A: %s" %self)

class B:
    class __private(A):
        def __init__(self):
            super().__init__()
            print("__private: %s" %self)

    def __init__(self):
        B.__private()
        print("B: %s" %self self)

>>> B()
A: <__main__.B.__private object at 0x7f52803002e8>
__private: <__main__.B.__private object at 0x7f52803002e8>
B: <__main__.B object at 0x7f52802f6dd8>

XV-2. L’héritage face à l’évolution

Il a été vu au chapitre précédent que la délégation des méthodes (la classe fille délègue certaines tâches à la classe mère) n’était pas implicite. Ce qui peut poser des soucis liés à l’évolution de la classe mère.

Reprenons notre objet vehicule contenant un attribut « vitesse maximale »…

 
Sélectionnez
1.
2.
3.
class vehicule:
    def __init__(self, vMax):
        self.vMax=vMax

Celui qui voudra en hériter, quoi qu’il mette dans son propre objet, devra quand même prendre en considération cette valeur vMax. Exemple avec un objet voiture contenant un attribut marque

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
class voiture(vehicule):
    def __init__(self, marque, vMax):
        super().__init__(vMax)
        self.vmarque=marque

>>> v=voiture("Lada", 10)
>>> print(v.marque)
Lada
>>> print(v.vMax)
10

Si demain la classe vehicule évolue pour s’enrichir d’un attribut vMin, tous ses héritiers devront évoluer de la même façon, et de même pour ceux qui hériteront des objets eux‑mêmes hérités. Très rapidement cette contrainte deviendra ingérable et l’héritage ne sera plus utilisé.

Une solution pourra être utilisée à base de valeurs par défaut, mais cette solution ne sera que bancale (tout le monde n’est pas obligé de vouloir mettre une valeur bidon dans son objet quand l’appelant ne met pas ce qu’il faut).

La bonne façon pour régler ce souci est d’utiliser les arguments spéciaux *args et **kwargs. L’utilisateur qui veut hériter d’un objet particulier pourra d’abord récupérer ses propres arguments puis n’aura ensuite qu’à passer ces arguments spéciaux à l’objet parent.

Reprenons notre héritage…

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
class voiture(vehicule):
    def __init__(self, marque, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.vmarque=marque

>>> v=voiture("Lada", 10)
>>> print(v.marque)
Lada
>>> print(v.vMax)
10

Cela ne change pas le fait que si demain l’objet vehicule évolue, l’utilisateur qui l’utilise par délégation devra évoluer avec, mais le programmeur de l’objet voiture, lui, n’a rien à faire, son objet s’adaptera automatiquement aux évolutions de son ancêtre.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
# Objet "vehicule" ayant évolué
class vehicule:
    def __init__(self, vMin, vMax):
        self.vMin=vMin
        self.vMax=vMax

# Objet "voiture " restant inchangé
class voiture(vehicule):
    def __init__(self, marque, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.vmarque=marque

# Seul l’appelant (l’utilisateur) doit s’adapter
>>> v=voiture("Lada", 0, 10)
>>> print(v.marque)
Lada
>>> print(v.vMin)
0
>>> print(v.vMax)
10

XV-3. Les attributs et méthodes statiques

Les attributs et méthodes vus jusqu’à présent s’appliquaient tous aux instances de classes. En effet, pour deux instances distinctes, les valeurs d’attributs ne sont pas forcément les mêmes (deux personnes distinctes n’ont pas forcément le même nom) et les méthodes (qui généralement utilisent les attributs) prennent alors fatalement en compte la valeur des attributs en question, valeur qui diffère alors selon les instances créées.

Toutefois, certains attributs et méthodes peuvent s'appliquer à la classe elle‑même et non à ses instances. Dans ce cas, il peut être alors judicieux de les définir en tant qu'attributs ou méthodes statiques (appelées aussi « attributs ou méthodes de classe »).

XV-3-a. Attribut statique

Pour l'attribut c'est implicite dès lors qu'il est défini en dehors de toute méthode. À partir de là, depuis l'extérieur, l’accès à cet attribut se fera depuis le nom de la classe elle‑même.

Exemple : création d’un compteur d’instances

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
class personne:
    cpt=0                                  # Compteur de personnes (membre statique)
    def __init__(self, nom):
        self.__nom=nom
        personne.cpt+=1                    # Une personne de plus
    def __del__(self):
        personne.cpt-=1                    # Une personne de moins

>>> p1=personne("Pierre")
>>> p2=personne("Paul")
>>> p3=personne("Jacques")
>>> personne.cpt
3
>>> del p3
>>> personne.cpt
2

Bien que l’accès à l’attribut se fasse depuis le nom de la classe elle‑même, il peut aussi se faire depuis une instance de la classe et se comporte alors comme un « attribut d’instance » tout en restant attribut de classe (donc unique dans la classe).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
class toto: item=123

>>> toto.item
123
>>> p1=toto()
>>> p1.item
123
>>> p2=toto()
>>> p2.item
123
>>> toto.item=456
>>> toto.item
456
>>> p2.item
456
>>> p1.item
456
>>> p3=toto()
>>> p3.item
456

L’attribut de classe peut toutefois devenir un vrai attribut d’instance si on le modifie (recrée) dans l’instance. Tout comme pour les variables globales et locales, l’attribut d’instance prend alors le pas sur l’attribut de classe.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
>>> class toto: item=123
...
>>> p1=toto()
>>> p2=toto()
>>> toto.item
123
>>> p1.item
123
>>> p2.item
123
>>> p1.item=456
>>> p1.item
456
>>> p2.item
123
>>> toto.item
123

XV-3-b. Méthode statique

Pour la méthode, cela se fait en l’encapsulant dans une surcouche staticmethod(). À ce moment‑là, la méthode ne recevra plus d’instance de classe dans ses arguments et n’a donc plus à définir de self.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
class point:
    def __milieu(x, y):
        return (x.__v + y.__v) / 2
    milieu1=staticmethod(__milieu)                              # Méthode statique
    milieu2=staticmethod(lambda x, y : (x.__v + y.__v) / 2)     # Méthode statique
    def __init__(self, v):
        self.__v=v

>>> p1=point(4)
>>> p2=point(8)
>>> point.milieu1(p1, p2)
6.0
>>> point.milieu2(p1, p2)
6.0

Comme on le voit, les méthodes milieu1() ou milieu2(), permettant de calculer le milieu barycentrique de deux points, ne peuvent pas s’appliquer à un point en particulier. Elles ne peuvent donc pas être utilisées comme des méthodes d’instance. Cependant, elles ne concernent que des points et s’appliquent donc à tous les points en général et peuvent par conséquent être intégrées dans l’objet point (ce qui renforce la modularité du code). La solution est d’en faire alors des « méthodes statiques ».

À noter : la méthode interne __milieu() étant définie dans la classe, elle bénéficie quand même d’un accès à tous les attributs de la classe, y compris ses attributs privés (elle reçoit en paramètre deux instances de point x et y, elle a alors accès à x.__v et y.__v). Et les méthodes statiques milieu1() et milieu2() ont aussi cette possibilité.

Et de même que pour les getters/setters/deleters, il existe un sucre syntaxique @staticmethod qui permet de raccourcir cette surcouche staticmethod().

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
class point:
    @staticmethod                            # Préparation à la méthode de classe
    def milieu(x, y):                        # Définition de la méthode de classe
        return (x.__v + y.__v) / 2
    def __init__(self, v):
        self.__v=v

>>> p1=point(4)
>>> p2=point(8)
>>> point.milieu(p1, p2)
6.0

XV-4. La fonction « classmethod() »

La fonction classmethod() permet de transformer une méthode pour lui faire recevoir en premier paramètre non plus l’instance, mais la classe qui l’évoque.

Prenons un exemple simple : un objet permettant de manipuler une date (jour, mois, année)…

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
class date:
    def __init__(self, jj, mm, aa):
        self.jj=jj
        self.mm=mm
        self.aa=aa

>>> a=date(1, 1, 2000)
>>> print(a.jj, a.mm, a.aa)
1 1 2000

L’utilisateur qui veut utiliser cet objet doit alors l’instancier selon une convention bien définie en lui passant les trois caractéristiques attendues (jour, mois et année) en argument.

Si maintenant l’utilisateur reçoit une date sous forme de chaîne ("jj/mm/aaaa"), il doit alors l’éclater pour pouvoir récupérer ses divers éléments avant de pouvoir créer son objet.

Si ce comportement se généralise, il peut alors avoir envie de modifier son objet pour qu’il puisse absorber ou une date classique ou une date chaîne. Petit travail facile de factorisation qui, en retour, lui simplifiera ensuite grandement l’écriture du code qui utilisera cet objet.

Une première solution sera alors de modifier __init__() pour l’autoriser à absorber les deux types de paramètres. Faisable, mais si demain un 3° format différent se présente, et un 4° après-demain…?

Une seconde solution sera de créer une méthode statique renvoyant un nouvel objet date :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
class date:
    def __init__(self, jj, mm, aa):
        self.jj=jj
        self.mm=mm
        self.aa=aa
    @staticmethod
    def fromString(string):
        (jj, mm, aa)=string.split("/")
        return date(jj, mm, aa)

>>> a=date.fromString("1/1/2000")
>>> print(a.jj, a.mm, a.aa)
1 1 2000

L’avantage c’est que si un troisième format d’entrée arrive demain, on pourra toujours rajouter une autre méthode fromXXX, fromYYY et ainsi de suite.

L’inconvénient c'est que la méthode utilise le nom de l’objet de façon littérale. Déjà cela induit des contraintes d’évolutivité (si le nom change demain…) et surtout cela peut poser des soucis liés à l’héritage (la méthode renvoie un objet date alors que l’utilisateur pourrait avoir envie d’un objet hérité).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
class date:
    def __init__(self, jj, mm, aa):
        self.jj=jj
        self.mm=mm
        self.aa=aa
    @staticmethod
    def fromString(string):
        return date(*string.split("/"))         # Remarquez l'utilisation de l'étoile (unpacking)…

class subDate(date): pass

>>> a=date.fromString("1/1/2000")
>>> type(a)
<class '__main__.date'>

>>> b=subDate.fromString("1/1/3000")
>>> type(b)
<class '__main__.date'>                         # Il voulait créer un subDate !!!

La bonne solution sera d’utiliser alors une méthode encapsulée dans une surcouche classmethod(). Cette méthode recevra alors en premier paramètre le nom de la classe qui l’invoque (généralement on stocke ce nom dans un paramètre conventionnellement nommé cls). L’utilisateur pourra alors utiliser ce nom de classe pour générer l’objet qui en découle.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
class date:
    def __init__(self, jj, mm, aa):
        self.jj=jj
        self.mm=mm
        self.aa=aa
    def __fromString(cls, string):
        return cls(*string.split("/"))
    fromString=classmethod(__fromString)

class subDate(date): pass

>>> a=date.fromString("1/1/2000")
>>> type(a)
<class '__main__.date'>

>>> b=subDate.fromString("1/1/3000")
>>> type(b)
<class '__main__.subDate'>                      # Il a bien son objet subDate !!!

Cette solution répond aux mêmes contraintes que la solution précédente, mais offre l’avantage de suivre l’évolution du code (si le nom de l’objet change, cela se fera de façon transparente sans avoir besoin de revérifier tout son contenu), ainsi que les contraintes liées à l'héritage.

Et de même que pour les getters/setters/deleters ou pour la méthode statique, le sucre syntaxique @classmethod est lui aussi autorisé.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
class date:
    def __init__(self, jj, mm, aa):
        self.jj=jj
        self.mm=mm
        self.aa=aa
    @classmethod
    def fromString(cls, string):
        return cls(*string.split("/"))

>>> a=date.fromString("1/1/2000")
>>> type(a)
<class '__main__.date'>

XV-5. Gestion interne des attributs

Dans ce chapitre, tous les attributs et méthodes décrits sont automatiquement créés par Python pour tout objet.

XV-5-a. L’attribut __dict__

L’attribut __dict__ référence tous les attributs créés dans l’instance.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
class xxx:
    def __init__(self):
        self.s="Hello"
        self.i=123

>>> x=xxx()
>>> print(x.__dict__)
{'s': 'Hello', 'i': 123}

Tout nouvel attribut viendra compléter cet attribut interne…

 
Sélectionnez
1.
2.
3.
>>> x.f=3.1416
>>> print(x.__dict__)
{'s': 'Hello', 'i': 123, 'f': 3.1416}

De même, tout remplissage manuel de cet attribut équivaudra à la création (ou la modification) de l’attribut correspondant…

 
Sélectionnez
1.
2.
3.
>>> x.__dict__["i"]=456
>>> print(x.i)
456

XV-5-b. La méthode __delattr__()

La méthode __delattr__() est automatiquement appelée à chaque fois qu’un attribut est supprimé de son instance.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
class xxx:
    def __init__(self):
        self.s="Hello"
        self.i=123
    def __delattr__(self, attr):
        print("deleting [%s]" % attr)
        super().__delattr__(attr)

>>> x=xxx()
>>> print(x.i)
123
>>> del x.i
deleting [i]
>>> print(x.i)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'xxx' object has no attribute 'i'

Toutefois, la méthode n’est pas invoquée si l’attribut est supprimé par le __dict__.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
>>> del x.__dict__["s"]
>>> print(x.s)
Traceback (most recent call last):
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'xxx' object has no attribute 's'

XV-5-c. La méthode __getattr__()

La méthode __getattr__() est automatiquement appelée à chaque fois qu’une instance invoque un attribut inexistant.

En revanche, pour des raisons de performances, si l’attribut existe, alors la méthode n’est pas appelée.

 
Sélectionnez
class xxx:
    def __init__(self):
        self.s="Hello"
        self.i=123
    def __getattr__(self, attr):
        print("getting [%s]" % attr)
        return "perdu"

>>> x=xxx()
>>> print(x.s)
Hello
>>> print(x.i)
123
>>> print(x.j)
getting [j]
Perdu

XV-5-d. La méthode __getattribute__()

La méthode __getattribute__() est automatiquement appelée à chaque fois qu’un attribut est invoqué par son instance.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
class xxx:
    def __init__(self):
        self.s="Hello"
        self.i=123
    def __getattribute__(self, attr):
        print("getting [%s]" % attr)
        return super().__getattribute__(attr)

>>> x=xxx()
>>> print(x.i)
getting [i]
123

Contrairement à ce qui se passe dans la méthode __delattr__(), ici, passer par le __dict__ pour tenter de contourner cette méthode en y cherchant directement l’attribut implique quand même un accès à l’attribut __dict__ ce qui provoque alors tout de même l’appel de la méthode.

 
Sélectionnez
1.
2.
3.
>>> print(x.__dict__["i"])
getting [__dict__]
123

XV-5-e. La méthode __setattr__()

La méthode __setattr__() est automatiquement appelée à chaque fois qu’un attribut est créé ou modifié par son instance.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
class xxx:
    def __init__(self):
        self.s="Hello"
        self.i=123
    def __setattr__(self, attr, value):
        print("setting [%s]: (%d)" % (attr, value))
        super().__setattr__(attr, value)

>>> x=xxx()
setting [s]: (Hello)
setting [i]: (123)
>>> print(x.i)
123
>>> x.i=456
setting [i]: (456)
>>> print(x.i)
456

Cependant, la méthode n’est pas invoquée si l’attribut est créé ou modifié par le __dict__ et la modification du __dict__ n’entraine pas non plus l’appel de cette méthode.

 
Sélectionnez
1.
2.
3.
>>> x.__dict__["i"]=789
>>> print(x.i)
789

XV-5-f. La méthode __new__()

En Python, la méthode spéciale __init__() est souvent appelée constructeur de l’objet. Il s’agit en fait d’un abus de langage : la méthode __init__() ne construit pas l’objet, elle intervient après la création de ce dernier pour l’initialiser.

Le vrai constructeur d’une classe est la méthode __new__(). Cette méthode est une méthode statique (un cas particulier qui n’a pas besoin d’être déclaré comme tel) qui prend une classe en premier paramètre (le paramètre self n’existe pas encore puisque l’objet n’est pas encore créé), et doit renvoyer l’objet nouvellement créé. Les autres paramètres sont identiques à ceux reçus par __init__() puisque la méthode doit ensuite appeler cette dernière en lui passant les paramètres qu’elle attend (ce qui se fait alors de façon automatique).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
class cercle:
    def __new__(cls, rayon):
        print("new [%s], rayon=%d" % (cls, rayon))
        return super().__new__(cls)
    def __init__(self, rayon):
        print("init [%s], rayon=%d" % (self, rayon))

>>> c=cercle(5)
new [<class '__main__.cercle'>], rayon=5
init [<__main__.cercle object at 0x7feff77427b8>], rayon=5
>>> print(c)
<__main__.cercle object at 0x7feff77427b8>

Cette méthode est particulièrement utile pour les objets immuables (str, tuple). En effet, il est impossible d’agir sur les objets dans la méthode __init__(), puisque celle-ci intervient après la création de l’objet, et que de fait, l’objet créé n’est alors plus modifiable.

Si l’on souhaite hériter d’un type immuable et agir sur l’initialisation de l’objet, il est donc nécessaire de redéfinir __new__(). Par exemple, pour créer une classe point qui hériterait de tuple…

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
class point(tuple):
    def __new__(cls, x, y):
        print("new [%s], x=%s, y=%s" % (cls, x, y))
        return super().__new__(cls, (x, y))
    x=property(lambda self: self[0])
    y=property(lambda self: self[1])

>>> t=point(2, 3)
new [<class '__main__.point'>], x=2, y=3
>>> print(t[0])
2
>>> print(t.x)
2
>>> print(t[1])
3
>>> print(t.y)
3

… tout en gardant le bénéfice de l’immuabilité du tuple.

 
Sélectionnez
1.
2.
3.
4.
>>> t.x=123
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: can't set attribute

XV-5-g. La méthode __init_subclass__()

En Python, la méthode spéciale __init_subclass__() est appelée par un objet quand il est instancié par l’un de ses héritiers.

C’est une méthode là aussi statique qui prend également une classe en premier paramètre.

Elle permet de créer si besoin des attributs statiques dans la classe fille en les créant « à la volée » dans la classe mère.

Prenons un exemple simple : une gestion de figures géométriques avec nombre de sommets.

Première solution : des classes simples…

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
class carre:
    sommets=4
    def __init__(self, cote):
        print("carre init: %s (cote=%d)" % (self, cote))
        self.cote=cote

class pentagone:
    sommets=5
    def __init__(self, cote):
        print("pentagone init: %s (cote=%d)" % (self, cote))
        self.cote=cote

>>> c=carre(10)
carre init: <__main__.carre object at 0x7f02cc573470> (cote=10)
>>> c.cote
10
>>> c.sommets
4
>>> p=pentagone(12)
pentagone init: <__main__.pentagone object at 0x7f02cc573438> (cote=12)
>>> p.cote
12
>>> p.sommets
5

C’est dommage qu’il n’y ait pas quelque part une espèce de centralisation des éléments redondants (ici le nombre de sommets).

Donc seconde solution : des classes qui héritent d’une classe mère en lui passant les sommets qu’elle a à gérer…

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
class figure:
    def __init__(self, sommets):
        print("figure init: %s (sommets=%d)" % (self, sommets))
        self.sommets=sommets

class carre(figure):
    def __init__(self, cote):
        super().__init__(4)
        print("carre init: %s (cote=%d)" % (self, cote))
        self.cote=cote

class pentagone(figure):
    def __init__(self, cote):
        super().__init__(5)
        print("pentagone init: %s (cote=%d)" % (self, cote))
        self.cote=cote

>>> c=carre(10)
figure init: <__main__.carre object at 0x7fcf87fea438> (sommets=4)
carre init: <__main__.carre object at 0x7fcf87fea438> (cote=10)
>>> c.cote
10
>>> c.sommets
4
>>> p=pentagone(12)
figure init: <__main__.pentagone object at 0x7fcf87fbcb38> (sommets=5)
pentagone init: <__main__.pentagone object at 0x7fcf87fbcb38> (cote=12)
>>> p.cote
12
>>> p.sommets
5

C’est fonctionnel, mais les sommets d’une figure n’ayant pas vocation à changer au cours du temps, c’est dommage de ne pas les stocker de façon statique.

D’où la 3° méthode : passer par __init_subclass__() qui aura pour rôle de paramétrer l’attribut statique de la classe mère en fonction de la configuration de la classe fille.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
class figure:
    def __init__(self):
        print("figure init: %s" % self)

    def __init_subclass__(cls, sommets):
        super().__init_subclass__()
        print("figure init_subclass: %s (sommets=%d)" % (cls, sommets))
        cls.sommets=sommets

>>> class carre(figure, sommets=4):
...    def __init__(self, cote):
...        super().__init__()
...        print("carre init: %s (cote=%d)" % (self, cote))
...        self.cote=cote
...
figure init_subclass: <class '__main__.carre'> (sommets=4)

>>> class pentagone(figure, sommets=5):
...    def __init__(self, cote):
...        super().__init__()
...        print("pentagone init: %s (cote=%d)" % (self, cote))
...        self.cote=cote
...
figure init_subclass: <class '__main__.pentagone'> (sommets=5)

>>> c=carre(10)
figure init: <__main__.carre object at 0x7fd04a525b70>
carre init: <__main__.carre object at 0x7fd04a525b70> (cote=10)
>>> print(c.cote)    
10
>>> print(c.sommets)
4
>>> p=pentagone(12)
figure init: <__main__.pentagone object at 0x7fd04a525b00>
pentagone init: <__main__.pentagone object at 0x7fd04a525b00> (cote=12)
>>> print(p.cote)
12
>>> print(p.sommets)
5

C’est un peu le principe en allégé de la métaclasse que l’on verra un peu plus tard.

XV-5-h. L’attribut __slots__

Il a été vu dans le chapitre sur les objets qu’il est possible de leur créer de nouveaux attributs de façon manuelle et externe…

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
class personne:
    def __init__(self, nom):
        self.nom=nom

>>> p=personne("LeRoi")
>>> p.prenom="Arthur"
>>> p.nom
'LeRoi'
>>> p.prenom
'Arthur'

… ce qui est rarement une bonne chose…

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
>>> p1=personne("LeRoi")
>>> p1.prenom="Arthur"
>>> p2=personne("Autre")
>>>
>>> for x in (p1, p2):
...    print(x.nom, x.prenom)
...
LeRoi, Arthur
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: personne' object has no attribute 'prenom'

L’attribut __slots__ (attribut statique) permet de protéger son objet contre de telles manipulations.

Il suffit de le créer dans son objet en lui donnant l’attribut ou la liste des attributs permis pour l’objet. Toute création d’un attribut non présent dans cette liste provoquera une exception.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
class personne:
    __slots__=("nom", "prenom")

    def __init__(self, nom, prenom):
        self.nom=nom
        self.prenom=prenom

>>> p=personne("LeRoi", "Arthur")
>>> p.age=800
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'personne' object has no attribute 'age'

Ce verrouillage se fait au travers de l’attribut __dict__ qui est alors purement supprimé de l’objet.

 
Sélectionnez
1.
2.
3.
4.
>>> print(p.__dict__)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'personne' object has no attribute '__dict__'

XV-6. Trucs et astuces divers sur les classes

XV-6-a. Rendre une classe « callable »

Une classe peut devenir « callable » ; c’est-à-dire qu’une instance de cette classe peut être appelée en tant que fonction ; par le biais de la méthode spécifique __call__().

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
class ccc:
    def __call__(self, x, y):
        print("ici (%s, %s)" % (x, y))

>>> x=ccc()                    # Création d’un objet "ccc" instancié dans une variable "x"
>>> x(2, 3)                    # Appel de "x" comme si c’était une fonction
ici (2, 3)

XV-6-b. Rendre une classe « indexable »

Une classe peut devenir « indexable » ; c’est-à-dire être utilisée comme si elle était une séquence (tuple, liste ou dictionnaire) dans laquelle on accèderait à un élément indexé ou slicé ; par le biais des méthodes spécifiques __getitem__() (pour récupérer un élément) et __setitem__() (pour positionner un élément).

Cela se pratique généralement quand la classe contient un attribut de nature séquentiel et qu’on désire accéder aux éléments distincts de l’attribut sans passer par son nom (surtout si cet attribut séquentiel est un attribut privé).

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
class maListe:
    def __init__(self, tab=None):
        self.__tab=list(tab or ())
    def __getitem__(self, idx):
        print("getitem (%d)" % idx)
        return self.__tab[idx]
    def __setitem__(self, idx, n):
        print("setitem (%d, %s)" % (idx, n))
        self.__tab[idx]=n

>>> m=maListe(("Pim", "Pam", "Poum"))
>>> print(m[2])
getitem (2)
Poum
>>> m[2]="toto"
setitem (2, toto)
>>> print(m[2])
toto

XV-6-c. Rendre une classe « itérable »

Une classe peut devenir « itérable » ; c’est-à-dire être utilisable dans une boucle d’itération ; par le biais de la méthode spécifique __iter__().

Cela se pratique généralement quand la classe contient un attribut de nature séquentielle et qu’on désire itérer directement sur les éléments distincts de l’attribut sans passer par son nom (surtout si cet attribut séquentiel est un attribut privé) ni avoir besoin de passer par son indexation.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
class maListe:
    def __init__(self, tab=None):
        self.__tab=list(tab or ())
    def __iter__(self):
        yield from self.__tab                # for x in self.__tab: yield x (en Python 2)

>>> m=maListe(("Pim", "Pam", "Poum"))
>>> for x in m : print(x)
Pim
Pam
Poum
>>> print(tuple(m))
('Pim', 'Pam', 'Poum')

Une telle classe qui se veut itérable peut aussi implémenter la méthode spécifique __next__() permettant alors d’y appliquer la fonction next().

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
class maListe:
    def __init__(self, tab=None):
        self.__tab=list(tab or ())
        self.__idx=0
    def __next__(self):
        if self.__idx == len(self.__tab):
            raise StopIteration
        t=self.__tab[self.__idx]
        self.__idx+=1
        return t

>>> m=maListe(("Pim", "Pam", "Poum"))
>>> while True: print(next(m))
...
Pim
Pam,
Poum
Traceback (most recent call last):
  File "<stdin>", line 13, in <module>
    while True: print(next(m))
  File "<stdin>", line 7, in __next__
    raise StopIteration
StopIteration

XV-6-d. Rendre une classe « mesurable »

Une classe peut devenir « mesurable » ; c’est-à-dire indiquer sa longueur ; par le biais de la méthode spécifique __len__().

Cela se pratique généralement quand la classe contient un attribut de nature séquentielle et qu’on désire avoir la longueur de cet attribut sans passer par son nom (surtout si cet attribut séquentiel est un attribut privé) ni avoir besoin de passer par son indexation.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
class maListe:
    def __init__(self, tab=None):
        self.__tab=list(tab or ())
    def __len__(self):
        return len(self.__tab)

>>> m=maListe(("Pim", "Pam", "Poum"))
>>> print(len(m))
3

XV-6-e. Indiquer une contenance dans une classe

Une classe peut indiquer une contenance ; c’est-à-dire vérifier si un élément est présent ou pas ; par le biais de la méthode spécifique __contains__().

Cela se pratique généralement quand la classe contient un attribut de nature séquentielle et qu’on désire simplement vérifier si cet attribut contient un élément particulier sans passer par son nom (surtout si cet attribut séquentiel est un attribut privé) ni avoir besoin de passer par son indexation ou son itération.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
class maListe:
    def __init__(self, tab=None):
        self.__tab=list(tab or ())
    def __contains__(self, x):
        return x in self.__tab

>>> m=maListe(("Pim", "Pam", "Poum"))
>>> print("Poum" in m)
True

XV-6-f. Définir les opérateurs usuels

Une classe peut définir des opérateurs usuels (comme l’addition, soustraction, etc.) permettant ensuite de l’utiliser dans des opérations classiques.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
class cPoint:
    def __init__(self, x=0, y=0):
        self.__x=x
        self.__y=y
    def __add__(self, n):                      # Redéfinit l’addition
        return cPoint(self.__x + n.__x, self.__y + n.__y)
    def __repr__(self):                        # Redéfinit la façon de représenter un objet
        return "x=%d, y=%d" % (self.__x, self.__y)

>>> a=cPoint(2, 3)
>>> b=cPoint(4, 5)
>>> a+b
x=6, y=8

Voici une liste non exhaustive des opérateurs les plus usuels :

  • mathématiques :

    • __add__ (addition) : permet d’appeler objet+n

    • __radd__ (addition renversée) : permet d’appeler n+objet,

    • __iadd__ (addition inplace) : permet d’appeler objet+=n (si cette méthode n’est pas présente, ce sera __add__ qui sera utilisée à la place),

    • __sub__ (soustraction) : permet d’appeler objet‑n

    • __rsub__ (soustraction renversée) : permet d’appeler n‑objet,

    • __isub__ (soustraction inplace) : permet d’appeler objet‑=n (si cette méthode n’est pas présente, ce sera __sub__ qui sera utilisée à la place),

    • __mul__ (multiplication) : permet d’appeler objet*n,

    • __rmul__ (multiplication renversée) : permet d’appeler n*objet,

    • __imul__ (multiplication inplace) : permet d’appeler objet*=n (si cette méthode n’est pas présente, ce sera __mul__ qui sera utilisée à la place),

    • __div__ (division dans Python 2, n’existe pas dans Python 3) : permet d’appeler objet/n,

    • __rdiv__ (division renversée dans Python 2, n’existe pas dans Python 3) : permet d’appeler n/objet,

    • __idiv__ (division inplace renversée dans Python 2, n’existe pas dans Python 3) : permet d’appeler objet/=n (si cette méthode n’est pas présente, ce sera __div__ qui sera utilisée à la place),

    • __floordiv__ (division entière) : permet d’appeler objet//n,

    • __rfloordiv__ (division entière renversée) : permet d’appeler n//objet,

    • __ifloordiv__ (division entière inplace) : permet d’appeler objet//=n (si cette méthode n’est pas présente, ce sera __floordiv__ qui sera utilisée à la place),

    • __truediv__ (division exacte dans Python 3, n’existe pas dans Python 2) : permet d’appeler objet/n,

    • __rtruediv__ (division exacte renversée dans Python 3, n’existe pas dans Python 2) : permet d’appeler n/objet,

    • __itruediv__ (division exacte inplace dans Python 3, n’existe pas dans Python 2) : permet d’appeler objet/=n (si cette méthode n’est pas présente, ce sera __truediv__ qui sera utilisée à la place),

    • __mod__ (modulo) : permet d’appeler objet%n,

    • __rmod__ (modulo renversé) : permet d’appeler n%objet,

    • __imod__ (modulo inplace) : permet d’appeler objet%=n (si cette méthode n’est pas présente, ce sera __mod__ qui sera utilisée à la place),

    • __pow__ (puissance) : permet d’appeler objet**n,

    • __rpow__ (puissance renversée) : permet d’appeler n**objet,

    • __ipow__ (puissance inplace) : permet d’appeler objet**=n (si cette méthode n’est pas présente, ce sera __pow__ qui sera utilisée à la place),

    • __and__ (« et » bit à bit) : permet d’appeler objet&n,

    • __rand__ (« et » bit à bit renversé) : permet d’appeler n&objet,

    • __iand__ (« et » bit à bit inplace) : permet d’appeler objet&=n (si cette méthode n’est pas présente, ce sera __and__ qui sera utilisée à la place),

    • __or__ (« ou » bit à bit) : permet d’appeler objet|n,

    • __ror__ (« ou » bit à bit renversé) : permet d’appeler n|objet,

    • __ior__ (« ou » bit à bit inplace) : permet d’appeler objet|=n (si cette méthode n’est pas présente, ce sera __or__ qui sera utilisée à la place),

    • __xor__ (« ou exclusif » bit à bit) : permet d’appeler objet^n,

    • __rxor__ (« ou » bit à bit renversé) : permet d’appeler n^objet,

    • __ixor__ (« ou » bit à bit inplace) : permet d’appeler objet^=n (si cette méthode n’est pas présente, ce sera __xor__ qui sera utilisée à la place),

    • __lshift__ (décalage vers la gauche) : permet d’appeler objet << n,

    • __ilshift__ (décalage vers la gauche inplace) : permet d’appeler objet<<=n (si cette méthode n’est pas présente, ce sera __lshift__ qui sera utilisée à la place),

    • __rshift__ (décalage vers la droite) : permet d’appeler objet >> n,

    • __irshift__ (décalage vers la droite inplace) : permet d’appeler objet>>=n (si cette méthode n’est pas présente, ce sera __rshift__ qui sera utilisée à la place),

    • __neg__ (passage en négatif) : permet d’appeler ‑objet,

    • __pos__ (identité neutre) : permet d’appeler +objet,

    • __inv__ (inversion bit à bit) : permet d’appeler ~objet,

    • __abs__ (valeur absolue) ;

  • booléens : __eq__ (égal à), __ne__ (différent de), __lt__ (inférieur à), __le__ (inférieur ou égal à), __gt__ (supérieur à), __ge__ (supérieur ou égal à), et . À noter sous Python 2 la présence de __cmp__ (comparaison riche devant renvoyer -1/0/1 si le premier élément est inférieur, égal ou supérieur au second élément et peut se substituer à tous les autres opérateurs booléens, mais ayant disparu dans Python 3).

XV-6-g. Redéfinir les casts

Une classe peut redéfinir des opérations de cast qui lui seront appliquées. Ainsi, elle peut redéfinir __bool__ (booléen), __float__ (flottant), __int__ (entier), __bytes__ (chaîne), __str__ (chaîne) : permettent de surcharger le cast d’un objet en bool, float, int, bytes, str. À noter sous Python 2 que __bool__ se nomme alors __nonzero__ ainsi que la présence supplémentaire de __long__ et __unicode__ qui permettent de surcharger le cast d’un objet en long et en unicode. De plus, __bytes__ n’existe pas dans cette version.

Elle peut aussi redéfinir __repr__ (représentation) utilisé par repr().

XV-6-h. Rendre une classe « hashable »

Une classe peut devenir « hashable » ; c’est-à-dire pouvoir faire l’objet d’un calcul d’empreinte numérique ; par le biais de la méthode spécifique __hash__().

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
class maClass:
    def __hash__(self):
        return 123

>>> m=maClass()
>>> print(hash(m))
123

XV-6-i. Gérer la liste des attributs listés

Il est possible de contrôler la liste des attributs donnés par la fonction dir() ; par le biais de la méthode spécifique __dir__(). Cette méthode, si elle est implémentée, doit impérativement renvoyer un itérable qui sera finalement transformé en liste.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
class maClass:
    def __dir__(self):
        return "123"

>>> m=maClass()
>>> dir(m)
[1, 2, 3]

XV-6-j. Gérer le formatage

Il est possible de contrôler l’affichage d’une classe grâce à la méthode format() ou celle des « f-strings ».

 
Sélectionnez
class maClass:
    def __init__(self, n):
        self.n=n
    def __format__(self, fmt):
        print("fmt: [%s]" % fmt)
        return "{0:{1}}".format(self.n, fmt)

>>> m=maClass(42)
>>> print("{:05d}".format(m))
fmt: [05d]
00042
>>> print(f"{m:04d}")
fmt: [04d]
0042

XV-7. Les fonctions sont aussi des objets

Toute fonction étant elle aussi un objet, cela permet alors d'y créer des attributs personnels, et ce, même en dehors du corps de la fonction…

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
def toto(n):
    toto.args.append(n)
    print("Historique des arguments: %s" % toto.args)

>>> toto.args=[]
>>> toto(1)
Historique des arguments: [1]
>>> toto(2)
Historique des arguments: [1, 2]
>>> toto(3)
Historique des arguments: [1, 2, 3]

De plus, elle possède aussi des attributs et méthodes internes qui permettent au programmeur Python de les inspecter ou de les manipuler directement dans son code.

Voici une liste non exhaustive des éléments pouvant être utiles :

  • fonction.__name__ : nom de la fonction ;

  • fonction.__doc__ : documentation de la fonction (sera vu ultérieurement) ;

  • fonction.__qualname__ : nom complet (dans le cas d'une fonction incluse dans un objet) de la fonction (n'existe pas dans Python 2) ;

  • fonction.__module__ : nom du module de la fonction (sera vu ultérieurement) ;

  • fonction.__defaults__ : tuple contenant les valeurs par défaut des arguments placés avant *args ;

  • fonction.__kwdefaults__ : dictionnaire contenant les valeurs par défaut des arguments placés entre *args et **kwargs (n'existe pas dans Python 2 dans lequel *args est collé à **kwargs) ;

  • fonction.__code__ : code de la fonction ;

  • fonction.__code__.co_varnames : liste des paramètres de la fonction (y compris *args et **kwargs s'ils y sont) ;

  • fonction.__code__.co_argcount : nombre de paramètres placés avant *args ;

  • fonction.__code__.co_kwonlyargcount : nombre de paramètres placés entre *args et **kwargs (n'existe pas dans Python 2 dans lequel *args est collé à **kwargs).


précédentsommairesuivant

Copyright © 2022 Svear (svear@free.fr) Permission est accordée de copier, distribuer ou modifier ce document selon les termes de la « Licence de Documentation Libre GNU » (GNU Free Documentation License), version 1.1 ou toute version ultérieure publiée par la Free Software Foundation.