大多数pythonic和/或performant方式将单个值分配给切片?

时间:2013-12-09 15:38:34

标签: python slice

我想为列表的一部分分配单个值。有没有更好的解决方案,而不是以下之一?

也许性能最好,但有些丑陋:

>>> l=[0,1,2,3,4,5]
>>> for i in range(2,len(l)): l[i] = None

>>> l
[0, 1, None, None, None, None]

简洁(但我不知道Python是否认识到不需要重新排列列表元素):

>>> l=[0,1,2,3,4,5]
>>> l[2:] = [None]*(len(l)-2)
>>> l
[0, 1, None, None, None, None]

同样的警告如上:

>>> l=[0,1,2,3,4,5]
>>> l[2:] = [None for _ in range(len(l)-2)]
>>> l
[0, 1, None, None, None, None]

不确定是否使用库进行这样一项简单的任务是明智的:

>>> import itertools
>>> l=[0,1,2,3,4,5]
>>> l[2:] = itertools.repeat(None,len(l)-2)
>>> l
[0, 1, None, None, None, None]

我看到切片的赋值(对于for循环)的问题是Python可能会尝试准备“l”长度的变化。毕竟,通过插入更短/更长的切片来更改列表涉及复制列表AFAIK的所有元素(即所有引用)。如果Python也在我的情况下这样做(虽然没有必要),操作变为O(n)而不是O(1)(假设我只是总是改变一些元素)。

6 个答案:

答案 0 :(得分:6)

时间安排:

python -mtimeit "l=[0,1,2,3,4,5]" "for i in range(2,len(l)):" "    l[i] = None"
1000000 loops, best of 3: 0.669 usec per loop

python -mtimeit "l=[0,1,2,3,4,5]" "l[2:] = [None]*(len(l)-2)"
1000000 loops, best of 3: 0.419 usec per loop

python -mtimeit "l=[0,1,2,3,4,5]" "l[2:] = [None for _ in range(len(l)-2)]"
1000000 loops, best of 3: 0.655 usec per loop

python -mtimeit "l=[0,1,2,3,4,5]" "l[2:] = itertools.repeat(None,len(l)-2)"
1000000 loops, best of 3: 0.997 usec per loop

看起来l[2:] = [None]*(len(l)-2)是您提供的最佳选项(对于您正在处理的范围)。

注意:

请记住,结果会因Python版本,操作系统,其他当前正在运行的程序而异,最重要的是 - 列表的大小和要替换的切片的大小。对于较大的范围,可能最后一个选项(使用itertools.repeat)将是最有效的,易于阅读(pythonic)和高效(性能)。

答案 1 :(得分:5)

您的所有解决方案都是Pythonic,同样具有可读性。如果您真的关心性能并认为在这种情况下它很重要,请使用timeit模块对它们进行基准测试。

话虽如此,我希望第一个解决方案几乎肯定不是性能最好的解决方案,因为它迭代Python中的列表元素。此外,Python不会优化远离列表在分配右侧创建的列表,但列表创建速度非常快,并且在大多数情况下,一个小的临时列表根本不会影响执行。就个人而言,对于一个简短的列表,我会选择你的第二个解决方案,对于更长的列表,我会选择itertools.repeat()

请注意,itertools并不真正算作“库”,它带有Python,并且经常被使用,因此它基本上是语言的一部分。

答案 2 :(得分:1)

我建议这样的事情:

>>> l = [0, 1, 2, 3, 4, 5]
>>> n = [None] * len(l)
>>> l[2:] = n[2:]
>>> l
[0, 1, None, None, None, None]

它看起来很漂亮:没有显式循环,没有'if',没有比较!以2N的复杂性为代价! (或不?)

答案 3 :(得分:1)

更多的Pythonic和更高性能的目标有时会发生碰撞。所以你基本上要问两个问题。如果真的需要性能:测量它并采取最快的速度。在所有其他情况下,只需使用最具可读性的内容,换句话说就是最Pythonic(其他Python程序员最可读/最熟悉的内容)。

我个人认为你的第二个解决方案非常易读:

>>> l=[0,1,2,3,4,5]
>>> l[2:] = [None]*(len(l)-2)
>>> l
[0, 1, None, None, None, None]

第二行的开头立刻告诉我你正在替换列表值的特定部分。

答案 4 :(得分:1)

我认为Python中没有直接的开箱即用的功能。我喜欢你的第二种方法,但要记住,在空间和时间之间需要权衡。这是@ user4815162342推荐的非常好的读数:Python Patterns - An Optimization Anecdote

无论如何,如果这是你最终将在你的代码中执行的操作,我认为你最好的选择是将它包装在一个辅助函数中:

def setvalues(lst, index=0, value=None):
    for i in range(index, len(lst)):
        lst[i] = value

>>>l=[1,2,3,4,5]
>>>setvalues(l,index=2)
>>>l
>>>[1, 2, None, None, None]

这有一些优点:

  1. 代码在函数内部重构,如果您改变主意如何执行操作,则很容易修改。
  2. 您可以使用多个功能来完成相同的目标,从而衡量他们的表现。
  3. 您可以为它们编写测试。
  4. 通过重构可以获得的其他优势:)
  5. 由于恕我直言,这个行动没有直接的Python未来,这是我能想象的最好的解决方法。

    希望这有帮助!

答案 5 :(得分:0)

编辑 - 立即编辑原始列表。

你忘了这一个,(IMO,这个更可读)

>>> l = [i if i < 2 else None for i in range(6)]
>>> l
[0, 1, None, None, None, None]

如果需要保留,

>>> l = range(6)
>>> l
[0, 1, 2, 3, 4, 5]
>>> l[:] = [l[i] if i < 2 else None
...              for i in range(len(l))]
>>> l
[0, 1, None, None, None, None]

比如说,性能比Inbar最快的方法慢大约2.5倍。