200字范文,内容丰富有趣,生活中的好帮手!
200字范文 > 元类(黑魔法)超级无敌宇宙详细

元类(黑魔法)超级无敌宇宙详细

时间:2020-10-12 22:41:26

相关推荐

元类(黑魔法)超级无敌宇宙详细

引入

Python中一切皆对象, 那么本质上也是一个对象

一.什么是元类

类既然也是对象, 那么就应该有另一个类来实例化得到它,实例化得到类的类就是元类

默认情況下, 元类是type这个类, 并且所有的类都是由元类实例化得到的, 包括他自己

1.先定义一个类来进行分析

class Immortal(object):def __init__(self,name,age):self.name = nameself.age = agep = Immortal("太白",4555)print(type(p)) # <class '__main__.Immortal'>

所有的对象都是通过[类名] + ( )得到的, 也叫做实例化,p对象就是由Immortal类实例化得到的

一切皆对象, 那么Immortal应该也是一个类实例化的结果, 于是我们可以推导出元类+( ) —> Immortal

print(type(Immortal)) # <class 'type'>print(type(type))# <class 'type'>

通过type函数我们发现Immortal的类就是type类, 并惊奇的发现type类的类是自身

由此我们可以推断出 : 元类实例化得到类, 类实例化得到对象(实例), 并且验证了所有类都是由type实例化得到的, 不信你试试

二.分析class关键字创建类的过程

上面我们都是使用class这个关键字来产生类的,class关键字在帮我们创建类的时候必然调用了Immortal = type( )这种方法, 那么type里的参数应该是什么呢?

1.内置函数 exec 的用法

在分析class工作流程之前我们先来了解一下exec函数作为储备知识 :exec有三个参数

第一个参数是包含的要执行的一系列 Python 代码(字符串格式)第二个参数是全局名称空间 (字典形式), 默认为globals( )第三个参数是局部名称空间 (字典形式), 默认为local( )

作用 : 可以将字符串里内容当做Python语句来执行, 并将期间产生的名字存放于局部名称空间中

msg = '''name = "shawn"age = 22dic = {"sex":"man"}def test():print("I am shawn")'''globals_dic = {} # 全局名称空间locals_dict = {} # 局部名称空间exec(msg,globals_dic,locals_dict)print(locals_dict) # {'name': 'shawn', 'age': 22, 'dic': {'sex': 'man'}, 'test': <function test at 0x000002596D9BC4C8>}print(locals_dict["name"]) # shawnlocals_dict["test"]() # I am shawn

2.调用 type + ( )来实现创建类

原来我们使用class来创建类, 其中必然调用了元类type,type中需要传入三个参数, 这三个参数是类的三大组成部分 :

类名: Immortal = type( )父类们(基类们): class_bases = (object , )类的名称空间: class_namespace (类的名称空间是执行类体代码而得到的)

接下来我们开始动手创建一个类了

💠设置类名class_name = "Immortal"💠设置基类们class_bases = (object,)💠设置类体(类的名称空间)class_body = '''name = "shawn"age = 22def print_name(self):print(f"I am {self.name}")'''class_namespace = {}💠运行"exec"函数exec(class_body,{},class_namespace)💠创建类Immortal = type(class_name,class_bases,class_namespace)💠实例化p = Immortal()print(p.name,p.age) # shawn 22p.print_name() # I am shawn

以上操作就是class关键字的工作流程, 相当于下面的效果

class Immortal(object):name = "shawn"age = 22def print_name(self):print(f"i am {self.name}")p = Immortal()

🔰由此可知“class”关键字的本质就是Immortal = type('Immortal',(object,),dic)

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

既然我们已经了解了class类的工作流程, 那我们就可以使用这种原理来自定义自己的元类

1.metaclass 关键字指定元类

首先我们得了解, 一个类如果没有声明自己的元类, 那么它默认的元类就是type, 除了使用默认的元类, 我们还可以通过继承type类来自定义元类, 指定元类的关键字是 :metaclass

class Monster(metaclass=type): # 一个类默认的元类是type, 像左边这样...class Demon(metaclass=Mytype): # 像这样, 我们可以使用metaclass关键字来指定一个类的元类...

2.自定义元类

值得注意的是 : 如果是元类, 那么必须继承type, 否则就是一个普通的自定义类

class Mytype(type): # 只有继承了 type 的类才能称之为一个元类, 否则就是一个普通的自定义类...class Monster(metaclass=Mytype): # 使用 metaclass 关键字来指定元类 Mytypedef __init__(self,name,age):self.name = nameself.age = age#🔰"class Monster(metaclass=Mytype):" = "Monster = Mytype('Monster',(object,),{})"

上面是一个简单版的自定义元类, 我们可以知道的是class Monster(metaclass=Mytype):这一句等于Monster = Mytype('Monster',(object,),{}),看到这是不是清晰了很多呢?

下面我们在Mytype中进行一些初始化类的设置

class Mytype(type): # 只有继承了 type 的类才能称之为一个元类def __init__(self,class_name,class_bases,class_namespace):print(self) # <class '__main__.Monster'>print(class_name) # Monsterprint(class_bases)# (<class 'object'>,)print(class_namespace) # {'__module__': '__main__', '__qualname__': 'Monster', '__init__': <function Monster.__init__ at 0x000002A528B4CDC8>}super().__init__(class_name,class_bases,class_namespace) # 使用父类的__init__来完成初始化class Monster(metaclass=Mytype): # 指定元类def __init__(self,name,age):self.name = nameself.age = ageprint(type(Monster)) # <class '__main__.Mytype'> (可以发现是自定义的元类创建出来的)

接下来我们再对这个元类添加一些对创建类的限制功能

需求1 : 类名必须首字母大写, 否则抛出异常需求2 : 类中必须有文档注释, 否则抛出异常

class Mytype(type):def __init__(self,class_name,class_bases,class_namespace):super().__init__(class_name,class_bases,class_namespace)# 设置需求1if not class_name.istitle():raise Exception('类名首字母必须大写')# 设置需求2doc = class_namespace.get("__doc__")if not doc or len(doc) == 0:raise Exception('注释文档不能为空')class Monster(metaclass=Mytype):'''我是Monster的注释'''def __init__(self,name,age):self.name = nameself.age = age

上面创建的类首字母大写了, 也有注释, 是正常运行的, 接下来我们来测试一下两种异常

💠首字母没有大写class monster(metaclass=Mytype):'''我是Monster的注释'''def __init__(self,name,age):self.name = nameself.age = age#🔰 抛出异常 : Exception: 类名首字母必须大写💠没有注释信息class Monster(metaclass=Mytype):def __init__(self,name,age):self.name = nameself.age = age#🔰 抛出异常 : Exception: 注释文档不能为空

需求3 : 在元类中控制把自定义类的数据属性都变成大写 (有坑)

💠刚学过使用元类来控制类的产生, 解决这个问题是不是非常简单呢? 直接干!class Mytype(type):def __init__(self,name,bases,dic): # 初始化类super().__init__(name,bases,dic)new_dic = {} # 创一个字典存放修改后的值for k,v in dic.items():if k.startswith("__") or callable(k): # "__"开头和可执行方法不修改new_dic[k] = velse:new_dic[k.upper()] = vself.__dict__ = new_dic # 将字典赋值给类的属性字典class Person(metaclass=Mytype):name = "shawn"age = 19sex = "man"🔰 AttributeError: ..... is not writable (抛出异常,提示我们根本不可写)🔰 我们在初始化方法的下方打印下"self.__dict__"的类型print(type(self.__dict__)) # <class 'mappingproxy'>🔰 发现并不是"dict"类型, 所以我们无法对他进行修改

需求3解决方法 : 既然在**"__init__“里面无法修改, 那我们就应该想到可以在对象出来的那一刻对其进行修改, 创建空对象的方法是”__new__"**

class Mytype(type):def __new__(self,name,bases,dic):new_dic = {} # 先创建一个空字典用来接收修改后的属性for k,v in dic.items():if k.startswith("__") or callable(k): # "__"开头以及可执行的属性不进行大写操作new_dic[k] = velse:new_dic[k.upper()] = vreturn super(Mytype, self).__new__(self,name,bases,new_dic) # 重新调用父类的"__new__"方法以新的名称空间创建一个对象并返回class Person(metaclass=Mytype): # 实际进行的操作 : Person = Mytype('Person',(object,),dic)name = "shawn"age = 12sex = "man"print(Person.__dict__)# {'__module__': '__main__', 'NAME': 'shawn', 'AGE': 12, 'SEX': 'man', '__dict__': <attribute '__dict__' of 'Person' objects>, '__weakref__': <attribute '__weakref__' of 'Person' objects>, '__doc__': None}🔰 因为在"__new__"里接收的"dic"是一个字典类型(名称空间), 我们可以对其进行修改, 或者传入新的字典进行创建

四.自定义元类来控制类的调用 (类实例化)

1.__call__ 方法的触发

在进行操作之前我们先复习一下__call__方法来进行储备

触发条件 : [对象] + ( ) 就触发__call__的执行

class Person:def __call__(self, *args, **kwargs):print(self)print(args)print(kwargs)P1 = Person()P1(1,2,3,4,name="shawn") # 对象 + ( )# <__main__.Person object at 0x0000019B82C6AC48># (1, 2, 3, 4)# {'name': 'shawn'}

由上可知, 调用一个对象最先触发的就是类中的__call__方法, 一切皆对象, 那么调用这个类的时候是不是应该触发这个类的类的__call__方法呢😂

下面我们来简单验证一下

class Mytype(type):def __call__(self, *args, **kwargs):print(self) # <class '__main__.Monster'>print(args) # (1, 2, 3, 'a')print(kwargs) # {'name': 'shawn'}return 111111class Monster(metaclass=Mytype):...p = Monster(1,2,3,"a",name = "shawn")print(p) # 111111

显而易见的结论 :

调用Monster就是在调用Mytype中的__call__方法然后将Monster传给Mytype中的self, 溢出的位置参数传给*,溢出的关键字参数传给**调用Monster的返回值就是调用__call__的返回值

2.重写元类中的 __call__ 方法来控制 Monster 的调用过程

默认的, 一个类的调用 (p = Monster(“name”,29)像这种) 会发生三件事 :

调用__new__方法创建一个空对象obj调用__init__方法初始化对象objreturn初始化后的对象obj

class Mytype(type):def __call__(self, *args, **kwargs):print("i am Mytype_call") # 添加测试obj = self.__new__(self) # 创建空对象 obj self.__init__(obj,*args,**kwargs) # 通过父类来完成obj的初始化 (self=Monster,obj=p)return obj# 返回初始化之后的 objclass Monstar(metaclass=Mytype):def __init__(self,name,age):self.name = nameself.age = agep = Monstar("shawn",22) # i am Mytype_callprint(p.name) # shawnprint(p.age) # 22print(p) # <__main__.Monstar object at 0x000001881394AD08>

当整个流程已经明了之后我们就可以随心所欲的对调用过程做一些"手脚"

需求1 : 在元类中控制自定义的类无需使用__init__方法

class Mytype(type):def __call__(self, *args, **kwargs):obj = self.__new__(self)for k,v in kwargs.items(): # 我们直接遍历 kwargs 取出 key 和 valueobj.__dict__[k] = v# 再将其放入到对象的属性字典中去return objclass Person(metaclass=Mytype):def print_dict(self):print(self.__dict__)p = Person(name="shawn",age=22) # 这种设计方式就需要你调用传参的时候按照键值对的形式来传print(p.name,p.age) # shawn 22p.print_dict() # {'name': 'shawn', 'age': 22}

需求2 : 在元类中控制自定义的类产生的对象相关的属性全部为隐藏属性

class Mytype(type):def __call__(self, *args, **kwargs):obj = self.__new__(self) # 得到空对象obj (self = Person)self.__init__(obj,*args,**kwargs) # 初始化对象obj (obj = p)# 循环取出对象 p 属性字典里面的 key 和 value 进行隐藏属性操作(这里使用的是字典推导式)obj.__dict__ = {f"_{self.__name__}__{k}":v for k,v in obj.__dict__.items()}return obj # 返回处理后的对象class Person(metaclass=Mytype):def __init__(self,name,age,sex):self.name = nameself.age = ageself.sex = sexdef print_name(self): # 设置一个查看 name 的方法print(self.__name)p = Person("shawn",22,"man")# print(p.name) # 抛出异常 : "AttributeError" 没有该属性# print(p.age) # 抛出异常 : "AttributeError" 没有该属性p.print_name() # shawnprint(p.__dict__) # {'_Person__name': 'shawn', '_Person__age': 22, '_Person__sex': 'man'}

至此,自定义元类来控制类的创建过程以及**自定义元类来控制类的调用 (类实例化)**已经介绍完毕了, 小伙伴有没有学废呢?

五.加入元类之后的属性查找

1.类属性查找顺序 :

先对象层 : Person -----> Foo -----> Bar -----> object (mro列表)再元类层 : Mytype -----> type对象的属性查找: 只会找到object. 不会找元类.

class Mytype(type):n = 555def __call__(self, *args, **kwargs):obj=self.__new__(self)self.__init__(obj,*args,**kwargs)return objclass Bar(object):# n=333...class Foo(Bar):# n=222...class Person(Foo,metaclass=Mytype):# n=111def __init__(self,name,age):self.name=nameself.age=ageprint(Person.n)

2.使用 __new__ 创建空对象的两种方式

obj = self.__new__(self): 就是上面我们使用的方法,推荐使用这种, 如果self中没有**__new__**方法, 它是按照继承关系去查找__new__的, 一层层往上找, 最终能在object中找到, 不必到 元类中去找obj = object.__new__(self): 这种方法是直接跳过前面三个类去找object__new__方法, 如下图 :应该是这样画

就这样吧!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。