【问题标题】:Can list comprehension assist in iterating through sqlalchemy query returns?列表理解可以帮助迭代 sqlalchemy 查询返回吗?
【发布时间】:2020-10-03 05:18:59
【问题描述】:

这是一个非常慢的循环(~1.5[it/s] 使用 tqdm 来测量它)

对于上下文,对象指的是本地的 flask-SQLAlchemy 管理的 postgres 数据库的模型。即:网络传输速度不是速度慢的原因。

for author in tqdm(authors):
    new_score = 0
    for book in author.maintitles:
        new_score = new_score + book.score
        author.score = new_score

进一步明确:大约有 50 万本书,有大约 5 万作者。每本书可以由多个作者撰写。

我没有返回列表,但我确信这可以改进 - 列表理解真的可以改进吗?

类似...

[[(new_score = new_score + book.score,
            author.score = new_score) for book in author.maintitles] for author in tqdm(authors)]

【问题讨论】:

  • 列表推导不会让事情变得更快。如果您不想构建列表,请不要使用列表推导式。
  • 无论是什么让这个循环如此缓慢,我们无法仅从发布的代码中判断,也无法对其进行优化。
  • 谢谢,在主要问题中添加了更多上下文 - 希望提供更多清晰度:)
  • 所以作者确实只有一大堆书?这个设置听起来很人为——这是某种挑战问题吗?如果是这样,您可能对某些本应使用更智能算法解决的潜在问题采取了错误的方法。
  • 提到数据库表明这不是一个挑战问题,但即使是历史上最多产的作者也几乎没有 1000 本书。

标签: python sqlalchemy flask-sqlalchemy list-comprehension nested-loops


【解决方案1】:

不,don't use a list comprehension for side effects。即使您打算使用该列表,comprehensions are only slightly faster than for-loops anyway

但是,您可以使用类似的生成器表达式来改进您的代码。

第 1 步:在末尾分配给 author.score,而不是每个循环,并使用增强分配。

for author in tqdm(authors):
    new_score = 0
    for book in author.maintitles:
        new_score += book.score
    author.score = new_score

第 2 步:现在很明显 new_score 是一个简单的求和,因此请改用 sum

for author in tqdm(authors):
    author.score = sum(book.score for book in author.maintitles)

旁注:您也可以使用列表推导式来编写此代码,但这会使其构建列表然后对其求和,而生成器表达式更有效,因为它会随其求和。

sum([book.score for book in author.maintitles])

【讨论】:

  • 不过,这也不会让事情变得更快。对于像发布的代码这样简单的东西来说,每秒 1.5 次迭代速度非常慢 - 也许有一个速率限制的 API 或二次字符串连接或其他相关的东西。
  • @user2357112 是的。虽然我们不知道所涉及的任何对象的类型,所以很难说。无论如何,这至少会提供边际速度提升,而且它绝对是一个更清洁的附带好处。
  • (听起来外部循环每秒迭代 1.5 次,而不是内部循环,但即便如此,除非作者每个人都有数百万本书,否则我们看不到的奇怪事情可能正在发生.)
  • 谢谢,在主要问题中添加了更多上下文 - 希望提供更多清晰度:)
  • @wjandrea - 遗憾的是,您的重构并没有稍微提高性能:| 36/44982 [00:23
【解决方案2】:

由于提供的重构仅表明列表理解不是解决方案 - 我已经发现了问题的根本原因,所以我添加以下内容作为答案。

上面的代码 sn-p 是从返回的 querylist 的操作的一部分 - 如前所述,在最终操作中遍历去重的 authors 列表(约 50K 个作者)是1.5 it/s 的 15 小时流程:

    # Make the popular books query
    popular_books = \
        db.session.query(Book).filter(Book.score > 0).all()
    
    # Make a list of all authors for each book returned in the query
    authors = []
    for book in popular_books:
        authors = authors + book.mainauthors
    
    # Remove duplicates using set()
    authors = list(set(authors))
    

    for author in tqdm(authors):
        author.score = sum(book.score for book in author.maintitles)
    db.session.commit()

只需调整查询以通过joinedload 返回作者并使用.distinct() 处理重复数据删除,我们不仅将上述所有内容简化为几行,而且操作在 查询返回后。

    for popular_author in db.session.query(Author).join(Book, Author.maintitles).options(db.joinedload(Book, Artist.maintitles)).filter(Book.popularity > 0).distinct().all():
        popular_author.score = sum(book.score for book in popular_author.maintitles)

但是我仍然不完全确定这种方法如何比旧版本快几个数量级。两者都以相同的方式迭代authors 列表并执行相同的简单求和操作。

作为参考,在此过程之后提交会话大约需要 2:00 小时,而之前的实现要快得多。总体上仍然是显着的(7.5 倍)改进。我的猜测是,从一开始就使用更优化的query,所有返回的 ORM 对象都放在 RAM 中,操作起来更快。在 query 上引入 python list 方法似乎会破坏它/分割内存中的 ORM。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 2012-10-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多