您的位置:首页 > 编程语言 > Python开发

深刻理解Python中的元类(metaclass)

2016-01-20 20:33 351 查看
本文是一篇网上资源的修改整理,其中红色部分是本人在整理过程中提出的问题,如果有读者知道答案还请评论一下

原文链接:http://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python


中文版链接:http://blog.jobbole.com/21351/

Python中的类也是一种对象。只要你使用关键字class,Python解释器在执行的时候就会创建一个对象。

下面的语句将在内存中创建一个对象,名字就是ObjectCreator。
>> class ObjectCreator(object):
...       pass
...


这个对象(类)自身拥有创建对象(类实例)的能力,而这就是为什么它是一个类的原因。但是,它的本质仍然是一个对象,于是乎你可以对它对象的操作


动态地创建类

因为类也是对象,你可以在运行时动态的创建它们,就像其他任何对象一样。首先,你可以在函数中创建类,使用class关键字即可。源文件中直接class定义的类对象是什么时候创建的?

>>> def choose_class(name):
...     if name == 'foo':
...         class Foo(object):
...             pass
...         return Foo # return the class, not an instance
...     else:
...         class Bar(object):
...             pass
...         return Bar
...
>>> MyClass = choose_class('foo')
>>> print(MyClass) # the function returns a class, not an instance
<class '__main__.Foo'>
>>> print(MyClass()) # you can create an object from this class
<__main__.Foo object at 0x89c6d4c>


但这还不够动态,因为你仍然需要自己编写整个类的代码。由于类也是对象,所以它们必须是通过什么东西来生成的才对。当你使用class关键字时,Python解释器自动创建这个对象。但就和Python中的大多数事情一样,Python仍然提供给你手动处理的方法。还记得内建函数type吗?这个古老但强大的函数能够让你知道一个对象的类型是什么,它也能动态的创建类。type可以接受一个类的描述作为参数,然后返回一个类。(我知道,根据传入参数的不同,同一个函数拥有两种完全不同的用法是一件很傻的事情,但这在Python中是为了保持向后兼容性)

type(类名, 父类的元组(针对继承的情况,可以为空),包含属性的字典(名称和值))

比如下面的代码:

>>> class MyShinyClass(object):
...       pass


可以手动像这样创建:

>>> MyShinyClass = type('MyShinyClass', (), {}) # returns a class object


你会发现我们使用“MyShinyClass”作为类名,并且也可以把它当做一个变量来作为类的引用。类和变量是不同的,这里没有任何理由把事情弄的复杂。这里两个名字不一样会怎样?

最终你会希望为你的类增加方法。只需要定义一个有着恰当签名的函数并将其作为属性赋值就可以了。

>>> def echo_bar(self):
...       print(self.bar)
>>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar})


这就是当你使用关键字class时Python在幕后做的事情,而这就是通过元类来实现的。


到底什么是元类

元类就是用来创建类的“东西”,元类就是类的类,你可以这样理解为:

MyClass = MetaClass()
MyObject = MyClass()


你已经看到了type可以让你像这样做:

MyClass = type('MyClass', (), {})


这是因为函数type实际上是一个元类。type就是Python在背后用来创建所有类的元类。现在你想知道那为什么type会全部采用小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来创建字符串对象的类,而int是用来创建整数对象的类。type就是创建类对象的类。因此, type就是Python的内建元类,当然了,你也可以创建自己的元类,需要_metaclass_来实现。


__metaclass__属性

当你写如下代码时 :

class Foo(Bar):
pass


Python做了如下的操作:

Foo中有__metaclass__这个属性吗?如果有,Python会在内存中通过__metaclass__创建一个名字Foo的类对象(我说的是类对象,请紧跟我的思路)。如果Python没有找到__metaclass__,它会继续在Bar(父类)中寻找__metaclass__属性,并尝试做和前面同样的操作。如果Python在任何父类中都找不到__metaclass__,它就会在模块层次中去寻找__metaclass__,并尝试做同样的操作。如果还是找不到__metaclass__,Python就会用内置的type来创建这个类对象。

现在的问题就是,你可以在__metaclass__中放置些什么代码呢?type,或者任何使用到type或者子类化type的东西。

你可以在写一个类的时候为其添加__metaclass__属性。

class Foo(object):
__metaclass__ = something


如果你这么做了,Python就会用使用something这个对象来创建类Foo.


自定义元类

我们这里就先以一个简单的函数作为例子开始。

def custom(future_class_name, future_class_parents, future_class_attr):_
dosomething...
return type(new_class_name, new_class_parents, new_class_attr)

class Foo():
__metaclass__ = custom


现在让我们再做一次,这一次用一个真正的class来当做元类。

class CustomMetaclass():
def __new__(Custom_metaclass, future_class_name,
future_class_parents, future_class_attr):
dosomething...
return type(new_class_name, new_class_parents, new_class_attr)


你可能已经注意到了有个额外的参数upperattr_metaclass,这并没有什么特别的。类方法的第一个参数总是表示当前的实例,就像在普通的类方法中的self参数一样。当然了,为了清晰起见,这里的参数名字我起的比较长。真实产品中他们的参数都有它们的传统名称:cls, name, bases, dct


为什么要用metaclass类而不是函数?

由于__metaclass__可以接受任何可调用的对象,那为何还要使用类呢,因为很显然使用类会更加复杂啊?这里有好几个原因:

1)  意图会更加清晰。当你读到UpperAttrMetaclass(type)时,你知道接下来要发生什么。

2) 你可以使用OOP编程。元类可以从元类中继承而来,改写父类的方法。元类甚至还可以使用元类。

3)  你可以把代码组织的更好。当你使用元类的时候肯定不会是像我上面举的这种简单场景,通常都是针对比较复杂的问题。将多个方法归总到一个类中会很有帮助,也会使得代码更容易阅读。

4) 你可以使用__new__, __init__以及__call__这样的特殊方法。它们能帮你处理不同的任务。就算通常你可以把所有的东西都在__new__里处理掉,有些人还是觉得用__init__更舒服些。

5) 哇哦,这东西的名字是metaclass,肯定非善类,我要小心!


结语

Python中的一切都是对象,它们要么是类的实例,要么是元类的实例,除了type。type实际上是它自己的元类,在纯Python环境中这可不是你能够做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很复杂的。对于非常简单的类,你可能不希望通过使用元类来对类做修改。你可以通过其他两种技术来修改类:

1) Monkey patching

2)   class decorators

当你需要动态修改类时,99%的时间里你最好使用上面这两种技术。当然了,其实在99%的时间里你根本就不需要动态修改类 :D
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息