【问题标题】:How to improve performance of many appends to a list?如何提高许多附加到列表的性能?
【发布时间】:2017-05-19 14:02:18
【问题描述】:

如何提高以下代码的性能?

BANNED_DOORBOTS = {...}

async def execute_query(self, query):
    async with self.pool.acquire() as conn:
        async with conn.cursor() as cur:
            await cur.execute(query)
            records = []
            async for row in cur:
                if row[0] not in BANNED_DOORBOTS:
                    records.append({
                        'key1': row[0],
                        'key2': row[1]
                    })

    return records

我不想每次都检查if row[0] not in BANNED_DOORBOTS。 如何避免这种情况?

通常,我在records 中有一百多个(最多 20 000 个)元素。也许我可以预先分配一些空间以避免重新分配?

【问题讨论】:

    标签: python algorithm postgresql async-await aiopg


    【解决方案1】:

    您每次都从数据库查询中重建一个列表。

    我会要求数据库不要返回被禁止的记录:

    from psycopg2 import sql  # safe SQL composing
    
    # Add a NOT IN clause to filter out banned doorbots, generating a
    # separate parameter placeholder per value
    query = sql.SQL(query) + sql.SQL(' WHERE ding_id NOT IN ({})').format(
        sql.SQL(', ').join([sql.Placeholder()] * len(BANNED_DOORBOTS)))
    await cur.execute(query, BANNED_DOORBOTS)
    

    我在这里使用psycopg.sql framework 进行合成,但您也可以使用字符串格式(使用'%s' 作为占位符)。

    考虑将BANNED_DOORBOTS 集放在数据库的表中,以便您可以使用WHERE ding_id NOT IN (SELECT id from BANNED_DOORBOTS WHERE id IS NOT NULL) 子查询。这样您仍然可以获得更好的性能(数据库可以为此优化),并且您不必生成占位符。

    接下来,使用列表推导来构建列表。这更快,因为它避免了重复的list.append 查找和方法调用。将您的列名定义为一个元组并将其与每一行一起压缩:

    keys = ('ding_id', 'doorbot_id', 'created_at', 'address', 'latitude', 
            'longitude', 'ding_kind')
    return [dict(zip(keys, row)) async for row in cur]
    

    async for 列表解析语法需要 Python 3.6 或更高版本。

    aiopg 驱动程序允许您配置一个替代游标工厂,即already produces dictionaries,它可能更快。然后,您根本不必使用任何列表推导:

    from psycopg2.extras import RealDictCursor
    
    # configure cursor to yield dictionaries rather than tuples
    async with conn.cursor(cursor_factory=RealDictCursor) as cur:
        await cur.execute(query, BANNED_DOORBOTS)
        # directly return the cursor; have the caller do the async iteration
        return cur
    

    如果您不想让调用者负责循环,但生成列表,请使用cursor.fetchall() method 生成该列表;每个元素都是一个字典:

    # configure cursor to yield dictionaries rather than tuples
    async with conn.cursor(cursor_factory=RealDictCursor) as cur:
        await cur.execute(query, BANNED_DOORBOTS)
        return await cur.fetchall()
    

    【讨论】:

    • 非常感谢您的回答。您能否提供带有列表理解的算法的完整代码,因为我是 python 新手,并不真正了解最终版本是什么(return [dict(zip(keys, row)) ...]
    • @ipetr:替换从records = [] 开始的所有内容。所以return [...]with asyc ... 上下文管理器中。
    • 抱歉,我不确定我是否完全理解如何执行此操作。如果您在答案末尾粘贴完整代码,我将不胜感激
    • @ipetr:问题是,我提供了几种不同的选择;仅选择一个作为完整示例会使事情变得相当混乱。就我个人而言,我会完全跳过理解并在此处使用 RealDictRow 光标工厂,并直接返回 cur 光标,如上一个示例所示。在该示例中,return cur 与列表理解位于同一位置。将该行替换为keys = ...return [...] 行,缩进相同。
    • @ipetr:async with conn.cursor(...): 上下文管理器为您设置了一个光标;您需要访问光标的所有操作都嵌套在其中。由于return [... for row in cur] 代码使用游标,因此您确实在上下文管理器中缩进它。
    猜你喜欢
    • 1970-01-01
    • 2014-01-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-11-09
    • 1970-01-01
    • 1970-01-01
    • 2016-11-05
    相关资源
    最近更新 更多