理解Python中的元类和继承

91

关于元类,我有些困惑。

通过继承

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
经验法则:如果不需要元类,就不要使用元类。 - user2357112
4
如果你需要问自己是否需要元类,那么你就不需要元类。 - Ignacio Vazquez-Abrams
6
这不是关于“元类是什么”的重复讨论,而是关于“元类 vs 继承”之间的比较讨论。 - Mateen Ulhaq
1个回答

60

1)什么是元类,什么时候使用它?

元类就像类对于对象一样,是类的类(因此称为“元”)。通常在想要超越面向对象编程常规限制时使用元类。

2)元类和继承的区别/相似之处是什么?

元类不是对象的类继承层次结构的一部分,而基类是。因此,当对象执行obj.some_method()时,它不会搜索元类中是否存在该方法,但是元类可能在类或对象创建期间创建了该方法。

在下面的示例中,元类MetaCar根据随机数为对象提供defect属性。这个defect属性没有在任何对象的基类或类本身中定义。然而,这可以仅使用类实现。

然而(与类不同),这个元类还重新路由对象的创建;在some_cars列表中,所有的Toyota都是使用Car类创建的。元类检测到Car.__init__包含一个与那个名称匹配的预先存在的类的make参数,因此返回该类的对象。

此外,您还会注意到在some_cars列表中,Car.__init__被调用时使用了make="GM"。此时程序的评估过程中还没有定义一个GM类。元类检测到在制造参数中不存在该名称的类,则创建并更新全局命名空间(因此不需要使用返回机制)。然后使用新定义的类创建对象并返回它。

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)时使用继承。元类用于在这些约束之外工作(请参阅示例),几乎总是不必要的,但某些非常高级和极其动态的程序流可以通过它们实现。这既是它们的优点,也是它们的危险所在


谢谢你,迪尔伯特..解释得非常好!可以从以下内容中注意到这种差异: 使用元类,issubclass(Car,AttributeInitType)返回-> False - Nikhil Rupanawar
9
没问题。我不知道为什么这被标记为重复;另一个帖子并没有真正说明为什么,也没有对普通面向对象编程实践进行比较和对比。 - dilbert
在Python 3中,您的示例会出现“RecursionError:maximum recursion depth exceeded while calling a Python object”错误。 - Bhanu Tez

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接