3.2.2 ORM框架的源码解析
在了解了mysqlclient模块的常用方法后,就可以开始全面学习ORM框架了,先来看django/db/__init__.py文件中的源码内容。在2.4节介绍makemigrations命令时曾出现导入connections变量的语句,当时并未详细说明,只知道它可以用来保存各种数据库的连接信息,下面详细分析这个变量:
从上面的语句可知,connections变量是一个ConnectionHandler对象。继续追踪ConnectionHandler对象的源码,它位于django/db/utils.py文件中,内容如下:
ConnectionHandler类的源码并不复杂,其中定义了许多魔法函数。只有理解这些魔法函数的功能,才能更好地理解ConnectionHandler类。下面先来看看databases()方法的实现代码,该方法的一个核心就是调用LazySetting对象,即源码中的settings模块,借助该对象可以获取项目中配置的DATABASES信息,具体操作如下:
提示:这里有两个地方需要注意。
(1)@cached_property装饰器可以将只有self参数的方法转换成缓存在实例中的属性。所以这里不能使用connections.databases()这样的形式访问databases。
(2)这里使用的是Python直接迚入命令行而不是python manage.py shell,所以要一开始就配置文件的环境变量,否则无法导入settings模块。
此外,ensure_defaults()函数和prepare_test_settings()函数只是单纯地给databases[alias]添加一些额外信息。接着上面的交互模式继续执行如下操作:
接下来解析魔法函数,这里先给出一些基础代码,在理解了这些基础代码后就可以继续学习上面的魔法函数了:
直接使用Python解释器执行该脚本,结果如下:
从上面的代码可以看到,魔法函数__getitem__()在类对象被索引时调用,而__setitem__()函数在给类对象的索引赋值时调用。接下来看ConnectionHandler类中的__getitem__()函数,它的执行逻辑如下:
◎ 如果_connections属性有alias索引的值,则直接获取其索引值并返回,否则执行下面的操作。
◎ 调用ensure_defaults()函数和prepare_test_settings()函数,给对应的数据库设置一些默认信息,正如上面演示的那样。
◎ 导入数据库配置的引擎模块,得到backend模块。后面在分析load_backend()函数时可以得到backend模块的默认具体路径。
◎ 根据数据库信息及导入的backend模块,得到一个该数据库的连接信息conn。
◎ 将得到的数据库连接信息conn设置到_connections属性的alias索引中。
为了更好地理解__getitem__()函数中的语句,先来看看load_backend()函数的源码:
在默认情冴下加载的数据库引擎模块路径为django.db.backends.mysql.base,最终得到的conn就是在该模块下定义的DatabaseWrapper对象。进入该模块的源码,就可以看到ORM框架的底层核心了:
在看完上面的代码后,可以发现这里的Database就是MySQLdb模块。再来看DatabaseWrapper类中的create_cursor()函数,该函数会调用类对象的connection属性的cursor()函数。而connection属性在当前类中并没有定义,因此需要继续查找其父类BaseDatabaseWrapper的实现源码:
从父类BaseDatabaseWrapper的源码可以看到,首先初始化__init__()函数中的设置self.connection=None;其次在connect()函数中调用了self.get_connection_params()函数,以获取数据库的连接参数(如MySQL服务地址、端口、账号及密码等);然后调用get_new_connection()函数获取连接对象并赋给self.connection;最后回到django/db/mysql/base.py中继续学习DatabaseWrapper类。由于在DatabaseWrapper类中并没有connect()函数,因此只有调用connect()函数(在父类中定义的该方法),才能给实例的connection属性赋值,而该值正是MySQLdb.connect()方法返回的数据库连接对象。
现在再来看在DatabaseWrapper类中定义的create_cursor()函数,在该函数中得到的cursor对象正是前面得到的数据库连接对象调用cursor()方法得到的结果,只不过其返回的结果对该游标对象进行了封装,得到CursorWrapper对象。而CursorWrapper对象的核心正是这个cursor对象。通过该类编写的魔法函数,可知这个CursorWrapper对象和mysqlclient中的cursor对象的功能几乎一致,只不过增加了对execute()函数和executemany()函数的异常处理。