59

详解Python元类

 6 years ago
source link: http://mp.weixin.qq.com/s/aSMdIfT5OwpjE0IhOcdegw
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

什么是元类?

理解元类(metaclass)之前,我们先了解下Python中的OOP和类(Class)。

面向对象全称 Object Oriented Programming 简称OOP,这种编程思想被大家所熟知。它是把对象作为一个程序的基本单元,把数据和功能封装在里面,能够实现很好的复用性,灵活性和扩展性。OOP中有2个基本概念:类和对象:

  1. 类是描述如何创建一个对象的代码段,用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法

  2. 对象是类的实例(Instance)。

我们举个例子:



  1. In : class ObjectCreator(object):

  2. ...:     pass

  3. ...:

  4. In : my_object = ObjectCreator()

  5. In : my_object

  6. Out: <__main__.ObjectCreator at 0x1082bbef0>

而Python中的类并不是仅限于此:



  1. In : print(ObjectCreator)

  2. <class '__main__.ObjectCreator'>

ObjectCreator竟然可以被print,所以它的类也是对象!既然类是对象,你就能动态地创建它们,就像创建任何对象那样。我在日常工作里面就会有这种动态创建类的需求,比如在mock数据的时候,现在有个函数func接收一个参数:



  1. In : def func(instance):

  2. ...:     print(instance.a, instance.b)

  3. ...:     print(instance.method_a(10))

  4. ...:

正常使用起来传入的instance是符合需求的(有a、b属性和method_a方法),但是当我想单独调试func的时候,需要「造」一个,假如不用元类,应该是这样写:



  1. In : def generate_cls(a, b):

  2. ...:     class Fake(object):

  3. ...:         def method_a(self, n):

  4. ...:             return n

  5. ...:     Fake.a = a

  6. ...:     Fake.b = b

  7. ...:     return Fake

  8. ...:

  9. In : ins = generate_cls(1, 2)()

  10. In : ins.a, ins.b, ins.method_a(10)

  11. Out: (1, 2, 10)

你会发现这不算是「动态创建」的:

  1. 类名(Fake)不方便改变

  2. 要创建的类需要的属性和方法越多,就要对应的加码,不灵活。

我平时怎么做呢:



  1. In : def method_a(self, n):

  2. ...:     return n

  3. ...:

  4. In : ins = type('Fake', (), {'a': 1, 'b': 2, 'method_a': method_a})()

  5. In : ins.a, ins.b, ins.method_a(10)

  6. Out: (1, 2, 10)

到了这里,引出了type函数。本来它用来能让你了解一个对象的类型:



  1. In : type(1)

  2. Out: int

  3. In : type('1')

  4. Out: str

  5. In : type(ObjectCreator)

  6. Out: type

  7. In : type(ObjectCreator())

  8. Out: __main__.ObjectCreator

另外,type如上所说还可以动态地创建类:type可以把对于类的描述作为参数,并返回一个类。

用来创建类的东东就是「元类」,放张图吧:

Image


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

这种用法就是由于type实际上是一个元类,作为元类的type在Python中被用于在后台创建所有的类。在Python语言上有个说法「Everything is an object」。包括整数、字符串、函数和类... 所有这些都是对象。所有这些都是由一个类创建的:



  1. In : age = 35

  2. In : age.__class__

  3. Out: int

  4. In : name = 'bob'

  5. In : name.__class__

  6. Out: str

  7. ...

现在,任何__class__中的__class__是什么?



  1. In : age.__class__.__class__

  2. Out: type

  3. In : name.__class__.__class__

  4. Out: type

  5. ...

如果你愿意,你可以把type称为「类工厂」。type是Python中内建元类,当然,你也可以创建你自己的元类。

创建自己的元类

Python2创建类的时候,可以添加一个__metaclass__属性:



  1. class Foo(object):

  2.    __metaclass__ = something...

  3.    [...]

如果你这样做,Python会使用元类来创建Foo这个类。Python会在类定义中寻找__metaclass__。如果找到它,Python会用它来创建对象类Foo。如果没有找到它,Python将使用type来创建这个类。

在Python3中语法改变了一下:



  1. class Simple1(object, metaclass=something...):

  2.    [...]

本质上是一样的。拿一个4年前写分享的元类例子(就是为了推荐你来阅读 😉我的PPT:《Python高级编程》(https://github.com/dongweiming/Expert-Python) )吧:



  1. class HelloMeta(type):

  2.    def __new__(cls, name, bases, attrs):

  3.        def __init__(cls, func):

  4.            cls.func = func

  5.        def hello(cls):

  6.            print 'hello world'

  7.        t = type.__new__(cls, name, bases, attrs)

  8.        t.__init__ = __init__

  9.        t.hello = hello

  10.        return t

  11. class New_Hello(object):

  12.    __metaclass__ = HelloMeta

New_Hello初始化需要添加一个参数,并包含一个叫做hello的方法:



  1. In : h = New_Hello(lambda x: x)

  2. In : h.func(10), h.hello()

  3. hello world

  4. Out: (10, None)

PS: 这个例子只能运行于Python2。

在Python里__new__方法创建实例,__init__负责初始化一个实例。对于type也是一样的效果,只不过针对的是「类」,在上面的HelloMeta中只使用了__new__创建类,我们再感受一个使用__init__的元类:



  1. In : class HelloMeta2(type):

  2. ...:     def __init__(cls, name, bases, attrs):

  3. ...:         super(HelloMeta2, cls).__init__(name, bases, attrs)

  4. ...:         attrs_ = {}

  5. ...:         for k, v in attrs.items():

  6. ...:             if not k.startswith('__'):

  7. ...:                 attrs_[k] = v

  8. ...:         setattr(cls, '_new_dict', attrs_)

  9. ...:

  10. ...:

别往下看。思考下这样创建出来的类有什么特殊的地方?

我揭晓一下(这次使用Python 3语法):



  1. In : class New_Hello2(metaclass=HelloMeta2):

  2. ...:     a = 1

  3. ...:     b = True

  4. In : New_Hello2._new_dict

  5. Out: {'a': 1, 'b': True}

  6. In : h2 = New_Hello2()

  7. In : h2._new_dict

  8. Out: {'a': 1, 'b': True}

有点明白么?其实就是在创建类的时候把类的属性循环了一遍把不是__开头的属性最后存在了_new_dict上。

什么时候需要用元类?

日常的业务逻辑开发是不太需要使用到元类的,因为元类是用来拦截和修改类的创建的,用到的场景很少。我能想到最典型的场景就是 ORM。ORM就是「对象 关系 映射」的意思,简单的理解就是把关系数据库的一张表映射成一个类,一行记录映射为一个对象。ORM框架中的Model只能动态定义,因为这个模式下这些关系只能是由使用者来定义,元类再配合描述符就可以实现ORM了。

在这里先做个预告,未来我会分享「如何写一个ORM」这个主题。

  1. What is a metaclass in Python?(https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python)

  2. Understanding Python metaclasses(https://blog.ionelmc.ro/2015/02/09/understanding-python-metaclasses/)


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK