Python元类详解
Python Meta Class
一、 万物皆对象
1、 简介
Python从设计之初就是一门面向对象的语言,它有一个重要的概念,即一切皆对象。
Python与java的区别:
Java虽然也是面向对象编程的语言,但是血统没有Python纯正。比如Java的八种基本数据类型之一int,在持久化的时候,就需要包装成Integer类对象。但是在python中,一切皆对象。数字、字符串、元组、列表、字典、函数、方法、类、模块等等都是对象,包括你的代码。
2、 Python对象
究竟何谓对象?不同的编程语言以不同的方式定义“对象”。某些语言中,它意味着所有对象必须有属性和方法;另一些语言中,它意味着所有的对象都可以子类化。
在Python中,定义是松散的,某些对象既没有属性也没有方法,而且不是所有的对象都可以子类化。但是Python的万物皆对象从感性上可以解释为:Python 中的一切都可以赋值给变量或者作为参数传递给函数。
符合Python对象的条件是:
- 能够直接赋值给一个变量
- 可以添加到集合对象中
- 能作为函数参数进行传递
- 可以作为函数返回值
从这里,大家就可以看出,Python中一切皆为对象,一切都符合这些条件
Python对象都会有三个特征:
- 身份,即是存储地址,可以通过id()这个方法来查询
- 类型,即对象所属的类型,可以用type()方法来查询
- 值,都会有各自的数据
i = 2 print(id(i)) # 查询i变量的内存地址,代码实例 print(type(i)) # 查询i变量的类型 print(i) # 获取i变量所对应的值
对象的属性:大部分 Python 对象有属性、值或方法,使用句点(.)标记法来访问属性。最常见的属性是函数和方法,一些 Python 对象也有数据属性,如:类、模块、文件等。
3、 type|object|class
3.1 关系
首先,我们通过一段代码来理解:
class MyClass(object): pass print(type(MyClass())) # 类的实例对象属于 MyClass print(type(MyClass)) # 发现类的对象属于 type print(type(int)) # 发现int类也属于 type print(type.__base__) # 发现其父类是 object
三个之间的关系:
object
:object类是所有类(class)的父类,包括type类,object类的父类为空type
:type类是所有类的类型,即为所有类(class)都可由type实例化而来,包括type类和父类objectclass
:继承自object
,同时,由type
进行实例化。其中,type
就是我们所要讲的元类(metaclass
)
3.2 创建类的第二中方式
传统创建类的方式:
# 我们传统的实现方式 class MyClass(object): def get(self) -> None: print("类获取了一个信息", self.info) info = "hello" c = MyClass() c.get()
那我们知道了所有类都可以由type实例化来的,那么我们怎么实例化呢?我们如何将上面传统创建类转化为使用type创建类呢?:
def get(self) -> None: """定义类内部需要运行的函数""" print("类获取了一个信息", self.info) MyClass = type("MyClass", (object, ), {"get": get, "info": "hello"}) c = MyClass() if hasattr(c, "get"): # 判断是否有get函数 getattr(c, "get", None)() # 获取get函数,并运行函数 else: print("该类内部没有get函数")
二、 元类
1、 什么是元类
MetaClass
元类,本质也是一个类,但和普通类的用法不同,它可以对类内部的定义(包括类属性和类方法)进行动态的修改。可以这么说,使用元类的主要目的就是为了实现在创建类时,能够动态地改变类中定义的属性或者方法。
不要从字面上去理解元类的含义,事实上 MetaClass 中的 Meta 这个词根,起源于希腊语词汇 meta,包含“超越”和“改变”的意思。
- 什么是类?可能谁都知道,类就是用来创建对象的「模板」。
- 那什么是元类呢?一句话通俗来说,元类就是创建类的「模板」。
- 为什么type能用来创建类?因为它本身是一个元类。使用元类创建类,那就合理了。
type是Python在背后用来创建所有类的元类,我们熟知的类的始祖 object
也是由type创建的。更有甚者,连type自己也是由type自己创建的,这就过份了。
2、 调用流程
python创建类的流程为:
通过创建流程,我们可以知道,元类接收的参数为
(__name: str, **__bases: tuple[type, ...]**, __dict: dict[str, Any], **kwds: Any)
,这个参数即为type()
函数需要传入的参数同时,这个元类可以是函数或者类,但是必须要是一个可调用对象,同时,需要返回一个类
3、 函数做元类
我们首先来学习,使用函数作为元类,
我们来实现一个将类里面的所有属性名称转换为大写:
# 元类会自动获取通常传给`type`的参数 def upper_attr(_class, _object, _attr): """ 返回一个类对象,将其属性置为大写 """ # 过滤出所有开头不为'__'的属性,置为大写 uppercase_attr = {} for name, val in _attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val # 利用'type'创建类,同时将这个类进行返回 return type(_class, _object, uppercase_attr) class Foo(metaclass=upper_attr): # 创建对象时,其会经过 metaclass 来创建,再使用自身的方法进行实例化 bar = 'bip' print(hasattr(Foo, 'bar')) print(hasattr(Foo, 'BAR')) f = Foo() print(f.BAR)
4、 类做元类
类做元类,有两种方式,一种是使用__new__
方法实现元类,另一种是使用__call__
来实现元类:
还是通过上面的实例:
使用__new__
方法来实现元类:
class UpperAttrMetaClass(type): # 注意,元类必须要继承type这个类 """ 返回一个类对象,将其属性置为大写 """ def __new__(cls, *args, **kwargs): _class, _object, _attr = args # 分别对应 类名,继承的类,属性 uppercase_attr = {} # 过滤出所有开头不为'__'的属性,置为大写 for name, val in _attr.items(): if not name.startswith('__'): uppercase_attr[name.upper()] = val else: uppercase_attr[name] = val return super(UpperAttrMetaClass, cls).__new__(cls, _class, _object, uppercase_attr) # 使用type的new方法,相当于使用type创建类 class Foo(metaclass=UpperAttrMetaClass): # 创建对象时,其会经过 metaclass 来创建,再使用自身的方法进行实例化 bar = 'bip' print(hasattr(Foo, 'bar')) print(hasattr(Foo, 'BAR')) f = Foo() print(f.BAR)
在有元类的情况下,每次创建类时,会都先进入 元类的
__new__
方法,如果你要对类进行定制,可以在这时做一些手脚。综上,元类的
__new__
和普通类的不一样:
- 元类的
__new__
在创建类时就会进入,它可以获取到上层类的一切属性和方法,包括类名,魔法方法。- 而普通类的
__new__
在实例化时就会进入,它仅能获取到实例化时外界传入的属性。
使用__call__
方法来实现元类,这种实现方式与函数作为元类的实现非常类似,但是需要使用__init__
来接收创建类所需要的参数,类似于创建类装饰器,相当于一个回调函数,无法体现出OOP。
总结,推荐使用__new__
的方式来实现元类,其更面向对象
三、 元类应用
1、 ORM中的元类
元类的使用场景是挺多的,这里略微讲解一些ORM
中表与对象的映射关系的元类,现在就让我们来一起阅读一下Tortoise ORM
中的元类的代码:
首先,看一下Model
类的创建代码:
class Model(metaclass=ModelMeta): """ Base class for all Tortoise ORM Models. """ ...
发现
Model
这个模型的基类在创建时,绑定了metaclass
,那么就让我们来看一下ModelMeta
里面的代码吧!
元类的代码有100多行,这里我就根据我的理解,在代码里面添加注释,进行解释,如果有问题可以在评论区留言。
class ModelMeta(type): __slots__ = () def __new__(mcs, name: str, bases: Tuple[Type, ...], attrs: dict): """ mcs:类的名称 bases:创建类的祖宗类 attrs:类属性 """ fields_db_projection: Dict[str, str] = {} # 存放字段名和属性名的映射表 fields_map: Dict[str, Field] = {} # 存放字段类型和字段名的映射表 filters: Dict[str, Dict[str, dict]] = {} # 存放所有字段的过滤器,用于后面对数据的过滤操作 fk_fields: Set[str] = set() # 存放外键约束的字段,即一对多的字段 m2m_fields: Set[str] = set() # 存放多对多的字段 o2o_fields: Set[str] = set() # 存放一对一的字段 meta_class: "Model.Meta" = attrs.get("Meta", type("Meta", (), {})) # 获取表的相关信息,即在我们的模型表中使用的Meta类 pk_attr: str = "id" # 设置表主键为id,默认为id这个字段,后面可以修改 # 在类层次结构中搜索字段属性,这个函数我不是很明白,知道的可以私信交流哦! def __search_for_field_attributes(base: Type, attrs: dict) -> None: for parent in base.__mro__[1:]: # 迭代取出所有祖宗类中的全部属性 __search_for_field_attributes(parent, attrs) meta = getattr(base, "_meta", None) # 获取数据表中的信息 MetaInfo类,其从 Meta类 中获取信息 if meta: # For abstract classes for key, value in meta.fields_map.items(): # 遍历字段和字符串的映射信息 attrs[key] = value # For abstract classes manager for key, value in base.__dict__.items(): # 获取字段的父类信息 if isinstance(value, Manager) and key not in attrs: attrs[key] = value.__class__() else: # For mixin classes for key, value in base.__dict__.items(): if isinstance(value, Field) and key not in attrs: attrs[key] = value # 开始再类层次结构中搜索字段属性 inherited_attrs: dict = {} for base in bases: __search_for_field_attributes(base, inherited_attrs) if inherited_attrs: # 确保搜索出来的字段属性排列在类属性前面 attrs = {**inherited_attrs, **attrs} if name != "Model": # 如果需要创建的类不是Model类 custom_pk_present = False for key, value in attrs.items(): if isinstance(value, Field): # 如果这个value的属性的一个字段类型 if value.pk: # 如果自定义字段中定义了主键 if custom_pk_present: # 如果前面的字段定义了主键约束 raise ConfigurationError(...) if value.generated and not value.allows_generated: # 如果这个字段不允许生成 raise ConfigurationError(...) custom_pk_present = True # 标记已经设置了主键 pk_attr = key # 将主键设置为新的自定义的字段 if not custom_pk_present and not getattr(meta_class, "abstract", None): # 如果没有设置主键,并且其不是一个抽象类 if "id" not in attrs: # 如果id不在字段属性里面 attrs = {"id": IntField(pk=True), **attrs} # 添加字段id,同时设置id为主键 if not isinstance(attrs["id"], Field) or not attrs["id"].pk: # 如果属性名为id的类型不是字段类型,或者id类型字段不是主键 raise ConfigurationError(...) for key, value in attrs.items(): # 遍历所有属性的键值对 if isinstance(value, Field): # 如果值属于字段类型 if getattr(meta_class, "abstract", None): # 如果其为抽象类,则进行拷贝 value = deepcopy(value) fields_map[key] = value # 把键值对添加到字符安映射的字典中 value.model_field_name = key # 设置字段名为key if isinstance(value, OneToOneFieldInstance): # 如果字段的类型为一对一表 o2o_fields.add(key) # 将该字段添加到一对一的字典中 elif isinstance(value, ForeignKeyFieldInstance): # 如果是外键约束,一对多 fk_fields.add(key) # 将该字段添加到外键约束的字典中 elif isinstance(value, ManyToManyFieldInstance): # 如果是多对多模型 m2m_fields.add(key) # 将该字段添加到多对多的字段中 else: # 如果是普通的字段 fields_db_projection[key] = value.source_field or key # 设置表的名字,source_filed 自定义名字,key 属性名字 filters.update( # def get_filters_for_field(field_name: str, field: Optional[Field], source_field: str) -> Dict[str, dict],这个函数可以自行在源码中阅读,在过滤器文件中 get_filters_for_field( field_name=key, field=fields_map[key], source_field=fields_db_projection[key], ) ) if value.pk: # 如果是主键 filters.update( get_filters_for_field( field_name="pk", field=fields_map[key], source_field=fields_db_projection[key], ) ) # 清除类属性 for slot in fields_map: attrs.pop(slot, None) # 在attrs中将类中的所有的源字段都删除,如:a = Field(...) attrs["_meta"] = meta = MetaInfo(meta_class) # 将_meta属性的值设置为MetaInfo # 将这里面创建的数据全部存入meta中,也就是属性attr['_meta']中 meta.fields_map = fields_map meta.fields_db_projection = fields_db_projection meta._filters = filters meta.fk_fields = fk_fields meta.backward_fk_fields = set() meta.o2o_fields = o2o_fields meta.backward_o2o_fields = set() meta.m2m_fields = m2m_fields meta.default_connection = None meta.pk_attr = pk_attr meta.pk = fields_map.get(pk_attr) # type: ignore if meta.pk: # 如果有主键 meta.db_pk_column = meta.pk.source_field or meta.pk_attr # 设置设置主键的字段名字 meta._inited = False if not fields_map: # 如果没有数据添加到字段映射表中,则说明是一个抽象类 meta.abstract = True new_class = super().__new__(mcs, name, bases, attrs) # 使用type创建一个类 for field in meta.fields_map.values(): field.model = new_class # 指定每一个字段的模型表 for fname, comment in _get_comments(new_class).items(): # _get_comments()获取一些字段的注释信息,返回字典 if fname in fields_map: # 如果fname fields_map[fname].docstring = comment if fields_map[fname].description is None: fields_map[fname].description = comment.split("\n")[0] if new_class.__doc__ and not meta.table_description: meta.table_description = inspect.cleandoc(new_class.__doc__).split("\n")[0] # 设置数据表的描述信息 for key, value in attrs.items(): if isinstance(value, Manager): value._model = new_class # 将值所对的模型指向生成的类 meta._model = new_class # type: ignore meta.manager._model = new_class meta.finalise_fields() # 最后确定模型域 return new_class # 把创建的类返回,生成新的类
有部分内容我也不是很明白,只是初略的注释了每一行代码的作用,如有错误,尽请指正。
2、 单例模式
使用__call__
来实现:
class MyMeta(type): def __init__(self, *args, **kwargs): self.__instance = None super(MyMeta,self).__init__(*args, **kwargs) def __call__(self, *args, **kwargs): if self.__instance is None: self.__instance = super(MyMeta,self).__call__(*args, **kwargs) return self.__instance class MyClass(metaclass=MyMeta): pass m = MyClass() m2 = MyClass() print(m2 == m)
使用__new__
实现:
class MyMeta(type): def __new__(cls, name, bases, attrs): attrs["_instance"] = None return super(MyMeta,cls).__new__(cls, name, bases, attrs) def __call__(self, *args, **kwargs): if self._instance is None: self._instance = super(MyMeta,self).__call__(*args, **kwargs) return self._instance class MyClass(metaclass=MyMeta): pass m = MyClass() m2 = MyClass() print(m2 == m)
注意,使用
__new__
的方式不是很优雅,推荐使用__init__
的方式
同时,为什么这里不是直接在__new__
方法里面来进行实现单例模式呢?
-
因为
__new__
是在实例化MyClass
(即MyClass()
)前创建类的时候调用的,在实例化MyClass
时,即运行MyClass()
时,如果元类实现了__call__
方法,则调用元类中的__call__
方法,然后再调用类本身的__new__
方法... -
我们可以来做一个测试,以此来增加大家对元类创建的理解:
class MyMeta(type): def __new__(cls, *args, **kwargs): print("元类中的__new__被调用") return super(MyMeta, cls).__new__(cls, *args, **kwargs) def __init__(self, *args, **kwargs): print("元类中的__init__被调用") return super(MyMeta, self).__init__(*args, **kwargs) def __call__(self, *args, **kwargs): print("元类中的__call__被调用") return super(MyMeta, self).__call__(*args, **kwargs) class MyClass(metaclass=MyMeta): def __new__(cls, *args, **kwargs): print("主类中的__new__被调用") return super(MyClass, cls).__new__(cls, *args, **kwargs) def __init__(self, *args, **kwargs): print("主类中的__init__被调用") return super(MyClass, self).__init__(*args, **kwargs) def __call__(self, *args, **kwargs): print("主类中的__call__被调用") return super(MyClass, self).__call__(*args, **kwargs) print("-------------------------\n创建具体的对象前\n----------------------------------") m = MyClass() 输出的内容:
元类中的__new__被调用 元类中的__init__被调用 ------------------------- 创建具体的对象前 ---------------------------------- 元类中的__call__被调用 主类中的__new__被调用 主类中的__init__被调用
3、 抽象类
与java一样,python也有抽象类的概念但是同样需要借助模块实现,抽象类是一个特殊的类,它的特殊之处在于只能被继承,不能被实例化 。
那么,我们如何进行这种规则的限制呢?
-
这里使用到了元类和装饰器,来创建抽象类
from abc import ABCMeta, abstractmethod, abstractclassmethod, abstractstaticmethod # 利用abc模块来实现抽象类 class Base(metaclass=ABCMeta): # 使用元类来创建抽象对象 """使用元类来创建对象""" @abstractmethod def eat(self): """定义吃的属性方法,其为抽象方法,必须在子类实现""" pass @abstractstaticmethod def watch(self): """定义看的静态方法,其为抽象静态方法,必须在子类实现""" pass @abstractclassmethod def find(self): """定义寻找的类方法,必须在子类实现""" pass class Person(Base): """创建具体的类""" def eat(self, a): print(f"{a}人在吃饭") @staticmethod def find(): print("正在查找东西") @classmethod def watch(cls): print(cls) p = Person() p.watch() p.eat("2")
本文来自博客园,作者:Kenny_LZK,转载请注明原文链接:https://www.cnblogs.com/liuzhongkun/p/16817578.html
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· NetPad:一个.NET开源、跨平台的C#编辑器