在Python中,我何时应该使用元类?

6
我已经阅读了这篇文章:Python中的元类是什么?。但是,有没有人可以更具体地解释一下何时应该使用元类概念以及何时非常方便呢?假设我有一个如下所示的类:
 class Book(object):
    CATEGORIES = ['programming','literature','physics']
    def _get_book_name(self,book):
        return book['title']
    def _get_category(self, book):
        for cat in self.CATEGORIES:
            if book['title'].find(cat) > -1:
                return cat  
        return "Other"


if __name__ == '__main__':
    b = Book()
    dummy_book = {'title':'Python Guide of Programming', 'status':'available'}
    print b._get_category(dummy_book)

针对这个类。

在什么情况下我应该使用元类,以及为什么它有用?

提前感谢。


13
元类的第一条规则是:如果你不知道需要一个元类,那么你不需要元类。 - Ignacio Vazquez-Abrams
5个回答

9

当您想要在创建过程中改变类时,使用元类。元类很少需要使用,难以调试和理解,但有时可以使框架更易于使用。在我们的600Kloc代码库中,我们使用了7次元类:一次是ABCMeta,4次是Django的models.SubfieldBase,另外两次是一个将类用作Django视图的元类。正如@Ignacio所写,如果您不知道需要元类(并已考虑过所有其他选项),则不需要元类。


5
即使你想改变类,使用类装饰器通常会更简单且同样强大。只有最高级的操作才能从元类中受益。 - user395760

4
从概念上讲,类的存在是为了定义一组对象(即类的实例)共同拥有的特性。这就是全部。它使您可以根据类定义的共享模式来思考类的实例。如果每个对象都不同,我们就不会使用类,而是使用字典。
元类是一个普通的类,它存在的原因也是相同的; 定义其实例共同拥有的内容。默认元类type提供了所有常规规则,使类和实例按照您习惯的方式工作,例如:
- 实例上的属性查找检查实例,然后是其类,然后是MRO顺序中的所有超类 - 调用MyClass(*args,**kwargs)会调用i = MyClass.__new__(MyClass,*args,**kwargs)以获取实例,然后调用i.__init__(*args,**kwargs)进行初始化 - 类是通过将类块中的定义绑定到类的属性中来创建的 - 等等
如果您想要一些与正常类不同的类,则可以定义一个元类,并使您的不寻常的类成为元类的实例,而不是type。您的元类几乎肯定是type的子类,因为您可能不希望使不同种类的类完全不同; 就像您可能希望有一些子集的书籍表现得有点不同(例如,是其他作品的汇编),并使用Book的子类而不是完全不同的类。
如果您不想定义使某些类与正常类的工作方式不同的方法,则元类可能不是最合适的解决方案。请注意,“类定义其实例如何工作”已经是一个非常灵活和抽象的范例; 大多数时候,您不需要更改类的工作方式。
如果您在Google上搜索,您会看到许多元类的示例实际上只是用于在类创建周围执行一堆操作的;通常自动处理类属性或自动从某处找到新属性。我不会真的称这些为元类的好用途。它们没有改变类的工作方式,它们只是处理一些类。在我看来,创建类的工厂函数、立即在类创建后调用的类方法或最好的类装饰器都是实现此类事情的更好方法。
但是有时您会发现自己编写复杂的代码以使Python的默认类行为执行某些概念上简单的操作,实际上将其实现在元类级别上会有所帮助。
一个相当简单的例子是“单例模式”,其中你有一个类,只能有一个实例;如果已经创建了一个实例,则调用该类将返回该现有实例。我个人反对单例并不建议使用它们(我认为它们只是全局变量,巧妙地伪装成新创建的实例,以便更有可能导致微妙的错误)。但人们使用它们,并且有大量的用__new____init__制作单例类的方法。用这种方式做起来可能有点烦人,主要是因为Python想要调用__new__,然后调用该结果的__init__,所以你必须找到一种方法来避免每次有人请求访问单例时重新运行初始化代码。但如果我们可以直接告诉Python我们在调用类时想要发生什么,而不是试图设置Python想要做的事情,以便最终做我们想要的事情,那岂不是更容易些?
class Singleton(type):
    def __init__(self, *args, **kwargs):
        super(Singleton, self).__init__(*args, **kwargs)
        self.__instance = None

    def __call__(self, *args, **kwargs):
        if self.__instance is None:
            self.__instance = super(Singleton, self).__call__(*args, **kwargs)
        return self.__instance

在不到10行的代码中,只需添加__metaclass__ = Singleton,即可将普通类转换为单例模式,也就是说,它们仅仅是一个声明,表明它们是单例模式。这种实现方式比直接在类级别上进行修改更加容易。但对于你的Book类来说,似乎没有必要使用元类。除非你发现普通类的规则阻止了你以一种简单的方式完成某些操作(这与“我希望不必为所有这些类打那么多字,我想知道是否可以自动生成公共部分”是不同的)。事实上,尽管我每天都在工作中使用Python,但我从未真正用过元类来完成实际任务;我的所有元类都是像上面的Singleton一样的玩具示例或者无聊的探索。

4
一个元类在需要覆盖类的默认行为,包括它们的创建时使用。一个类从名称,基类元组和类字典创建。你可以截取创建过程,对这些输入进行更改。你也可以覆盖类提供的任何服务,包括__call__(用于创建实例),__getattribute__(用于查找类上的属性和方法),__setattr__(控制设置属性)和__repr__(控制类的显示方式)。总之,当需要控制类的创建方式或者需要更改类提供的任何服务时,就会使用元类。

1

如果您因为某种原因想要执行像 Class[x]x in Class 等操作,您必须使用元类:

class Meta(type):
    def __getitem__(cls, x):
        return x ** 2

    def __contains__(cls, x):
        return int(x ** (0.5)) == x ** 0.5

# Python 2.x
class Class(object):
    __metaclass__ = Meta

# Python 3.x
class Class(metaclass=Meta):
    pass

print Class[2]
print 4 in Class

3
想要做某件事情,但这真的是最好的选择吗?很少,甚至从未有过。我想不出任何时候想要做这种事情是正确的方式,即使我曾经想过,这也会强烈提示我我是在错误的道路上。 - Chris Morgan

-2

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