前言
记得在学习了一段时间python之后,开始涉及比较复杂的概念。 其中看到了元类这个东西,很抽象,看完很迷惑,于是开始一番搜索。 搜索完让我更加迷惑。
首先是元类的定义比较难以理解,其次是搜索元类的应用的时候,发现出来的资料,全都是与ORM相关的。
由此我有几个问题:
元类是什么?
元类有什么用?
ORM一定要用元类实现吗?
在python官方文档上介绍了元类很多的应用。但是搜索资料的时候,都是通过元类实现的ORM。
python的对象
要了解元类有什么作用,要先了解一下python的面向对象的概念。
所有都是对象
在python中,不管字符串、列表、字典都是对象,包括类本身也是对象。
使用type()函数可获得对象的类型:
print(type(1))
print(type('python'))
class A:
pass
print(type(A))
打印结果:
<class 'int'>
<class 'str'>
<class 'type'>
题外话,如果print(type(type))
呢?
由此可知,当你定义一个类的时候,类本身也是一个对象。所有通过class
定义的类都是type的实例。
type的作用
type函数除了能够获得对象的类型,还能创建一个新的type实例,也就是和class
的作用是一样的。
#通过class创建类
class Person:
def __init__(self, age):
self.age = age
# 通过type创建类
def init(self, age):
self.age = age
Person2 = type('Person', (), {'__init__': init})
当type接收一个参数的时候就获取它的类型,当接收三个参数的时候,就返回一个type的实例,也就是一个类。
type(name, bases, dict, ***kwargs)
name就是class的名字,当我们用type()去检查Person的实例的时候,会获得class的name。
p = Person(21)
print(p)
<class '__main__.Person'>
bases是父类的集合,注意它是一个元组,可以多继承。 如果bases是空的,那么就会继承object
dict是一个字典,用来传递类的属性,上面的例子传递了一个init函数。
由此,我们可以通过type动态生成一个类,再由这个类去实例化一个对象。
类也是对象,我们不妨称为【类对象】区别普通【对象】
graph TD
type --实例化--> 类对象
类对象 --实例化--> 对象
元类
通过上述内容,我们可以得出2个重要结论:
类也是对象
类可以被动态创建
可以通过type进行创建
metaclass
除了使用type来创建类之外,python还提供了metaclass
的来影响类的创建。
看一个简单的例子:
class MetaClass(type):
def __new__(cls, *args, **kwargs):
print('运行到元类的__new__')
return super().__new__(cls, *args, **kwargs)
def __init__(self, *args, **kwargs):
print('运行到元类的__init__')
def __call__(self, *args, **kwargs):
print('运行到元类的__call__')
return self
class A(metaclass=MetaClass):
pass
print('开始实例化A')
a = A()
输出:
运行到元类的__new__
运行到元类的__init__
开始实例化A
运行到元类的__call__
在创建类的时候,通过传入metaclass
关键字参数时,python会用metaclass的值去创建类。
上文说过,python中的类也是对象。 我们结合一下普通类和对象,以及对象的实例化过程:
class A:
def __new__(cls, *args, **kwargs):
print('运行到普通类的__new__')
return super().__new__(cls)
def __init__(self):
print('运行到普通类的__init__')
a = A()
输出:
运行到普通类的__new__
运行到普通类的__init__
对比二者的创建过程是非常相似的。 也就是普通对象创建过程是:类的new——>类的init
类对象的创建过程:元类的new——>元类的init
普通类的实例化——>对象
元类的实例化——>类对象
- 这边元类的call有一些特殊的地方,因为普通类的call是在实例被调用的时候触发,而元类的实例就是类对象,那么在类对象进行实例化的时候,就会触发。后面有机会深入探讨元类的执行过程的时候再补充。
由此,我们可以通过metaclass对类的创建进行拦截,完成我们的需求。接下来举几个例子来说明一下体验一下元类的实际应用。
元类的应用
单例
先看一下不用元类实现的单例:
class Singleton:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super().__new__(cls)
return cls._instance
a = Singleton()
b = Singleton()
print(a == b)
输出:
True
用元类实现的单例:
class MetaSingleton(type):
def __init__(self, *args, **kwargs):
self._instance = None
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if self._instance is None:
self._instance = super().__call__(*args, **kwargs)
return self._instance
class Singleton(metaclass=MetaSingleton):
pass
a = Singleton()
b = Singleton()
print(a == b)
输出:
True
注意看二者的差别,普通类通过new在实例化对象的时候,判断是否已经实例化过了。而元类的new实例化的是一个类对象,在创建出来的类对象进行实例化的时候才会调用元类的call,所以在call中进行判断类对象是否已经实例化了。
缓存实例
类似上面的单例,我们创建一个缓存实例的例子。
不使用元类实现:
class ObjectCache:
def __new__(cls, *args, **kwargs):
if not hasattr(cls, '_obj_cache'):
cls._obj_cache = {}
if args not in cls._obj_cache:
cls._obj_cache[args] = super().__new__(cls)
return cls._obj_cache[args]
a = ObjectCache('a')
b = ObjectCache('a')
c = ObjectCache('c')
print(a == b)
print(a == c)
输出:
True
False
使用元类实现:
class MetaObjectCache(type):
def __init__(self, *args, **kwargs):
self._object_cache = {}
super().__init__(*args, **kwargs)
def __call__(self, *args, **kwargs):
if args not in self._object_cache:
self._object_cache[args] = super().__call__(*args, **kwargs)
return self._object_cache[args]
class ObjectCache(metaclass=MetaObjectCache):
def __init__(self, *args, **kwargs):
pass
a = ObjectCache('a')
b = ObjectCache('a')
c = ObjectCache('c')
print(a == b)
print(a == c)
输出:
True
False
实现接口类
python虽然是鸭子类型,但是其中的abc
模块也提供了抽象基类的功能。
import abc
class Base(metaclass=abc.ABCMeta):
@abc.abstractmethod
def test(self):
pass
class A(Base):
pass
a2 = A()
这段代码中,如果子类A没有实现test的方法,就会抛出错误:
TypeError: Can't instantiate abstract class A with abstract method test
可以通过元类实现类似的功能:
class Base(type):
def __new__(cls, *args, **kwargs):
name, base, attrs = args
if not base:
cls._methods = {}
for k, v in attrs.items():
if callable(v):
cls._methods[k] = v
else:
for k in cls._methods:
if k not in attrs:
raise Exception(f'子类没有实现{k}方法')
return super().__new__(cls, *args, **kwargs)
class A(metaclass=Base):
def test(self):
pass
class B(A):
pass
输出:
Exception: 子类没有实现test方法
代码中的name, base, attrs = args
就是上文提到tpye的参数,这里直接用*args传参。
在元类的new函数中,先判断类对象是否有父类,如果没有父类,就将方法储存起来。 如果有父类,就遍历_methods,判断每一个方法都存在attrs中,否则就是子类没有实现某个方法。
这里也体现了元类的一个特征,父类如果通过metaclass创建,那么子类也会受到影响。类B会首先检查此类是否有metaclass,如果没有就会去检查父类是否有metaclass.
拓展类功能
class MetaList(type):
def __new__(cls, *args, **kwargs):
_, _, attrs = args
attrs['add'] = lambda self, val: self.append(val)
return super().__new__(cls, *args, **kwargs)
class Mylist(list, metaclass=MetaList):
pass
L = Mylist()
print(L)
L.add(3)
print(L)
输出:
[]
[3]
这样便利用了元类拓展了类的功能。
元类的实质
通过上面的分析和例子,可以得出结论,元类可以用来控制类、拦截类,包括创建,实例化。python通过元类提供了一种动态创建类的功能。
在python中确实是比较难以理解的概念,因此Tim Peters曾说:
元类就是深度的魔法,99%的⽤户应该根本不必为此操⼼。
如果你想搞清楚 究竟是否需要⽤到元类,那么你就不需要它。
那些实际⽤到元类的⼈都⾮常 清楚地知道他们需要做什么,⽽且根本不需要解释为什么要⽤元类。
—— TimPeters
特别是上面的几个例子,好像也没有非要通过元类来实现不可。并且,虽然元类能够拦截类的创建过程,但是“动态”的概念体现不够深刻。
后面的内容会解决开头的疑问:ORM和元类有什么关系?ORM一定要用元类实现吗?
通过ORM这个例子,也能更好体现,“动态”创建类。