Подтвердить что ты не робот

Понимание метакласса и наследования в Python

У меня есть путаница в отношении метаклассов.

С наследованием

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

Как приведенный выше пример не полезен практически, а просто для понимания,

У меня есть некоторые вопросы:

  • Что такое использование метакласса и когда я его использую?

  • В чем разница/сходство между мета-классом и наследованием?

  • Где использовать мета-класс или наследование?

4b9b3361

Ответ 1

1) что такое использование метакласса и когда его использовать?

Метаклассы относятся к классам, поскольку классы относятся к объектам. Это классы для классов (отсюда и выражение "мета" ).

Метаклассы обычно используются, когда вы хотите работать вне обычных ограничений ООП.

2) какова разница/сходство между метаклассом и наследованием?

Метакласс не является частью иерархии классов объектов, тогда как базовые классы. Поэтому, когда объект выполняет "obj.some_method()", он не будет искать метакласс для этого метода, однако метакласс может создать его во время создания класса или объекта.

В этом примере ниже метакласс meta_car предоставляет объектам атрибут "дефект", основанный на случайном числе. Атрибут "дефект" не определен ни в одном из базовых классов объектов или самого класса. Это, однако, могло быть достигнуто только с использованием классов.

Однако (в отличие от классов) этот метакласс также перенаправляет создание объекта; в списке some_cars все Toyotas создаются с использованием класса Car. Метакласс определяет, что Car __init__ содержит аргумент make, который соответствует ранее существующему классу этим именем и поэтому возвращает объект этого класса.

Кроме того, вы также заметите, что в списке some_cars вызывается Car __init__ с make = "GM". На данный момент в оценке программы класс GM не определен. Метакласс определяет, что класс не существует этим именем в аргументе make, поэтому он создает один и обновляет глобальное пространство имен (поэтому ему не нужно использовать механизм возврата). Затем он создает объект, используя новый класс и возвращает его.

import random

class CarBase(object):
    pass

class meta_car(type):
    car_brands = {}
    def __init__(cls, cls_name, cls_bases, cls_dict):
        super(meta_car, cls).__init__(cls_name, cls_bases, cls_dict)
        if(not CarBase in cls_bases):
            meta_car.car_brands[cls_name] = cls

    def __call__(self, *args, **kwargs):
        make = kwargs.get("make", "")
        if(meta_car.car_brands.has_key(make) and not (self is meta_car.car_brands[make])):
            obj = meta_car.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 meta_car.car_brands.has_key(self.__name__)):
                new_class = meta_car(make, (GenericCar,), {})
                globals()[make] = new_class
                obj = new_class(*args, **kwargs)
            else:
                obj = super(meta_car, self).__call__(*args, **kwargs)
        return obj

class Car(CarBase):
    __metaclass__ = meta_car

    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), где следует использовать метакласс или наследование?

Как упоминалось в этом ответе и в комментариях, почти всегда используйте наследование при выполнении ООП. Метаклассы предназначены для работы вне этих ограничений (см. Пример) и почти всегда не нужны, однако с ними может быть достигнут очень продвинутый и чрезвычайно динамичный поток программ. Это и их сила, и их опасность.