J'ai une certaine confusion concernant les méta-classes.
class AttributeInitType(object):
def __init__(self,**kwargs):
for name, value in kwargs.items():
setattr(self, name, value)
class Car(AttributeInitType):
def __init__(self,**kwargs):
super(Car, self).__init__(**kwargs)
@property
def description(self):
return "%s %s %s %s" % (self.color, self.year, self.make, self.model)
c = Car(make='Toyota', model='Prius', year=2005, color='green')
print c.description
class AttributeInitType(type):
def __call__(self, *args, **kwargs):
obj = type.__call__(self, *args)
for name, value in kwargs.items():
setattr(obj, name, value)
return obj
class Car(object):
__metaclass__ = AttributeInitType
@property
def description(self):
return "%s %s %s %s" % (self.color, self.year, self.make, self.model)
c = Car(make='Toyota', model='Prius', year=2005,color='blue')
print c.description
Comme l'exemple ci-dessus n'est pas utile dans la pratique, mais juste pour comprendre,
J'ai quelques questions comme,
Qu'est-ce que la métaclasse et quand dois-je l'utiliser?
Quelle est la différence/similitude entre une méta-classe et l'héritage?
Où doit-on utiliser une méta-classe ou l'héritage?
1) À quoi sert la métaclasse et quand l'utiliser?
Les métaclasses sont aux classes comme les classes 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 vous souhaitez 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 fait obj.some_method()
, il ne recherchera pas la métaclasse pour cette méthode, mais la métaclasse peut l'avoir créée lors de la création de la classe ou de l'objet.
Dans cet exemple ci-dessous, la métaclasse MetaCar
donne aux objets un attribut defect
basé sur un nombre aléatoire. L'attribut defect
n'est défini dans aucune des classes de base des objets ou dans la classe elle-même. Cependant, cela aurait 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 le some_cars
liste, tous les Toyotas sont créés en utilisant la classe Car
. La métaclasse détecte que Car.__init__
contient un argument make
qui correspond à une classe préexistante de ce nom et renvoie donc un objet de cette classe à la place.
De plus, vous remarquerez également que dans le some_cars
liste, Car.__init__
est appelé avec make="GM"
. Une classe GM
n'a pas été définie à ce stade de l'évaluation du programme. La métaclasse détecte qu'une classe n'existe pas sous ce nom dans l'argument make, elle en crée donc une et met à jour l'espace de noms global (elle n'a donc pas besoin d'utiliser le mécanisme de retour). Il 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 la métaclasse ou l'héritage?
Comme mentionné dans cette réponse et dans les commentaires, utilisez presque toujours l'héritage lorsque vous effectuez la POO. Les métaclasses sont destinées à travailler en dehors de ces contraintes (voir l'exemple) et ne sont presque toujours pas nécessaires, mais un flux de programme très avancé et extrêmement dynamique peut être obtenu avec elles. C'est à la fois leur force et leur danger.