浅析 DjangoModel 设计禅道

前言

在阅读源码前一直有个疑问,我一直好奇DjangoModel是如何将字段转换成属性? ... 带着这样的疑问,我开始阅读Django框架Model部分源码。

在通过简单的阅读剖析后,愈发感受到Django框架设计的精妙与优美。

单刀直入

与往常一样,剖析前先寻找一个适合的切入点,打开VScode,在引入model那一行代码按下command + b..

我看到这一行陌生的写法metaclass=ModelBase

# 版本: Django 2.2.2
class Model(metaclass=ModelBase):

原来是你!

在看到这么一个陌生写法后,我决定对ModelBase类,进行分析。

ModelBase继承了type,实现了5个方法

def __new__(cls, name, bases, attrs, **kwargs):
    ...

def  add_to_class(cls, name, value):
    ...

def  _prepare(cls):
    ...

def  _base_manager(cls):
    ...

def  _default_manager(cls):
    ...

Python原类

这五个方法中,重点在__new__这个方法,而且能够实现字段生成属性的功能也来源于此。

随着查阅资料,了解到一个之前没接触到的?姿势点:原类

那什么是原类?

A metaclass is the class of a class.

原类是类的类.

选取stackoverflow高赞

原来在Python世界里,所有的类都是由type创建生成的!而继承type实现__new__方法,是生成类其中一种方法。omgomgomgomg.(在没了解之前,我一直以为type只是用于判断类型?)

这里的new做了什么?

回到源码,简单分析一下创建都做了些什么.

def __new__(cls, name, bases, attrs, **kwargs):
    # 继承__new__
    super_new = super().__new__

    # 判断子类是否是modelBase
    parents = [b for b in bases if isinstance(b, ModelBase)]
    if not parents:
        return super_new(cls, name, bases, attrs)

    # 创建__module__
    module = attrs.pop('__module__')
    new_attrs = {'__module__': module}

    # 创建classcell
    classcell = attrs.pop('__classcell__', None)
    if classcell is not None:
        new_attrs['__classcell__'] = classcell

    # 对Meta属性的处理
    attr_meta = attrs.pop('Meta', None)
    # Pass all attrs without a (Django-specific) contribute_to_class()
    # method to type.__new__() so that they're properly initialized
    # (i.e. __set_name__()).
    contributable_attrs = {}
    for obj_name, obj in list(attrs.items()):
        if _has_contribute_to_class(obj):
            contributable_attrs[obj_name] = obj
        else:
            new_attrs[obj_name] = obj

    # 建立一个新的class
    new_class = super_new(cls, name, bases, new_attrs, **kwargs)

    # 获取Meta里的abstract
    abstract = getattr(attr_meta, 'abstract', False)
    meta = attr_meta or getattr(new_class, 'Meta', None)

    # 获取_meta属性
    base_meta = getattr(new_class, '_meta', None)

    app_label = None

    # 加载配置附加到attach里
    app_config = apps.get_containing_app_config(module)
    # 如果meta里面没有app_label
    if getattr(meta, 'app_label', None) is None:
        # 如果app_config里面也是空的
        if app_config is None:
            # 如果Meta里的abstract也是空的
            if not abstract:
                raise RuntimeError(
                    "Model class %s.%s doesn't declare an explicit "
                    "app_label and isn't in an application in "
                    "INSTALLED_APPS." % (module, name)
                )
        else:
            # app_config不为空则覆盖
            app_label = app_config.label

    new_class.add_to_class('_meta', Options(meta, app_label))

    ...(偷懒省略)
    new_class._prepare()

    # 新class调用apps.register_model进行注册
    new_class._meta.apps.register_model(new_class._meta.app_label, new_class)
    return new_class

这一部分代码工作主要是: 按照一些特定的方式创建类属性,重点 __module__Meta的处理。

Model主体

弄明白了ModelBase的工作后,再来简单看看主体部分做了什么

class Model(metaclass=ModelBase):
    def __init__(self, *args, **kwargs):
        cls = self.__class__
        opts = self._meta
        _setattr = setattr

        # 自我描述类
        # 实现__repr__与__str__
        _DEFERRED = DEFERRED
        pre_init.send(sender=cls, args=args, kwargs=kwargs)

        # 重写默认__get__方法, 读取缓存
        self._state = ModelState()

        # 返回模型及其父项上所有具体字段的列表。
        if len(args) > len(opts.concrete_fields):
            # Daft, but matches old exception sans the err msg.
            raise IndexError("Number of args exceeds number of fields")

        if not kwargs:
            # 所有字段的迭代器
            for val, field in zip(args, fields_iter):
                # 如果val是描述类 跳过
                if val is _DEFERRED:
                    continue
                # 设置属性
                _setattr(self, field.attname, val)
        else:  
            # 设置属性
            fields_iter = iter(opts.fields)
            for val, field in zip(args, fields_iter):
                if val is _DEFERRED:
                    continue
                _setattr(self, field.attname, val)
                kwargs.pop(field.name, None)

        # 对属性进行处理 
        for field in fields_iter:
            is_related_object = False
            # Virtual field
            if field.attname not in kwargs and field.column is None:
                continue

            if kwargs:                
                # 判断是否为远程连接属性 (one to one, many to many 等类型)
                # 下面是一些具体处理
                if isinstance(field.remote_field, ForeignObjectRel):
                    try:
                        # Assume object instance was passed in.

                        # 假设传递了实例
                        rel_obj = kwargs.pop(field.name)
                        is_related_object = True
                    except KeyError:
                        try:
                            val = kwargs.pop(field.attname)
                        except KeyError:
                            val = field.get_default()
                    else:
                        # Object instance was passed in. Special case: You can
                        # pass in "None" for related objects if it's allowed.
                        if rel_obj is None and field.null:
                            val = None
                # 普通类型的处理
                else:
                    try:
                        val = kwargs.pop(field.attname)
                    except KeyError:
                        val = field.get_default()
            else:
                # 返回字段默认结果
                val = field.get_default()

            # 针对实例或非实例对象进行属性设置
            if is_related_object:
                if rel_obj is not _DEFERRED:
                    _setattr(self, field.name, rel_obj)
            else:
                if val is not _DEFERRED:
                    _setattr(self, field.attname, val)

        ...(偷懒省略)
        super().__init__()
        post_init.send(sender=cls, instance=self)

这一部分代码主要工作内容:遍历属性,对不同的属性做不同的设置操作.

结语

在弄明白DjangoModel是如何将字段转换成属性? 问题后,对Model部分的分析就暂时告一段落了。

总结:Model将字段生成类是由ModelBase生成类属性,再由Model遍历属性,对不同属性进行设置。 完成这样一个功能。

最后建议: command + b一把梭,哪里不会b哪里。

本作品采用《CC 协议》,转载必须注明作者和本文链接
欲目千里,更上一层
讨论数量: 0
(= ̄ω ̄=)··· 暂无内容!

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!