直接行为
混合属性, 官方文档中称之为Hybrid Attributes
. 这种机制表现为, 一个属性, 在 类 和层面, 和实例 的层面, 其行为是不同的. 之所以需要关注这部分的差异, 原因源于 Python 上下文和 SQL 上下文的差异.
类 层面经常是作为 SQL 查询时的一部分, 它面向的是 SQL 上下文. 而 实例 是已经得到或者创建的结果, 它面向的是 Python 上下文.
定义模型的 Column() 就是一个典型的混合属性. 作为实例属性时, 是具体的对象值访问, 而作为类属性时, 则有构成 SQL 语句表达式的功能.
1 | class Interval(BaseModel): |
实例行为:
1 | ins = session.query(Interval).first() |
类行为:
1 | ins = session.query(Interval).filter(Interval.end - Interval.start > 10).first() |
这种机制其实一直在被使用, 但是可能大家都没有留意一个属性在类和实例上的区别.
如果属性需要被进一步封装, 那么就需要明确声明Hybrid Attributes
了:
1 | from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method |
setter
的定义同样使用对应的装饰器即可:
1 | class Interval(BaseModel): |
表达式行为
前面说的属性, 在类和实例上有不同行为, 可以看到, 在类上的行为, 其实就是生成 SQL 表达式时的行为. 上面的例子只是简单的运算, SQLAlchemy 可以自动处理好 Python 函数和 SQL 函数的区别. 但是如果是一些特性更强的 SQL 函数, 就需要手动指定了. 于时, 这时的情况变成, 实例行为是 Python 范畴的调用行为, 而类行为则是生成SQL 函数的相关表达式.
同时是前面的例子, 对于 length 的定义, 更严格上来说, 应该是取绝对值的.
1 | class Interval(BaseModel): |
但是, 如果使用了 Python 的abs()
函数, 在生成 SQL 表达式时显示有无法处理了. 所以, 需要手动定义:
1 | from sqlalchemy import func |
这样查询时就可以直接使用:
1 | ins = session.query(Interval).filter(Interval.length > 1).first() |
对应的 SQL :
1 | SELECT * |
应用于关系
总体上没有特别之处:
1 | class Account(BaseModel): |
查询时:
1 | user = session.query(User).first() |
这里涉及的东西都是 Python 自己的, 包括那个sum()
函数, 和SQL
没有关系.
如果想实现的是, 使用SQL
的sum()
函数, 取出指定用户的总账户金额数, 那么就要考虑把balance 作成表达式的形式:
1 | from sqlalchemy import select |
这样的话,User.balance
只是单纯的一个表达式了, 查询时指定字段:
1 | user = session.query(User, User.balance).first() |
注意, 如果写成:
1 | session.query(User.balance).first() |
意义就不再是”获取第一个用户的总金额”, 而变成”获取总金额的第一个”. 这里很坑吧.
像上面这样改, 实例层面就无法使用 balance 属性. 所以, 还是先前介绍的, 表达式可以单独处理:
1 |
|
定义了表达式的 balance , 这部分作为查询条件上当然也是可以的:
1 | user = session.query(User).filter(User.balance > 1).first() |