web-dev-qa-db-ja.com

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

上記の例は実用的ではありませんが、理解のためだけに役立ちます。

いくつか質問があります

  1. メタクラスの使用とは何ですか?いつ使用しますか?

  2. メタクラスと継承の違い/類似点は何ですか?

  3. どこでメタクラスまたは継承を使用する必要がありますか?

55

1)メタクラスの使用とは何ですか?

クラスがオブジェクトに対するものであるように、メタクラスはクラスに対するものです。これらはクラスのクラスです(したがって、「メタ」という表現)。

メタクラスは、通常、OOPの通常の制約の外で作業する場合に使用します。

2)メタクラスと継承の違い/類似点は何ですか?

メタクラスはオブジェクトのクラス階層の一部ではありませんが、基本クラスはそうです。したがって、オブジェクトがobj.some_method()を実行すると、このメソッドのメタクラスは検索されませんが、クラスまたはオブジェクトの作成中にメタクラスが作成した可能性があります。

以下のこの例では、メタクラスMetaCarは、乱数に基づいてオブジェクトにdefect属性を与えます。 defect属性は、オブジェクトの基本クラスまたはクラス自体のいずれにも定義されていません。ただし、これはクラスのみを使用して実現できます。

ただし(クラスとは異なり)、このメタクラスはオブジェクトの作成も再ルーティングします。 some_carsリストでは、すべてのトヨタはCarクラスを使用して作成されます。メタクラスは、Car.__init__にその名前で既存のクラスと一致するmake引数が含まれていることを検出し、代わりにそのクラスのオブジェクトを返します。

さらに、some_carsリストでは、Car.__init__make="GM"で呼び出されることにも注意してください。 GMクラスは、プログラムの評価のこの時点では定義されていません。メタクラスは、make引数内にその名前ではクラスが存在しないことを検出するため、クラスを作成し、グローバル名前空間を更新します(したがって、リターンメカニズムを使用する必要はありません)。次に、新しく定義されたクラスを使用してオブジェクトを作成し、それを返します。

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)どこでメタクラスまたは継承を使用する必要がありますか?

この回答とコメントで述べたように、OOPを行うときはほとんど常に継承を使用します。メタクラスはこれらの制約の外で動作するためのものであり(例を参照)、ほとんどの場合必要ではありませんが、非常に高度で、非常に動的プログラムフローを実現できます。これは、彼らの強さと危険の両方です。

37
dilbert