在閱讀原始碼前一直有個疑問,我一直好奇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.
原類是
類的類
.
原來在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
哪裡。