Python 元类及with_metaclass
python使用__new__和__init_来创建和初始化一个对象[4]。
在基础类object中,__new__被定义成了一个静态方法,并且需要传递一个参数cls。cls表示需要实例化的类,此参数在实例化时由Python解析器自动提供。__new__会返回一个对象,传输给__init__的self。
class Test(object):
# 1. 创建对象
def __new__(cls):
print("new ", cls)
return object.__new__(cls) # 必须有return
# 2. 将创建的对象传给self,init不需要返回值
def __init__(self):
print("init ", self)
object.__init__(self)
# 实例化类
tt = Test()
# 输出(注意二者顺序):
# new <class '__main__.Test'>
# init <__main__.Test object at 0x7fc1aa0dbe50>
元类(metaclass)可以控制类的创建行为,元类生成的实例是类。那么怎么创建metaclass呢?——以type为基类。定义一个类,指定它的元类,就可以通过元类对类进行修改。
在python3里面[3],首先定义元类TestMeta3和要继承的父类Pa3:
class TestMeta3(type):
def __new__(cls, name,bases,attrs):
print(cls)
print(name)
print(bases)
print(attrs)
return type.__new__(cls,name,bases,attrs)
class Pa3:
pass
接着定义带有元类和父类的类:
class Eg3(Pa3, metaclass=TestMeta3):
@classmethod
def get(self):
kkk=[]
kkk.append(self.__skiless__)
return kkk
def acc2(self):
return 'a2'
# 输出
# <class '__main__.TestMeta3'>
# Eg3
# (<class '__main__.Pa3'>,)
# {'__module__': '__main__', '__qualname__': 'Eg3', 'get': <classmethod object at 0x7fc1aa0cbed0>, 'acc2': <function Eg3.acc2 at 0x7fc1aa1063b0>}
在定义的时候,发现竟然有输出。因为定义的时候,python解释器会在当前类中查找metaclass[3],如果找到了,就使用该metaclass创建Eg3类。所以打印出来的name、bases、attrs都和Eg3有关。
由于python2和python3中元类使用方法的不同,我们需要使用一种兼容的方式[1],如下所示:
def with_metaclass(meta, *bases):
print("with metaclass")
return meta('temp_class', bases, {})
class TestMeta(type):
def __new__(cls, name, bases, d):
d['a'] = 'xyz'
print("new ", cls, name, bases)
return type.__new__(cls, name, bases, d)
def __init__(cls, name, bases, d):
print("init ", cls, name, bases)
type.__init__(cls, name, bases, d)
class Foo(object):pass
temp = with_metaclass(TestMeta, Foo)
# 输出:
# with metaclass
# new <class '__main__.TestMeta'> temp_class (<class '__main__.Foo'>,)
# init <class '__main__.temp_class'> temp_class (<class '__main__.Foo'>,)
为了创建temp,先调用with_metaclass(meta,*bases),然后调用meta('temp_class', bases,{}),因为meta此时对应TestMeta,所以调用TestMeta('temp_class', bases,{}),输出<class '__main__.TestMeta'> temp_class (<class '__main_.Foo'>,)。
这样我们就得到了一个名为temp_class的类temp,以TestMeta为元类,以Foo为父类,这个类是TestMeta创建出来的。但是这样不方便对它定义函数,所以我们新定义一个class Bar,继承temp_class。
class Bar(temp): pass
# 输出:
# new <class '__main__.TestMeta'> Bar (<class '__main__.temp_class'>,)
# init <class '__main__.Bar'> Bar (<class '__main__.temp_class'>,)
当用户定义 Bar(temp) 时,Python解释器首先在当前类Bar的定义中查找metaclass,如果没有找到,就继续在父类temp
中查找metaclass
,找到了,就使用temp的metaclass
来创建Bar
类,也就是说,metaclass可以隐式地继承到子类,但子类自己却感觉不到[3]。
这样我们就得到了一个以TestMeta为元类,继承Foo的名为Bar的类。
但我们看到在Bar
的继承关系(使用Bar.__mro__查看)里混进了一个临时类temp_class
,你忽略它吧,有时会很麻烦。作为完美主义者,我想寻找一种解决办法,不要在mro中引入多余的类。Python的six
模块专门为解决Python 2to3兼容问题而生,模块里带有一个with_metaclass
函数[1]。
# 这里的meta继承type类,是一个元类
def with_metaclass(meta, *bases):
class metaclass(meta):
# 注意这里的this_bases被省略了
def __new__(cls, name, this_bases, d):
print("m new ",cls, name, this_bases)
return meta(name, bases, d)
# 创建一个名为temp_class的类,这个类未被初始化
return type.__new__(metaclass, 'temp_class', (), {})
# 注意,type.__new__()需要与type()区分 [2]
# 一个名为temp_class的类,是元类metaclass的实例,和TestMeta、Foo无关
temp = with_metaclass(TestMeta, Foo)
class Bar(temp): pass
# 输出
# m new <class '__main__.with_metaclass.<locals>.metaclass'> Bar (<class '__main__.temp_class'>,)
# new <class '__main__.TestMeta'> Bar (<class '__main__.Foo'>,)
# init <class '__main__.Bar'> Bar (<class '__main__.Foo'>,)
这个with_metaclass返回type.__new__(metaclass,'temp_class',(),{}),也就是使用metaclass元类创建一个名为temp_class的类,返回的这个类此时还没有进行__init__。
接着,定义Bar,因为Bar继承的temp是元类metaclass创建的,所以:
- 首先会找到metaclass的定义,实例化metaclass,也就是调用metaclass的__new__。
- 当前的this_bases是'temp_class',bases才是真正的父类,所以直接跳过this_bases,以bases作为父类,即meta(name, bases, d)。
- meta指的是TestMeta,因此meta(name, bases, d即TestMeta(name, bases, d)。
但是为什么实例化metaclass的时候没有调用metaclass的__init_呢?因为metaclass的__new返回meta的实例,而在一个类中,返回其他类的实例会跳过__init_,所以这里没有执行__init__。
with_metaclass
返回的临时类中,本身无任何属性,但包含了元类和基类的所有信息,并在下一步定义类时将所有信息解包出来[1]。
参考: