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