Python 中 add()、append()、update() 和 extend() 的用法
有没有什么文章或者论坛讨论,能解释一下为什么列表用的是append和extend,而集合和字典用的是add和update呢?
我经常需要把列表转换成集合,这种差别让我觉得很麻烦,所以为了让我自己心里舒服点,我想知道背后的原因是什么。
在开发过程中,我们经常需要在这些数据结构之间转换。随着程序的不断变化,某些结构会有不同的需求,比如需要有序或者不能有重复的元素。
举个例子,最开始可能是一个无序的列表,后来可能需要确保里面没有重复的元素,这样就需要把它转换成集合。
每次这样的变化都需要找到并修改所有相关的地方,看看是用add还是用append,或者是用update还是extend。
所以我很好奇最初是怎样讨论出这种语言选择的,但可惜我在网上搜索的时候没有找到相关的信息。
3 个回答
这样写是为了让你感到烦恼。
说真的。它的设计就是让人不能轻易地把一种东西转换成另一种。历史上,set
(集合)是基于dict
(字典)来的,所以它们在命名上有很多相似之处。虽然你可以很简单地写一个set
的包装器来添加这些方法……
class ListlikeSet(set):
def append(self, x):
self.add(x)
def extend(self, xs):
self.update(xs)
……但更重要的问题是,为什么你会这么频繁地把list
(列表)转换成set
。它们代表了两种完全不同的对象集合模型;如果你经常需要在这两者之间转换,说明你可能对程序的概念架构理解得不够透彻。
set
和 dict
是无序的。这意味着它们里面的元素没有固定的顺序。 "添加" 和 "扩展" 这些概念只适用于有顺序的类型。
append
通常的意思是“加到最后”,而 extend
也可以理解为“扩展到某个点之后”。但是集合(sets)没有“结束”这个概念,也没有办法在里面指定某个“点”或者“边界”,因为它们根本没有“边界”!所以说这些操作可以在集合上进行是非常误导的。
x.append(y)
总是会让 len(x)
增加一个(无论 y
是否已经在列表 x
里);而对于 s.add(z)
来说,这个长度可能会增加,也可能保持不变。此外,在这些例子中,y
可以是任何值(也就是说,append 操作几乎不会失败,除了内存不足的特殊情况)——但是对于 z
来说就不一样了,它必须是可哈希的,否则 add 操作就会失败并抛出异常。类似的区别也适用于 extend
和 update
。用同样的名字来表示这些语义差别如此大的操作,确实会让人感到困惑。
在第一次处理时,直接用列表似乎更符合 Python 的风格,性能问题可以在后续再考虑。
性能问题其实不是最重要的!list
支持重复项、保持顺序,并且可以包含任何类型的项——而 set
确保每个项都是唯一的,没有顺序的概念,并且要求项是可哈希的。用列表(还要进行一些奇怪的重复检查等)来代替集合,这在 Python 中并没有什么“Pythonic”的地方——无论性能如何,遵循“说出你的意思!”才是 Python 的风格。;-)。在像 Fortran 或 C 这样的语言中,内置的容器类型只有数组,如果你想避免使用额外的库,可能需要进行这样的“心理映射”;但在 Python 中,根本没有这个必要。
编辑:原作者在评论中提到,他们一开始并不知道某个算法不允许重复(这听起来有点奇怪,但无所谓)——他们希望找到一种简单的方法,将列表转换为集合,一旦发现重复是个问题(我还要补充:顺序不重要,项是可哈希的,不需要索引/切片等)。为了达到与 Python 的 set
有“同义词”方法时完全相同的效果:
class somewhatlistlikeset(set):
def append(self, x): self.add(x)
def extend(self, x): self.update(x)
当然,如果唯一的变化是在集合创建时(之前是列表创建),那么代码可能会变得更难理解,因为失去了使用 add
和 append
的清晰性,这样阅读代码的人就能“局部”知道这个对象是集合还是列表……但这也是上面提到的“完全相同的效果”的一部分!-)