如果要用更少的代码构建更高效的应用程序,并与高度可扩展的数据库建立连接,Python,尤其是Django往往是必不可少的。本文将简要谈谈在使用SQL和Python构建和支持现代应用的过程中,该如何减小日常工作中的摩擦,并将应用中蕴含的复杂性抽象出来,从而简化开发者的日常工作。
过于深入的技术细节先抛开不谈,不妨就让我们假设:
- SQL是专为SQL数据库优化的
- Python并没有为SQL数据库进行优化
下文将介绍如何在无需实际编写SQL语句的情况下,通过Python和相关工具来执行原始SQL命令。为此我们将使用Django的数据建模能力,但具体语法其实与Python中的SQLAlchemy包非常类似。
这就开始吧!
这是一个Django数据模型范例:
class BlogArticle(models.Model):
user = models.ForeignKey(User, default=1, on_delete=models.SET_DEFAULT)
title = models.CharField(max_length=120)
slug = models.SlugField(blank=True, null=True)
content = models.TextField(blank=True, null=True)
publish_timestamp = models.DateTimeField(
auto_now_add=False,
auto_now=False,
blank=True,
null=True,
)
假设该模型位于一个名为Articles的Django应用中(Django应用本质上是指构成整个Django项目的不同组件)。
那么我们就有两个名称需要处理:
- Articles(应用名)
- BlogArticle(模型名)
结合在一起,就可以形成如下的SQL表名称:
articles_blog_article
接下来就可以看着Django为我们变魔术了。
如果使用MySQL shell,将能看到这样的结果:
mysql> SHOW TABLES;
+------------------------------+
| Tables_in_cfe_django_blog_db |
+------------------------------+
| articles_blog_article |
| auth_group |
| auth_group_permissions |
| auth_permission |
| auth_user |
| auth_user_groups |
| auth_user_user_permissions |
| django_admin_log |
| django_content_type |
| django_migrations |
| django_session |
+------------------------------+
11 rows in set (0.01 sec)
接下来一起看看Django模型中的列:
mysql> DESCRIBE articles_blog_article;
+------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+--------------+------+-----+---------+----------------+
| id | bigint | NO | PRI | NULL | auto_increment |
| title | varchar(120) | NO | | NULL | |
| slug | varchar(50) | YES | MUL | NULL | |
| content | longtext | YES | | NULL | |
| publish_timestamp | datetime(6) | YES | | NULL | |
| user_id | int | NO | MUL | NULL | |
+------------------------+--------------+------+-----+---------+----------------+
12 rows in set (0.00 sec)
除了编写一个最基本的配置外,Django在这个MySQL数据库中帮助我们完成了所有与SQL有关的工作。其实到这里,Django并没有表现出什么让人印象深刻的作用。实际上,本文中涉及到的大部分内容并不是为了凸显可将Django或Python作为SQL的替代品,而是为了向大家展示它在“抽象”方面的作用。
Python的崛起和“摩擦”的代价
一起来看看阻力最小的路径是什么样的,以及为什么说Python是利用SQL的最佳方式。
Python代码的撰写、阅读、运行以及发布都很容易。但如果将“Python”换成其他任何一种编程语言,这个结论几乎就可以肯定无法成立了。JavaScript虽然也是Python的“竞争者”,但不少人至今依然会把它和Java混淆。这些观点都是概括性的,并非总是成立,但却是很多开发者经常会遇到的难题。
但实际上这种概括性的观点大部分时候都能成立,因为Python的语法实际上和英语语法几乎没有差别!
不信就一起比一比SQL语句以及Python和Django的语句吧:
- SQL:SELECT * from articles_blog_article;
- Python和Django:items = BlogArticle.objects.all()
这两条语句可以产生相同的数据,不过Python语句会返回一个Python对象(项)的列表,几乎任何有经验的Python开发者都可以直接使用该列表。而SQL语句产生的结果必须首先进行转换,随后才能供Python应用使用。
字段描述
再来仔细看看SQL的字段描述:
+------------------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------------------+--------------+------+-----+---------+----------------+
| title | varchar(120) | NO | | NULL | |
+------------------------+--------------+------+-----+---------+----------------+
将上述内容与Django的字段描述对比一下:
title = models.CharField(max_length=120)
- 哪个结果更直观?
- 哪个结果更容易让人理解发生了什么?
- 哪个结果能够提供“刚好够用”的信息?
如果一个并非开发者的人看到了varchar(120),会怎样理解这个内容?相信大家至少可以猜到max_length=120是什么意思。最酷的地方来了:完全正确!就是“将字段限制在120个字符以内”的意思。
向数据库添加数据
如果使用Django:
BlogArticle.objects.create(
title="Hello World",
content="Coming Soon",
slug="hello-world",
publish_timestamp=None,
)
如果使用SQL:
INSERT INTO `articles_blog_article` (`user_id`, `title`, `slug`, `content`, `publish_timestamp`)
VALUES (1, 'Hello World', 'hello-world', 'Coming Soon', NULL);
说到简洁明了,这方面的冠军无疑就是Django和Python了。title = "Hello World"无疑要比弄清楚SQL中的等效列(字段)值是怎么回事更容易。没错,只有当你明确知道自己在所什么时,SQL中的这种写法才会足够高效。
添加多个行
如果使用Django:
items = [
BlogArticle(title='Hello Again 0', slug='hello-again-0', content="Coming Soon"),
BlogArticle(title='Hello Again 1', slug='hello-again-1', content="Coming Soon"),
BlogArticle(title='Hello Again 2', slug='hello-again-2', content="Coming Soon"),
BlogArticle(title='Hello Again 3', slug='hello-again-3', content="Coming Soon"),
BlogArticle(title='Hello Again 4', slug='hello-again-4', content="Coming Soon"),
]
BlogArticle.objects.bulk_create(items)
如果使用SQL:
INSERT INTO `articles_blog_article` (`user_id`, `title`, `slug`, `content`, `publish_timestamp`)
VALUES (1, 'Hello Again 0', 'hello-again-0', 'Coming Soon', NULL),
(1, 'Hello Again 1', 'hello-again-1', 'Coming Soon', NULL),
(1, 'Hello Again 2', 'hello-again-2', 'Coming Soon', NULL),
(1, 'Hello Again 3', 'hello-again-3', 'Coming Soon', NULL),
(1, 'Hello Again 4', 'hello-again-4', 'Coming Soon', NULL);
这再次证明了Python代码有着更高可读性,而SQL代码能针对实际数据提供更深入的见解。不过上述SQL代码依然是通过Python使用Django写出来的。很棒对吧!
请注意:上述对比并不是为了确定哪种技术才是利用SQL数据库的最佳方式,而是为了凸显Python在帮助开发者减轻学习和直接编写SQL语句的负担方面所蕴含的潜力。
有很多Python包可以帮助开发者编写原始SQL语句,包括但不限于:
- Django
- Pandas
- SQLAlchemy
- Polars
- Dask
- Vaex
- Python’s built-in CSV Module
- Tortoise ORM
- Pony ORM
- SQLObject
Django替你负重前行
Python这样利用SQL数据库的秘诀在于对象关系映射包(通常也被称之为ORM)。我们可以将ORM看作一种“中间人”,它可以帮助我们在任何特定语言的原生语法中移动数据。
上文我们展示了如何将SQL转换为Django,接下来不放再发散思考一下还能怎样进行扩展。
假设数据库中有大量数据,并且我们编写了这样一条命令:
my_post = BlogArticle.objects.first()
该语句可以查询数据库,提取数据,将数据载入Python Class实例,并将其分配给变量my_post。
这样我们就可以执行类似下面这样的操作:
# using a django-managed python shell
# via python manage.py shell
>>> print(my_post.title)
Hello World
本例中我们使用“dot”符号来访问上文提到的BlogPost这个Django模型的title字段。该字段对应于SQL数据库表articles_blog_article中的一个列。
借助ORM,我们将能做到:>>> my_post.title = "some other title"
在这个Python Shell会话的例子中,my_post.title将始终保持为"some other title"。然而底层的SQL数据库依然能将这个数据识别为Hello World。数据库将保持原始数据,直到Python最终将数据的变化提交(即.save())到数据库。如果Python从不提交该数据,那么数据库的内容将永不更新。这也恰恰是ORM的神奇之处。我们可以在不影响实际已存储数据的前提下使用和更改数据。当需要对数据库中的内容进行实际修改时,可以运行:
>>> my_post.title = "some other title again"
>>> my_post.save()
在针对数据库运行.save()后,数据库中特定的行将被更新为新的标题,进而与上述Python代码中给出的字符串保持一致。别忘了,.save()方法是专门用于向Django模型提交数据库改动的,.save()对Python的Class没有任何实际意义,除非首先继承了形式的Django模型类。