元类的理解

回忆一下以前type的介绍python

class Foo(object):
  
  
    def __init__(self,name):
        self.name = name
  
  
f = Foo("lxj")

print( type(f)) # 输出:<class '__main__.Foo'>     表示,obj 对象由Foo类建立
print( type(Foo)) # 输出:<type 'type'>              表示,Foo类对象由 type 类建立

  能够得出f对象是Foo类的一个实例Foo类是 type 类的一个实例,即:Foo类 是经过type类建立的实例。type就是一个类的类,咱们也称为元类git

 

因为元类是一个类的类,因此它被用来构造类(就像一个类用于构造对象同样)。可是等一下,咱们不是建立一个具备标准类定义的类吗?固然,可是Python在底下作了什么:github

当它看到一个类定义时,Python会执行它来收集字典中的属性(包括方法)。
当类定义结束时,Python肯定类的元类。咱们称之为Meta
最终,Python执行Meta(name,base,dct),其中:
Meta是元类,因此这个调用正在实例化它。
name是新建立的类的名称
base:类的基类的一个元组
dct将属性名称映射到对象,列出全部类的属性
咱们如何肯定一个类的元类?简单地说,若是一个类或它的一个基类有一个__metaclass__属性,它就被看成元类。不然,类型是元类。django

当咱们定义一个函数

class MyKlass(object):
  foo = 2

  在这个例子中,没有__metaclass__属性,因此就用type代替,建立过程像下面这样debug

MyKlass = type(name, bases, dct)

  若是metaclass被定义调试

class MyMeta():
    pass
class MyKlass(object,metaclass=MyMeta):  # python3写法

    foo = 2

  建立过程就像这样对象

MyKlass = MyMeta(name, bases, dct)

  

__new__ :当你想要控制一个新的对象的建立(在咱们的例子中是类)blog

__init__ :应该在建立新对象后控制新对象的初始化时执行。内存

MyKlass = MyMeta.__new__(MyMeta, name, bases, dct)
MyMeta.__init__(MyKlass, name, bases, dct)

 看个例子 

class MyMeta(type):
    def __new__(meta, name, bases, dct):
        print('-----------------------------------')
        print ("Allocating memory for class", name)
        print ("meta>>",meta)   #经过程序meta:<class '__main__.MyMeta'>
        print (bases)
        print (dct)


        return super(MyMeta, meta).__new__(meta, name, bases, dct)
    def __init__(cls, name, bases, dct):
        print ('-----------------------------------')
        print ("Initializing class", name)   
        print ("cls>>",cls)    #cls:<class '__main__.MyKlass'>
        print (bases)
        print (dct)

        super(MyMeta, cls).__init__(name, bases, dct)

class MyKlass(object,metaclass=MyMeta):
    # __metaclass__ = MyMeta

    def foo(self, param):
        pass

    barattr = 2

  

 咱们使用pycharm的debug模式来看下程序的运行顺序:

一、解释器从上到下执行,class MyMeat - __new__ - __init__ - class MyKclass -def foo -barattr  个人理解是依次得到函数或则属性的内存地址

二、检测到有metaclass字段,运行__new__ ,__init__

有调试结果得出来,当有metaclass字段时,当解释器编译完全部代码时,会回过头再执行metaclass指向的类中的__new__和__init__方法

用元类实现一个单例模式:

当咱们建立一个类时,每次实例化都是一个新的内存地址,看代码

class Spam(object):
    def __init__(self):
        print("creating Spam")

s = Spam()
a =Spam()
print(s is a )
#结果为False

 再看用元类实现单例模式:

class Singleton(type):
    def __init__(cls,*args,**kwargs): #cls类
        print("Singleton __init__")
        print(cls,*args,*kwargs)
        cls.__instance = None
        super().__init__(*args,**kwargs)

    def __call__(cls, *args, **kwargs):

        print("Singleton __call__")
        print(cls)
        if cls.__instance is None: #判断实例是否被建立
            cls.__instance = super().__call__(*args,**kwargs)
            print(cls.__instance)
            return cls.__instance
        else:
            return cls.__instance

class Spam(metaclass=Singleton):
    def __init__(self,name):
        print("creating Spam name is ",name)
# sp= Singleton(1)
s = Spam('lxj')
a =Spam('sx')
print(s is a )

结果:
Singleton __init__
<class '__main__.Spam'> Spam () {'__module__': '__main__', '__init__': <function Spam.__init__ at 0x7fc0096ff8c8>, '__qualname__': 'Spam'}
Singleton __call__
<class '__main__.Spam'>
creating Spam name is  lxj
<__main__.Spam object at 0x7fc00970dcf8>
Singleton __call__
<class '__main__.Spam'>
True

  

一、首先在实例化前,由于有metaclas存在,咱们调用了__init__函数(参考上面的解释),把cls._instance置为None(即还未实例化),这里cls为Spam,为何不用__new__方法,由于__new__方法中cls为Singleton。上面的知识点咱们也知道__init__方法是用在控制新对象的初始化

二、在结果中咱们看到,在咱们实例化过程当中,调用的是__call__,每实例话一次,就调用一次__call__方法,在call方法中对实例化进行控制

三、结果中咱们看到a和s为指向同一内存地址

 

 

在学到django时再仔细深刻https://github.com/inglesp/Metaclasses

https://stackoverflow.com/questions/100003/what-are-metaclasses-in-python

相关文章
相关标签/搜索