了解 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. 应该在哪里使用元类或继承?

23489 次浏览

1) What is use of metaclass and when to use it?

Metaclasses are to classes as classes are to objects. They are classes for classes (hence the expression "meta").

Metaclasses are typically for when you want to work outside of the normal constraints of OOP.

2) What is difference/similarity between metaclass and inheritance?

A metaclass is not part of an object's class hierarchy whereas base classes are. So when an object does obj.some_method() it will not search the metaclass for this method however the metaclass may have created it during the class' or object's creation.

In this example below, the metaclass MetaCar gives objects a defect attribute based on a random number. The defect attribute is not defined in any of the objects' base classes or the class itself. This, however, could have been achieved using classes only.

However (unlike classes), this metaclass also re-routes object creation; in the some_cars list, all the Toyotas are created using the Car class. The metaclass detects that Car.__init__ contains a make argument that matches a pre-existing class by that name and so returns a object of that class instead.

Additionally, you'll also note that in the some_cars list, Car.__init__ is called with make="GM". A GM class has not been defined at this point in the program's evaluation. The metaclass detects that a class doesn't exist by that name in the make argument, so it creates one and updates the global namespace (so it doesn't need to use the return mechanism). It then creates the object using the newly defined class and returns it.

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) Where should one use metaclass or inheritance?

As mentioned in this answer and in the comments, almost always use inheritance when doing OOP. Metaclasses are for working outside those constraints (refer to example) and is almost always not necessary however some very advanced and extremely dynamic program flow can be achieved with them. This is both their strength and their danger.