1) Qu'est-ce que la métaclasse et quand l'utiliser ?
Les métaclasses sont aux classes ce que les classes sont aux objets. Ce sont des classes pour les classes (d'où l'expression "méta").
Les métaclasses sont généralement utilisées lorsque l'on souhaite travailler en dehors des contraintes normales de la POO.
2) Quelle est la différence/similitude entre la métaclasse et l'héritage ?
Une métaclasse ne fait pas partie de la hiérarchie des classes d'un objet, contrairement aux classes de base. Ainsi, lorsqu'un objet obj.some_method()
il ne recherchera pas cette méthode dans la métaclasse, bien que celle-ci puisse l'avoir créée lors de la création de la classe ou de l'objet.
Dans l'exemple ci-dessous, la métaclasse MetaCar
donne aux objets une defect
sur la base d'un nombre aléatoire. L'attribut defect
n'est défini dans aucune des classes de base des objets ni dans la classe elle-même. Cela aurait toutefois pu être réalisé en utilisant uniquement des classes.
Cependant (contrairement aux classes), cette métaclasse réoriente également la création d'objets ; dans la classe some_cars
toutes les Toyota sont créées à l'aide de l'option Car
classe. La métaclasse détecte que Car.__init__
contient un make
qui correspond à une classe préexistante portant ce nom et renvoie donc un objet de cette classe à la place.
En outre, vous noterez que dans la rubrique some_cars
liste, Car.__init__
est appelé avec make="GM"
. A GM
n'a pas été définie à ce stade de l'évaluation du programme. La métaclasse détecte qu'il n'existe pas de classe portant ce nom dans l'argument make, elle en crée donc une et met à jour l'espace de noms global (de sorte qu'elle n'a pas besoin d'utiliser le mécanisme de retour). Elle crée ensuite l'objet à l'aide de la classe nouvellement définie et le renvoie.
import random
class CarBase(object):
pass
class MetaCar(type):
car_brands = {}
def __init__(cls, cls_name, cls_bases, cls_dict):
super(MetaCar, cls).__init__(cls_name, cls_bases, cls_dict)
if(not CarBase in cls_bases):
MetaCar.car_brands[cls_name] = cls
def __call__(self, *args, **kwargs):
make = kwargs.get("make", "")
if(MetaCar.car_brands.has_key(make) and not (self is MetaCar.car_brands[make])):
obj = MetaCar.car_brands[make].__call__(*args, **kwargs)
if(make == "Toyota"):
if(random.randint(0, 100) < 2):
obj.defect = "sticky accelerator pedal"
elif(make == "GM"):
if(random.randint(0, 100) < 20):
obj.defect = "shithouse"
elif(make == "Great Wall"):
if(random.randint(0, 100) < 101):
obj.defect = "cancer"
else:
obj = None
if(not MetaCar.car_brands.has_key(self.__name__)):
new_class = MetaCar(make, (GenericCar,), {})
globals()[make] = new_class
obj = new_class(*args, **kwargs)
else:
obj = super(MetaCar, self).__call__(*args, **kwargs)
return obj
class Car(CarBase):
__metaclass__ = MetaCar
def __init__(self, **kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
def __repr__(self):
return "<%s>" % self.description
@property
def description(self):
return "%s %s %s %s" % (self.color, self.year, self.make, self.model)
class GenericCar(Car):
def __init__(self, **kwargs):
kwargs["make"] = self.__class__.__name__
super(GenericCar, self).__init__(**kwargs)
class Toyota(GenericCar):
pass
colours = \
[
"blue",
"green",
"red",
"yellow",
"orange",
"purple",
"silver",
"black",
"white"
]
def rand_colour():
return colours[random.randint(0, len(colours) - 1)]
some_cars = \
[
Car(make="Toyota", model="Prius", year=2005, color=rand_colour()),
Car(make="Toyota", model="Camry", year=2007, color=rand_colour()),
Car(make="Toyota", model="Camry Hybrid", year=2013, color=rand_colour()),
Car(make="Toyota", model="Land Cruiser", year=2009, color=rand_colour()),
Car(make="Toyota", model="FJ Cruiser", year=2012, color=rand_colour()),
Car(make="Toyota", model="Corolla", year=2010, color=rand_colour()),
Car(make="Toyota", model="Hiace", year=2006, color=rand_colour()),
Car(make="Toyota", model="Townace", year=2003, color=rand_colour()),
Car(make="Toyota", model="Aurion", year=2008, color=rand_colour()),
Car(make="Toyota", model="Supra", year=2004, color=rand_colour()),
Car(make="Toyota", model="86", year=2013, color=rand_colour()),
Car(make="GM", model="Camaro", year=2008, color=rand_colour())
]
dodgy_vehicles = filter(lambda x: hasattr(x, "defect"), some_cars)
print dodgy_vehicles
3) Où doit-on utiliser les métaclasses ou l'héritage ?
Comme indiqué dans cette réponse et dans les commentaires, il faut presque toujours utiliser l'héritage lorsqu'on fait de la POO. Les métaclasses permettent de travailler en dehors de ces contraintes (voir l'exemple) et ne sont presque jamais nécessaires. extrêmement dynamique Il est possible d'obtenir un flux de programmes avec eux. C'est à la fois leur force et leur danger .