当前位置:天才代写 > tutorial > Python教程 > 深刻领略Python中的元类(metaclass)

深刻领略Python中的元类(metaclass)

2017-11-02 08:00 星期四 所属: Python教程 浏览:478

译注:这是一篇在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%的时间里你基础就不需要动态修改类 😀

 

    关键字:

天才代写-代写联系方式