前面一节留了点悬念,这一节来做解释,相信看完这节你会对元类有更加深刻的认识。
元类其实和普通类一样,普通类的__new__方法是创建实例,__init__方法是初始化实例,说是初始化,其实就是可以给实例添加一些属性。在元类中也是一样,只是元类__new__创建的是类实例,__init__是对类实例做修改。
元类__init__中的第一个参数是cls(普通类是self)表示的是类实例本身,有了类实例本身,当然能对类做一些修改。那么在__new__和__init__方法中都可以对类实例做什么样的修改?
建议:在看本节之前建议先debug下一节的最后一个案例(传送门)
元类中,__new__和__init__中除了第一个参数不一样,其它参数都是一样的,参数都是类名,基类,类属性字典。这是元类创建一个类的三要素。在下面这个案例中,我将分别在__new__和__init__方法中做一些手术。
from pprint import pprint
import inspect
class Tag1: pass
class Tag2: pass
class Tag3:
def tag3_method(self): pass
class MetaNewVSInit(type):
def __new__(mcs, *args, **kwargs):
print('MetaNewVSInit.__new__')
name, bases, nmspc = args[0], args[1], args[2]
for x in (mcs, name, bases, nmspc): pprint(x)
print('')
if 'foo' in nmspc: nmspc.pop('foo')
nmspc['__slots__'] += ('z',)
name += '_x'
bases += (Tag1,)
nmspc['baz'] = 42
args = (name, bases, nmspc)
return super(MetaNewVSInit, mcs).__new__(mcs, *args, **kwargs)
def __init__(cls, *args, **kwargs):
name, bases, nmspc = args[0], args[1], args[2]
print('MetaNewVSInit.__init__')
for x in (cls, name, bases, nmspc): pprint(x)
print('')
nmspc['__slots__'] += ('m',) # 无效
if 'bar' in nmspc: nmspc.pop('bar') # 无效
name += '_y' # 无效
bases += (Tag2,) # 无效
nmspc['pi'] = 3.14159 # 无效
args = (name, bases, nmspc)
super(MetaNewVSInit, cls).__init__(*args, **kwargs)
class Test(metaclass=MetaNewVSInit):
__slots__ = ('x', 'y')
def __init__(self):
print('Test.__init__')
def foo(self): print('foo still here')
def bar(self): print('bar still here')
print(inspect.getmro(Test))
print(Test.mro())
print(Test.__mro__)
t = Test()
print('class name: ' + Test.__name__)
print('base classes: ', [c.__name__ for c in Test.__bases__])
print([m for m in dir(t) if not m.startswith("__")])
t.bar()
print(t.e)
输出:
AttributeError: 'Test_x' object has no attribute 'e'
MetaNewVSInit.__new__
<class '__main__.MetaNewVSInit'>
'Test'
()
{'__init__': <function Test.__init__ at 0x000001E37BEB1160>,
'__module__': '__main__',
'__qualname__': 'Test',
'__slots__': ('x', 'y'),
'bar': <function Test.bar at 0x000001E37BEB1280>,
'foo': <function Test.foo at 0x000001E37BEB11F0>}
MetaNewVSInit.__init__
<class '__main__.Test'>
'Test'
()
{'__init__': <function Test.__init__ at 0x000001E37BEB1160>,
'__module__': '__main__',
'__qualname__': 'Test',
'__slots__': ('x', 'y', 'z'),
'bar': <function Test.bar at 0x000001E37BEB1280>,
'baz': 42}
(<class '__main__.Test'>, <class '__main__.Tag1'>, <class 'object'>)
[<class '__main__.Test'>, <class '__main__.Tag1'>, <class 'object'>]
(<class '__main__.Test'>, <class '__main__.Tag1'>, <class 'object'>)
Test.__init__
class name: Test_x
base classes: ['Tag1']
['bar', 'baz', 'x', 'y', 'z']
bar still here
如果对创建类的三要素做了修改,那么是不是就修改了类实例。在这个案例中分别在__new__和__init__方法对三要素做了修改,在__new__方法中改了__slots__属性,加了基类tag2,改了类名,加了属性,删除了bar方法。在__init__中做了类似的操作。从最后输出结果可以看出__new__的修改起作用了,__init__的操作并没其作用。
主要原因是创建类实例是在__new__方法中执行的,在__init__方法中实例已经生成了,改三要素也不会再一次生成类实例。所以要想在__init__方法中起到作用,只能修改实例本身。这就好比普通类中的__new__是创建对象实例,__init__只是修改这个实例,添加一些属性。将__init__改成下面这样,修改就能起到效果了。
def __init__(cls, *args, **kwargs):
cls.__name__ += '_z'
cls.__bases__ += (Tag3,)
cls.e = 2.718
在__init__中只有直接对cls做修改才有效。看完这些是不是对元类有了进一步的认识,实际上元类和普通类是一样的,只是元类创建的实例是类,普通类创建的实例是对象。
本人是做大数据开发的,在微信上开了个个人号,会经常在上面分享一些学习心得,原创文章都会首发到公众号上,感兴趣的盆友可以关注下哦!
备注:微信公众号搜索‘大数据入坑指南’