元类就是深度的魔法,99%的⽤户应该根本不必为此操⼼。
如果你想搞清楚 究竟是否需要⽤到元类,那么你就不需要它。
那些实际⽤到元类的⼈都⾮常 清楚地知道他们需要做什么,⽽且根本不需要解释为什么要⽤元类。
—— TimPeters
元类和元编程
一、元类
概念
元类(metaclass )1,就是创建类的类。
这么说可能不太好理解,下面我们来解释下上面这句话:
在⼤多数编程语⾔中,类就是⼀组⽤来描述如何⽣成⼀个对象的代码段,在python中也不例外。
实例对象是由类生成的,而python中,类本身也是可以被传递和自省的对象。
类对象是用什么创建和生成的呢?答案是元类,元类就是一种知道如何创建和管理类的对象,也可以叫做类生成器。
__ class __ 和type
知道了元类的概念,那么我们怎么查看python中的元类具体是什么东西呢?
python中对象有个魔法属性__class__
,可以查看对象是由哪个类创建出来的:
Python 3.7.0 (default, Jun 28 2018, 08:04:48) [MSC v.1912 64 bit (AMD64)]
>>> "abc".__class__
<class ''str''> # 字符串“abc”是由“str”类实例化出来的
>>> num = 123
>>> num.__class__
<class ''int''> # 没错,int、str等数据类型也是一个个的类,
# 具体的字符串和数字其实是他们实例化对象。
>>> class Demo(object):
... pass
>>> demo = Demo()
>>> demo.__class__
<class '__main__.Demo'>
>>> Demo.__class__
<class 'type'>
可以看到类对象的类,就是type
。
这个type
其实就是平时我们用来查看数据类型的内建函数:
>>> type(num)
<class ''int''>
>>> type(Demo)
<class 'type'>
在只使用这个功能时,作用与__class__
相同。
而其实type
就是一个元类,它还有⼀种完全不同的功能:动态的创建类。2
语法为:type(类名, 由⽗类名称组成的元组(针对继承的情况,可以为空), 包含属性的字典)
例如:
>>> type("Demo2",(object),{})
<class ''__main__.Demo2''>
>>> demo2 = Demo2()
>>> demo2
<__main__.Demo2 object at 0x000001F71DBE5EF0>
由type动态创建的 Demo2
类对象,和由“class ...”
创建的Demo
,两者完全等价。
python 3以后3,默认的元类皆为type
,即所有的类对象默认都是由type
创建的,接下来让我们来自定义一个元类。
创建一个元类
最简单的,自定义一个继承自type的子类,想要使用它创建类对象时在类中使用__metaclass__
声明一下:
>>> class Meta(type):
... pass
...
>>> class Base(object):
... __metaclass__ = Meta # python2
...
>>> class Base(metaclass=Meta): # python3
... pass
一般来说,定义的元类应该重新实现__init__()
与__new__()
方法。
- 如果需要修改类的属性,使用元类的
__new__
方法 - 如果只是做一些类属性检查的工作,使用元类的
__init__
方法。
>>> class Meta(type):
... def __new__(cls, name, bases, dct): # 注意第一个参数是cls而不是self
... print('create class %s' % name)
... return type.__new__(cls, name, bases, dct)
...
... def __init__(cls, name, bases, dct):
... print('Init class %s' % name)
... type.__init__(cls, name, bases, dct) # 注意__init__方法禁止返回值
...
>>> class Base(object, metaclass=Meta):
... pass
...
create class Base
Init class Base
>>> Base
<class '__main__.Base'>
metaclass拓展
事实上,__metaclass__
实际上可以被任意调⽤,它只是规定了类“按照什么样的规则去生成”,并不需要是⼀个正式 的类。
比如,我们有一个比较二的需求:你决定在你的模块⾥,所有的类的属性都应该是⼤写形式。
>>> def upper_attr(future_class_name, future_class_parents, future_class_attr):
... """遍历属性字典,把不是__开头的属性名字变为⼤写"""
... newAttr = {}
... for name,value in future_class_attr.items():
... if not name.startswith("__"):
... newAttr[name.upper()] = value
... return type(future_class_name, future_class_parents, newAttr)
>>> class Foo(object, metaclass=upper_attr):
... bar= 'bip'
...
>>> foo = Foo()
>>> foo.bar
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: 'Foo' object has no attribute 'bar'
>>> foo.BAR
'bip'
元类冲突
假如有两个不同的元类,要生成一个继承这两个类的子类,会产生什么情况呢?
>>> class MetaA(type):
... pass
...
>>> class MetaB(type):
... pass
...
>>> class A(object, metaclass=MetaA):
... pass
...
>>> class B(object, metaclass=MetaB):
... pass
...
>>> class C(A, B):
... pass
...
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: metaclass conflict: the metaclass of a derived class must be a (non-strict) subclass of the metaclasses of all its bases
这时会报错, 元类冲突。
我们需要手动构造新的子类元类,让新的子类元类继承自A和B的元类:
>>> class MetaC(MetaA, MetaB):
... pass
...
>>> class C(object, metaclass=MetaC):
... pass
...
这样就不会报错了。
二、元类的应用
应用元类之前我们首先要知道使用元类编程的缺点:
- 实现麻烦
- 代码可读性不高
- 不易维护
其实在开头引用TimPeters的话就说明,不要随意在生产代码中使用元类,而且现有的编码规范也极不推荐使用。
好吧,了解元类的使用对你更深入理解一些框架是有帮助的,如果你执意要学习这门禁术,那就往下看吧!
===========================禁术分割线 =======================
就元类本身而言,它的作用是:
- 拦截类的创建
- 修改类
- 返回修改之后的类
使用元类还是有一些好处的:
- 意图更加明确。当然你的metaclass名字要起好
- 面向对象。可以隐式继承到子类
- 可以更好地组织代码
- 可以用
__new__
,__init__,__call__
等方法更好地控制
这里先简单说几个简单的应用实例:
实现单例模式
class Singleton(type):
instance = None
def __call__(cls, *args, **kw):
"""通过重写__call__拦截实例的创建,(实例通过调用括号运算符创建的)"""
if not cls.instance:
cls.instance = super().__call__(*args, **kw)
return cls.instance
面向切面(AOP)编程
在运行时,动态地将代码切入到类的指定方法、指定位置上的编程称为面向切面的编程(AOP)。
简单地说,如果不同的类要实现相同的功能,可以将其中相同的代码提取到一个切片中,等到需要时再切入到对象中去。这些相同的代码片段称为切面,而切入到哪些类、哪些方法则叫切入点。
【注意】:python的装饰器天然适合AOP,简单易读又好用。使用元类AOP纯属自找麻烦,这里主要是说下原理,并不是让大家去用。(在生产代码中炫技很讨厌的说(′゜c_,゜` ) )
面向切面比较常用的是为类方法添加记录日志功能,下面使用元类来实现此功能:
def trace(func):
def callfunc(self, *args, **kwargs):
with open('debug_log.txt', 'a')as f:
f.write('Calling %s: %s ,%s\n'%(func.__name__, args, kwargs))
result = func(self, *args, **kwargs)
f.write('%s returned %s\n'%(func.__name__, result))
return result
return callfunc
class LogMeta(type):
def __new__(cls, name, bases, attr_dct):
for k, v in attr_dct.items():
if isinstance(v, types.FunctionType):
# 如果v是函数类型, 使用trace处理,添加日志
attr_dict[k] = trace(v)
return type.__new__(cls, name, bases, attr_dct)
class Foo(object, metaclass=LogMeta):
num = 0
def spam(self):
Foo.num += 1
return Foo.num
对抽象基类的支持
简单的说,抽象基类就是包含一个或者多个抽象方法的类。
它本身不实现抽象方法,强制子类去实现,同时抽象基类自己不能被实例化,没有实现抽象方法的子类也无法实例化。
python内置的abc(abstract base class)来实现抽象基类。
from abc import ABCMeta, abstractmethod
class Base(metaclass=ABCMeta):
@abstractmethod
def foo(self):
pass
使用这种方式如果没有在子类里实现foo方法你是没有办法实例化抽象基类Base的子类的 。
另外应该优先使用collections
定义的抽象基类,比如要实现一个容器我们可以继承 collections.Container
实现ORM
这个比较复杂,在下一章再详细说。
三、元编程
概念
元编程(英语:Metaprogramming),又译超编程,是指某类计算机程序的编写,这类计算机程序编写或者操纵其它程序(或者自身)作为它们的数据,或者在运行时完成部分本应在编译时完成的工作。多数情况下,与手工编写全部代码相比,程序员可以获得更高的工作效率,或者给与程序更大的灵活度去处理新的情形而无需重新编译。
编写元程序的语言称之为元语言。被操纵的程序的语言称之为“目标语言”。一门编程语言同时也是自身的元语言的能力称之为“反射”或者“自反”。
以上引自维基百科,元编程。
许多人将「元类编程」和「元编程」混为一谈,甚至说“元编程 == 元类编程”,这是非常不对的。
就比如python,使用Cpython解释器时,C语言就是元语言,python就是目标语言,C语言对python进行了元编程。
元编程在不同的语言中有不同的实现,在Python中,元编程实现通常有这几个手段:
- 魔法方法
- 描述器(descriptor)
- 元类
- eval函数
应用
元编程常见的应用场景很多,扩展(重构)语法、开发DSL、生成代码、根据特定场景自动选择代码优化、解决一些正交的架构设计问题、AOP等等。
所以元编程存在的目的,就是多提供了一个抽象层次。
至于元编程有什么缺点,争议还是比较大的。比如以重构语法的应用为例,很多元编程的反对者就认为这样会导致代码的可读性、可维护性降低,分化社区,影响交流,因为每个开发人员都能搞一个自己的方言 。
对于作为”胶水语言”的python,对各种其他语言库的支持(ctypes、js2py等),更是元编程应用很好的实例。
很经典的一个应用就是使用元类在python语言和数据库中间增加一个抽象层:ORM层。
ORM的简单实现
ORM
ORM全称“Object Relational Mapping”,即对象-关系映射,就是把关系数据库的一行映射为一个对象,也就是一个类对应一个表,这样,写代码更简单,不用直接操作SQL语句。
以下代码参考自文章使用元类-—廖雪峰的官方网站。
编写底层模块的第一步,就是先把调用接口写出来。比如,使用者如果使用这个ORM框架,想定义一个User
类来操作对应的数据库表User
,我们期待他写出这样的代码:
class User(Model):
# 定义类的属性到列的映射:
id = IntegerField('id')
name = StringField('username')
email = StringField('email')
password = StringField('password')
# 创建一个实例:
u = User(id=12345, name='Michael', email='test@orm.org', password='my-pwd')
# 保存到数据库:
u.save()
其中,父类Model
和属性类型StringField
、IntegerField
是由ORM框架提供的,剩下的魔术方法比如save()
全部由metaclass自动完成。虽然metaclass的编写会比较复杂,但ORM的使用者用起来却异常简单。
class Field(object):
"""负责保存数据库表的字段名和字段类型"""
def __init__(self, name, column_type):
self.name = name
self.column_type = column_type
def __str__(self):
return '<%s:%s>' % (self.__class__.__name__, self.name)
class StringField(Field):
def __init__(self, name):
super().__init__(name, 'varchar(100)') #python2中super里要带参数
class IntegerField(Field):
def __init__(self, name):
super().__init__(name, 'bigint')
class ModelMetaclass(type):
"""自定义元类"""
def __new__(cls, name, bases, attrs):
if name=='Model':
return type.__new__(cls, name, bases, attrs)
print('Found model: %s' % name)
# 排除掉对Model类的修改
mappings = {}
for k, v in attrs.items():
if isinstance(v, Field):
print('Found mapping: %s ==> %s' % (k, v))
mappings[k] = v
# 从类属性中删除该Field属性,否则,实例的属性会遮盖类的同名属性,运行错误
for k in mappings.keys():
attrs.pop(k)
attrs['__mappings__'] = mappings # 保存属性和列的映射关系
attrs['__table__'] = name # 假设表名和类名一致(简化)
return type.__new__(cls, name, bases, attrs)
class Model(dict, metaclass=ModelMetaclass):
"""只简单实现了INSERT功能"""
def __init__(self, **kw):
super(Model, self).__init__(**kw)
def __getattr__(self, key):
try:
return self[key]
except KeyError:
raise AttributeError(r"'Model' object has no attribute '%s'" % key)
def __setattr__(self, key, value):
self[key] = value
def save(self):
fields = []
params = []
args = []
for k, v in self.__mappings__.items():
fields.append(v.name)
params.append('?')
args.append(getattr(self, k, None))
sql = 'insert into %s (%s) values (%s)' % (self.__table__, ','.join(fields), ','.join(params))
print('SQL: %s' % sql)
print('ARGS: %s' % str(args))
这样,就实现了一个简单的ORM框架。
后记
本来想在这里一起把抽象基类(abc)和面向切面编程(AOP)的几种实现一起谈谈,结果篇幅又是远超我的想象,还是以后有机会慢慢写吧….
参考链接
[1].Python 中的元类编程
[2]. 使用元类-—廖雪峰的官方网站
[5].『简单的』Python 元类 - Pegasus Wang的文章 - 知乎
[6]. What are metaclasses in Python?——StackOverflow
[7]. Python元编程-被遗忘的远古凶兽
-
Meta- 这个前缀在希腊语中的本意是「在…后,越过…的」,类似于拉丁语的 post-,比如 metaphysics 就是「在物理学之后」(形而上学),这个词最开始指一些亚里士多德的著作,因为它们通常排序在《物理学》之后。 但西方哲学界在几千年中渐渐赋予该词缀一种全新的意义:关于某事自身的某事。比如 meta-knowledge 就是「关于知识本身的知识」,meta-data 就是「关于数据的数据」,meta-language 就是「关于语言的语言」,类似的,metaclass就是「关于类的类」。 ↩
-
在python 2中,默认的元类是types.ClassType,就是所谓的旧样式类。python2.2以后已不提倡使用。 ↩
-
既然type也是一种“类”,为什么不写成“Type”呢? 想想“str”、“int”等“类”,这里应该是为了和他们保持一致。 ↩