译注:这是一篇在Stack overflow上很热的帖子。提问者自称已经把握了有关Python OOP编程中的各类观念,但始终以为元类(metaclass)难以领略。他知道这必定和自省有关,但仍然以为不太大白,但愿各人可以给出一些实际的例子和代码片断以辅佐领略,以及在什么环境下需要举办元编程。于是e-satis同学给出了神一般的回覆,该回覆得到了985点的附和点数,更有人评论说这段回覆应该插手到Python的官方文档中去。而e-satis同学本人在Stack Overflow中的声望积分也高达64271分。以下就是这篇出色的回覆(提示:很是长)
类也是工具
在领略元类之前,你需要先把握Python中的类。Python中类的观念警惕于Smalltalk,这显得有些怪异。在大大都编程语言中,类就是一组用来描写如何生成一个工具的代码段。在Python中这一点仍然创立:
>>> class ObjectCreator(object): … pass … >>> my_object = ObjectCreator() >>> print my_object
<__main__.objectcreator object="" at="" 0x8974f2c="">
可是,Python中的类还远不止如此。类同样也是一种工具。是的,没错,就是工具。只要你利用要害字class,Python表明器在执行的时候就会建设一个工具。下面的代码段:
>>> class ObjectCreator(object): … pass …
将在内存中建设一个工具,名字就是ObjectCreator。这个工具(类)自身拥有建设工具(类实例)的本领,而这就是为什么它是一个类的原因。可是,它的本质仍然是一个工具,于是乎你可以对它做如下的操纵:
1) 你可以将它赋值给一个变量
2) 你可以拷贝它
3) 你可觉得它增加属性
4) 你可以将它作为函数参数举办通报
下面是示例:
>>> print ObjectCreator # 你可以打印一个类,因为它其实也是一个工具 >>> def echo(o): … print o … >>> echo(ObjectCreator) # 你可以将类做为参数传给函数 >>> print hasattr(ObjectCreator, 'new_attribute') Fasle >>> ObjectCreator.new_attribute = 'foo' # 你可觉得类增加属性 >>> print hasattr(ObjectCreator, 'new_attribute') True >>> print ObjectCreator.new_attribute foo >>> ObjectCreatorMirror = ObjectCreator # 你可以将类赋值给一个变量 >>> print ObjectCreatorMirror() <__main__.objectcreator object="" at="" 0x8997b4c="">
动态地建设类
因为类也是工具,你可以在运行时动态的建设它们,就像其他任何工具一样。首先,你可以在函数中建设类,利用class要害字即可。
>>> def choose_class(name): … if name == 'foo': … class Foo(object): … pass … return Foo # 返回的是类,不是类的实例 … else: … class Bar(object): … pass … return Bar … >>> MyClass = choose_class('foo') >>> print MyClass # 函数返回的是类,不是类的实例>>> print MyClass() # 你可以通过这个类建设类实例,也就是工具 <__main__.foo object="" at="" 0x89c6d4c="">
但这还不足动态,因为你仍然需要本身编写整个类的代码。由于类也是工具,所以它们必需是通过什么对象来生成的才对。当你利用class要害字时,Python表明器自动建设这个工具。但就和Python中的大大都工作一样,Python仍然提供应你手动处理惩罚的要领。还记得内建函数type吗?这个陈腐但强大的函数可以或许让你知道一个工具的范例是什么,就像这样:
>>> print type(1) >>> print type("1") >>> print type(ObjectCreator) >>> print type(ObjectCreator())
#p#分页标题#e#
这里,type有一种完全差异的本领,它也能动态的建设类。type可以接管一个类的描写作为参数,然后返回一个类。(我知道,按照传入参数的差异,同一个函数拥有两种完全差异的用法是一件很傻的工作,但这在Python中是为了保持向后兼容性)
type可以像这样事情:
type(类名, 父类的元组(针对担任的环境,可觉得空),包括属性的字典(名称和值))
好比下面的代码:
>>> class MyShinyClass(object): … pass
可以手动像这样建设:
>>> MyShinyClass = type('MyShinyClass', (), {}) # 返回一个类工具 >>> print MyShinyClass >>> print MyShinyClass() # 建设一个该类的实例 <__main__.myshinyclass object="" at="" 0x8997cec="">
你会发明我们利用“MyShinyClass”作为类名,而且也可以把它当做一个变量来作为类的引用。类和变量是差异的,这里没有任何来由把工作弄的巨大。
type 接管一个字典来为类界说属性,因此
>>> class Foo(object): … bar = True
可以翻译为:
>>> Foo = type('Foo', (), {'bar':True})
而且可以将Foo当成一个普通的类一样利用:
>>> print Foo >>> print Foo.bar True >>> f = Foo() >>> print f <__main__.foo object="" at="" 0x8a9b84c=""> >>> print f.bar True
虽然,你可以向这个类担任,所以,如下的代码:
>>> class FooChild(Foo): … pass
就可以写成:
>>> FooChild = type('FooChild', (Foo,),{}) >>> print FooChild >>> print FooChild.bar # bar属性是由Foo担任而来 True
最终你会但愿为你的类增加要领。只需要界说一个有着得当签名的函数并将其作为属性赋值就可以了。
>>> def echo_bar(self): … print self.bar … >>> FooChild = type('FooChild', (Foo,), {'echo_bar': echo_bar}) >>> hasattr(Foo, 'echo_bar') False >>> hasattr(FooChild, 'echo_bar') True >>> my_foo = FooChild() >>> my_foo.echo_bar() True
你可以看到,在Python中,类也是工具,你可以动态的建设类。这就是当你利用要害字class时Python在幕后做的工作,而这就是通过元类来实现的。
到底什么是元类(终于到主题了)
元类就是用来建设类的“对象”。你建设类就是为了建设类的实例工具,不是吗?可是我们已经进修到了Python中的类也是工具。好吧,元类就是用来建设这些类(工具)的,元类就是类的类,你可以这样领略 为:
MyClass = MetaClass() MyObject = MyClass()
你已经看到了type可以让你像这样做:
MyClass = type('MyClass', (), {})
这是因为函数type实际上是一个元类。type就是Python在背后用来建设所有类的元类。此刻你想知道那为什么type会全部回收小写形式而不是Type呢?好吧,我猜这是为了和str保持一致性,str是用来建设字符串工具的类,而int是用来建设整数工具的类。type就是建设类工具的类。你可以通过查抄__class__属性来看到这一点。Python中所有的对象,留意,我是指所有的对象——都是工具。这包罗整数、字符串、函数以及类。它们全部都是工具,并且它们都是从一个类建设而来。
>>> age = 35 >>> age.__class__ >>> name = 'bob' >>> name.__class__ >>> def foo(): pass >>>foo.__class__ >>> class Bar(object): pass >>> b = Bar() >>> b.__class__
#p#分页标题#e#
此刻,对付任何一个__class__的__class__属性又是什么呢?
>>> a.__class__.__class__ >>> age.__class__.__class__ >>> foo.__class__.__class__ >>> b.__class__.__class__
因此,元类就是建设类这种工具的对象。假如你喜欢的话,可以把元类称为“类工场”(不要和工场类搞混了:D) type就是Python的内建元类,虽然了,你也可以建设本身的元类。
__metaclass__属性
你可以在写一个类的时候为其添加__metaclass__属性。
class Foo(object): __metaclass__ = something… […]
假如你这么做了,Python就会用元类来建设类Foo。小心点,这内里有些能力。你首先写下class Foo(object),可是类工具Foo还没有在内存中建设。Python会在类的界说中寻找__metaclass__属性,假如找到了,Python就会用它来建设类Foo,假如没有找到,就会用内建的type来建设这个类。把下面这段话重复读屡次。当你写如下代码时 :
class Foo(Bar):
pass
Python做了如下的操纵:
Foo中有__metaclass__这个属性吗?假如是,Python会在内存中通过__metaclass__建设一个名字为Foo的类工具(我说的是类工具,请紧跟我的思路)。假如Python没有找到__metaclass__,它会继承在Bar(父类)中寻找__metaclass__属性,并实验做和前面同样的操纵。假如Python在任何父类中都找不到__metaclass__,它就会在模块条理中去寻找__metaclass__,并实验做同样的操纵。假如照旧找不到__metaclass__,Python就会用内置的type来建设这个类工具。
此刻的问题就是,你可以在__metaclass__中安排些什么代码呢?谜底就是:可以建设一个类的对象。那么什么可以用来建设一个类呢?type,可能任何利用到type可能子类化type的东东都可以。
自界说元类
元类的主要目标就是为了当建设类时可以或许自动地改变类。凡是,你会为API做这样的工作,你但愿可以建设切合当前上下文的类。假想一个很傻的例子,你抉择在你的模块里所有的类的属性都应该是大写形式。有好几种要领可以办到,但个中一种就是通过在模块级别设定__metaclass__。回收这种要领,这个模块中的所有类城市通过这个元类来建设,我们只需要汇报元类把所有的属性都改成大写形式就万事大吉了。
幸运的是,__metaclass__实际上可以被任意挪用,它并不需要是一个正式的类(我知道,某些名字里带有‘class’的对象并不需要是一个class,画绘图领略下,这很有辅佐)。所以,我们这里就先以一个简朴的函数作为例子开始。
# 元类会自动将你凡是传给‘type’的参数作为本身的参数传入
def upper_attr(future_class_name, future_class_parents, future_class_attr): '''返回一个类工具,将属性都转为大写形式''' # 选择所有不以'__'开头的属性 attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) # 将它们转为大写形式 uppercase_attr = dict((name.upper(), value) for name, value in attrs) # 通过'type'来做类工具的建设 return type(future_class_name, future_class_parents, uppercase_attr) __metaclass__ = upper_attr # 这会浸染到这个模块中的所有类 class Foo(object): # 我们也可以只在这里界说__metaclass__,这样就只会浸染于这个类中 bar = 'bip' print hasattr(Foo, 'bar') # 输出: False print hasattr(Foo, 'BAR') # 输出:True f = Foo() print f.BAR # 输出:'bip'
此刻让我们再做一次,这一次用一个真正的class来当做元类。
# 请记着,'type'实际上是一个类,就像'str'和'int'一样 # 所以,你可以从type担任 class UpperAttrMetaClass(type): # __new__ 是在__init__之前被挪用的非凡要领 # __new__是用来建设工具并返回之的要领 # 而__init__只是用来将传入的参数初始化给工具 # 你很罕用到__new__,除非你但愿可以或许节制工具的建设 # 这里,建设的工具是类,我们但愿可以或许自界说它,所以我们这里改写__new__ # 假如你但愿的话,你也可以在__init__中做些工作 # 尚有一些高级的用法会涉及到改写__call__非凡要领,可是我们这里不消 def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) return type(future_class_name, future_class_parents, uppercase_attr) 可是,这种方法其实不是OOP。我们直接挪用了type,并且我们没有改写父类的__new__要领。此刻让我们这样去处理惩罚: class UpperAttrMetaclass(type): def __new__(upperattr_metaclass, future_class_name, future_class_parents, future_class_attr): attrs = ((name, value) for name, value in future_class_attr.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) # 复用type.__new__要领 # 这就是根基的OOP编程,没什么邪术 return type.__new__(upperattr_metaclass, future_class_name, future_class_parents, uppercase_attr)
#p#分页标题#e#
你大概已经留意到了有个特另外参数upperattr_metaclass,这并没有什么出格的。类要领的第一个参数老是暗示当前的实例,就像在普通的类要领中的self参数一样。虽然了,为了清晰起见,这里的名字我起的较量长。可是就像self一样,所有的参数都有它们的传统名称。因此,在真实的产物代码中一个元类应该是像这样的:
class UpperAttrMetaclass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith('__') uppercase_attr = dict((name.upper(), value) for name, value in attrs) return type.__new__(cls, name, bases, uppercase_attr) 假如利用super要领的话,我们还可以使它变得更清晰一些,这会缓解担任(是的,你可以拥有元类,从元类担任,从type担任) class UpperAttrMetaclass(type): def __new__(cls, name, bases, dct): attrs = ((name, value) for name, value in dct.items() if not name.startswith('__')) uppercase_attr = dict((name.upper(), value) for name, value in attrs) return super(UpperAttrMetaclass, cls).__new__(cls, name, bases, uppercase_attr)
就是这样,除此之外,关于元类真的没有此外可说的了。利用到元类的代码较量巨大,这背后的原因倒并不是因为元类自己,而是因为你凡是会利用元类去做一些艰涩的工作,依赖于自省,节制担任等等。确实,用元类来搞些“暗中邪术”是出格有用的,因而会搞出些巨大的对象来。但就元类自己而言,它们其实是很简朴的:
1) 拦截类的建设
2) 修改类
3) 返回修改之后的类
为什么要用metaclass类而不是函数?
由于__metaclass__可以接管任何可挪用的工具,那为何还要利用类呢,因为很显然利用类会越发巨大啊?这里有好几个原因:
1) 意图会越发清晰。当你读到UpperAttrMetaclass(type)时,你知道接下来要产生什么。
2) 你可以利用OOP编程。元类可以从元类中担任而来,改写父类的要领。元类甚至还可以利用元类。
#p#分页标题#e#
3) 你可以把代码组织的更好。当你利用元类的时候必定不会是像我上面举的这种简朴场景,凡是都是针比拟力巨大的问题。将多个要领归总到一个类中会很有辅佐,也会使得代码更容易阅读。
4) 你可以利用__new__, __init__以及__call__这样的非凡要领。它们能帮你处理惩罚差异的任务。就算凡是你可以把所有的对象都在__new__里处理惩罚掉,有些人照旧以为用__init__更舒服些。
5) 哇哦,这对象的名字是metaclass,必定非善类,我要小心!
毕竟为什么要利用元类?
此刻回到我们的大主题上来,毕竟是为什么你会去利用这样一种容易堕落且艰涩的特性?好吧,一般来说,你基础就用不上它:
“元类就是深度的邪术,99%的用户应该基础不必为此劳神。假如你想搞清楚毕竟是否需要用到元类,那么你就不需要它。那些实际用到元类的人都很是清楚地知道他们需要做什么,并且基础不需要表明为什么要用元类。” —— Python界的首脑 Tim Peters
元类的主要用途是建设API。一个典范的例子是Django ORM。它答允你像这样界说:
class Person(models.Model): name = models.CharField(max_length=30) age = models.IntegerField()
可是假如你像这样做的话:
guy = Person(name='bob', age='35') print guy.age
这并不会返回一个IntegerField工具,而是会返回一个int,甚至可以直接从数据库中取出数据。这是有大概的,因为models.Model界说了__metaclass__, 而且利用了一些邪术可以或许将你方才界说的简朴的Person类转酿成对数据库的一个巨大hook。Django框架将这些看起来很巨大的对象通过袒暴露一个简朴的利用元类的API将其化简,通过这个API从头建设代码,在背后完成真正的事情。
结语
首先,你知道了类其实是可以或许建设出类实例的工具。好吧,事实上,类自己也是实例,虽然,它们是元类的实例。
>>>class Foo(object): pass >>> id(Foo)
Python中的一切都是工具,它们要么是类的实例,要么是元类的实例,除了type。type实际上是它本身的元类,在纯Python情况中这可不是你可以或许做到的,这是通过在实现层面耍一些小手段做到的。其次,元类是很巨大的。对付很是简朴的类,你大概不但愿通过利用元类来对类做修改。你可以通过其他两种技能来修改类:
1) Monkey patching
2) class decorators
当你需要动态修改类时,99%的时间里你最好利用上面这两种技能。虽然了,其实在99%的时间里你基础就不需要动态修改类 😀