为什么很多ORM在查询中不使用纯python比较?
Why a lot of ORM does not use pure python comparison in query?
很多 ORM(比如 Django ORM 或 MongoEngine)使用 "keywords magic" 就像 id__lte=1
而不是更原生的 Model.id <= 1
(比如在 SQLAlchemy 中)。
但是为什么呢?它通过 IDE(重构、自动完成和类型检查)完全打破了静态分析。
可能有一些我看不到的原因?或者只是没有那么多人认为 Model.id <= 1
比 id__lte=1
好?
Django ORM 示例:
Blog.objects.get(name="Cheddar Talk", user_reviews__lte=5)
SQLAlchemy 示例
session.query(Blog).filter(Blog.name == "Cheddar Talk", Blog.user_reviews <= 5)
像 SQLAlchemy 使用的 "expression tree" 魔术的一个问题是,并非所有运算符都可以重载以捕获表达式。
比如,你想让it <= 1
被it
捕获,从而在SQL中变成WHERE it <= 1
表达式?很简单:
class Magic:
def __le__(self, other):
return MagicExpression(operator.le, self, other)
您为这些 MagicExpression
个对象构建了一棵树,将那棵树变成 SQL 查询的代码非常简单。
但是如果您还想捕获 it in other_query
并将其转换为 SQL IN
或 BETWEEN
表达式怎么办?那是行不通的。一方面, __contains__
在另一件事上被调用,而不是你的魔法。而且,更糟糕的是,任何 __contains__
returns 都被解释器转换为 bool
。所以,如果你试试这个:
class Magic:
def __contains__(self, other):
return MagicExpression(operator.contains, other, self)
… 然后尝试 it in it2
,你将得到 True
(或任何 MagicExpression.__bool__
returns),这不是你可以转换成的东西SQL IN
或 BETWEEN
稍后。
如果您尝试 and
将两个条件放在一起会怎样?好吧,您甚至不能重载 and
运算符。如果左边 MagicExpression
是假的,你就会得到它;否则,你会得到正确的。
此外,当然还有一个简单的问题,即有时您需要一个甚至不使用任何魔法的静态测试,至少出于调试目的。 "OK, something is wrong in my test on that column… what if I just test against 0 and see what the query returns?" 但是如果 count
和 0
都是纯 Python 整数,则不能将 count > 0
变成 MagicExpression
。
有些人认为这些限制是可以接受的。也许你可以设计一些东西来使用 &
和 |
代替 and
和 or
,并让人们写 "uglier" 版本而不是 in
, 等等。所以很多查询最终都很简单,但有些最终是简单和丑陋的混合体——总比所有的都丑陋要好,对吧?问题是它可能有点像磁铁——很容易忘记限制并使用 and
或 in
,最后得到一个看起来正确但不正确的表达式做你想做的。
因此,其他人认为这是不可接受的,并且只要求您按照 "ugly" 的方式做所有事情,这样就不会想编写 hard-to-debug 损坏的查询。
当然,有些人会在列表理解之上构建 LINQ-esque DSL,并使用导入挂钩将其转换为有效的 SQL-building 或 ORM-using Python。
很多 ORM(比如 Django ORM 或 MongoEngine)使用 "keywords magic" 就像 id__lte=1
而不是更原生的 Model.id <= 1
(比如在 SQLAlchemy 中)。
但是为什么呢?它通过 IDE(重构、自动完成和类型检查)完全打破了静态分析。
可能有一些我看不到的原因?或者只是没有那么多人认为 Model.id <= 1
比 id__lte=1
好?
Django ORM 示例:
Blog.objects.get(name="Cheddar Talk", user_reviews__lte=5)
SQLAlchemy 示例
session.query(Blog).filter(Blog.name == "Cheddar Talk", Blog.user_reviews <= 5)
像 SQLAlchemy 使用的 "expression tree" 魔术的一个问题是,并非所有运算符都可以重载以捕获表达式。
比如,你想让it <= 1
被it
捕获,从而在SQL中变成WHERE it <= 1
表达式?很简单:
class Magic:
def __le__(self, other):
return MagicExpression(operator.le, self, other)
您为这些 MagicExpression
个对象构建了一棵树,将那棵树变成 SQL 查询的代码非常简单。
但是如果您还想捕获 it in other_query
并将其转换为 SQL IN
或 BETWEEN
表达式怎么办?那是行不通的。一方面, __contains__
在另一件事上被调用,而不是你的魔法。而且,更糟糕的是,任何 __contains__
returns 都被解释器转换为 bool
。所以,如果你试试这个:
class Magic:
def __contains__(self, other):
return MagicExpression(operator.contains, other, self)
… 然后尝试 it in it2
,你将得到 True
(或任何 MagicExpression.__bool__
returns),这不是你可以转换成的东西SQL IN
或 BETWEEN
稍后。
如果您尝试 and
将两个条件放在一起会怎样?好吧,您甚至不能重载 and
运算符。如果左边 MagicExpression
是假的,你就会得到它;否则,你会得到正确的。
此外,当然还有一个简单的问题,即有时您需要一个甚至不使用任何魔法的静态测试,至少出于调试目的。 "OK, something is wrong in my test on that column… what if I just test against 0 and see what the query returns?" 但是如果 count
和 0
都是纯 Python 整数,则不能将 count > 0
变成 MagicExpression
。
有些人认为这些限制是可以接受的。也许你可以设计一些东西来使用 &
和 |
代替 and
和 or
,并让人们写 "uglier" 版本而不是 in
, 等等。所以很多查询最终都很简单,但有些最终是简单和丑陋的混合体——总比所有的都丑陋要好,对吧?问题是它可能有点像磁铁——很容易忘记限制并使用 and
或 in
,最后得到一个看起来正确但不正确的表达式做你想做的。
因此,其他人认为这是不可接受的,并且只要求您按照 "ugly" 的方式做所有事情,这样就不会想编写 hard-to-debug 损坏的查询。
当然,有些人会在列表理解之上构建 LINQ-esque DSL,并使用导入挂钩将其转换为有效的 SQL-building 或 ORM-using Python。