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

时间:2012-04-28 06:24:53

标签: python oop

我已经完成了这个:What is a metaclass in 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)

这个班。

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

提前致谢。

5 个答案:

答案 0 :(得分:8)

当您想要在创建时改变它时,可以使用元类。几乎不需要元类,它们很难调试,而且很难理解 - 但偶尔它们可以使框架更易于使用。在我们的600Kloc代码库中,我们使用了7次metaclasses:ABCMeta一次,4x models.SubfieldBase来自Django,两次是元类,使得类可用作Django中的视图。正如@Ignacio所写,如果你知道你需要一个元类(并考虑了所有其他选项),你需要一个元类。

答案 1 :(得分:3)

从概念上讲,存在一个类来定义一组对象(类的实例)的共同点。就这样。它允许您根据类定义的共享模式考虑类的实例。如果每个对象都不同,我们就不会打扰使用类,我们只使用字典。

元类是普通类,它存在的原因相同;定义其实例的共同点。默认元类type提供了使类和实例按照您习惯的方式工作的所有常规规则,例如:

  • 实例上的属性查找检查实例后跟其类,后跟MRO命令中的所有超类
  • 调用MyClass(*args, **kwargs)调用i = MyClass.__new__(MyClass, *args, **kwargs)获取实例,然后调用i.__init__(*args, **kwargs)初始化
  • 通过使类块中绑定的所有名称成为类的属性,从类块中的定义创建类

如果您希望某些类与普通类的工作方式不同,则可以定义元类,并使您的异常类成为元类的实例,而不是type。您的元类几乎肯定会成为type的子类,因为您可能不希望使您的不同类完全不同;就像你可能想要一些书籍子集的行为有点不同(比如,书籍是其他作品的汇编)并使用Book的子类而不是完全不同的类。

如果您没有尝试定义使某些类与普通类的工作方式不同的方法,那么元类可能不是最合适的解决方案。请注意,“类定义其实例的工作方式”已经是一个非常灵活和抽象的范例;大多数情况下,您不需要更改类的工作方式。

如果你在谷歌周围,你会看到许多元类的例子,它们真正被用来围绕类创建做一些事情;经常自动处理类属性,或从某个地方自动查找新属性。我不会真正称之为元类的那些伟大用法。他们没有改变课程的工作方式,他们只是处理一些课程。在我看来,创建类的工厂函数或者在类创建后立即调用的类方法,或者最重要的是类装饰器,将是实现此类事情的更好方法。

但偶尔您会发现自己编写复杂的代码来获取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类,看起来您不需要做任何可能由元类帮助的事情。你真的不需要触及元类,除非你发现类的工作原理正常规则阻止你做一些简单的方法(这与不同来自“man,I希望我不必为所有这些类打字太多,我想知道我是否可以自动生成公共位?“)。事实上,尽管每天都在使用Python,但我从来没有真正使用过元类来实现真正的东西;我所有的元类都是像上面Singleton一样的玩具例子,或者只是愚蠢的探索。

答案 2 :(得分:3)

只要您需要覆盖类的默认行为(包括它们的创建),就会使用元类。

名称 bases 的元组和类 dict 创建一个类。您可以拦截创建过程以对任何输入进行更改。

您还可以覆盖类提供的任何服务:

  • __ call__用于创建实例
  • __ getattribute__,用于查找类
  • 上的属性和方法
  • __ setattr__控制设置属性
  • __ repr__控制着类的显示方式

总之,当您需要控制类的创建方式或何时需要更改类提供的任何服务时,将使用元类。

答案 3 :(得分: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

答案 4 :(得分:-1)

检查链接Meta Class Made Easy以了解如何以及何时使用元类。