Py学习  »  Python

分享下自己对python的metaclass的知识

Py站长 • 9 年前 • 2255 次点击  

分享下自己对python的metaclass的知识。

一 你可以从这里获取什么?

1. 也许你在阅读别人的代码的时候碰到过metaclass,那你可以参考这里的介绍。
2. 或许你需要设计一些底层的库,也许metaclass能帮你简化你的设计(也有可能复杂化:)
3. 也许你在了解metaclass的相关知识之后,你对python的类的一些机制会更了解。
4. more......

二 metaclass的作用是什么?(感性认识)

metaclass能有什么用处,先来个感性的认识:

1. 你可以自由的、动态的修改/增加/删除 类的或者实例中的方法或者属性
2. 批量的对某些方法使用decorator,而不需要每次都在方法的上面加入@decorator_func
3. 当引入第三方库的时候,如果该库某些类需要patch的时候可以用metaclass
4. 可以用于序列化(参见yaml这个库的实现,我没怎么仔细看)
5. 提供接口注册,接口格式检查等
6. 自动委托(auto delegate)
7. more...

三 metaclass的相关知识

1. what is metaclass?

1.1 在wiki上面,metaclass是这样定义的:In object-oriented programming,

a metaclass is a class whose instances are classes. Just as an ordinary class defines the behavior of certain objects, a metaclass defines the behavior of certain classes and their instances.

也就是说metaclass的实例化结果是类,而class实例化的结果是instance。我是这么理解的: metaclass是类似创建类的模板,所有的类都是通过他来create的(调用new),这使得你可以自由的控制 创建类的那个过程,实现你所需要的功能。

1.2 metaclass基础

  • 一般情况下, 如果你要用类来实现metaclass的话,该类需要继承于type,而且通常会重写type的new方法来控制创建过程。 当然你也可以用函数的方式(下文会讲)
  • 在metaclass里面定义的方法会成为类的方法,可以直接通过类名来调用

2. 如何使用metaclass

2.1 用类的形式

2.1.1 类继承于type, 例如: class Meta(type):pass

2.1.2 将需要使用metaclass来构建class的类的metaclass属性(不需要显示声明,直接有的了)赋值为Meta(继承于type的类)

2.2 用函数的形式

2.2.1 构建一个函数,例如叫metaclass_new, 需要3个参数:name, bases, attrs,

name: 类的名字 bases: 基类,通常是tuple类型 attrs: dict类型,就是类的属性或者函数

2.2.2 将需要使用metaclass来构建class的类的metaclass属性(不需要显示声明,直接有的了)赋值为函数metaclas_new

3 metaclass 原理

3.1 basic

metaclass的原理其实是这样的:当定义好类之后,创建类的时候其实是调用了type的new方法为这个类分配内存空间,创建好了之后再调用type的init方法初始化(做一些赋值等)。所以metaclass的所有magic其实就在于这个new方法里面了。 说说这个方法:new(cls, name, bases, attrs)

cls: 将要创建的类,类似与self,但是self指向的是instance,而这里cls指向的是class name: 类的名字,也就是我们通常用类名.name获取的。 bases: 基类 attrs: 属性的dict。dict的内容可以是变量(类属性),也可以是函数(类方法)。

所以在创建类的过程,我们可以在这个函数里面修改name,bases,attrs的值来自由的达到我们的功能。这里常用的配合方法是 getattr和setattr(just an advice)

3.2 查找顺序

再说说关于metaclass这个属性。这个属性的说明是这样的: This variable can be any callable accepting arguments for name, bases, and dict. Upon class creation, the callable is used instead of the built-in type(). New in version 2.2.(所以有了上面介绍的分别用类或者函数的方法)

The appropriate metaclass is determined by the following precedence rules: If dict['metaclass'] exists, it is used. Otherwise, if there is at least one base class, its metaclass is used (this looks for a class attribute first and if not found, uses its type). Otherwise, if a global variable named metaclass exists, it is used. Otherwise, the old-style, classic metaclass (types.ClassType) is used.

这个查找顺序也比较好懂,而且利用这个顺序的话,如果是old-style类的话,可以在某个需要的模块里面指定全局变量 metaclass = type就能把所有的old-style 变成 new-style的类了。(这是其中一种trick)

四 例子

针对第二点说的metaclass的作用,顺序来给些例子:

  1. 你可以自由的、动态的修改/增加/删除 类的或者实例中的方法或者属性
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    #!/usr/bin/python  
    #coding :utf-8
    
    def ma(cls):  
        print 'method a'
    
    def mb(cls):  
        print 'method b'
    
    method_dict = {  
        'ma': ma,  
        'mb': mb,  
    }
    
    class DynamicMethod(type):  
        def __new__(cls, name, bases, dct):  
            if name[:3] == 'Abc':  
                dct.update(method_dict)  
            return type.__new__(cls, name, bases, dct)
    
        def __init__(cls, name, bases, dct):  
            super(DynamicMethod, cls).__init__(name, bases, dct)
    
    class AbcTest(object):  
        __metaclass__ = DynamicMethod  
        def mc(self, x):  
            print x * 3
    
    class NotAbc(object):  
        __metaclass__ = DynamicMethod  
        def md(self, x):  
            print x * 3
    
    def main():  
        a = AbcTest()  
        a.mc(3)  
        a.ma()  
        print dir(a)
    
        b = NotAbc()  
        print dir(b)
    
    if __name__ == '__main__':  
        main()
    

通过DynamicMethod这个metaclass的原型,我们可以在那些指定了metaclass属性位DynamicMethod的类里面,

根据类名字,如果是以'Abc'开头的就给它加上ma和mb的方法(这里的条件只是一种简单的例子假设了,实际应用上 可能更复杂),如果不是'Abc'开头的类就不加. 这样就可以打到动态添加方法的效果了。其实,你也可以将需要动态 添加或者修改的方法改到new里面,因为python是支持在方法里面再定义方法的. 通过这个例子,其实可以看到 只要我们能操作new方法里面的其中一个参数attrs,就可以动态添加东西了。

  1. 批量的对某些方法使用decorator,而不需要每次都在方法的上面加入@decorator_func 这个其实有应用场景的,就是比如我们cgi程序里面,很多需要验证登录或者是否有权限的,只有验证通过之后才 可以操作。那么如果你有很多个操作都需要这样做,我们一般情况下可以手动在每个方法的前头用@login_required 类似这样的方式。那如果学习了metaclass的使用的话,这次你也可以这样做:
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #!/usr/bin/python
    #coding :utf-8
    from types import FunctionType
    
    def login_required(func):
        print 'login check logic here'
        return func
    
    class LoginDecorator(type):
        def __new__(cls, 
    
    
        
    name, bases, dct):
            for name, value in dct.iteritems():
                if name not in ('__metaclass__', '__init__', '__module__') and\
                    type(value) == FunctionType:
                    value = login_required(value)
    
                dct[name] = value
            return type.__new__(cls, name, bases, dct)
    
    class Operation(object):
        __metaclass__ = LoginDecorator
    
        def delete(self, x):
            print 'deleted %s' % str(x)
    
    def main():
        op = Operation()
        op.delete('test')
    
    if __name__ == '__main__':
        main()
    

这样子你就可以不用在delete函数上面写@login_required, 也能达到decorator的效果了。不过可读性就差点了。

  1. 当引入第三方库的时候,如果该库某些类需要patch的时候可以用metaclass

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    #!/usr/bin/python  
    #coding :utf-8
    
    def monkey_patch(name, bases, dct):  
        assert len(bases) == 1  
        base = bases[0]  
        for name, value in dct.iteritems():  
            if name not in ('__module__', '__metaclass__'):  
                setattr(base, name, value)  
        return base
    
    class A(object):  
        def a(self):  
            print 'i am A object'
    
    class PatchA(A):  
        __metaclass__ = monkey_patch
    
        def patcha_method(self):  
            print 'this is a method patched for class A'
    
    def main():  
        pa = PatchA()  
        pa.patcha_method()  
        pa.a()  
        print dir(pa)  
        print dir(PatchA)
    
    if __name__ == '__main__':  
        main()
    
  2. 提供接口注册,接口格式检查等, 这个功能可以参考这篇文章:

http://mikeconley.ca/blog/2010/05/04/python-metaclasses-in-review-board/

  1. 自动委托(auto delegate)

以下是网上的例子:

http://django-china.cn/topic/930/

五 总结

  1. metaclass的使用原则:

If you wonder whether you need them, you don't (the people who actually need them know with certainty that they need them, and don't need an explanation about why). --Tim Peters 也就是说如果你不知道能用metaclass来干什么的话,你尽量不要用,因为通常metaclass的代码会增加代码的复杂度, 降低代码的可读性。所以你必需权衡metaclass的利弊。

  1. metaclass的优势在于它的动态性和可描述性(比如上面例子中的self.delegate.getitem(i)这样的代码,它 可以用另外的函数代码生成,而无需每次手动编写), 它能把类的动态性扩展到极致。
Python社区是高质量的Python/Django开发社区
本文地址:http://www.python88.com/topic/931
 
2255 次点击