详解Python元类
/ / / 阅读数:5222什么是元类?
理解元类(metaclass)之前,我们先了解下 Python 中的 OOP 和类(Class)。
面向对象全称 Object Oriented Programming 简称 OOP,这种编程思想被大家所熟知。它是把对象作为一个程序的基本单元,把数据和功能封装在里面,能够实现很好的复用性,灵活性和扩展性。OOP 中有 2 个基本概念:类和对象:
- 类是描述如何创建一个对象的代码段,用来描述具有相同的属性和方法的对象的集合,它定义了该集合中每个对象所共有的属性和方法
- 对象是类的实例(Instance)。
我们举个例子:
In : class ObjectCreator(object): ...: pass ...: In : my_object = ObjectCreator() In : my_object Out: <__main__.ObjectCreator at 0x1082bbef0> |
而 Python 中的类并不是仅限于此:
In : print(ObjectCreator) <class '__main__.ObjectCreator'> |
ObjectCreator 竟然可以被 print,所以它的类也是对象!既然类是对象,你就能动态地创建它们,就像创建任何对象那样。我在日常工作里面就会有这种动态创建类的需求,比如在 mock 数据的时候,现在有个函数 func 接收一个参数:
In : def func(instance): ...: print(instance.a, instance.b) ...: print(instance.method_a(10)) ...: |
正常使用起来传入的 instance 是符合需求的(有 a、b 属性和 method_a 方法),但是当我想单独调试 func 的时候,需要「造」一个,假如不用元类,应该是这样写:
In : def generate_cls(a, b): ...: class Fake(object): ...: def method_a(self, n): ...: return n ...: Fake.a = a ...: Fake.b = b ...: return Fake ...: In : ins = generate_cls(1, 2)() In : ins.a, ins.b, ins.method_a(10) Out: (1, 2, 10) |
你会发现这不算算是「动态创建」的:
- 类名(Fake)不方便改变
- 要创建的类需要的属性和方法越多,就要对应的加码,不灵活。
我平时怎么做呢:
In : def method_a(self, n): ...: return n ...: In : ins = type('Fake', (), {'a': 1, 'b': 2, 'method_a': method_a})() In : ins.a, ins.b, ins.method_a(10) Out: (1, 2, 10) |
到了这里,引出了 type 函数。本来它用来能让你了解一个对象的类型:
In : type(1) Out: int In : type('1') Out: str In : type(ObjectCreator) Out: type In : type(ObjectCreator()) Out: __main__.ObjectCreator |
另外,type 如上所说还可以动态地创建类:type 可以把对于类的描述作为参数,并返回一个类。
用来创建类的东东就是「元类」,放张图吧:
MyClass = type('MyClass', (), {}) |
这种用法就是由于 type 实际上是一个元类,作为元类的 type 在 Python 中被用于在后台创建所有的类。在 Python 语言上有个说法「Everything is an object」。包整数、字符串、函数和类... 所有这些都是对象。所有这些都是由一个类创建的:
In : age = 35 In : age.__class__ Out: int In : name = 'bob' In : name.__class__ Out: str ... |
现在,任何__class__中的特定__class__是什么?
In : age.__class__.__class__ Out: type In : name.__class__.__class__ Out: type ... |
如果你愿意,你可以把 type 称为「类工厂」。type 是 Python 中内建元类,当然,你也可以创建你自己的元类。
创建自己的元类
Python2 创建类的时候,可以添加一个__metaclass__属性:
class Foo(object): __metaclass__ = something... [...] |
如果你这样做,Python 会使用元类来创建 Foo 这个类。Python 会在类定义中寻找__metaclass__。如果找到它,Python 会用它来创建对象类 Foo。如果没有找到它,Python 将使用 type 来创建这个类。
在 Python3 中语法改变了一下:
class Simple1(object, metaclass=something...): [...] |
本质上是一样的。拿一个 4 年前写分享的元类例子(就是为了推荐你来阅读 😉 我的 PPT:《Python 高级编程》 )吧:
class HelloMeta(type): def __new__(cls, name, bases, attrs): def __init__(self, func): self.func = func def hello(self): print 'hello world' t = type.__new__(cls, name, bases, attrs) t.__init__ = __init__ t.hello = hello return t class New_Hello(object): __metaclass__ = HelloMeta |
New_Hello 初始化需要添加一个参数,并包含一个叫做 hello 的方法:
In : h = New_Hello(lambda x: x) In : h.func(10), h.hello() hello world Out: (10, None) |
PS: 这个例子只能运行于 Python2。
在 Python 里__new__方法创建实例,__init__负责初始化一个实例。对于 type 也是一样的效果,只不过针对的是「类」,在上面的 HelloMeta 中只使用了__new__创建类,我们再感受一个使用__init__的元类:
In : class HelloMeta2(type): ...: def __init__(cls, name, bases, attrs): ...: super(HelloMeta2, cls).__init__(name, bases, attrs) ...: attrs_ = {} ...: for k, v in attrs.items(): ...: if not k.startswith('__'): ...: attrs_[k] = v ...: setattr(cls, '_new_dict', attrs_) ...: ...: |
别往下看。思考下这样创建出来的类有什么特殊的地方?
我揭晓一下(这次使用 Python 3 语法):
In : class New_Hello2(metaclass=HelloMeta2): ...: a = 1 ...: b = True In : New_Hello2._new_dict Out: {'a': 1, 'b': True} In : h2 = New_Hello2() In : h2._new_dict Out: {'a': 1, 'b': True} |
有点明白么?其实就是在创建类的时候把类的属性循环了一遍把不是__开头的属性最后存在了_new_dict 上。
什么时候需要用元类?
日常的业务逻辑开发是不太需要使用到元类的,因为元类是用来拦截和修改类的创建的,用到的场景很少。我能想到最典型的场景就是 ORM。ORM 就是「对象 关系 映射」的意思,简单的理解就是把关系数据库的一张表映射成一个类,一行记录映射为一个对象。ORM 框架中的 Model 只能动态定义,因为这个模式下这些关系只能是由使用者来定义,元类再配合描述符就可以实现 ORM 了,现在做个预告,未来我会分享「如何写一个 ORM」这个主题。