Python中Sqlalchemy在同一transaction中的BinaryExpression比较问题
假设有一个 model 叫 StockModel, 有一个 Integer 型的 remaining 字段,然后有以下操作:
stock = session.query(StockModel).first()
with session.no_autoflush:
try:
stock = StockModel.remaining - 1 # sql: remaining = remaining - 1
# 此时 stock 是 <sqlalchemy.sql.elements.BinaryExpression>
# 若要再对 stock 进行比较,如下:
if stock.remaining > 10:
pass
# 则会直接报错:
# TypeError: Boolean value of this clause is not defined
# 我现在的解决方式是,如果 stock 是 BinaryExpression 的话,就避免
# 进行比较,直接做减法操作,然后在 commit 之后做二次校验。。。
# 如下:
if isinstance(stock.remaining, BinaryExpression):
stock.remaining = StockModel.remaining - (stock.remaining.right.value + decrease_amount)
session.commit()
# 在这里做二次校验:
if stock.remaining < 0:
raise Expression("Stock not enough")
except BaseException:
session.rollback()
finally:
session.close()
请问各位大佬,有没有更优雅的解决方案呢……在 sqlalchemy 的文档里徜徉了许久还是木有找到想要的解决办法……
Python中Sqlalchemy在同一transaction中的BinaryExpression比较问题
1 回复
这个问题我遇到过,确实挺坑的。在同一个SQLAlchemy事务里,直接比较两个BinaryExpression对象(比如Column == value这种表达式)会返回False,即使它们逻辑上应该相等。
根本原因是SQLAlchemy的BinaryExpression没有实现值相等的比较(__eq__方法)。当你写expr1 == expr2时,Python默认使用对象标识(is)进行比较,也就是比较它们是不是同一个内存对象,而不是比较它们代表的SQL逻辑是否相同。
看个具体例子:
from sqlalchemy import Column, Integer, create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
Base = declarative_base()
engine = create_engine('sqlite:///:memory:')
Session = sessionmaker(bind=engine)
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
age = Column(Integer)
Base.metadata.create_all(engine)
session = Session()
user = User(age=25)
session.add(user)
# 创建两个看起来一样的表达式
expr1 = User.age == 25
expr2 = User.age == 25
print(f"expr1 is expr2: {expr1 is expr2}") # False - 不是同一个对象
print(f"expr1 == expr2: {expr1 == expr2}") # False - 默认比较失败
# 正确的比较方式:比较表达式的组成部分
print(f"比较left: {expr1.left is expr2.left}") # True - 都是User.age
print(f"比较right: {expr1.right.value == expr2.right.value}") # True - 都是25
要正确比较两个BinaryExpression是否代表相同的SQL条件,你需要手动比较它们的各个组成部分:
def compare_expressions(expr1, expr2):
"""比较两个BinaryExpression是否逻辑等价"""
if not isinstance(expr1, type(expr2)):
return False
# 比较操作符
if expr1.operator.__name__ != expr2.operator.__name__:
return False
# 比较左侧(通常是Column)
if expr1.left is not expr2.left:
return False
# 比较右侧值
from sqlalchemy.sql.elements import BindParameter
if isinstance(expr1.right, BindParameter) and isinstance(expr2.right, BindParameter):
return expr1.right.value == expr2.right.value
return expr1.right == expr2.right
# 使用比较函数
print(f"逻辑等价: {compare_expressions(expr1, expr2)}") # True
如果你需要频繁进行这种比较,可以考虑封装一个工具函数,或者直接比较编译后的SQL字符串(但要注意参数绑定可能不同)。
简单说:别直接用==比较表达式对象,拆开比较各部分。

