元少带你看元类

一、什么是元类?

基于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就是所有类的元类

二、不依赖于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 """

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

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__方法来拦截类的创建过程,在类被创建出来之前进行一系列其他操作

 

转载于:https://www.cnblogs.com/Dominic-Ji/p/10520256.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值