POO

MRO

Quoi qu’il arrive, en python2 il faut hériter de object. On bénéficie alors du MRO, qui permet de se débrouiller avec l’héritage multiple. Cf. le tuto de Makina Corpus.

En python3 tous les objets héritent de object par défaut.

Setters/Getters

via des méthodes génériques

Les setters et getters sont implicites en python, on peut cependant les créer pour permettre une validation des entrées/sorties.

Les méthodes object.__getattr__(), object.__setattr__() et object.__delattr__() sont là pour intéragir avec des attributs existants ou non.

Pour les objets héritants de object, on a également accès à la méthode object.__getattribute__().

via @property

On peut également passer par les décorateurs property.

Transforme une méthode en attribut (read-only):

>>> class Parrot(object):
...     def __init__(self):
...         self._voltage = 100000
...
...     @property
...     def voltage(self):
...         """Get the current voltage."""
...         return self._voltage
>>> parrot = Parrot()
>>> parrot.voltage
100000
>>> parrot.voltage = 50
Traceback (most recent call last):
  File "<input>", line 1, in <module>
AttributeError: can't set attribute
>>> parrot._voltage = 40
>>> parrot.voltage
40

Si on veut mettre en place des setter et deleter, la classe Parrot devient:

class Parrot(object):

    def __init__(self):
        self._voltage = 10000

    @property
    def voltage(self):
        return self._voltage

    @voltage.setter
    def voltage(self, value):
        self._voltage = value

    @voltage.deleter
    def voltage(self):
        raise Exception("Impossible de supprimer cet élément")

Attributs privés

En python l’attribut privé est plus une convention qu’une règle. L’attribut privé d’instance se préfixe d’un underscore (_spam)

On peut également créer des attributs privés de classe préfixés de deux underscore et suffixé d’un underscore ou moins (__spam ou __spam_). Ces attributs sont immédiatements renommés (_ClassName__spam ou _ClassName__spam_). C’est ce qu’on appèle du name mangling

>>> class A(object):
...     __bonjour = "Hello"
...
>>> class B(A):
...     __bonjour = "World"
...
>>> print(B._A__bonjour)
Hello
>>> print(B._B__bonjour)
World
>>> print(B.__yo)  # L'attribut de base disparait
Traceback (most recent call last):
    ...
    B.__bonjour
AttributeError: type object 'B' has no attribute '__bonjour'

Attributs spéciaux

Attribut

Description

__call__

Rend l’objet appellable

__dict__

Dictionnaire contenant tous les constantes, attributs et méthodes de l’objet/la classe

__slots__

Pour la linéarisation d’objets, on sélectionne les attributs qui seront conservés en mémoire (à la manière de __all__ pour les modules)

__[a-Z0-9]+_?

Les attributs préfixés de 2 « _ » et d’un « _ » au plus en suffixe sont des attributs spéciaux. Ils n’est pas possible de les overrider dans les classes filles.

D’autres attributs sont disponibles ici.

Métaclasses

Fabriquer/Modifier des classes à la volée, équivalent des lambda mais pour les classes.

Le constructeur d’une classe se fait en deux étapes.

  1. Le __new__ s’occupe de créer la classe

  2. le __init__ s’occupe de créer de l’instance.

En définissant le __new__() on peut donc créer une classe en lui ajoutant des attributs et méthodes.

Note

Pour créer une métaclasse, il faut la faire hériter de type.

class MyClass(type):
    def __new__(cls, name):
        # ...

On peut également créer des métaclasse grâce à l’outils abc.

On va également pouvoir modifier le comportement de la classe (et non plus de l’instance). On peut par exemple faire des surcharges de méthodes spéciales:

# Classe n'ayant pas de métaclasse particulière
class A:
    pass

print(A)  # affiche "<class '__main__.A'>"

# *** Exemple avec une métaclasse ***
# déclaration de la métaclasse
class MyMeta(type):
    def __str__(self):
        return "<my super class '{}'>".format(self.__name__)

# déclaration de la classe utilisant la métaclasse
class A(metaclass=MyMeta):
    pass

print(A)  # affiche "<my super class 'A'>"

On modifie donc bien le comportement de la classe et non plus de l’objet.

Des articles explicatifs ici et

Cependant les métaclasses sont plutôt lourdes à mettre en place, et beaucoup d’utilisations ne nécessitent pas tout le paquetage. Ils est donc parfois plus simple d’utiliser __init_subclass__(), comme montré ici et .

Singleton

Cet objet ne peut être instancié qu’une seule fois. C’est dans la méthode __new__() que cela doit être fait.

class MySingleton():

    _instance = None

    def __new__(cls, *args, **kwargs):

        if cls._instance is None:
            cls._instance = super(MySingleton, cls).__new__(cls, *args, **kwargs)

        return cls._instance

Warning

Le désavantage de cette méthode est qu’en cas d’héritage multiple une classe fille peut surcharger __new__.

Pour contrer cet effet il faut passer par une métaclasse.

class MySingleton(type):

    _instance = None

    def __call__(cls, *args, **kwargs):

        if cls._instances is None:
            cls._instances = super(Singleton, cls).__call__(*args, **kwargs)

        return cls._instances

#Python2
class MyClass(BaseClass):
    __metaclass__ = MySingleton

#Python3
class MyClass(BaseClass, metaclass=MySingleton):
    pass

Il existe un pattern de Singleton alternatif : le Borg. Il permet le partage des états entre objets et non de l’instance.

Les patterns

Brandon Rhodes fait un site détaillant les Designs Patterns appliqués à Python