zoukankan      html  css  js  c++  java
  • 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__的实现,后期发现经常的继续补上。

  • 相关阅读:
    发现不错的cache系统Cache Manager Documentation
    List.Sort用法
    Database Initialization Strategies in Code-First:
    git rebase
    osharpV3数据库初始化
    IdentityDbContext
    AspNetUsers
    VS2015 推荐插件
    ELMAH日志组件数据库脚本
    C#如何把List of Object转换成List of T具体类型
  • 原文地址:https://www.cnblogs.com/sidianok/p/14231515.html
Copyright © 2011-2022 走看看