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 0×7fc1aa0dbe50>
元类(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 0×7fc1aa0cbed0>, ‘acc2’: <function Eg3.acc2 at 0×7fc1aa1063b0>}
在定义的时候,发现竟然有输出。因为定义的时候,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]。
with_metaclass()是six库提供的实用程序类工厂函数,可以更轻松地为Python 2和3开发代码.
它为您创建一个带有指定元类的基类