【问题标题】:Loop in Python: Do stuff before first iterationPython中的循环:在第一次迭代之前做一些事情
【发布时间】:2016-07-23 19:50:29
【问题描述】:

我要优化。

简单的解决方案

connection = get_db_connection()
for item in my_iterator:
    push_item_to_db(item, connection)

缺点:

get_db_connection() 很慢。如果my_iterator 为空,那么我想避免调用它。

“如果没有”解决方案

connection = None
for item in my_iterator:
    if connection is None:
        connection = get_db_connection()
    push_item_to_db(item, connection)

缺点:

如果my_iterator 中有 100k 个项目,则 if connection is None 被调用 100k 次(尽管只需要一次)。我想避免这种情况。

完美解决方案...

  1. 如果迭代器为空,则不要调用get_db_connection()
  2. 不要在每次迭代时都调用if connection is None:

有什么想法吗?

【问题讨论】:

  • 这是大规模的过度优化。与push_item_to_db 中发生的任何事情相比,if not i 的开销微不足道。
  • 如果get_db_connection 很慢,“优化”以避免if 语句似乎不是正确的做法......也就是说,你的迭代器应该抛出一个StopIteration为空时终止 for each 循环。
  • @DanielRoseman 是的,这是“大规模过度优化”。但是我还是喜欢这个问题,因为我不知道如何解决它。对我来说,这比一个真正“伤害”我的问题更有趣。
  • 为什么需要枚举?在您的 sn-p 中您不使用它。为什么不直接对 my_iterator 进行交互呢?在 for 循环中,仅在尚未连接时通过检查连接值进行连接
  • @joelgoldstick 是的,你是对的。我将 enumerate() 更改为“如果没有”。

标签: python loops optimization


【解决方案1】:

你可以这样做:

connection = None
for item in my_iterator:
    if connection is None:
        connection = get_db_connection()
    push_item_to_db(item, connection)

简单的解决方案。不需要多想。即使有 10 万次操作,x is None 也只是采用一个 Python 操作码的参考比较。与每次插入时发生的完整 tcp 往返 + 磁盘写入相比,您真的不需要优化这一点。

【讨论】:

  • 是的,你的“if”比我的快。但我还是想避免它。
  • 那你就不得不接受@coredump 的回答了。这几乎是避免显式if 的唯一方法。请记住,它仍然不能保证是优化。它实际上可能会更慢,如果我正在查看您的代码,我肯定更愿意查看更简单的解决方案。
  • 你认为没有其他解决方案?
  • 您在寻找什么样的解决方案?你有一些集合的循环,你需要在第一个元素上启动一个数据库连接 - 有很多方法可以编写它,但它基本上总是一样的。
  • 我搜索的解决方案确实符合上述文本:完美解决方案... 1:如果迭代器为空,则不要调用 get_db_connection(),2:不要调用“如果连接为无:”对于每次迭代都是无用的。
【解决方案2】:
for item in my_iterator:
    # First item (if any)
    connection = get_db_connection()
    push_item_to_db(item, connection)
    for item in my_iterator:
        # Next items
        push_item_to_db(item, connection)

【讨论】:

  • 唯一的缺点是可能被某些人标记为“太聪明”(对我来说这很好)。
  • @coredump:我看到的缺点(在其他一些解决方案中也是如此)是循环体需要复制。
  • 是的,我在其他地方对此发表了评论,但不知何故并没有在您的回答中注意到这一点。不过,我更喜欢这个,因为它看起来更简单。
  • @YvesDaoust:我的解决方案 4 避免使用 'itertools.chain()` 复制循环体。
  • @MikeMüller:我们正在进行微优化(避免 if 测试),很可能会添加任何机制(例如 next 中的 try 块或额外参数call) 比单独的 if 增加了更多的开销。如果没有精确的基准测试,我们就无法得出结论,而且很可能所有这些努力都是毫无价值的。
【解决方案3】:

我不是 Python 方面的专家,但我会做这样的事情:

def put_items_to_database (iterator):
    try:
        item = next(iterator)

        # We connect to the database only after we 
        # know there at least one element in the collection            
        connection = get_db_connection()

        while True:
            push_item_to_db(item, connection)
            item = next(iterator)
    except StopIteration:
        pass

这里的性能可能与数据库有关。然而问题在于找到一种方法来避免做不必要的工作,而以上是精确控制迭代过程中发生的事情的基本方法。

在某些方面,其他解决方案“更简单”,但另一方面,我认为这个解决方案更明确,并遵循最小惊讶原则。

【讨论】:

    【解决方案4】:

    解决方案 1

    这可以在没有while True 循环的情况下工作。

    try:
        next(my_iterator)
        connection = get_db_connection()
        push_item_to_db(item, connection)
    except StopIteration:
        pass
    for item in my_iterator:
        push_item_to_db(item, connection)
    

    解决方案 2

    如果您知道该迭代器从不返回 None(或任何其他唯一对象),则可以利用默认值 next()

    if next(my_iterator, None) is not None:
        connection = get_db_connection()
        push_item_to_db(item, connection)
    for item in my_iterator:
        push_item_to_db(item, connection)
    

    解决方案 3

    如果你不能保证迭代器永远不会返回一个值,你可以使用哨兵。

    sentinel = object()
    if next(my_iterator, sentinel) is not sentinel:
        connection = get_db_connection()
        push_item_to_db(item, connection)
    for item in my_iterator:
        push_item_to_db(item, connection)
    

    解决方案 4

    使用itertools.chain()

    from itertools import chain
    
    for first_item in my_iterator:
        connection = get_db_connection()
        for item in chain([first_item], my_iterator):
            push_item_to_db(item, connection)
    

    【讨论】:

    • “while True”循环有什么问题?我真的很好奇,因为您似乎更喜欢在所有解决方案中对“push_item_to_db”进行两次调用,我觉得这不是特别好。
    • 没什么问题。如果可能的话,我只是更喜欢for 循环而不是while 循环。 “感觉”更好或更蟒蛇(也许;))。
    • 为什么不将for 语句放在解决方案1-3 中的else 分支中?当迭代器不返回任何内容时,这将避免 for 的无用执行。
    • 如果迭代器有任何成员,else 将永远不会被执行。只有当迭代器为空时,才会到达 else。除了 for 在空迭代器上进行零次迭代。
    猜你喜欢
    • 1970-01-01
    • 2012-01-02
    • 1970-01-01
    • 2013-02-19
    • 2020-07-02
    • 1970-01-01
    • 2010-12-28
    • 2022-10-20
    • 2014-10-11
    相关资源
    最近更新 更多