Python和Java都是面向对象的编程语言,但是Python真正做到了万物皆对象。有对象就有类,有类就有父类,那么Python中类的父类是什么呢?比如Python内置模块中定义的object类,它就是type类的子类。这个type就是元类。Python中,元类定义类,类定义对象。那么我们如何对类的行为进行修改呢?只需要让类继承自元类,然后修改元类的行为,就可以定义类的行为了。
元类
元类(metaclass)是Python中一个特殊的概念,可以用来创建类对象。在Python中,类是对象的模板,而元类则是类的模板。元类控制着类的创建过程,允许我们在类定义阶段做一些自定义的操作。
在Python中,大部分类都是type类的实例。type是Python的内建元类,用于创建所有的类对象。当我们定义一个新的类时,Python解释器会调用type元类来创建这个类对象。实际上,当我们使用class
关键字定义一个类时,Python解释器会将该类的定义转换为对元类的调用。
当使用class关键字创建一个类的实例时,由于类为元类创建的,元类也有它的属性和方法,因此类中如成员变量名称、成员方法名称、参数等等都是作为成员变量存储在元类对象中。通过对这些属性做更改,我们就可以更改类的行为。
我们也可以定义自己的元类,通过继承type类并重写其中的方法来实现。通过定义元类,我们可以在类创建时动态地修改类的行为,从而实现一些高级的编程技巧和灵活性。元类的应用包括动态修改类、实现ORM框架、实现单例模式等。
需要注意的是,在使用元类时需要谨慎,并遵循相关的最佳实践。元类是一种高级编程技巧,滥用可能会导致代码难以理解和维护。因此,在使用元类时应当明确其用途,并确保不会引入过多复杂性和混乱。
定义元类
定义元类有两种方式:函数做元类或类做元类。
函数做元类
函数做元类需要满足以下要求。
- 函数接受三个参数,第一个参数是一个元类对象,第二个参数是一个元组,他代表被修饰的类的所有父类(因为Python支持多继承),第三个参数是一个字典,它包含了列表里的所有变量和方法,键为变量或方法的名称,值则为它们本身(如成员函数则值就为它本身,可直接调用它)。
下面用一个具体的例子——将类中所有以下画线命名法命名的变量转换为驼峰命名法的命名(my_value->myValue)。
python 代码:def change(cls, base, attrs):
new_attrs = {}
for attr, value in attrs.items():
attr = attr.split('_')
camel_case = attr[0]
for word in attr[1:]:
camel_case += word.capitalize()
new_attrs[camel_case] = value
return type(cls, base, new_attrs)
class Demo(metaclass=change):
my_value = 1
d = Demo()
print(hasattr(d, 'my_value')) # False,因为无此属性
print(hasattr(d, 'myValue')) # True,因为my_value已经改为myValue
print(d.myValue) # 1
try:
print(d.my_value) # 无输出
except:
...
类做元类
类做元类需要重写该类的__new__
方法。该方法接收三个参数cls、args和kwargs,其中args为具有类对象、基类元组、成员变量和成员函数组成的字典三个元素的元组。这三个参数和使用函数作为元类时相同。下面就将上方的代码改写一下。
class MetaClass(type):
def __new__(cls, *args, **kwargs):
_class, base, attrs = args
new_attrs = {}
for attr, value in attrs.items():
attr = attr.split('_')
camel_case = attr[0]
for word in attr[1:]:
camel_case += word.capitalize()
new_attrs[camel_case] = value
return super().__new__(cls, _class, base, new_attrs)
class Demo(object, metaclass=MetaClass):
my_value = 1
d = Demo()
print(hasattr(d, 'my_value')) # False,因为无此属性
print(hasattr(d, 'myValue')) # True,因为my_value已经改为myValue
print(d.myValue) # 1
try:
print(d.my_value) # 无输出
except:
...
除此之外,还可以通过实现魔法函数__call__
使对象可调用,也可将此类用作元类,与第一种方法类似,但是无法体现面向对象。
元类的应用
ORM中常常会应用元类。这里引一下文章Python元类详解中的内容举例。
首先,看一下Model
类的创建代码:
class Model(metaclass=ModelMeta):
"""
Base class for all Tortoise ORM Models.
"""
...
发现Model
这个模型的基类在创建时,绑定了metaclass
,那么就让我们来看一下ModelMeta
里面的代码吧!
元类的代码有100多行,这里我就根据我的理解,在代码里面添加注释,进行解释。
python 代码: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 # 把创建的类返回,生成新的类
其实说白了所有的类,都继承Type了,元类也是如此,其实可以讲讲类的继承算法
我不是很懂,你说的继承算法是指Python的继承吗
对,几乎是py最难得部分,包括潜在的循环继承问题,而且继承可以继承多个,这时候也又是什么顺序,你要想学这些高级的其实看这里就行,https://space.bilibili.com/245645656/channel/collectiondetail?sid=346060
OK感谢
这个是微软大佬讲的,我是觉得挺好,通俗易懂
暂无点赞
这期https://www.bilibili.com/video/BV1V5411S7dY/?spm_id_from=333.999.0.0
嗯我现在正在看,不过我真的感觉多继承没什么用,很难代码可读性也很差
暂无点赞
暂无点赞
暂无点赞
暂无点赞
暂无点赞
暂无点赞