91 votes

Comprendre les métaclasses et l'héritage en Python

J'ai une certaine confusion concernant les métaclasses.

Avec l'héritage

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

Avec la classe méta

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

L'exemple ci-dessus n'est pas utile en pratique, mais juste pour la compréhension,

J'ai quelques questions,

  1. Quelle est la différence/similitude entre une méta-classe et l'héritage ?

  2. Où doit-on utiliser une méta-classe ou un héritage ?

60voto

dilbert Points 2818

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 .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X