8.6 选择单个记录
您可以使用该Model.get()方法检索与给定查询匹配的单个实例。对于主键查找,您还可以使用快捷方式Model.get_by_id()。
此方法是使用给定查询调用的快捷方式Model.select(),但将结果集限制为单行。此外,如果没有模型与给定查询匹配,DoesNotExist则会引发异常。
>>> User.get(User.id == 1)
<__main__.User object at 0x25294d0>
>>> User.get_by_id(1) # Same as above.
<__main__.User object at 0x252df10>
>>> User[1] # Also same as above.
<__main__.User object at 0x252dd10>
>>> User.get(User.id == 1).username
u'Charlie'
>>> User.get(User.username == 'Charlie')
<__main__.User object at 0x2529410>
>>> User.get(User.username == 'nobody')
UserDoesNotExist: instance matching query does not exist:
SQL: SELECT t1."id", t1."username" FROM "user" AS t1 WHERE t1."username" = ?
PARAMS: ['nobody']
对于更高级的操作,您可以使用SelectBase.get(). 以下查询检索来自名为charlie的用户的最新推文:
>>> (Tweet
... .select()
... .join(User)
... .where(User.username == 'charlie')
... .order_by(Tweet.created_date.desc())
... .get())
<__main__.Tweet object at 0x2623410>
有关更多信息,请参阅以下文档:
- Model.get()
- Model.get_by_id()
- Model.get_or_none()- 如果没有找到匹配的行,则返回None。
- Model.select()
- SelectBase.get()
- SelectBase.first()- 返回结果集的第一条记录或None。
8.7 创建或获取
Peewee 有一个辅助方法来执行“get/create”类型的操作: Model.get_or_create(),它首先尝试检索匹配的行。否则,将创建一个新行。
对于“创建或获取”类型逻辑,通常会依赖唯一 约束或主键来防止创建重复对象。例如,假设我们希望使用示例 User 模型实现注册新用户帐户。User模型对用户名字段有一个唯一的 约束,所以我们将依赖数据库的完整性保证来确保我们不会得到重复的用户名:
try:
with db.atomic():
return User.create(username=username)
except peewee.IntegrityError:
# `username` is a unique column, so this username already exists,
# making it safe to call .get().
return User.get(User.username == username)
您可以轻松地将这种类型的逻辑封装为classmethod您自己的 Model类。
上面的示例首先尝试创建,然后返回检索,依靠数据库强制执行唯一约束。如果您希望先尝试检索记录,则可以使用 get_or_create(). 此方法的实现方式与同名的 Django 函数相同。您可以使用 Django 风格的关键字参数过滤器来指定您的WHERE条件。该函数返回一个包含实例的 2 元组和一个指示对象是否已创建的布尔值。
以下是您可以使用以下方法实现用户帐户创建的方法 get_or_create():
user, created = User.get_or_create(username=username)
假设我们有一个不同的模型Person并且想要获取或创建一个人对象。检索时我们关心的唯一条件Person 是他们的名字和姓氏,但如果我们最终需要创建新记录,我们还将指定他们的出生日期和最喜欢的颜色:
person, created = Person.get_or_create(
first_name=first_name,
last_name=last_name,
defaults={'dob': dob, 'favorite_color': 'green'})
传递给的任何关键字参数get_or_create()都将在get()逻辑部分中使用,但字典除外,defaults字典将用于在新创建的实例上填充值。
有关更多详细信息,请阅读Model.get_or_create().
8.8 选择多条记录
我们可以使用Model.select()从表中检索行。当您构造一个SELECT查询时,数据库将返回与您的查询相对应的任何行。Peewee 允许您遍历这些行,以及使用索引和切片操作:
>>> query = User.select()
>>> [user.username for user in query]
['Charlie', 'Huey', 'Peewee']
>>> query[1]
<__main__.User at 0x7f83e80f5550>
>>> query[1].username
'Huey'
>>> query[:2]
[<__main__.User at 0x7f83e80f53a8>, <__main__.User at 0x7f83e80f5550>]
Select查询很聪明,因为您可以多次迭代、索引和切片查询,但查询只执行一次。
在下面的示例中,我们将简单地调用select()并迭代返回值,它是Select. 这将返回User表中的所有行:
>>> for user in User.select():
... print(user.username)
...
Charlie
Huey
Peewee
笔记
由于结果被缓存,同一查询的后续迭代不会命中数据库。要禁用此行为(以减少内存使用),请 Select.iterator()在迭代时调用。
在迭代包含外键的模型时,请注意访问相关模型上的值的方式。意外解析外键或迭代反向引用可能会导致N+1 查询行为。
当您创建外键时,例如Tweet.user,您可以使用 反向引用来创建反向引用 ( User.tweets)。反向引用作为Select实例公开:
>>> tweet = Tweet.get()
>>> tweet.user # Accessing a foreign key returns the related model.
<tw.User at 0x7f3ceb017f50>
>>> user = User.get()
>>> user.tweets # Accessing a back-reference returns a query.
<peewee.ModelSelect at 0x7f73db3bafd0>
user.tweets您可以像任何其他一样 迭代反向引用Select:
>>> for tweet in user.tweets:
... print(tweet.message)
...
hello world
this is fun
look at this picture of my food
除了返回模型实例之外,Select查询还可以返回字典、元组和命名元组。根据您的用例,您可能会发现将行用作字典更容易,例如:
>>> query = User.select().dicts()
>>> for row in query:
... print(row)
{'id': 1, 'username': 'Charlie'}
{'id': 2, 'username': 'Huey'}
{'id': 3, 'username': 'Peewee'}
请参阅namedtuples(), tuples(), dicts()了解更多信息。
8.8.1 迭代大型结果集
默认情况下,peewee 将缓存迭代 Select查询时返回的行。这是一种优化,允许多次迭代以及索引和切片,而不会导致额外的查询。但是,当您计划迭代大量行时,这种缓存可能会出现问题。
要在遍历查询时减少 peewee 使用的内存量,请使用该iterator()方法。此方法允许您在不缓存返回的每个模型的情况下进行迭代,在迭代大型结果集时使用更少的内存。
# Let's assume we've got 10 million stat objects to dump to a csv file.
stats = Stat.select()
# Our imaginary serializer class
serializer = CSVSerializer()
# Loop over all the stats and serialize.
for stat in stats.iterator():
serializer.serialize_object(stat)
对于简单的查询,您可以通过将行作为字典、命名元组或元组返回来进一步提高速度。以下方法可用于任何 Select查询以更改结果行类型:
- dicts()
- namedtuples()
- tuples()
不要忘记附加iterator()方法调用以减少内存消耗。例如,上面的代码可能如下所示:
# Let's assume we've got 10 million stat objects to dump to a csv file.
stats = Stat.select()
# Our imaginary serializer class
serializer = CSVSerializer()
# Loop over all the stats (rendered as tuples, without caching) and serialize.
for stat_tuple in stats.tuples().iterator():
serializer.serialize_tuple(stat_tuple)
当迭代包含来自多个表的列的大量行时,peewee 将为返回的每一行重建模型图。对于复杂的图形,此操作可能会很慢。例如,如果我们选择推文列表以及推文作者的用户名和头像,Peewee 必须为每一行创建两个对象(推文和用户)。除了上述行类型之外,还有第四种方法objects() 会将行作为模型实例返回,但不会尝试解析模型图。
例如:
query = (Tweet
.select(Tweet, User) # Select tweet and user data.
.join(User))
# Note that the user columns are stored in a separate User instance
# accessible at tweet.user:
for tweet in query:
print(tweet.user.username, tweet.content)
# Using ".objects()" will not create the tweet.user object and assigns all
# user attributes to the tweet instance:
for tweet in query.objects():
print(tweet.username, tweet.content)
为了获得最佳性能,您可以执行查询,然后使用底层数据库游标对结果进行迭代。Database.execute() 接受一个查询对象,执行查询,并返回一个 DB-API 2.0Cursor 对象。游标将返回原始行元组:
query = Tweet.select(Tweet.content, User.username).join(User)
cursor = database.execute(query)
for (content, username) in cursor:
print(username, '->', content)