什么是Metaclass
关于Python的Metaclass,个人所见最好的介绍见于Stack Overflow上What is a metaclass in python?这一问题,下面有不少非常精彩的回答。为了便于查找,这里也作下相应的记录。
Metaclass语法
class Metaclass(type):
def __new__(cls, name, parents, attrs):
return super(Metaclass, cls).__new__(cls, name, parents, attrs)
def __init__(cls, name, parents, attrs):
super(Metaclass, cls).__init__(name, parents, attrs)
def __call__(cls, *args, **kwargs):
return super(Metaclass, cls).__call__(*args, **kwargs)
class Foobar(object):
__metaclass__ = Metaclass
def __init__(self):
super(Foobar, self).__init__()
自定义Metaclass需要继承自type,type是Python的预定义函数,用于创建Class。在需要应用Metaclass的类中,以 __metaclass__
指定所需的Metaclass。
__new__
, __init__
, __call__
Class如果存在自定义的Metaclass,在其被创建时会依次调用Metaclass上的__new__
,__init__
方法。在这两个函数中可以去动态修改类的定义,比如增删成员变量、成员函数等等。这两个函数有什么区别呢?前者是在Class构造时调用的,后者是在创建完成之后用于初始化的。在自定义Metaclass的时候,个人觉得使用任何一个都可以,在其它场景下,__new__
主要用于实现继承immutable types(str,int,etc)。
class UpperStr(str):
def __new__(cls, value):
return super(UpperStr, cls).__new__(cls, value.upper())
print UpperStr('a')
另一方面,__call__
则是每次在Class实例创建时调用,调用顺序如下,
- Metaclass的
__call__
- Class的
__new__
- Class的
__init__
Metaclass查找顺序
Class在查找应用Metaclass时,遵循如下顺序,
- 在当前Class中定义的Metaclass
- 父类中定义的Metaclass
- 当前module中定义的Metaclass
- 对于old-style Class来说,会应用classic metaclass (types.ClassType)
Metaclass用例
什么场景比较适合使用Metaclass呢?需要对不同Class进行相同的操作时。其实就是Class层面上的代码复用以既定语法来进行表现。
Singleton
在实际开发中单例是一种强需求。在Python中单例有几种不同实现方式。Stack Overflow上的问题Creating a singleton in python中介绍了很多种可能的写法,个人觉得还是Metaclass的方式看着最简洁。
# singleton.py
class Singleton(type):
def __call__(cls, *args, **kwargs):
if not hasattr(cls, '_instance'):
cls._instance = super(Singleton, cls).__call__(*args, **kwargs)
return cls._instance
# foobar.py
class Foobar(object):
__metaclass__ = Singleton
def __init__(self):
super(Foobar, self).__init__()
foobar = Foobar()
函数替换
在过往的开发中,还遇到了进行函数替换的场景。
在__init__
调用之后重载__setattr__
class SetAttrMeta(type):
def __call__(cls, *args, **kwargs):
real_setattr = cls.__setattr__
cls.__setattr__ = object.__setattr__
self = super(SetAttrMeta, cls).__call__(*args, **kwargs)
cls.__setattr__ = real_setattr
return self
如果不进行特定处理那么在__init__
函数调用之前__setattr__
就已经被替换的话,很容易导致递归操作,或者未定义字段访问。这里借用了Metaclass中__call__
的调用顺序来手动替换类的__setattr__
方法。在Stack Overflow上回答过Overriding setattr at runtime这个相关问题。
替换Class的__getattribute__
在类定义中定义的__getattribute__
只会想要实例的属性获取,但对于Class上定义的字段是无能为力的。这时候就需要在Metaclass上做文章了。曾经实现过一个Empty类,具体的功能就是无限支持对象上的级联调用,
class EmptyMeta(type):
def __getattribute__(self, name):
try:
return object.__getattribute__(self, name)
except:
return empty
class Empty(object):
"""
e.g.
foo.bar
foo.bar.hello.world()
"""
__metaclass__ = EmptyMeta
def __init__(self, *args, **kwargs):
super(Empty, self).__init__()
def __call__(self, *args, **kwargs):
return empty
def __getattribute__(self, item):
return empty
def __nonzero__(self):
return False
def __iter__(self):
return self
def next(self):
raise StopIteration
empty = Empty()
特定语法
一个比较广泛应用的例子就是Django中的model定义。Python Essential Reference里给出了一个TypedProperty例子,其用法接近,这里直接引用下,
class TypedProperty(object):
def __init__(self, type, default=None):
self.name = None
self.type = type
self.default = default if default else type()
def __get__(self, instance, cls=None):
return getattr(instance, self.name, self.default)
def __set__(self, instance, value):
if not isinstance(value, self.type):
raise TypeError
setattr(instance, self.name, value)
class TypedMeta(type):
def __new__(cls, name, parents, attrs):
slots = []
for key, value in attrs.iteritems():
if isinstance(value, TypedProperty):
value.name = '_' + key
slots.append(value.name)
attrs['__slots__'] = slots
return type.__new__(cls, name, parents, attrs)
class Foobar(object):
__metaclass__ = TypedMeta
name = TypedProperty(str)
num = TypedProperty(int, 42)
总结
Metaclass是Python提供的一种动态编程的特性,然而其用法并不那么简明直观。在官方文档上也没有很好的文档进行说明,在多次反复遗忘之后,在这里进行记录与总结。
一切都是约定。针对对象的特殊操作,在对应的Class里修改,针对Class的特殊操作,在Metaclass里修改。