天天看点

python元类Metaclass

高票回答一

在了解元类之前,需要掌握python的类。python从Smalltalk编程语言中借鉴了非常特殊的类的概念。

在大多数编程语言中,类只是描述如何创建对象的代码片段,这在python中也是成立的:

但是类在python中又不仅仅如此,类也是对象。

只要你使用了关键字​<code>​class​</code>​,python就会去执行它并且创建一个对象。

上面这段代码在内存中创建名为ObjectCreator的对象。

这个对象(类)拥有创建对象(实例)的能力,所以它是一个类。

但是它也仍然是一个对象,因此:

你可以把它赋值给一个变量

可以拷贝它

可以给它添加属性

可以把它作为函数参数传递

比如:

既然类是对象,那么可以像任意对象那样动态地创建它。

首先,可以在一个函数中使用​<code>​class​</code>​关键字创建类

但是因为还需要自己去写整个类,所以这个不是那么动态。

既然类是对象,那么他们一定可以使用某些东西生成。

当你使用​<code>​class​</code>​关键字,python自动创建一个对象,但是和python中大多数的事情一样,它也提供了手动实现的方法。

​<code>​type​</code>​是一个可以让你知道对象类型的函数

​<code>​type​</code>​拥有完全不同的能力,它也可以动态地创建类,​<code>​type​</code>​也可以接收类的描述作为参数返回一个类。

(某些函数根据参数的不同而拥有完全不同的用途是有点傻,但这是python向后兼容导致的问题)

​<code>​type​</code>​是这样工作的:

在这个段代码中:

​<code>​name​</code>​: 类命

​<code>​bases​</code>​: 父类元组(为了继承关系,可以为空)

​<code>​attrs​</code>​: 包含属性名和值的字典

可以用下面的方法手动创建:

在这里使用​<code>​MyShinyClass​</code>​作为类命和保存类引用的变量,它们可以不同,但是没必要复杂化。

​<code>​type​</code>​接受字典去定义类的属性,所以

可以写作:

可以像普通类那样使用

当然也可以继承它:

如果想要给类添加方法,可以定义一个拥有合适签名的函数并将它作为属性赋值给类

甚至可以像给普通创建的类对象添加方法那样,可以在动态地创建类之后,给类添加更多的方法。

到这里你可以明白:在python中,类是对象,可以动态地创建类。

这就是当你使用​<code>​class​</code>​关键字时python的行为,它是使用元类来实现的。

元类是创建类的东西。我们为了创建对象而定义类,但是在python中类也是对象。所以,元类就是创建这些对象的东西。它们是类的类。

你已经看过了​<code>​type​</code>​允许你做的操作:

是因为​<code>​type​</code>​其实是一个元类,​<code>​type​</code>​是python在幕后用来创建所有类的元类。

你可能会疑惑它为什么是小写的,为什么不写作Type?

我猜是为了保持一致性,str是创建strings对象的类,int是创建integer对象的类,type是创建类对象的类。你可以通过​<code>​__class__​</code>​属性来查看。

python中的一切都是对象,包括整型,字符串,函数和类。所有的都是对象,并且它们的都是被类创建的。

那么__class__的__class__是什么呢?

所以元类只是创建类对象的东西,如果你想也可以叫它类工厂(class factory),​<code>​type​</code>​是python内置的元类,当然也可以创建自己的元类。

在python2中,当你写一个类实现代码的时候可以添加一个​<code>​__metaclass__​</code>​属性。

如果你像上面这样做,python使用这个元类来创建类​<code>​Foo​</code>​。小心,这种方式很棘手。你先写下​<code>​class Foo(object)​</code>​,但是类对象​<code>​Foo​</code>​还没有在内存中被创建。

python会在类定义中查找​<code>​__metaclass__​</code>​,如果找到,他就会用元类来创建对象类​<code>​Foo​</code>​,如果没有,使用​<code>​type​</code>​来创建类。

当执行上面的代码的时候,python执行以下操作:

先查找​<code>​Foo​</code>​有没有​<code>​__metaclass__​</code>​属性。如果有,使用​<code>​__metaclass__​</code>​在内存中创建一个类对象。如果找不到​<code>​__metaclass__​</code>​,将在模块级别查找​<code>​__metaclass__​</code>​,然后尝试做同样的操作(但仅限于不继承任何东西的类,基本是旧式类)。

如果它找不到任何的​<code>​__metaclass__​</code>​,将会使用​<code>​bar​</code>​(第一个父类)的元类(可能会是默认的​<code>​type​</code>​)来创建类对象。

要小心​<code>​__metaclass__​</code>​属性不会被继承,父类的元类(​<code>​Bar.__class__​</code>​)会被继承。如果​<code>​Bar​</code>​使用了​<code>​__metaclass__​</code>​属性来创建一个带有​<code>​type()​</code>​方法(而不是​<code>​type.__new__​</code>​)的​<code>​Bar​</code>​,子类将不会继承这个行为。

现在问题是可以在__metaclass__里放进什么?答案就是可以创建类的东西。那什么可以创建类呢?​<code>​type​</code>​,或者任何它的子类或者使用它的东西。

设置元类的语法在python3中已被改变

比如,​<code>​__metaclass__​</code>​属性不再使用,而是作为基类列表的关键字参数。但是元类的行为基本保持不变。

python3中的元类新增的是你也可以使用关键字参数给元类传递属性,比如:

下面的内容将讲述python是如何处理的

元类的主要目的是在创建类的时候自动地去改变类。

通常是为了创建匹配当前上下文的类的API这样做。

想象你决定在你的模块中的所有的类的属性都要大写。有几种方法可以实现,但是其中一种是设置一个模块级别的​<code>​__metaclass__​</code>​。用这种方法,这个模块的所有类都使用这个元类创建,我们只需要告诉这个元类把所有的属性转成大写即可。

幸运得,​<code>​__metaclass__​</code>​可以是任何可调用的对象,它不需要是一个普通类。

所以,我们可以用一个函数来开始一个简单的例子

可以验证一下:

现在我们使用一个真正的类作为元类来实现同样的功能

现在我们知道了他们的含义,让我们用更短更现实的变量名来重写上面的方法

你可能已经注意到了额外的参数​<code>​cls​</code>​,它没什么特殊的:​<code>​__new__​</code>​始终接收定义它的类作为第一个参数,就像普通方法的​<code>​self​</code>​参数,它接受实例作为第一个参数,或者作为类方法的时候接受定义它的类作为第一个参数。

但是这不是合适的面向对象OOP思想。我们可以直接调用​<code>​type​</code>​,并且不重写或者调用父类的​<code>​__new__​</code>​:

使用super可以让它更清晰明了,它将会简化继承(因为你当然可以从type中继承,从元类中继承,从而拥有元类)

在python3中,如果你像下面这样使用关键字参数调用

它在元类中会转化成这样去使用:

之所以使用元类的代码这么复杂不是因为元类,而是因为通常你使用元类去操作依赖于内省,操纵继承,变量如​<code>​__dict__​</code>​等等的扭曲的事情上

确实元类在做这些黑魔法操作上特别有用,所以才会有这么复杂的东西,但是他们本身是很简单的:

拦截类的创建

修改类

返回修改后的类

既然​<code>​__metaclass__​</code>​接受任何可调用对象,既然使用类明显得更复杂为什么还要使用类?

下面是这么做的几个原因:

拦截更清晰,当你去看​<code>​UpperAttrMetaclass(type)​</code>​的时候,你知道接下来会发生什么

可以使用面像对象思想OOP,元类可以从元类继承,重写父类方法,元类甚至可以使用元类

如果你指定了一个一个元类类,不是元类函数,类的子类将会是元类的实例

可以使你的代码更加的结构化,你不会像上面的例子一样使用元类做一些琐碎的小事。它通常用于更复杂的功能。拥有创建几个方法并且组织他们在一个类中的能力对代码的易读性是非常有用的。

你可以执着于​<code>​__new__​</code>​,​<code>​__init__​</code>​和​<code>​__call__​</code>​这些允许你做不同操作的函数,即使通常你可以全部在​<code>​__new__​</code>​中实现,也有一些人更乐意使用​<code>​__init__​</code>​.

他们被称为元类一定意味着什么。

问题是为什么要用一些隐晦的容易出错的特性呢?

其实通常我们不用。

元类是更深奥的魔法,99%的用户都不需要担心它。如果你不确定你是否需要它们,那么你就不需要(需要它们的人一定很确定他们需要元类并且不需要解释) _Python Guru Tim Peters

使用元类的主要应用场景是创建API。一个典型的例子就是Django的ORM,它允许你像下面这样去定义:

但是如果你这样做:

它不会返回一个​<code>​IntegerField​</code>​对象,而是返回一个​<code>​int​</code>​,甚至可以直接从数据库中获取。

这可能是因为​<code>​models.Model​</code>​定义了​<code>​__metaclass__​</code>​,它使用了一些魔法将你使用简单语句定义的​<code>​Person​</code>​转化为连接到数据库字段的复杂钩子。

Django通过暴露一个简单的API和使用元类,从这个API中重建代码来完成幕后的实际操作,使复杂的东西看起来简单。

首先,你知道类是可以创建实例的对象。

实际上类就是他们本身的实例,元类的实例。

python中的一切都是对象,它们都要么是类的实例,要么是元类的实例。除了​<code>​type​</code>​。

type其实是它自己的元类。它不是你可以在纯python中重现的东西,它是在实现级别上做了一些欺骗才实现的。

其次,元类很复杂,你可能不像用它们来做很简单的类修改,你可以通过下面两个不同的技术来改变类:

猴子补丁

类装饰器

99%的情况,你最好使用这些来改变类。

但是98%的情况你完全不需要修改类。

高票回答二

元类是类的类,一个类定义了类的实例的行为,同样一个元类定义了类的行为。类是元类的实例。

在python中可以为元类使用任意的可调用对象,但是更好的实现是让它成为一个真正的类。​<code>​type​</code>​是python的常见元类,​<code>​type​</code>​本身是一个类,并且是它自己的类型。虽然在python中不能完全重现类似​<code>​type​</code>​的东西,但是python里还是有些小技巧,为了创建自己的元类,只需要继承​<code>​type​</code>​。

一个元类最常用作类工厂(class-factory),当你通过调用类来创建对象的时候,python通过调用元类来创建一个新类(当执行class语句的时候)。通过​<code>​__init__​</code>​和​<code>​__new__​</code>​方法的结合,元类允许你在创建一个一个类的时候做一些额外的操作,比如用一些注册信息来注册类,或者使用其他东西完全取代类。

当执行​<code>​class​</code>​语句(class statement)的时候,python首先像执行普通的代码块那样去执行​<code>​class​</code>​语句体。结果命名空间(一个字典)暂存了这个即将生成的类(class-to-be)的属性。元类是通过查找这个即将生成的类(元类被他继承)的​<code>​__metaclass__​</code>​属性或者​<code>​__metaclass__​</code>​全局变量来决定的。然后使用此类类命,基类,属性调用元类来实例化它。

然而,元类实际定义了类的类型,而不仅仅是类工厂,所以可以使用元类来做的事情很多,比如,可以在元类上定义常规方法,这些元类方法就类似于类方法,可以在不实例化类的时候被调用,但是又不像类方法,他们不能被类的实例对象调用. ​<code>​type.__subclasses__()​</code>​就是一个在type元类里这样的方法的例子。同样也可以定义常规魔术方法,比如​<code>​__add__​</code>​,​<code>​__iter__​</code>​,​<code>​__getattr__​</code>​来实现或者改变类行为。

下面是一些零碎的例子: