SQLAlchemy混合属性机制

直接行为

混合属性, 官方文档中称之为Hybrid Attributes. 这种机制表现为, 一个属性, 在 类 和层面, 和实例 的层面, 其行为是不同的. 之所以需要关注这部分的差异, 原因源于 Python 上下文和 SQL 上下文的差异.
类 层面经常是作为 SQL 查询时的一部分, 它面向的是 SQL 上下文. 而 实例 是已经得到或者创建的结果, 它面向的是 Python 上下文.
定义模型的 Column() 就是一个典型的混合属性. 作为实例属性时, 是具体的对象值访问, 而作为类属性时, 则有构成 SQL 语句表达式的功能.

1
2
3
4
5
6
7
8
9
class Interval(BaseModel):
__tablename__ = 'interval'

id = Column(Integer, autoincrement=True, primary_key=True)
start = Column(Integer)
end = Column(Integer)

session.add(Interval(start=0, end=100))
session.commit()

实例行为:

1
2
ins = session.query(Interval).first()
print ins.end - ins.start

类行为:

1
ins = session.query(Interval).filter(Interval.end - Interval.start > 10).first()

这种机制其实一直在被使用, 但是可能大家都没有留意一个属性在类和实例上的区别.
如果属性需要被进一步封装, 那么就需要明确声明Hybrid Attributes了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from sqlalchemy.ext.hybrid import hybrid_property, hybrid_method

class Interval(BaseModel):
__tablename__ = 'interval'

id = Column(Integer, autoincrement=True, primary_key=True)
start = Column(Integer)
end = Column(Integer)

@hybrid_property
def length(self):
return self.end - self.start

@hybrid_method
def bigger(self, i):
return self.length > i


session.add(Interval(start=0, end=100))
session.commit()

ins = session.query(Interval).filter(Interval.length > 10).first()
ins = session.query(Interval).filter(Interval.bigger(10)).first()
print ins.bigger(1)

setter的定义同样使用对应的装饰器即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Interval(BaseModel):
__tablename__ = 'interval'

id = Column(Integer, autoincrement=True, primary_key=True)
start = Column(Integer)
end = Column(Integer)

@hybrid_property
def length(self):
return abs(self.end - self.start)

@length.setter
def length(self, l):
self.end = self.start + l

表达式行为

前面说的属性, 在类和实例上有不同行为, 可以看到, 在类上的行为, 其实就是生成 SQL 表达式时的行为. 上面的例子只是简单的运算, SQLAlchemy 可以自动处理好 Python 函数和 SQL 函数的区别. 但是如果是一些特性更强的 SQL 函数, 就需要手动指定了. 于时, 这时的情况变成, 实例行为是 Python 范畴的调用行为, 而类行为则是生成SQL 函数的相关表达式.
同时是前面的例子, 对于 length 的定义, 更严格上来说, 应该是取绝对值的.

1
2
3
4
5
6
7
8
9
10
class Interval(BaseModel):
__tablename__ = 'interval'

id = Column(Integer, autoincrement=True, primary_key=True)
start = Column(Integer)
end = Column(Integer)

@hybrid_property
def length(self):
return abs(self.end - self.start)

但是, 如果使用了 Python 的abs()函数, 在生成 SQL 表达式时显示有无法处理了. 所以, 需要手动定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from sqlalchemy import func

class Interval(BaseModel):
__tablename__ = 'interval'

id = Column(Integer, autoincrement=True, primary_key=True)
start = Column(Integer)
end = Column(Integer)

@hybrid_property
def length(self):
return abs(self.end - self.start)

@length.expression
def length(self):
return func.abs(self.end - self.start)

这样查询时就可以直接使用:

1
ins = session.query(Interval).filter(Interval.length > 1).first()

对应的 SQL :

1
2
3
4
SELECT *
FROM interval
WHERE abs(interval."end" - interval.start) > ?
LIMIT ? OFFSET ?

应用于关系

总体上没有特别之处:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Account(BaseModel):
__tablename__ = 'account'

id = Column(Integer, autoincrement=True, primary_key=True)
user = Column(Integer, ForeignKey('user.id'), index=True)
balance = Column(Integer, server_default='0')


class User(BaseModel):
__tablename__ = 'user'

id = Column(Integer, autoincrement=True, primary_key=True)
name = Column(Unicode(32), nullable=False, server_default='')

accounts = relationship('Account')
#balance = association_proxy('accounts', 'balance')

@hybrid_property
def balance(self):
return sum(x.balance for x in self.accounts)

查询时:

1
2
user = session.query(User).first()
print user.balance

这里涉及的东西都是 Python 自己的, 包括那个sum()函数, 和SQL没有关系.
如果想实现的是, 使用SQLsum()函数, 取出指定用户的总账户金额数, 那么就要考虑把balance 作成表达式的形式:

1
2
3
4
5
6
from sqlalchemy import select

@hybrid_property
def balance(self):
return select([func.sum(Account.balance)]).where(Account.user == self.id).label('balance_v')
#return func.sum(Account.balance)

这样的话,User.balance只是单纯的一个表达式了, 查询时指定字段:

1
2
user = session.query(User, User.balance).first()
print user.balance

注意, 如果写成:

1
session.query(User.balance).first()

意义就不再是”获取第一个用户的总金额”, 而变成”获取总金额的第一个”. 这里很坑吧.
像上面这样改, 实例层面就无法使用 balance 属性. 所以, 还是先前介绍的, 表达式可以单独处理:

1
2
3
4
5
6
7
@hybrid_property
def balance(self):
return sum(x.balance for x in self.accounts)

@balance.expression
def balance(self):
return select([func.sum(Account.balance)]).where(Account.user == self.id).label('balance_v')

定义了表达式的 balance , 这部分作为查询条件上当然也是可以的:

1
user = session.query(User).filter(User.balance > 1).first()
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值