Python自学成才之路 元类中的__new__和__init__方法

时间:2022-07-23
本文章向大家介绍Python自学成才之路 元类中的__new__和__init__方法,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前面一节留了点悬念,这一节来做解释,相信看完这节你会对元类有更加深刻的认识。

元类其实和普通类一样,普通类的__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做修改才有效。看完这些是不是对元类有了进一步的认识,实际上元类和普通类是一样的,只是元类创建的实例是类,普通类创建的实例是对象。