Django的models实现分析
1 引子
1.1 神奇的Django中的models
我们先来看一段在Django项目中常用的代码:
设置数据库models代码:
class Students(models.Model):
name = models.CharField()
age = models.IntegerField()
这里有几个神奇的地方,涉及到了python中最神秘的几个特性。
先看下有哪些神奇的地方:
- 字段名称name\age自动转换为了数据库中的字段名称
- 自动校验数据类型,models.IntegerField(),会校验设置的数据类型
这里用的是python的两个语法特性:
- 描述符协议
- 元类
我们来一步一步解开神秘面纱。
2 数据校验
2.1 数据校验难点
Python虽然是强类型的脚本语言,但是在定义变量时却无法指定变量的类型。
例如,我们在Student类中定义一个age字段,合法值一般为包含0的正整数,但是在python中无正整数的类型,只能自己来校验。
class Student:
def __init__(self, name, age):
if isinstance(name,str):
self.name = name
else:
raise TypeError("Must be a string") if isinstance(int, age):
self.age = age
else:
raise TypeError("Must be an int")
但是,如果更新年龄时就会遇到问题,无法重用校验逻辑。
有没有简洁的方法呢?
2.2 使用property装饰器
使用property也是一个方法,可以针对每个属性来设置,但是如果一个类有多个属性,代码就会非常的多,并且产生大量的冗余,就像这样。
class Student:
def __init__(self, name, age, class_no, address, phone):
self._name = None
self._age = None
self.__class_no = None
self._address = None
self._phone = None self.name = name
self.age = age
self.class_no = class_no
self.address = address
self.phone = phone @property
def name(self):
return self._name @name.setter
def name(self, value):
if not isinstance(value, str):
raise ValueError("Must be string")
self._name = value @property
def age(self):
return self._age @age.setter
def age(self, value):
if isinstance(value, int) and value > 0:
self._age = value
else:
raise ValueError("age value error") @property
def address(self):
return self._address @address.setter
def address(self, value):
if not isinstance(value, str):
raise ValueError("Must be string")
self._address = value
代码冗余太多,每个检查str的都要复制一遍代码。
3 Python描述符
描述符提供了优雅、简洁、健壮和可重用的解决方案。简而言之,一个描述符就是一个对象,该对象代表了一个属性的值。
这就意味着如果一个Student对象有一个属性“name”,那么描述符就是另一个能够用来代表属性“name”持有值的对象。
描述符协议中“定义了__get__”、“__set__”或”__delete__” 这些特殊方法,描述符是实现其中一个或多个方法的对象。
3.1 版本一
class NameProperty:
def __init__(self, name=""):
self.name = name def __get__(self, instance, owner):
if instance is None:
return self
return instance.__dict__.get(self.name) def __set__(self, instance, value):
if not isinstance(value, str):
raise TypeError("name must be string")
instance.__dict__[self.name] = value class Student:
name = NameProperty('name')
age = None
heghth = None
weight = None def __init__(self, name):
self.name = name def __str__(self):
return self.name @property
def age(self):
return self.age @age.setter
def age(self, value):
if not isinstance(value, int):
raise ValueError("must be int")
self.age = value s = Student("Stitch")
print(s)
s.name = 'name'
print(s.name)
这个版本存在一个问题,就是name = NameProperty("sss"),必须设置一个名称,才可以使用。这个与我们使用django的models时不太一样,在使用models时,不写参数也可以的。
3.2 版本二
不用输入变量名称。
class NameProperty:
index = 0 def __init__(self):
self.name = str(self.__class__.index) # 使用类的变量
self.__class__.index += 1 def __get__(self, instance, owner):
return getattr(instance, self.name) def __set__(self, instance, value):
if not isinstance(value, str):
raise TypeError("name must be string")
instance.__dict__[self.name] = value class Student:
name = NameProperty()
age = None def __str__(self):
return self.name s = Student()
s.name = "www"
print(s) s2 = Student()
s2.name = "http"
print(s2)
print(s.name)
这个版本还存在一个问题,如果一个类型有多个字段使用了NameProperty时,错误提示时,无法表示出此变量的名称,只能表示出一个index值。用户看到这个时,无法判断是那个变量出了问题。
4 使用元类
元类是python的中一个难点,在大部分场景下都不会用到。但是在编写框架方面却是必不可缺少的利器。
4.1 版本三
使用元类来控制类的行为:
class NameProperty:
index = 0 def __init__(self):
self.storage_name = str(self.__class__.index) # 使用类的变量
self.__class__.index += 1 def __get__(self, instance, owner):
return getattr(instance, self.storage_name) def __set__(self, instance, value):
if not isinstance(value, str):
raise TypeError("%s must be string" % self.storage_name)
instance.__dict__[self.storage_name] = value class EntityMeta(type):
def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict)
for key, attr in attr_dict.items():
if isinstance(attr, NameProperty):
type_name = type(attr).__name__
attr.storage_name = '{} property {}'.format(type_name, key) class Student(metaclass=EntityMeta):
name = NameProperty()
age = None
nicky_name = NameProperty() def __str__(self):
return self.name s = Student()
s.name = "www"
print(s) s2 = Student()
s2.name = "test"
s2.nicky_name = 4444
print(s2)
print(s2.nicky_name)
执行输出为:
raise TypeError("%s must be string" % self.storage_name)
TypeError: NameProperty property nicky_name must be st
语法解释:
版本三相比版本二,最大的变化在于Student类继承了自定义元类EntityMeta。
如果对于python面向对象编程有了解的话,python的所有类都继承自type,type是所有类的元类。。
在这里,我们自定义的元类EntityMeta,具备一个功能就是判断类属性是否为NameProperty类型,如果为这个类型,则这个类型的实例属性storage_name值赋值为类名和属性名。
4.2 版本四—模仿django的models
模仿Django的models实现:
import abc class NameProperty:
index = 0 def __init__(self):
self.storage_name = str(self.__class__.index) # 使用类的变量
self.__class__.index += 1 def __get__(self, instance, owner):
return getattr(instance, self.storage_name) def __set__(self, instance, value):
# instance.__dict__[self.storage_name] = value
setattr(instance, self.storage_name, value) class Validated(abc.ABC, NameProperty):
def __set__(self, instance, value):
value = self.validate(instance, value)
super().__set__(instance, value) @abc.abstractclassmethod
def validate(self, instance, value):
"""return validated value or raise ValueError""" class ChartField(Validated):
def validate(self, instance, value):
if not isinstance(value, str):
raise TypeError("{} must be str".format(self.storage_name))
return value class IntegerField(Validated):
def __init__(self, min_value=None):
self.min_value = min_value def validate(self, instance, value):
if not isinstance(value, int):
raise TypeError("{} must be int".format(self.storage_name))
if self.min_value and value < self.min_value:
raise ValueError("{} must larger min_value".format(self.storage_name))
return value class EntityMeta(type):
def __init__(cls, name, bases, attr_dict):
super().__init__(name, bases, attr_dict)
for key, attr in attr_dict.items():
if isinstance(attr, Validated):
type_name = type(attr).__name__
attr.storage_name = "{} property {}".format(type_name, key) class Entity(metaclass=EntityMeta):
pass class Student(Entity):
name = ChartField()
age = IntegerField(min_value=0)
nicky_name = ChartField() def __init__(self, name, age, nicky_name):
self.name = name
self.age = age
self.nicky_name = nicky_name def __str__(self):
return self.name s2 = Student("test", 12, "toddy")
s2.age = -1
print(s2.nicky_name)
s2.nicky_name = 4444
执行结果:
raise ValueError("{} must larger min_value".format(self.storage_name))
ValueError: IntegerField property age must larger min_value
这样,完全模仿了models的定义。
类的初始化和后续属性赋值,都会自动调用__set__来设置并校验。
5 原理解释
5.1 属性读取顺序
通过实例读取属性时,通常返回的是实例中定义的属性。读取顺序如下:
- 实例属性
- 类属性
- 父类属性
- __getattr__()方法
先记住这个顺序,后面理解描述需要。属性描述符都是定义在类中的,而不是在对象中。
5.2 描述符
某个类,只要是内部定义了方法 __get__, __set__, __delete__ 中的一个或多个(set,delete必须有一个),就可以称为描述符。
方法的原型为:
① __get__(self, instance, owner)
② __set__(self, instance, value)
③ __del__(self, instance)
描述符只绑定到类上,在实例上不生效。
描述的调用实质为:type(objectA).__dict__[“key”].__get__(None, objectB),objectB为描述符,objectA为定义类。
5.3 元类
元类,就是创建类的类。一般类都继承自object类,默认会创建一些方法。
元类决定了类出初始化后有哪些特征和行为。如果我们想自定义一个类,具备某种特殊的行为,则需要自定义元类。
- 类也是对象,所有的类都是type的实例
- 元类(Meta Classes)是类的类
- __metaclass__ = Meta 是 Meta(name, bases, dict) 的语法糖
- 可以通过重载元类的 __new__ 方法,修改定义的行为
6 其他案例
Django的django-rest-framework框架的serializer 也是用的这个语法实现的。
7 参考资料
|
编号 |
标题 |
链接 |
|
1 |
元类 |
https://stackoverflow.com/questions/100003/what-is-a-metaclass-in-python |
|
2 |
描述符 |
http://python.jobbole.com/81899/ |
|
3 |
《流畅的python》 |
元类部分 |
Django的models实现分析的更多相关文章
- 【Django】--Models 和ORM以及admin配置
Models 数据库的配置 1 django默认支持sqlite,mysql, oracle,postgresql数据库 <1>sqlite django默认使用sqlite的数据库 ...
- Django之Models(一)
Django之Models(一) 目录 数据库的配置 模型代码与参数解析 ORM对单表的增删改查 查询的补充 数据库配置 django默认支持sqlite,mysql, oracle,postgres ...
- 八、Django之Models(译)
模型(Models) 模型是你的数据的唯一的.确定的信息源. 它包含你所储存数据的必要字段和行为. 通常,每个模型对应数据库中唯一的一张表. 基础: 每个模型都是一个Python类,它们都是djang ...
- Python3:Django根据models生成数据库表时报 __init__() missing 1 required positional argument: 'on_delete'
Python3:Django根据models生成数据库表时报 __init__() missing 1 required positional argument: 'on_delete' 一.分析 在 ...
- Django 补充models操作,中间件, 缓存,信号,分页
1.Model 一对多 补充 models如下: class UserType(models.Model): caption = models.CharField(max_length=16) cla ...
- Django(35)Django请求生命周期分析(超详细)
Django请求生命周期分析 1.客户端发送请求 在浏览器输入url地址,例如www.baidu.com,浏览器会自动补全协议(http),变为http://www.baidu.com,现在部分网站都 ...
- Django 之 models的 F() 和 Q() 函数
前提: app名称为core,models.py 如下: #coding: utf8 import datetime from django.db import models class Order( ...
- 【Python】django模型models的外键关联使用
Python 2.7.10,django 1.8.6 外键关联:http://www.bubuko.com/infodetail-618303.html 字段属性:http://www.cnblogs ...
- django - from django.db.models import F - class F
F() 的执行不经过 python解释器,不经过本机内存,是生成 SQL语句的执行. # Tintin filed a news story! reporter = Reporters.objects ...
随机推荐
- 揽货最短路径解决方案算法 - C# 蚁群优化算法实现
需求为(自己编的,非实际项目): 某配送中心进行揽货,目标客户数为50个客户,配送中心目前的运力资源如下: 现有车辆5台 单台运力最大行驶距离200千米 单台运力最大载重公斤1吨 问:运力怎样走法才能 ...
- 使用PowerDesigner对NAME和COMMENT互相转换
本文来自我的github pages博客http://galengao.github.io/ 即www.gaohuirong.cn 在使用PowerDesigner对数据库进行概念模型和物理模型设计时 ...
- 自动统计安卓log中Anr,Crash,Singnal出现数量的Python脚本
作为测试,在测试工作中一定会经常抓log,有时log收集时间很长,导致log很大,可能达到几G,想找到能打开如此大的log文件的工具都会变得困难:即使log不大时,我们可以直接把log发给开发同学去分 ...
- 高可用之KeepAlived(2):keepalived+lvs
*/ .hljs { display: block; overflow-x: auto; padding: 0.5em; color: #333; background: #f8f8f8; } .hl ...
- Nginx和Apache有什么区别?
Nginx抗并发,nginx 处理请求是异步非阻塞的,而apache 则是阻塞型的,在高并发下nginx 能保持低资源低消耗高性能. Apache rewrite ,比nginx 的rewrite ...
- ACdream 1015 Double Kings 树的重心
思路:删除根结点,其最大子树的节点最少.求一次树的重心即可. AC代码 #include <cstdio> #include <cmath> #include <ccty ...
- HDU - 4496 City 逆向并查集
思路:逆向并查集,逆向加入每一条边即可.在获取联通块数量的时候,直接判断新加入的边是否合并了两个集合,如果合并了说明联通块会减少一个,否则不变. AC代码 #include <cstdio> ...
- uva10410 栈
根据DFS和BFS重建树. BFS反映了当前节点到达根结点的距离,通过栈把当前处理的树或则子树的根结点放在栈顶,通过遍历DFS序列,判断当前元素与栈顶元素的关系,如果是子节点,就将它压入栈中成为新的栈 ...
- HDU - 2102 A计划 (BFS) [kuangbin带你飞]专题二
思路:接BFS判断能否在限制时间内到达公主的位置,注意如果骑士进入传送机就会被立即传送到另一层,不会能再向四周移动了,例如第一层的位置(x, y, 1)是传送机,第二层(x, y, 2)也是传送机,这 ...
- mysql无法启动的结果问题解决
mac 上homebrew 安装的mysql,已经用了很长时间都没什么问题,今天 ERROR! The server quit without updating PID file (/usr/loca ...