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 |
---|---|
|
Rend l’objet appellable |
|
Dictionnaire contenant tous les constantes, attributs et méthodes de l’objet/la classe |
|
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) |
|
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.
Le __new__ s’occupe de créer la classe
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 là
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 là.
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