细说元类

Posted waller

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细说元类相关的知识,希望对你有一定的参考价值。

 

基于python的宗旨:一切皆对象。而对象都是由类实例化得到的

class OldboyTeacher(object):
    school = oldboy
    def __init__(self,name):
        self.name = name
    def run(self):
        print(%s is running%self.name)
t1 = OldboyTeacher(jason)
# 对象t1是由类OldboyTeacher实例化得到

那么类也是对象,它又是谁实例化得到的呢?

# 分别查看对象t1和OldboyTeacher的类型
print(type(t1))
print(type(OldboyTeacher))

# 结果为:
<class __main__.OldboyTeacher>
<class type>

结论1:元类就是产生类的类,默认情况下type就是所有类的元类

0|1二、不依赖于class关键字创建类


根据第一个结论我们能理出两条对应关系

  1.调用元类得到自定义的类

  2.调用自定义的类得到自定义的类的对象

现在我们来看第一对关系,调用元类来得到自定义的类,都需要哪些参数(OldboyTeacher=type(...),括号内传什么?)

我们自定义一个类的时候都有哪些关键的组成部分:

  1.类名

  2.类的父类

  3.类的名称空间

就以第一阶段的OldboyTeacher类为例,calss关键字创建自定义类的步骤

"""
1.获取类名(OldboyTeacher)

2.获取类的父类(object,)

3.执行类体代码获取产生的名称空间(如何获取???)

4.调用元类得到自定义类OldboyTeacher = type(class_name,class_bases,...)
"""

知识点补充:

如何执行一段字符串内部的代码并将产生的名称空间交给对应的参数?  >>>   exec()

class_body = """
school = ‘oldboy‘
def __init__(self,name):
      self.name = name
def run(self):
      print(‘%s is running‘%self.name)
"""
class_dic = 
class_global = 

exec(class_body,class_global,class_dic)
# class_global一般情况下都为空,除非在字符串代码内部用global关键字声明,才会将产生的名字丢到class_global全局名称空间中
print(class_dic)

school: oldboy, __init__: <function __init__ at 0x000000B5D2771EA0>, 

run: <function run at 0x000000B5DB5B7400>

有了这个exec方法后,我们就可以不依赖于calss关键字创建自定义类

# 类名
class_name = OldgirlTeacher
# 类的父类
class_bases = (object,)  # 注意必须是元祖,逗号不能忘
# 名称空间
class_body = """
school = ‘oldgirl‘

def __init__(self,name):
    self.name = name

def run(self):
    print(self.name)
"""
class_dic = 
exec(class_body,,class_dic)

#调用元类创建自定义类
OldgirlTeacher = type(class_name,class_bases,class_dic)
print(OldgirlTeacher)

# 结果为:<class ‘__main__.OldgirlTeacher‘>

# 并且它可以访问自身的属性和方法,并实例化产生对象
print(OldgirlTeacher.school)
print(OldgirlTeacher.run)

# 结果为:
"""
oldgirl
<function run at 0x000000229B157378>
"""

obj = OldgirlTeacher(jason)
print(obj.school)
obj.run()


"""
oldgirl
jason
"""

0|1三、自定义元类控制类的创建过程


1.如何自定义元类

class Mymeta(type):  # 必须是继承了type的类才是自定义元类
    pass

class oldboyTeacher(metaclass=Mymeta):  # 通过metaclass可以指定类的元类
    school = oldboy

    def __init__(self,name):
        self.name = name

    def run(self):
        print(%s is running%self.name)

2.__call__

思考:一个类的对象加括号调用会执行该对象父类中的__call__方法,那么类也是对象,它在加括号实例化对象的时候,是不是也应该走它父类的__call_方法?

class Mymeta(type):
    def __call__(self, *args, **kwargs):
        print(self)
        print(args)
        print(kwargs)

class OldboyTeacher(object,metaclass=Mymeta):
    school = oldboy
    def __init__(self,name):
        self.name = name
    def run(self):
        print(%s is running%self.name)
obj = OldboyTeacher(jason)

"""
打印结果:
<class ‘__main__.OldboyTeacher‘>
(‘jason‘,)

"""

思考:类加括号实例化对象的时候,有哪几个步骤?

  1.创建一个该类的空对象

  2.实例化该空对象

  3.将实例化完成的空对象返回给调用者

# 也就是说__call__里面需要做三件事
class Mymeta(type):
    def __call__(self, *args, **kwargs):
        # 1.产生空对象
        # 2.初始化对象
        # 3.返回该对象
        # 那我先做最后一件事,返回一个123,发现
        return 123

obj = OldboyTeacher(jason)
print(obj)  
# 结果就是123    

那接下来就需要我手动去干这三件事了

class Mymeta(type):  
  def __call__(self, *args, **kwargs):
        # 1.产生一个空对象
        obj = self.__new__(self)
        # 2.实例化该对象
        self.__init__(obj,*args,**kwargs)
        # 3.返回该对象
        return obj
# 关于这个__new__,我们是不是不知道是个啥,我这里直接告诉你,它就是用来创建空对象的

思考:这是类加括号产生对象的过程,那么我元类加括号产生类的过程是不是也应该是这个三步

  1.产生一个空对象(指类)

  2.实例化该空对象(实例化类)

  3.将实例化完成的类对象返回

那依据上面的推导,self.__new__就是关键了,我可以在我的自定义元类里面定义一个__new__方法,看看它到底是个啥

class Mymeta(type):
    def __new__(cls, *args, **kwargs):
        print(cls)
        print(args)
        print(kwargs)


class OldboyTeacher(object,metaclass=Mymeta):
    school = oldboy

    def __init__(self, name):
        self.name = name

    def run(self):
        print(%s is running % self.name)

"""
<class ‘__main__.Mymeta‘>
(‘OldboyTeacher‘, (object,), ‘__module__‘: ‘__main__‘, ‘__qualname__‘: ‘OldboyTeacher‘,
‘school‘: ‘oldboy‘, ‘__init__‘: <function OldboyTeacher.__init__ at 0x000000323CEB9510>,
‘run‘: <function OldboyTeacher.run at 0x000000323CEE7158>)
"""

我们发现__new__里面的*args参数接收到了三个位置参数,并且很容易辨认它们对应的就是类名,类的父类,类体代码执行后的名称空间

那么我们可不可以将__new__()的形参换一种写法

class Mymeta(type):
    def __new__(cls, class_name,class_bases,class_dic):
        print(class_name)
        print(class_bases)
        print(class_dic)
     # 这里需要记住的是,必须在最后调用元类type中的__new__方法来产生该空对象
        return type.__new__(cls,class_name,class_bases,class_dic)
    
class OldboyTeacher(metaclass=Mymeta):
    school = oldboy
    def __init__(self,name):
        self.name = name
    def run(self):
        print(%s is running%self.name)

验证:

class Mymeta(type):
    def __new__(cls, class_name,class_bases,class_dic):
        print(class_name)
        print(class_bases)
        print(class_dic)
        class_dic[xxx] = 123
        if school in class_dic:
            class_dic[school] = DSB
        return type.__new__(cls,class_name,class_bases,class_dic)
    
class OldboyTeacher(metaclass=Mymeta):
    school = oldboy
    def __init__(self,name):
        self.name = name
    def run(self):
        print(%s is running%self.name)

print(OldboyTeacher.xxx)  # 发现可以打印出来    123
print(OldboyTeacher.school) # DSB

结论:

由此我们就可以通过自定义元类,并重写__new__方法来拦截类的创建过程,在类被创建出来之前进行一系列其他操作

 

以上是关于细说元类的主要内容,如果未能解决你的问题,请参考以下文章

1104课堂小结

细说元类

096 元类

面向对象之元类

面向对象-元类

python 吐血总结面向对象元类