Python 元类详解 __new__、__init__、__call__[补充说明]

昨天收官之作,以为自己懂的差不多了,但今天看了工作中的框架源码。

def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(meta):
        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)
    return type.__new__(metaclass, str('temporary_class'), (), {})

  直接一脸懵逼了。

参考链接:https://liqiang.io/post/understanding-python-class-instantiation

从头开始梳理一遍,先写轻松一点的从类到对象,前一篇已经讲过,类实例化的过程是在调用元类的__call__属性,参考链接找到

type中的__call__的逻辑如下

class Mymeta(type):

    def __call__(self, *args, **kwargs):
        obj = self.__new__(self, *args, **kwargs)
        # 返回对象是类的实例才执行__init__
        if isinstance(obj, self):
            # self.__init__(obj, *args, **kwargs)
            obj.__init__(*args, **kwargs)
        return obj

  这个应该是Mymeta的__call__的逻辑,首先执行类的__new__方法,创建一个空对象,默认使用的object.__new__方法,而且只接受一个类对象。然后通过判断生成对象是否为类的实例判断是否执行__init__。

所以当你从__new__随便返回任何数据都可以,只有是否执行__init__的区别。

今天晚上看来剩下的时间还是留给__new__。上面的函数卡住我将近一天,主要还是我对类的继承理解有问题,对__new__的理解也不够测底。

个人的理解:一个类对象初始状态是没有任何属性的。就因为没有任何的属性,所以任何一个类都继承与object,由object给予子类与属性。

这里我个人可以分为两条线,一条线是比较容易理解的普通类,普通类默认的状态下,所有的类都继承与obejct。所以很明显,__new__的属性也继承来至object,这个一个没有__get__属性的可调用对象[也被称为静态方法]。

所以不管是类还是实例,调用该object.__new__都需要传入一个被实例的类,返回值是属于该类的实例。在普通的情况下,实例化一个对象的逻辑也是通过调用元类的__calll__属性,然后通过object.__new__来首先创建一个无私有属性的对象。所以当一个类继承了一个修改过__new__属性的类的时候,如果自身没有重写__new__属性,他就会执行Python的继承原则,优先调用父类的__new__属性。

所以当你通过修改__new__内部逻辑实现单例的类,所有继承与它的类一般情况下都是单例。

还有一条线就是元类,所有的类的创建都是基础元类中的__new__方法实现,如有特殊需求,可以在创建的元类中自定义返回的初始类。

如果理解了这个,那下面这个函数就可以理解了。

 

def with_metaclass(meta, *bases):
    """Create a base class with a metaclass."""
    # This requires a bit of explanation: the basic idea is to make a dummy
    # metaclass for one level of class instantiation that replaces itself with
    # the actual metaclass.
    class metaclass(meta):
        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)
    return type.__new__(metaclass, str('temporary_class'), (), {})

 

  首先前提已经提过了,通过type可以直接创建一个类,通过type.__new__也可以创建一个类,多了第一个参数的元类选项,创建一个属于这个元类的类。

我没由去寻找type的源码,很明显type的__new__执行跟object.__new__逻辑应该一样,但实现方式有所不同。元类的__new__需要创建一个类,一个初始化的类。

前面那篇博文我已经写名了,我们无法再去定义类->元类->上一级造物主对象的__call__,因为type还是type创建的,所以根据常规的类的初始化的逻辑,先执行__new__创建一个新对象,然后再执行__init__初始化对象的属性。

type.__new__前面已经介绍,功能比较强大,默认情况下传入第一个参数为创建类的元类。在一个没有修改过__new__参数的元类。

Mymeta('Myclass', (), {})
type.__new__(Mymeta, 'Myclass', (), {})

Mymeta是一个没有修改__new__属性的元类,这两个效果是一样的

 

  

class Mymeta(type):
    def run(cls):
        print('run')


    name = 'sidian'


D1 = Mymeta('D1', (),{})

D2 = type.__new__(Mymeta, 'D1', (),{})

print(dir(D1))
print(dir(D2))

D1.run()
print(D2.name)

  运行的这段代码的输出,元类的属性虽然被创建的类可以调用,但在dir的时候都不能显式该属性,只能通过hasattr来判断。元类由太多的骚操作了。

时间迟了,对于框架中的这个函数。首先它返回的是一个类,这个要确认,这个方法很的使用场景也很特别,一般写在这种地方。

class Strategy(with_metaclass(MetaStrategy, StrategyBase)):
    '''
    Base class to be subclassed for user defined strategies.
    '''

  在这里定义,可以让类继承这个函数创建的类,但这个函数创建的类是一个傀儡,这个傀儡类由一个傀儡元类创建,内部定义的__new__返回的才是真正需要的类。

class metaclass(meta):
        def __new__(cls, name, this_bases, d):
            return meta(name, bases, d)
    return type.__new__(metaclass, str('temporary_class'), (), {})

  默认情况下,当一个类被继承创建一个类的情况是在__new__中的默认操作为type('子类的类名', (父类,),{子类的命名空间})

但很明显这个函数的作用,是指定了父类,还有指定了元类的作用,而且还可以操作自己的属性空间。这个真的是相当骚的写法。

 就这个写法

class Strategy(with_metaclass(MetaStrategy, StrategyBase)):

  如果转成为显式的写法

class Strategy(StrategyBase, metaclass = MetaStrategy):

  

实际测试效果是一样的,一个明明可以很简单就可以看懂的继承,框架用了这么复杂的函数,实在让人震惊,也可能当时局限在py2的情况下,才不得已用了该途径的操作。

但也让我对__new__这个对象有了更加深入的理解。

最后我也总算知道了,为什么元类都喜欢在元类的定义上面加一个Meta,主要是为了便于从名称标记哪个是元类,那个是普通类。因为说到底,这两个都是类,一定元类定义完成,从print输出,你更本无法判断那个是元类哪个是类。

class Mymeta(type):
    def run(cls):
        print('run')


    name = 'sidian'

class Demo:
    ...

print(Mymeta)
print(Demo)
print(Mymeta.__class__)
print(Demo.__class__)

  输出

<class '__main__.Mymeta'>
<class '__main__.Demo'>
<class 'type'>
<class 'type'>

  今天就先写到这里,在工作中用到的那个框架里面,用到了大量的元类操作,元类中定义了大量的方法,修改了__call__的实现,后期发现经常的继续补上。

 

posted @ 2021-01-04 18:40  就是想学习  阅读(313)  评论(0编辑  收藏  举报