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)

对于这堂课。

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

提前致谢。

4

5 回答 5

9

当您想要在创建类时对其进行变异时,您可以使用元。几乎不需要元类,它们很难调试,也很难理解——但有时它们可​​以使框架更易于使用。在我们的 600Kloc 代码库中,我们使用了 7 次元类:一次 ABCMeta,4 个来自 Django 的 models.SubfieldBase,以及两次使类可用作 Django 中的视图的元类。正如@Ignacio 所写,如果您不知道您需要一个元类(并且已经考虑了所有其他选项),那么您不需要一个元类。

于 2012-04-28T07:10:05.613 回答
4

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

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

  • 实例上的属性查找检查实例后跟它的类,然后按照 MRO 顺序检查所有超类
  • 调用MyClass(*args, **kwargs)调用i = MyClass.__new__(MyClass, *args, **kwargs)来获取一个实例,然后调用i.__init__(*args, **kwargs)来初始化它
  • 通过将类块中绑定的所有名称变为类的属性,从类块中的定义创建类
  • ETC

如果您想要一些与普通类不同的类,您可以定义一个元类并使您的不寻常类成为元类的实例,而不是type. 您的元类几乎肯定是 的子类type,因为您可能不想让您的不同类型的类完全不同;就像您可能希望让某些 Books 子集的行为有所不同(例如,作为其他作品汇编的书籍)并使用子类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课程,您似乎不需要做任何可以由元类帮助的事情。你真的不需要接触元类,除非你发现类如何工作的正常规则阻止你以简单的方式做一些应该简单的事情(这不同于“伙计,我希望我不必为所有这些类输入这么多,我想知道我是否可以自动生成公共位?”)。事实上,尽管我每天在工作中都使用 Python,但我从来没有真正使用过元类。我所有的元类都是像上面这样的玩具例子,Singleton或者只是愚蠢的探索。

于 2012-04-28T09:15:48.837 回答
4

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

一个类是从名称、一个元组和一个类dict创建的。您可以拦截创建过程以更改任何这些输入。

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

  • __call__ 用于创建实例
  • __getattribute__ 用于查找类的属性和方法
  • __setattr__ 控制设置属性
  • __repr__ 控制类的显示方式

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

于 2012-04-28T09:54:48.940 回答
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
于 2012-04-28T07:33:21.890 回答
-1

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

于 2012-04-28T10:24:26.430 回答